1. 程式人生 > >Leetcode拾萃(算法篇)——暴力破解(DFS、BFS、permutation)

Leetcode拾萃(算法篇)——暴力破解(DFS、BFS、permutation)

turn 層次 traversal 雞兔同籠 pda www str fine ace

引言

現在互聯網的招工流程,算法題是必不可少的,對於像我這種沒搞過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拾萃(算法篇)——暴力破解(DFS、BFS、permutation)