Leetcode拾萃(算法篇)——暴力破解(DFSBFSpermutation)

Posted Go_uP

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Leetcode拾萃(算法篇)——暴力破解(DFSBFSpermutation)相关的知识,希望对你有一定的参考价值。

引言

现在互联网的招工流程,算法题是必不可少的,对于像我这种没搞过ACM的吃瓜群众,好在有leetcode,拯救我于水火。于是乎,断断续续,刷了一些题,其中一些题还是值得细细品味的,现把一些问题整理一下,有些解法是我自己写的,也有些解法是参考了discuss中的答案,当做是秋招的一个小小总结。由于水平有限,代码写得并不好,选的题目也只能用于入门,希望大家见谅。

暴力破解

暴力破解,据我个人理解,就是遍历整棵搜索树,没有删繁就简,紧是单单的搜索和匹配。

1、基本暴力破解:对于最基本的暴力破解,就是取出所有的可能,和题目中的条件相比较,直到找到符合题意的答案。

 举例:鸡兔同笼问题,已知x个头,y只脚,问兔和鸡的个数。

 解法:从0~x个枚举鸡(或兔)的只数,计算脚的个数是否为y。

2、DFS:深度优先搜索。从原始状态出发,选择任一状态,沿这个状态一直走到没有下一个状态为止,这时候进行回溯,到当前状态的上一个状态,选择另一个状态进行遍历。DFS在代码实现上常使用递归方法来实现。

 举例1:Leetcode 129. Sum Root to Leaf Numbers

 

Given a binary tree containing digits from 0-9 only, each root-to-leaf path could represent a number.

An example is the root-to-leaf path 1->2->3 which represents the number 123.

Find the total sum of all root-to-leaf numbers.

For example,

    1
   / \\
  2   3

 

The root-to-leaf path 1->2 represents the number 12.
The root-to-leaf path 1->3 represents the number 13.

Return the sum = 12 + 13 = 25.

 

 解法:最直观的思路,就是找到所有路径,记录路径上所有的值,并且求和,代码如下:

class Solution {
public:
    int ret = 0;
    int sumNumbers(TreeNode* root) {
        if(root == nullptr) return ret;
        getSum(root,root->val);
        return ret;
    }
    //递归调用
    void getSum(TreeNode* root,int preval){
        if(root->left==nullptr&&root->right==nullptr){
            ret+=preval;
            return;
        }
        if(root->left)getSum(root->left,preval*10+root->left->val);
        if(root->right)getSum(root->right,preval*10+root->right->val);
        return;
    }
};

除此之外,DFS还可常用于寻找所有可达状态:

举例二:Leetcode200. Number of Islands

Given a 2d grid map of \'1\'s (land) and \'0\'s (water), count the number of islands. An island is surrounded by water and is formed by connecting adjacent lands horizontally or vertically. You may assume all four edges of the grid are all surrounded by water.

Example 1:

11110
11010
11000
00000

Answer: 1

Example 2:

11000
11000
00100
00011

Answer: 3

思路:在这个题目中,认为所有为1(上下左右)组成的是一个岛,求给出数组中岛的个数。主要的思想就是从任一个为值1的点开始,将所有能达到的点都标记为已访问,如果所有可达的点都为已访问或0,说明这个岛中所有的数据已经被搜索完毕,则可以去找下一个岛。最后可以得到岛的个数。在下面的代码中,已访问的点标记为0,如果相邻的点的值为1,说明可扩展,则进行扩展。

解法:

class Solution {
public:
    int dir[4][2]={{-1,0},{1,0},{0,-1},{0,1}};
    int m,n,ret = 0;
    int numIslands(vector<vector<char>>& grid) {
        if(grid.empty())return 0;
        m = grid.size(),n = grid[0].size();
        for(int i=0;i<m;i++){
            for(int j=0;j<n;j++){
                if(grid[i][j]==\'1\'){
                    dfs(i,j,grid);
                    ++ret;
                }
            }
        }
        return ret;
    }
    
    void dfs(int s,int t,vector<vector<char>>& grid) {
     //如果已经访问过或超界,则不需要再扩展
if(s<0||s>=m||t<0||t>=n||grid[s][t]==\'0\'){ return; }
     //标记为已经访问过 grid[s][t]
=\'0\';
     //向下一个状态扩展,进行递归访问
for(int i=0;i<4;i++){ dfs(s+dir[i][0],t+dir[i][1],grid); } return; } };

 

3.BFS:广度优先搜索,是和DFS相对的一种方法,从初始状态开始,用“辐射状”的形式遍历其余所有状态。对于状态的遍历,BFS和DFS能使用的场景有很多有很多相似之处,例如上面的Leetcode129,就也有BFS解法。

BFS一般使用队列实现:将初始状态放到队列中,当出队的时候,将出队状态的所有未访问过的后续状态放到队列中进行后续访问。BFS的一个典型应用是用于(最短)路径的寻找,从初始状态到目标状态,因为BFS可以保证每次遍历的时候从初始状态到当前队列中的状态的步数是相同的;另一个典型应用是对于某种“分层”的场合,对于某一状态,其后续的所有状态具有相同的性质。

 举例1:LeetCode 490,但这个题是付费的,我只看过解法,并没有做过,迷宫类的题是BFS最经典的应用。题目和解法可以参考我一直比较崇拜的一个博主的博客:https://www.cnblogs.com/grandyang/p/6381458.html

 举例2:LeetCode 542. 01 Matrix

Given a matrix consists of 0 and 1, find the distance of the nearest 0 for each cell.

The distance between two adjacent cells is 1.

Example 1: 
Input:

0 0 0
0 1 0
0 0 0

Output:

0 0 0
0 1 0
0 0 0

 

Example 2: 
Input:

0 0 0
0 1 0
1 1 1

Output:

0 0 0
0 1 0
1 2 1

 

Note:

  1. The number of elements of the given matrix will not exceed 10,000.
  2. There are at least one 0 in the given matrix.
  3. The cells are adjacent in only four directions: up, down, left and right.

 

解法:此题即是“分层”场景的一个典型应用”、对于每一个0,其周围的非0点的状态相同:周围最近的0距离为1;再以这些1为中心,扩散出去的第二层具有相同的性质。

class Solution {
public:
    int dir[4][2]={{0,1},{0,-1},{-1,0},{1,0}};
    #define pii pair<int,int>
    #define mp make_pair
    vector<vector<int>> updateMatrix(vector<vector<int>>& matrix) {
        int m = matrix.size();
        if(m==0)return matrix;
        int n = matrix[0].size();
        queue<pii> q;
        for(int i=0;i<m;i++){
            for(int j=0;j<n;j++){
                if(matrix[i][j]==0){
                    q.push(mp(i,j));
                }else{
                    matrix[i][j]=INT_MAX;
                }
            }
        }
        while(!q.empty()){
            pii tmp = q.front();
            q.pop();
            for(int k=0;k<4;k++){
                int a = tmp.first+dir[k][0],b = tmp.second+dir[k][1];
                if(a<0||a>=m||b<0||b>=n)continue;
                if(matrix[a][b]<=matrix[tmp.first][tmp.second]) continue;
                matrix[a][b]=min(matrix[a][b],matrix[tmp.first][tmp.second]+1);
                q.push({a,b});
            }
        }
        return matrix;
    }
};

举例3:102. Binary Tree Level Order Traversal

BFS另一个典型应用就是用于树或图的层次遍历,这个比较基本,此处不再赘述。

 

4、Permutation:即全排列,可以获取1~n的全排列

在C++中,可以使用STL里面的接口来实现,例如求一个数组的全排列:

class Solution {
public:
    vector<vector<int>> permute(vector<int>& nums) {
        sort(nums.begin(),nums.end());
        vector<vector<int>> ret;
        ret.push_back(nums);
        while(next_permutation(nums.begin(),nums.end())){
            ret.push_back(nums);
        }
        return ret;
    }
};

这个函数在使用之前要注意,需要将数组排序,这样才能检测出来当前的排列是否生成过。

除此之外,permutation也可以用来求子集(m中选k个),即使用m的全排列的前k个。

permutation的复杂度为O(n!),一般只有数据量较小的时候可以使用。

 

以上是关于Leetcode拾萃(算法篇)——暴力破解(DFSBFSpermutation)的主要内容,如果未能解决你的问题,请参考以下文章

[LeetCode]419 Battleships in a Board(暴力,dfs)

Leetcode 5. 最长回文子串

Leetcode 1. 两数之和(带图)

算法学习——DFS(暴力搜索)N皇后问题

PHP代码审计入门-DVWA靶场暴力破解篇

破解aes密码