图(bfs)

本文介绍了宽度优先搜索(BFS)在解决多种算法问题中的应用,包括拓扑排序、找到矩阵中元素到最近0的距离、最短路径、二叉树直径、最小深度、洪水填充、完美平方数、字母排列、解码字符串和金字塔过渡矩阵问题。通过BFS,可以高效地解决这些问题,例如在拓扑排序中寻找课程的学习顺序,或在二叉树问题中计算直径和最小深度。
摘要由CSDN通过智能技术生成

1. (Course Schedule) 现在你总共有 n 门课需要选,记为 0 到 n-1。
在选修某些课程之前需要一些先修课程。例如,想要学习课程 0,你需要先完成课程 1 ,我们用一个匹配来表示他们: [0,1]
给定课程总量以及它们的先决条件,返回你为了学完所有课程所安排的学习顺序。
可能会有多个正确的顺序,你只要返回一种就可以了。如果不可能完成所有课程,返回一个空数组。
在这里插入图片描述
(拓扑排序) O(n+m)

  • 将先修关系构成一张图,由每个数对的第二个数字向第一个数字连边。
  • 首先将所有入度为 0 的点进队,准备用宽度优先搜索进行拓扑排序。
  • 宽搜过程中,将当前结点所关联的结点的入度减 1;若发现新的入度为 0 的结点,则将其进队。
  • 最后如果遍历了所有结点,则说明可以满足要求;否则,先修关系存在环。
class Solution {
public:
    vector<int> canFinish(int n, vector<vector<int>>& pre) {
        
        vector<vector<int>> graph(n);
        vector<int> degree(n,0);
        
        for(int i=0;i<pre.size();i++){
            degree[pre[i][0]]++;
            graph[pre[i][1]].push_back(pre[i][0]);
        }
        
        vector<int> vis(n,0);
        queue<int> q;
        for(int i=0;i<n;i++){
            if(degree[i]==0)
                q.push(i);
        }
        vector<int> res;
        while(q.size()){
            int cnt=q.front();
            q.pop();
            res.push_back(cnt);
            vis[cnt]=1;
            
            for(int i=0;i<graph[cnt].size();i++){
                degree[graph[cnt][i]]--;
                if(degree[graph[cnt][i]]==0)
                    q.push(graph[cnt][i]);
            }
        }
        
        for(int i=0;i<n;i++){
            if(vis[i]==0)
                return vector<int> {};
        }
        return res;
        
    }
};

2.( 01 Matrix ) 给定一个由 0 和 1 组成的矩阵,找出每个元素到最近的 0 的距离。
两个相邻元素间的距离为 1 。
在这里插入图片描述
(宽度优先搜索) O(nm)

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

3(Word Ladder)给定两个单词 (beginWord和endWord)以及一个单词列表,求从beginWord到endWord的转化序列的最短长度。
在这里插入图片描述
(最短路,BFS) O(n2L)
我们对问题进行抽象:
将单词看做点,如果两个单词可以相互转化,则在相应的点之间连一条无向边。那问题就变成了求从起点到终点的最短路。
由于边权都相等,所以可以用BFS求最短路。

时间复杂度分析: BFS的过程中每个点会遍历一次,每次遍历时需要枚举所有节点,判断是否存在边,总共需要判断 n2次,假设单词长度是 L,则每次判断时还需要 O(L)的计算量,所以总时间复杂度是 O(n2L)

class Solution {
public:
    
    bool check(string a,string b){
        int cnt=0;
        for(int i=0;i<a.size();i++){
            if(a[i]!=b[i])
                cnt++;
        }
        return cnt==1;
    }
    
    int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
        unordered_map<string,int> res;
        queue<string> q;
        
        q.push(beginWord);
        res[beginWord]=1;
        
        while(q.size()){
            auto t=q.front();
            q.pop();
            
            if(t==endWord) return res[t];
            
            for(auto &word:wordList){
                if(check(t,word)&&res[word]==0)
                {
                    res[word]=res[t]+1;
                    q.push(word);
                }
            }
        }
        
        return 0;
    }
};

4 (Diameter of Binary Tree ) 给定一棵二叉树,你需要计算它的直径长度。一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径穿过或者不穿过根结点。

在这里插入图片描述

class Solution {
public:
    int dfs(TreeNode *r, int &ans) {
        if (r == NULL)
            return -1;
        int d1 = dfs(r -> left, ans);
        int d2 = dfs(r -> right, ans);
        ans = max(ans, d1 + d2 + 2);//回溯到上一层,所有左右两边都要+1
        return max(d1, d2) + 1;//返回某一边最长的那一边
    }
    int diameterOfBinaryTree(TreeNode* root) {
        int ans = 0;
        dfs(root, ans);
        return ans;
    }
};

5 (Minimum Depth of Binary Tree) 给定一棵二叉树,找到它的最小深度。
最小深度定义为:从根节点到叶节点的路径长度的最小值。

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

6 (Flood Fill) 一幅图像(image)由二维整数数组表示,每个整数表示图像的像素值(从0到65535)。

给定坐标(sr, sc)和一个像素值newColor,其中(sr, sc)表示Flood Fill的起始像素点(行和列。

在一次Flood Fill 的执行过程中,依次把下列点的颜色换成newColor:

(1)从起始像素点(sr, sc)开始

(2)然后是起始像素点四周(上下左右)的点,要求和起始像素点颜色一致,

(3)然后是上述点四周(上下左右)的点,同样要求和起始像素点颜色一致,

(4)以此类推。
在这里插入图片描述

//bfs:
class Solution {
public:
    vector<vector<int>> floodFill(vector<vector<int>>& image, int sr, int sc, int newColor) {
        int n=image.size(),m=image[0].size();
        if(image[sr][sc]==newColor) return image;
        vector<vector<int>> vis(n,vector<int>(m,0));
        int dx[4]={-1,0,1,0},dy[4]={0,1,0,-1};
        int old=image[sr][sc];
        queue<pair<int,int>> q;
        q.push({sr,sc});
        vis[sr][sc]=1;
        image[sr][sc]=newColor;
        
        while(q.size()){
            auto t=q.front();
            int a=t.first,b=t.second;
            q.pop();
            
            for(int i=0;i<4;i++){
                int x=a+dx[i],y=b+dy[i];
                if(x>=0&&x<n&&y>=0&&y<m&&image[x][y]==old&&!vis[x][y]){
                    vis[x][y]=1;
                    image[x][y]=newColor;
                    q.push({x,y});
                }
            }
        }
        return image;
    }
};

//dfs:

class Solution {
public:
    vector<vector<int>> floodFill(vector<vector<int>>& image, int sr, int sc, int newColor) {
        if (image[sr][sc] == newColor) return image;
        int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
        int rawColor = image[sr][sc];
        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] == rawColor)
                floodFill(image, x, y, newColor);
        }
        return image;
    }
};

7 (Perfect Squares ) 给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, …)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。
在这里插入图片描述
设 f(i)表示通过平方数组成 i 所需要完全平方数的最少数量。

  • 初始时, f(0)=0 ,其余待定。
  • 转移时,对于一个 i,枚举 j,f(i)=min(f(i−j∗j)+1),其中1≤j≤√i。
  • 最终答案为 f(n)。

时间复杂度

class Solution {
public:
    int numSquares(int n) {
        vector<int> f(n+1,n);
        f[0]=0;
        
        for(int i=1;i<=n;i++)
            for(int j=1;j*j<=i;j++){
                f[i]=min(f[i],f[i-j*j]+1);
            }
        return f[n];
    }
};

8(Letter Case Permutation) 给定一个字符串S,我们可以将其中的大写字母换成小写字母,或将小写字母换成大写字母,从而得到一个新的字符串。
请返回所有可能得到的字符串。
在这里插入图片描述
(DFS) O(n×2n)
深度优先搜索。从左到右一位一位枚举:
如果遇到数字,则直接跳过当前位,枚举下一位;
如果遇到字母,则分别将当前位设成小写字母和大写字母,然后递归到下一位;
小技巧:可以用位运算改变当前字母的大小写,从而简化代码:将一个字母异或32,即可改变这个字母的大小写。

class Solution {
public:

    vector<string> ans;

    vector<string> letterCasePermutation(string S) {
        dfs(S, 0);
        return ans;
    }

    void dfs(string &S, int u)
    {
        if (u == S.size())
        {
            ans.push_back(S);
            return;
        }
        dfs(S, u + 1);
        if (S[u] >= 'A')
        {
            S[u] ^= 32;
            dfs(S, u + 1);
        }
    }
};

9(Decode String) 给定一个经过编码的字符串,返回它解码后的字符串。
编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k ,例如不会出现像3a或2[4]的输入。
在这里插入图片描述
(DFS) O(k^n)
用递归的思想来解决这个问题, 当遇到一个括号时,就将括号内的字符串截取出来作为一个新的子问题递归求解,比如对于s = “3[a2[c]]”,我们可以将a2[c]]作为一个新问题来求解,并将该子问题求解的结果加到上一层的答案中去。

时间复杂度
最坏情况下,假如字符串形如k[k[k[k[k[string]]]]]这种形式,不妨设嵌套层数为n,那么最后形成的字符串长度为 len(string)×k^n , 则时间复杂度至少与字符串的长度成正比。

class Solution {
public:
    string decodeString(string s) {
        
        string res="";
        for(int i=0;i<s.size();){
            if(!isdigit(s[i])){
                res+=s[i];
                i++;
            }
            else{
                int j=i;
                int t=0;
                while(isdigit(s[j])){
                    t=t*10+s[j]-'0';
                    j++;
                }
                int cnt=1;
                i=++j;
                while(cnt>0){
                    if(s[j]=='[') cnt++;
                    if(s[j]==']') cnt--;
                    j++;
                }
                while(t--)
                    res+=decodeString(s.substr(i,j-i-1));//最外层的]去掉
                i=j;
            }
        }
        return res;
    }
};

10 (Pyramid Transition Matrix) 给定一个bottom字符串,和allowed数组。
其中allowed数组中每一个字符串都含有三个字符(A,B,C)allowed数组表达一种规则,表示C的左孩子可以是A,右孩子可以是B。写一个程序判断给定bottom和allowed,是否能构造一个金字塔的结构,满足allowed的规则,并且金字塔的最后一层就是bottom字符串。
在这里插入图片描述
(DFS) O(2n)
在确定了一行的字符串以后,可以利用当前状态确定上一行,这样一直递推,可以确定最上面一层的状态。
如果最上面一层有字符出现,那么表明有存在的解,否则没有存在的解。
可以考虑从最底层开始搜索,遍历所有可能的字符串,在每一次都利用当前的bottom,标记当前扫描到的位置idx。
去allowed数组中查询是否有符合规则的字符,如果有,在当前行加上,并且进行下一轮的搜索。
如果没有就说明无法找到。

class Solution {
public:
    int n;
    bool dfs(string b,int depth,int index,vector<string>& allowed,string cur){
        if(cur.size()==b.size()-1){
            if(depth==n-1) return true;
            else 
               return dfs(cur,depth+1,0,allowed,"");
        }
        
        for(auto &a:allowed){
            if(a[0]==b[index]&&a[1]==b[index+1])
                if(dfs(b,depth,index+1,allowed,cur+a[2]))
                    return true;
        }
        return false;
    }
    
    bool pyramidTransition(string bottom, vector<string>& allowed) {
        n=bottom.size();
        return dfs(bottom,0,0,allowed,"");
    }
};
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值