BFS与DFS专题

本文探讨了BFS(广度优先搜索)和DFS(深度优先搜索)在解决LeetCode问题中的应用,如求二叉树的最小深度、完美平方组合、图像渲染、岛屿数量、被X包围的区域、树的直径、单词阶梯、01矩阵、课程安排等。通过实例展示了如何使用这两种算法解决问题。
摘要由CSDN通过智能技术生成
/*
BFS:
1.空间是指数级别的  大!!
2.不会有爆栈的风险
3.方便求最短、最小

DFS:
1.空间和深度成正比 小!!
2.有爆栈的风险,比如树的深度最坏可能有十万层
3.不能搜 最短,最小


*/

LeetCode 111. Minimum Depth of Binary Tree

给定一个二叉树,找出其最小深度。

最小深度是从根节点到最近叶子节点的最短路径上的节点数量。

说明: 叶子节点是指没有子节点的节点。

示例:

给定二叉树 [3,9,20,null,null,15,7],

    3
   / \
  9  20
    /  \
   15   7

返回它的最小深度 2.

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    int minDepth(TreeNode* root) {
        if(!root) return 0;
        int left = minDepth(root->left);
        int right = minDepth(root->right);
        if(!left || !right) return left + right + 1;
        return min(left, right) + 1;
    }
};

LeetCode 279. Perfect Squares

给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, …)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。

示例 1:

输入: n = 12
输出: 3
解释: 12 = 4 + 4 + 4.

示例 2:

输入: n = 13
输出: 2
解释: 13 = 4 + 9.

class Solution {
public:
    //bfs
    int numSquares(int n) {
        queue<int> q;//定义一个队列
        vector<int> dist(n + 1, INT_MAX);//距离 所有点到0 的距离 初始化为正无穷
        q.push(0);//开始push一个元素
        dist[0] = 0; //初始化距离
        while(q.size()) //q有元素一直循环
        {
            int t = q.front();//每次把队头元素拿出来
            q.pop();
            if(t == n) return dist[t];
            for(int i = 1; i * i + t <= n; i++)
            {
                int j = i * i + t;
                if(dist[j] > dist[t] + 1)
                {
                    dist[j] = dist[t] + 1;
                    q.push(j);
                }
            }
        }
        return 0;
    }
};

LeetCode 733. Flood Fill

有一幅以二维整数数组表示的图画,每一个整数表示该图画的像素值大小,数值在 0 到 65535 之间。

给你一个坐标 (sr, sc) 表示图像渲染开始的像素值(行 ,列)和一个新的颜色值 newColor,让你重新上色这幅图像。

为了完成上色工作,从初始坐标开始,记录初始坐标的上下左右四个方向上像素值与初始坐标相同的相连像素点,接着再记录这四个方向上符合条件的像素点与他们对应四个方向上像素值与初始坐标相同的相连像素点,……,重复该过程。将所有有记录的像素点的颜色值改为新的颜色值。

最后返回经过上色渲染后的图像。

示例 1:

输入:
image = [[1,1,1],[1,1,0],[1,0,1]]
sr = 1, sc = 1, newColor = 2
输出: [[2,2,2],[2,2,0],[2,0,1]]
解析:
在图像的正中间,(坐标(sr,sc)=(1,1)),
在路径上所有符合条件的像素点的颜色都被更改成2。
注意,右下角的像素没有更改为2,
因为它不是在上下左右四个方向上与初始点相连的像素点。

注意:

image 和 image[0] 的长度在范围 [1, 50] 内。
给出的初始点将满足 0 <= sr < image.length 和 0 <= sc < image[0].length。
image[i][j] 和 newColor 表示的颜色值在范围 [0, 65535]内。

class Solution {
public:
    /*
    1 1 1     2 2 2
    1 1 0  -> 2 2 0
    1 0 1     2 0 1
    
    dfs
    */
    vector<vector<int>> floodFill(vector<vector<int>>& image, int sr, int sc, int newColor) {
        if(image.empty() || image[0].empty()) return image;
        int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};//顺时针
        int oldColor = image[sr][sc];
        if(oldColor == newColor) return image;//颜色相同 直接放回
        image[sr][sc] = newColor;
        for(int i = 0; i < 4; i++)
        {
            int x = sr + dx[i], y = sc + dy[i];
            if(x >= 0 && x < image.size() && y >= 0 && y < image[0].size() && image[x][y] == oldColor)
                floodFill(image, x, y, newColor);
        }
        return image;
    }
};

LeetCode 200. Number of Islands

给定一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,计算岛屿的数量。一个岛被水包围,并且它是通过水平方向或垂直方向上相邻的陆地连接而成的。你可以假设网格的四个边均被水包围。

示例 1:

输入:
11110
11010
11000
00000

输出: 1

示例 2:

输入:
11000
11000
00100
00011

输出: 3

class Solution {
public:
    int n, m;
    int numIslands(vector<vector<char>>& grid) {
        if(grid.empty() || grid[0].empty()) return 0;
        n = grid.size(), m = grid[0].size();
        int res = 0;
        for(int i = 0; i < n; i++)
            for(int j = 0; j < m; j++)
                if(grid[i][j] == '1') //要先判断
                {
                    res ++;
                    dfs(grid, i, j);
                }
        return res;
    }
    
    void dfs(vector<vector<char>>& grid, int x, int y)
    {
        int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
        grid[x][y] = '0';//每次开始赋值'0'
        for(int i = 0; i < 4; i++)
        {
            int a = x + dx[i], b = y + dy[i];
            if(a >= 0 && a < n && b >= 0 && b < m && grid[a][b] == '1')
                dfs(grid, a, b);
        }
    }
};

LeetCode 130. Surrounded Regions

给定一个二维的矩阵,包含 ‘X’ 和 ‘O’(字母 O)。

找到所有被 ‘X’ 围绕的区域,并将这些区域里所有的 ‘O’ 用 ‘X’ 填充。

示例:

X X X X
X O O X
X X O X
X O X X

运行你的函数后,矩阵变为:

X X X X
X X X X
X X X X
X O X X

解释:

被围绕的区间不会存在于边界上,换句话说,任何边界上的 ‘O’ 都不会被填充为 ‘X’。 任何不在边界上,或不与边界上的 ‘O’ 相连的 ‘O’ 最终都会被填充为 ‘X’。如果两个元素在水平或垂直方向相邻,则称它们是“相连”的。

class Solution {
public:
    int n, m;
    vector<vector<bool>> st;
    
    void solve(vector<vector<char>>& board) {
        if(board.empty() || board[0].empty()) return;
        n = board.size(), m = board[0].size();
        
        // vector<vector<bool>> st(n, vector<bool>(m, false));//初始化
        for(int i = 0; i < n; i++)//当前格子有没有被扫描过
        {
            vector<bool> temp;
            for(int j = 0; j < m; j++)
                temp.push_back(false);
            st.push_back(temp);
        }
        
        for(int i = 0; i < n; i++)//遍历左右两条边
        {
            if(board[i][0] == 'O') dfs(board, i, 0);
            if(board[i][m - 1] == 'O') dfs(board, i, m - 1);
        }
        for(int i = 0; i < m; i++)//遍历上下两条边
        {
            if(board[0][i] == 'O') dfs(board, 0, i);
            if(board[n - 1][i] == 'O') dfs(board, n - 1, i);
        }
        
        for(int i = 0; i < n; i++)//扫描一遍 赋值 X
            for(int j = 0; j < m; j++)
                if(!st[i][j]) //将所有未遍历到的位置变成'X'。
                    board[i][j] = 'X';       
    }
    
    void dfs(vector<vector<char>>& board, int x, int y)
    {
        st[x][y] = true;
        int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
        for(int i = 0; i < 4; i ++)
        {
            int a = x + dx[i], b = y + dy[i];
            if(a >= 0 && a < n && b >= 0 && b < m && !st[a][b] && board[a][b] == 'O')
                dfs(board, a, b);
        }
        
    }
};

LeetCode 543. Diameter of Binary Tree

给定一棵二叉树,你需要计算它的直径长度。一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过根结点。

示例 :
给定二叉树

      1
     / \
    2   3
   / \     
  4   5    

返回 3, 它的长度是路径 [4,2,1,3] 或者 [5,2,1,3]。

注意:两结点之间的路径长度是以它们之间边的数目表示。

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    int ans = 0;
    int diameterOfBinaryTree(TreeNode* root) {
        dfs(root);
        return ans;
    }
    
    int dfs(TreeNode * u)
    {
        if(!u) return 0;
        int l = dfs(u->left);
        int r = dfs(u->right);
        ans = max(ans, l + r);
        return max(l, r) + 1; //更新当前点
    }
};

LeetCode 127. Word Ladder

给定两个单词(beginWord 和 endWord)和一个字典,找到从 beginWord 到 endWord 的最短转换序列的长度。转换需遵循如下规则:

每次转换只能改变一个字母。
转换过程中的中间单词必须是字典中的单词。

说明:

如果不存在这样的转换序列,返回 0。
所有单词具有相同的长度。
所有单词只由小写字母组成。
字典中不存在重复的单词。
你可以假设 beginWord 和 endWord 是非空的,且二者不相同。

示例 1:

输入:
beginWord = “hit”,
endWord = “cog”,
wordList = [“hot”,“dot”,“dog”,“lot”,“log”,“cog”]

输出: 5

解释: 一个最短转换序列是 “hit” -> “hot” -> “dot” -> “dog” -> “cog”,
返回它的长度 5。

示例 2:

输入:
beginWord = “hit”
endWord = “cog”
wordList = [“hot”,“dot”,“dog”,“lot”,“log”]

输出: 0

解释: endWord “cog” 不在字典中,所以无法进行转换。

class Solution {
public:
    /*
    (最短路,BFS) O(n^2L)
    对问题进行抽象:
    将单词看做点,如果两个单词可以相互转化,则在相应的点之间连一条无向边。
    问题就变成了求从起点到终点的最短路。
    由于边权都相等,所以可以用BFS求最短路。
    */  
    
    int ladderLength(string beginWord, string endWord, vector<string>& wordList) { 
        unordered_set<string> dict(wordList.begin(), wordList.end());
        if(!dict.count(endWord)) return 0;
       
        queue<string> que;
        que.push(beginWord);//初始化
        int step = 0;
        while(!que.empty())
        {
            step ++;    
            for(int size = que.size(); size; size--)
            {
                string w = que.front(); //w = hit
                // cout << w << endl;
                que.pop();  
                for(int i = 0; i < beginWord.size(); i++)
                {
                    char c = w[i];
                    for(int j = 'a'; j <= 'z'; j++)
                    {
                        w[i] = j;
                        if(w == endWord) return step + 1;
                        if(!dict.count(w)) continue;//当前w不在 dict 继续下一次循环
                        dict.erase(w);//找到 则删除
                        que.push(w);//加入 栈
                    }
                   w[i] = c;//恢复
                }            
            }            
        }
        return 0;
    }
};

LeetCode 542. 01 Matrix

给定一个由 0 和 1 组成的矩阵,找出每个元素到最近的 0 的距离。

两个相邻元素间的距离为 1 。

示例 1:
输入:

0 0 0
0 1 0
0 0 0

输出:

0 0 0
0 1 0
0 0 0

示例 2:
输入:

0 0 0
0 1 0
1 1 1

输出:

0 0 0
0 1 0
1 2 1

注意:

给定矩阵的元素个数不超过 10000。
给定矩阵中至少有一个元素是 0。
矩阵中的元素只在四个方向上相邻: 上、下、左、右。

class Solution {
public:
    /*
    (宽度优先搜索) O(nm)
    1.定义一个队列,初始时将所有 0 元素的坐标进队;定义答案数组 dis,0 元素位置的值为 0,其余为 -1。
    2.对这个队列开始进行宽度优先搜索,每次扩展上下左右四个方向,若发现新的位置在 dis 中值为 -1,则更新新位置的答案为当前位置答案加 1。    
   
    */
    vector<vector<int>> updateMatrix(vector<vector<int>>& matrix) {
        int n = matrix.size(), m = matrix[0].size();
        vector<vector<int>> dis(n, vector<int>(m, -1));//初始化-1
        queue<pair<int, int>> q;
        for(int i = 0; i < n; i++)
            for(int j = 0; j < m; j++)
                if(!matrix[i][j])//如果原数组是0 则输出数组是0
                {
                    dis[i][j] = 0;
                    q.push(make_pair(i, j));
                }
        int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
        while(q.size())
        {
            auto t = q.front();
            q.pop();
            for(int i = 0; i < 4; i ++)
            {
                int x = t.first + dx[i], y = t.second + dy[i];
                if(x >= 0 && x < n && y >= 0 && y < m && dis[x][y] == -1)
                {
                    dis[x][y] = dis[t.first][t.second] + 1;
                    q.push(make_pair(x, y));
                }
            }
            
        }
        return dis;
    }
};

LeetCode 207. Course Schedule

现在你总共有 n 门课需要选,记为 0 到 n-1。

在选修某些课程之前需要一些先修课程。 例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示他们: [0,1]

给定课程总量以及它们的先决条件,判断是否可能完成所有课程的学习?

示例 1:

输入: 2, [[1,0]]
输出: true
解释: 总共有 2 门课程。学习课程 1 之前,你需要完成课程 0。所以这是可能的。

示例 2:

输入: 2, [[1,0],[0,1]]
输出: false
解释: 总共有 2 门课程。学习课程 1 之前,你需要先完成​课程 0;并且学习课程 0 之前,你还应先完成课程 1。这是不可能的。

说明:
1.输入的先决条件是由边缘列表表示的图形,而不是邻接矩阵。详情请参见图的表示法。
2.你可以假定输入的先决条件中没有重复的边。

提示:
1.这个问题相当于查找一个循环是否存在于有向图中。如果存在循环,则不存在拓扑排序,因此不可能选取所有课程进行学习。
2.通过 DFS 进行拓扑排序。
3.拓扑排序也可以通过 BFS 完成。

class Solution {
public:
    /*
    拓扑排序 O(n+m)
    1.将先修关系构成一张图,由每个数对的第二个数字向第一个数字连边。
    2.首先将所有入度为0的点进队,准备拓扑排序。
    3.宽搜过程中,将当前结点所关联的结点的入度减1;若发现新的入度为0的结点,则将其进队。
    4.最后如果遍历了所有结点,则说明可以满足要求;否则,先修关系存在环。
    */
                                //注意这里有pair 才可以用 first second
    bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
        vector<vector<int>> graph(numCourses);
        vector<int> in_degree(numCourses, 0);
        for(int i = 0; i < prerequisites.size(); i++)
        {
            in_degree[prerequisites[i][0]]++; //先修关系构成一张图
            graph[prerequisites[i][1]].push_back(prerequisites[i][0]);//第二个数字向第一个数字连边
        }
        
        queue<int> q;
        vector<bool> vis(numCourses, false);
        for(int i = 0; i < numCourses; i ++)
            if(in_degree[i] == 0)
                q.push(i);
        while(!q.empty()) //队列不为空 拓扑排序
        {
            int st = q.front();
            q.pop();
            vis[st] = true;
            for(int i = 0; i < graph[st].size(); i++)
            {
                in_degree[graph[st][i]] --;
                if(in_degree[graph[st][i]] == 0)
                    q.push(graph[st][i]);
            }
        }
        for(int i = 0; i < numCourses; i++)//如果遍历了所有结点,则说明可以满足要求        
            if(vis[i] == false)
                return false;
        return true;
    }
};

LeetCode 210. Course Schedule II

现在你总共有 n 门课需要选,记为 0 到 n-1。

在选修某些课程之前需要一些先修课程。 例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示他们: [0,1]

给定课程总量以及它们的先决条件,返回你为了学完所有课程所安排的学习顺序。

可能会有多个正确的顺序,你只要返回一种就可以了。如果不可能完成所有课程,返回一个空数组。

示例 1:

输入: 2, [[1,0]]
输出: [0,1]
解释: 总共有 2 门课程。要学习课程 1,你需要先完成课程 0。因此,正确的课程顺序为 [0,1] 。

示例 2:

输入: 4, [[1,0],[2,0],[3,1],[3,2]]
输出: [0,1,2,3] or [0,2,1,3]
解释: 总共有 4 门课程。要学习课程 3,你应该先完成课程 1 和课程 2。并且课程 1 和课程 2 都应该排在课程 0 之后。
因此,一个正确的课程顺序是 [0,1,2,3] 。另一个正确的排序是 [0,2,1,3] 。

说明:
1.输入的先决条件是由边缘列表表示的图形,而不是邻接矩阵。详情请参见图的表示法。
2.你可以假定输入的先决条件中没有重复的边。

提示:
1.这个问题相当于查找一个循环是否存在于有向图中。如果存在循环,则不存在拓扑排序,因此不可能选取所有课程进行学习。
2.通过 DFS 进行拓扑排序。
3.拓扑排序也可以通过 BFS 完成。

class Solution {
public:
    /*
    (拓扑排序) O(n+m)
    见 LeetCode207
    拓扑排序时用的队列的进队顺序就是一个可行的输出方案。
    */
    vector<int> findOrder(int numCourses, vector<vector<int>>& prerequisites) {
                                   //注意这里有pair 才可以用 first second
        vector<int> ans;
        vector<vector<int>> graph(numCourses);
        vector<int> in_degree(numCourses, 0);
        for(int i = 0; i < prerequisites.size(); i++)
        {
            in_degree[prerequisites[i][0]]++; //先修关系构成一张图
            graph[prerequisites[i][1]].push_back(prerequisites[i][0]);//第二个数字向第一个数字连边
        }
        
        queue<int> q;
        vector<bool> vis(numCourses, false);
        for(int i = 0; i < numCourses; i ++)
            if(in_degree[i] == 0)
                q.push(i);
        while(!q.empty()) //队列不为空 拓扑排序
        {
            int st = q.front();
            q.pop();
            
            ans.push_back(st);
            
            vis[st] = true;
            for(int i = 0; i < graph[st].size(); i++)
            {
                in_degree[graph[st][i]] --;
                if(in_degree[graph[st][i]] == 0)
                    q.push(graph[st][i]);
            }
        }
        for(int i = 0; i < numCourses; i++)//如果遍历了所有结点,则说明可以满足要求        
            if(vis[i] == false)
                return vector<int>{};//返回空
        return ans;
    }
};        
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值