leetcode经典题目(11)--BFS和DFS

1. BFS

广度优先搜索一层一层地进行遍历,每层遍历都是以上一层遍历的结果作为起点,遍历一个距离能访问到的所有节点。需要注意的是,遍历过的节点不能再次被遍历。

1.1. 二进制矩阵中的最短路径(NO.1091)

题目描述:0 表示可以经过某个位置,求解从左上角到右下角的最短路径长度。
解题思路:使用BFS,设置一个队列保存节点,访问过将其在矩阵中对应的值改为1.每次处理一层中所有的节点。

class Solution {
    
    
public:
    int shortestPathBinaryMatrix(vector<vector<int>>& grid) {
    
    
        if (grid.size() == 0 || grid[0].size() == 0)
            return -1;
        int m = grid.size(), n = grid[0].size();
        if (grid[0][0] == 1)//开始节点就走不通
            return -1;
        vector<vector<int>> adj = {
    
    {
    
    -1,-1},{
    
    -1,0},{
    
    -1,1},{
    
    0,-1},{
    
    0,1},{
    
    1,-1},{
    
    1,0},{
    
    1,1}};
        queue<pair<int,int>> qu;
        qu.push(make_pair(0,0));
        grid[0][0] = 1;
        int length = 1;
        while (!qu.empty()){
    
    //队列不为空时循环
            int len = qu.size();//每层的节点数
            for (int i = 0; i < len; i++){
    
    
                //出队一个元素
                int x = qu.front().first;
                int y = qu.front().second;
                qu.pop();
                if (x == m-1 && y == n-1)
                    return length;
                //将该元素周围可通过的元素进队
                for (int j = 0; j < 8; j++){
    
    
                    int x1 = x + adj[j][0];
                    int y1 = y + adj[j][1];
                    if (x1 < 0 || x1 >= m || y1 < 0 || y1 >= n || grid[x1][y1] == 1)
                        continue;//越界或者不通
                    else{
    
    
                        qu.push(make_pair(x1,y1));
                        grid[x1][y1] = 1;
                    }
                }
            }
            length++;
        }
        return -1;
    }
};

2. DFS

2.1 查找最大的连通面积–岛屿的最大面积

题目描述:给定一个包含了一些 0 和 1 的非空二维数组 grid 。
一个 岛屿 是由一些相邻的 1 (代表土地) 构成的组合,这里的「相邻」要求两个 1 必须在水平或者竖直方向上相邻。你可以假设 grid 的四个边缘都被 0(代表水)包围着。
找到给定的二维数组中最大的岛屿面积。(如果没有岛屿,则返回面积为 0 。)

class Solution {
    
    
public:
    int maxAreaOfIsland(vector<vector<int>>& grid) {
    
    
        if (grid.size() == 0 || grid[0].size() == 0)
            return 0;
        int maxArea = 0;
        //遍历所有的点,得出以该点开始的岛屿面积
        int m = grid.size(), n = grid[0].size();
        for (int i = 0; i < m; i++){
    
    
            for (int j = 0; j < n; j++){
    
    
                int tmp = dfs(grid,i,j);
                if (tmp > maxArea)
                    maxArea = tmp;
            }
        }
        return maxArea;
    }
    //从(i,j)点进行深度优先遍历,判断岛屿面积
    int dfs(vector<vector<int>>& grid, int i, int j){
    
    
        if (i >= grid.size() || i < 0)
            return 0;
        if (j >= grid[0].size() || j < 0)
            return 0;
        if (grid[i][j] == 1){
    
    
            grid[i][j] = 0;
            return 1 + dfs(grid,i-1,j) + dfs(grid,i+1,j) + dfs(grid,i,j-1) + dfs(grid,i,j+1);
        } 
        return 0; 
    }
};
2.2 矩阵中的连通分量数目–岛屿数量(NO.200)

题目描述:给你一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,请你计算网格中岛屿的数量。岛屿总是被水包围,并且每座岛屿只能由水平方向或竖直方向上相邻的陆地连接形成。
解题思路:遍历矩阵的所有位置,若当前位置值为1,则进行深度优先遍历,遍历的过程中将遍历过的点值为0.

class Solution {
    
    
private:
    vector<vector<int>> around = {
    
    {
    
    -1,0},{
    
    1,0},{
    
    0,-1},{
    
    0,1}};
    void dfs(vector<vector<char>>& grid, int i, int j){
    
    
        if (i < 0 || i >= grid.size() || j < 0 || j >= grid[0].size() || grid[i][j] == '0')
            return;
        grid[i][j] = '0';
        for (auto d : around)
            dfs(grid,i+d[0],j+d[1]);
    }

public:
    int numIslands(vector<vector<char>>& grid) {
    
    
        if (grid.size() == 0 || grid[0].size() == 0)
            return 0;
        int cnt = 0;
        for (int i = 0; i < grid.size(); i++){
    
    
            for (int j = 0; j < grid[0].size(); j++){
    
    
                if (grid[i][j] != '0'){
    
    
                    dfs(grid,i,j);
                    cnt++;
                }
            }
        }
        return cnt;
    }
};
2.3 无向图连通块的个数–朋友圈数量(NO.547)

题目描述:给定一个 N * N 的矩阵 M,表示班级中学生之间的朋友关系。如果M[i][j] = 1,表示已知第 i 个和 j 个学生互为朋友关系,否则为不知道。你必须输出所有学生中的已知的朋友圈总数。
解题思路:给定的矩阵可以看成图的邻接矩阵。这样我们的问题可以变成无向图连通块的个数。visited[i] 表示第 i 个元素是否被深度优先搜索访问过。从第一个节点开始,访问其任一相邻的节点。然后再访问这一节点的任一相邻节点。这样不断遍历到没有未访问的相邻节点时,回溯到之前的节点进行访问。

class Solution {
    
    
private:
    void dfs(vector<vector<int>>& M, int i, vector<int>& visited){
    
    
        if (visited[i] == 1)
            return;
        visited[i] = 1;
        for (int j = 0; j < M.size(); j++){
    
    
            if (M[i][j] == 1 && visited[j] == 0)
                dfs(M, j, visited);
        }
    }
public:
    int findCircleNum(vector<vector<int>>& M) {
    
    
        if (M.size() == 0)
            return 0;
        int m = M.size(), cnt = 0;
        vector<int> visited(m,0);
        for (int i = 0; i < m; i++){
    
    
            if (visited[i] == 0){
    
    
                dfs(M,i,visited);
                cnt++;
            }
        }
        return cnt;
    }
};

3. 回溯

普通 DFS 主要用在可达性问题,这种问题只需要执行到特点的位置然后返回即可。而 Backtracking 主要用于求解排列组合问题,例如有 { ‘a’,‘b’,‘c’ } 三个字符,求解所有由这三个字符排列得到的字符串,这种问题在执行到特定的位置返回之后还会继续执行求解过程。

因为 Backtracking 不是立即返回,而要继续求解,因此在程序实现时,需要注意对元素的标记问题:
在访问一个新元素进入新的递归调用时,需要将新元素标记为已经访问,这样才能在继续递归调用时不用重复访问该元素;
但是在递归返回时,需要将元素标记为未访问,因为只需要保证在一个递归链中不同时访问一个元素,可以访问已经访问过但是不在当前递归链中的元素。

3.1 数字的全排列(NO.46)

题目描述:定一个 没有重复 数字的序列,返回其所有可能的全排列。
输入: [1,2,3];输出:[[1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1]]

class Solution {
    
    
public:
    vector<vector<int>> permute(vector<int>& nums) {
    
    
        int n = nums.size();
        vector<vector<int>> res;
        if (n == 0)
            return res;
        vector<bool> visited(n,false);//用于标记数字是否已经用过
        vector<int> path;//保存遍历的路径结果
        dfs(nums,path,visited,res);
        return res;
    }
    void dfs(vector<int>& nums, vector<int>& path, vector<bool>& visited, vector<vector<int>>& res){
    
    
        if (path.size() == nums.size()){
    
    
            res.push_back(path);
            return;
        }
        for (int i = 0; i < nums.size(); i++){
    
    
            if (!visited[i]){
    
    //第i个数字没有访问过
                path.push_back(nums[i]);//将数字i加入路径中
                visited[i] = true;
                dfs(nums,path,visited,res);//继续遍历
                visited[i] = false;//回溯,将该数弹出并置为没有访问过
                path.pop_back();
            }
        }
    }
};
3.2 含有重复数字的数组全排列(NO.47)

题目描述:给定一个可包含重复数字的序列,返回所有不重复的全排列。
输入: [1,1,2];输出:[ [1,1,2], [1,2,1], [2,1,1]]
分析:上题中数组中数字不重复,本题数字可以重复。
当遍历到nums[i]时,如果nums[i]等于nums[i-1],则要判断是否进行剪枝。
如果visited[i-1]为真,则nums[i-1]访问过了,表示i与i-1是在同一条路径上;如果visited[i-1]为假,表示nums[i-1]刚刚被撤销选择,不在一条路径上(nums[i]与nums[i-1]在同一层,两个数对应的情况是一样的),要进行剪枝。
所以与上题相比,本题多了一个剪枝条件:

if (i > 0 && nums[i] == nums[i - 1] && !visited[i - 1])
    continue;
class Solution {
    
    
public:
    vector<vector<int>> permuteUnique(vector<int>& nums) {
    
    
        int n = nums.size();
        vector<vector<int>> res;
        if (n == 0)
            return res;
        sort(nums.begin(),nums.end());//先进行排序,剪枝的前提
        vector<bool> visited(n,false);//用于标记数字是否已经用过
        vector<int> path;//保存遍历的路径结果
        dfs(nums,path,visited,res);
        return res;
    }
    void dfs(vector<int>& nums, vector<int>& path, vector<bool>& visited, vector<vector<int>>& res){
    
    
        if (path.size() == nums.size()){
    
    
            res.push_back(path);
            return;
        }
        for (int i = 0; i < nums.size(); i++){
    
    
            if (visited[i])
                continue;
            //第i个数字没有访问过
            //剪枝条件:i > 0 是为了保证 nums[i - 1] 有意义
            //写 !visited[i-1]是因为nums[i-1]在深度优先遍历的过程中刚刚被撤销选择
            if (i > 0 && nums[i] == nums[i - 1] && !visited[i - 1])
                continue;
            path.push_back(nums[i]);//将数字i加入路径中
            visited[i] = true;
            dfs(nums,path,visited,res);//继续遍历
            visited[i] = false;//回溯,将该数弹出并置为没有访问过
            path.pop_back();
        }
    }
};
3.3 数组中数字组合的和为给定值(NO.39)

题目描述:给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。candidates 中的数字可以无限制重复被选取。
输入:candidates = [2,3,6,7], target = 7;所求解集为:[ [7], [2,2,3]]
解题思路:使用回溯法+剪枝。
如果不剪枝,那么遍历到叶子节点的结果如上,会产生3个等效的结果,[2,2,3], [2,3,2], [3,2,2]。
遇到这一类相同元素不计算顺序的问题,我们在搜索的时候就需要按某种顺序搜索。具体的做法是:每一次搜索的时候设置 下一轮搜索的起点 begin。即:从每一层的第 2 个结点开始,都不能再搜索产生同一层结点已经使用过的 candidate 里的元素。
在这里插入图片描述

class Solution {
    
    
public:
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
    
    
        int n = candidates.size();
        vector<vector<int>> res;
        if (n == 0)
            return res;
        vector<int> path;
        dfs(candidates,0,target,path,res);
        return res;
    }
    void dfs(vector<int>& candidates, int begin, int target, vector<int>& path, vector<vector<int>>& res){
    
    
        if (target < 0)
            return;
        if (target == 0){
    
    
            res.push_back(path);
            return;
        }
        for (int i = begin; i < candidates.size(); i++){
    
    
            path.push_back(candidates[i]);
            dfs(candidates,i,target-candidates[i],path,res);
            path.pop_back();
        }
    }
};
3.4 组合总和II (NO.40)

题目描述:给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。candidates 中的每个数字在每个组合中只能使用一次。
输入: candidates = [10,1,2,7,6,1,5], target = 8;所求解集为:[ [1, 7], [1, 2, 5], [2, 6], [1, 1, 6]]
解题思路:与上一题的区别在于:本题数组中含有重复元素,且每个数字在每个组合中只能使用一次。因为含有重复元素,所以要采用类似于47题的剪枝方法;每个数字只能使用一次,所以begin = i + 1,即在下一层遍历中不能再用nums[i]。

class Solution {
    
    
public:
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
    
    
        vector<vector<int>> res;
        int n = candidates.size();
        if (n == 0)
            return res;
        sort(candidates.begin(),candidates.end());
        vector<int> path;
        int begin = 0;
        dfs(candidates,begin,target,path,res);
        return res;
    }
    void dfs(vector<int>& candidates, int begin, int target, vector<int>& path, vector<vector<int>>& res){
    
    
        if (target == 0){
    
    
            res.push_back(path);
            return;
        }
        if (target < 0)//剪枝
            return;
        for (int i = begin; i < candidates.size(); i++){
    
    
            if (i > begin && candidates[i] == candidates[i - 1])//剪枝
                continue;
            path.push_back(candidates[i]);
            // 因为元素只能使用一次,这里递归传递下去的是 i + 1 而不是 i
            dfs(candidates,i+1,target-candidates[i], path, res);
            path.pop_back();
        }
    }
};
3.5 组合总和III(NO.216)

题目描述:找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。
注意:所有数字都是正整数。解集不能包含重复的组合。
输入: k = 3, n = 9;输出: [[1,2,6], [1,3,5], [2,3,4]]
解题思路:本题有两个条件:(1)数字不能重复使用,所以在继续进行深度遍历的时候,下一次从i+1开始;(2)不能包含重复的集合,所以使用begin按顺序搜索。

class Solution {
    
    
public:
    vector<vector<int>> combinationSum3(int k, int n) {
    
    
        vector<vector<int>> res;
        vector<int> path;
        dfs(k,n,1,path,res);
        return res;
    }
    void dfs(int k, int n, int begin, vector<int>& path, vector<vector<int>>& res){
    
    
        if (k == 0 && n == 0){
    
    //结束条件
            res.push_back(path);
            return;
        }
        if (k <= 0 || n <= 0)//剪枝
            return;
        for (int i = begin; i <= 9; i++){
    
    
            path.push_back(i);
            dfs(k-1, n-i, i+1, path, res);//继续深度遍历
            path.pop_back();//回溯
        }
    }
};
3.6 子集(NO.78)

t题目描述:给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。解集不能包含重复的子集。
输入: nums = [1,2,3];输出:[ [3], [1], [2], [1,2,3], [1,3], [2,3], [1,2], []]
解题思路:使用回溯法。因为相同元素不考虑顺序,所以使用39题的剪枝方法。

class Solution {
    
    
public:
    vector<vector<int>> subsets(vector<int>& nums) {
    
    
        vector<vector<int>> res;
        int n = nums.size();
        if (n == 0)
            return res;
        for (int len = 0; len <= nums.size(); len++){
    
    
            vector<int> path;
            dfs(nums,0,len,path,res);
        }
        return res;
    }
    void dfs(vector<int>& nums, int begin, int len, vector<int>& path, vector<vector<int>>& res){
    
    
        if (path.size() == len){
    
    
            res.push_back(path);
            return;
        }
        for (int i = begin; i < nums.size(); i++){
    
    
            path.push_back(nums[i]);
            dfs(nums,i+1,len,path,res);
            path.pop_back();
        }
    }
};
3.7 子集II(NO.90)

题目描述:给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。解集不能包含重复的子集。
输入: [1,2,2];输出:[ [2], [1], [1,2,2], [2,2], [1,2], []]
解题思路: 与上一题的区别在于数组中含有重复元素。所以使用类似于47题的方法进行了剪枝。

class Solution {
    
    
public:
    vector<vector<int>> subsetsWithDup(vector<int>& nums) {
    
    
        vector<vector<int>> res;
        int n = nums.size();
        if (n == 0)
            return res;
        sort(nums.begin(),nums.end());
        for (int len = 0; len <= n; len++){
    
    
            vector<int> path;
            dfs(nums,0,len,path,res);
        }
        return res;
    }
    void dfs(vector<int>& nums, int begin, int len, vector<int>& path, vector<vector<int>>& res){
    
    
        if (path.size() == len){
    
    
            res.push_back(path);
            return;
        }
        for (int i = begin; i < nums.size(); i++){
    
    
        	//剪枝
            if (i > begin && nums[i] == nums[i-1])
                continue;
            path.push_back(nums[i]);
            dfs(nums,i+1,len,path,res);
            path.pop_back();
        }
    }
};
3.8 分割回文串(NO.131)

题目描述:给定一个字符串 s,将 s 分割成一些子串,使每个子串都是回文串。返回 s 所有可能的分割方案。
输入: “aab”; 输出:[ [“aa”,“b”], [“a”,“a”,“b”]]
解题思路:使用回溯法。start表示所截取子串的开始下标,在每一个分支中,截取结束下标可以取start到s.size()-1中的任意值,表示截取了不同长度的子串。判断这个子串是否为回文串,如果不是,进行剪枝,如果是,则继续截取。

class Solution {
    
    
public:
    vector<vector<string>> res;
    void backtrack(string s,vector<string>&path,int start){
    
    
        string temp;
        if(start == s.size()){
    
    //判断结束条件
            res.push_back(path);
            return;
        }
        for(int i=start;i<s.size();i++){
    
    
            bool flag=true;
            //从start截取到i得到的子串
            temp=s.substr(start, i-start+1); 
            //判断是否为回文
            int wide=temp.size();
            for(int j=0;j<wide;j++){
    
            
                if(temp[j]!=temp[wide-1-j]){
    
    
                    flag=false;
                    break;
                }
            }
            //不是回文子串,剪枝
            if(flag==false) continue;
            //是回文子串
            path.push_back(temp);
            backtrack(s,path,i+1);//下一次从i+1开始截取,start=i+1
            path.pop_back();    //还原

        }
    }
    vector<vector<string>> partition(string s) {
    
    
        vector<string> path;
        backtrack(s,path,0);
        return res;
    }
};

3.9 电话号码的字母组合(NO.17)

题目描述:给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
解题思路

class Solution {
    
    
private:
    unordered_map<char,string> myMap = 
    {
    
    {
    
    '2',"abc"},{
    
    '3',"def"},{
    
    '4',"ghi"},{
    
    '5',"jkl"},
    {
    
    '6',"mno"},{
    
    '7',"pqrs"},{
    
    '8',"tuv"},{
    
    '9',"wxyz"}};
    vector<string> res;
    string current;
    void dfs(int index, string digits){
    
    
        if (index == digits.size()){
    
    
            res.push_back(current); 
            return;
        }
        string tmp = myMap[digits[index]];
        for (int i = 0; i < tmp.size(); i++){
    
    
            current.push_back(tmp[i]);//添加一个字符
            dfs(index+1, digits);//继续递归
            current.pop_back();//回溯
        }
    }
public:
    vector<string> letterCombinations(string digits) {
    
    
        if (digits.size() == 0)
            return vector<string>();
        dfs(0, digits);
        return res;
    }
};
3.10 字符串与矩阵路径(剑指offer)

题目描述:请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一格开始,每一步可以在矩阵中向左、右、上、下移动一格。如果一条路径经过了矩阵的某一格,那么该路径不能再次进入该格子。例如,在下面的3×4的矩阵中包含一条字符串“bfce”的路径。
[[“a”,“b”,“c”,“e”],
[“s”,“f”,“c”,“s”],
[“a”,“d”,“e”,“e”]]
但矩阵中不包含字符串“abfb”的路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入这个格子。

class Solution {
    
    
public:
    bool exist(vector<vector<char>>& board, string word) {
    
    
        int rows = board.size();
        int cols = board[0].size();
        bool res = false;
        vector<vector<bool>> visited(rows,vector<bool>(cols,false));
        for (int i = 0; i < rows; i++){
    
    
            for (int j = 0; j < cols; j++){
    
    
                string path;
                res = res || dfs(board,word,path,visited,rows,cols,i,j);
            }
        }
        return res;
    }
    bool dfs(vector<vector<char>>& board, string word, string& path, vector<vector<bool>>& visited, int rows, int cols, int row, int col ){
    
    
        if (path.size() == word.size())
            return true;
        bool hasPath = false;
        if (row >= 0 && col >= 0 && row < rows && col < cols && board[row][col] == word[path.size()] && !visited[row][col]){
    
    
            path.push_back(board[row][col]);
            visited[row][col] = true;
            hasPath = dfs(board,word,path,visited,rows,cols,row-1,col)
                || dfs(board,word,path,visited,rows,cols,row,col-1)
                || dfs(board,word,path,visited,rows,cols,row+1,col)
                || dfs(board,word,path,visited,rows,cols,row,col+1);
            if (!hasPath){
    
    
                visited[row][col] = false;
                path.pop_back();
            }
        }
        return hasPath;
    }
};
3.11 括号生成(NO.22)

题目描述:数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。
输入:n = 3输出:[ “((()))”, “(()())”, “(())()”, “()(())”,"()()()" ]
解题思路:可以使用暴力解法,往长度为2n的字符串中添加左括号或者右括号,然后判断是否有效。也可以使用回溯法。

class Solution {
    
    
public:
    vector<string> generateParenthesis(int n) {
    
    
        string str;
        vector<string> res;
        dfs(n,n,str,res);
        return res;
    }
    void dfs(int left, int right, string& str, vector<string>& res){
    
    
        if (left == 0 && right == 0){
    
    
            res.push_back(str);
            return;
        }
        if (left > 0){
    
    //左括号只要还有,就可以随便放
            str.push_back('(');
            dfs(left-1, right, str, res);
            str.pop_back();
        }
        if (right > left){
    
    //添加右括号时,字符串中左括号的个数要大于右括号的个数
            str.push_back(')');
            dfs(left, right-1, str, res);
            str.pop_back();
        }
    }
};

猜你喜欢

转载自blog.csdn.net/qq_42820853/article/details/108150037