常见搜索问题

深度优先算法

题目:695 岛屿的最大数量
给了一个01组成的数组,1为岛屿,求其中最大岛屿的面积。

解题
使用DFS+沉岛法,把搜索过了的岛屿下沉

class Solution {
    public int maxAreaOfIsland(int[][] grid) {
        //特判
        if(grid.length==0||grid[0].length==0)return 0;
        //DFS+沉岛思想
        //主函数遍历从所有点作为起点时,对其相连的面积做判断,找出最大的一块连续区域
        int ans = 0;
        for(int i = 0;i<grid.length;i++){
            for(int j = 0;j<grid[0].length;j++){
                if(grid[i][j]==1){
                    ans = Math.max(ans,dfs(grid,i,j));
                }
            }
        }
        return ans;

    }
    public int dfs(int[][] grid,int x,int y){
        //返回值为该块为起点的整个岛屿的大小
        //辅函数做递归,从主函数找到的第一块陆地开始,往其四周扩展,将这个岛屿的四周使用dfs的方式全部沉落
        //如果越界了,或者本身就是0,则返回0
        if(x<0||x>=grid.length||y<0||y>=grid[0].length||grid[x][y]==0)return 0;
        //否则说明该块为岛屿,下沉其和其周围的所有岛屿
        grid[x][y] = 0;
        int num = 1;
        num += dfs(grid,x-1,y);
        num += dfs(grid,x+1,y);
        num += dfs(grid,x,y-1);
        num += dfs(grid,x,y+1);
        return num;
    } 
}

剑指 Offer II 116. 朋友圈
题目:
给了一个数组,ij位置的元素表示i与j之间的关系,所有直接/间接认识的人组成了一个朋友圈,求一共有多少个朋友圈。

使用DFS+每个人是否加入朋友圈的一个一维数组实现

class Solution {
    public int findCircleNum(int[][] isConnected) {
        //特判
        if(isConnected.length==0||isConnected[0].length==0)return 0;
        //使用一个数组表示每一个元素是否有归属
        //使用DFS遍历所有元素,直至所有元素都有了自己的所属
        int cnt = 0;
        boolean used[] = new boolean[isConnected.length];
        for(int i =0;i<isConnected.length;i++){
            //遍历所有元素,如果它没有被遍历过,则从它开始找到所有属于它的圈子的
            if(used[i]==false){
                used[i] = true;
                dfs(used,isConnected,i);
                cnt++;
            }
            
        }
        return cnt;

    }

    public void dfs(boolean used[] ,int[][] isConnected,int x){
        for(int j = 0;j<isConnected[0].length;j++){
            if(isConnected[x][j]==1&&used[j]==false){
                used[j] = true;
                dfs(used,isConnected,j);
            }
        }
        return;
    }
}

417、大西洋、太平洋水流
给了一个矩阵,其中的元素表示每个位置的高度,求哪些位置放水可以流到两个洋?

解法:从每个点出发去看水能不能往低处流流入到两个洋比较耗时,不如直接从两个洋的水往高处流,看能能流到哪一些格子。看两个洋共同能流到的格子。

class Solution {
private:
    vector<vector<int>> ans;
    vector<int> direction{-1,0,1,0,-1};
public:
    vector<vector<int>> pacificAtlantic(vector<vector<int>>& heights) {
        //特判
        if(heights.size()==0||heights[0].size()==0)return{};
        //水往高处流,从大陆的边界进行遍历,找到两个海域分别能到达的位置
        int m = heights.size(), n = heights[0].size();
        vector<vector<bool>> pacifit_reach(m,vector<bool>(n,false));
        vector<vector<bool>> atlantic_reach(m,vector<bool>(n,false));
        for(int i = 0;i<m;i++){
            // cout<<"i = "<<i<<" pacific"<<endl;
            dfs(heights,pacifit_reach,i,0);
            // cout<<"i = "<<i<<" atlantic"<<endl;
            dfs(heights,atlantic_reach,i,n-1);
        }
        for(int j = 0;j<n;j++){
            // cout<<"j = "<<j<<" pacific"<<endl;
            dfs(heights,pacifit_reach,0,j);
            // cout<<"j = "<<j<<" atlantic"<<endl;
            dfs(heights,atlantic_reach,m-1,j);
        }
        for(int i = 0;i<m;i++){
            for(int j =0;j<n;j++){
                if(atlantic_reach[i][j]==true&&pacifit_reach[i][j]==true){
                    ans.push_back(vector<int>{i,j});
                }
            }
        }
        return ans;


    }
    void dfs(vector<vector<int>>& heights,vector<vector<bool>>& can_reach,int x,int y){
        
        //从某块海域出发,dfs搜索其周围的所有自己可到达的,海水倒流
        //只有对可以到达的地方,才使用dfs来遍历
        if(can_reach[x][y]==true)return;
        can_reach[x][y] = true;
        cout<<"x = "<<x<<" y = "<<y<<endl;
        //继续向四个方向找
        for(int i = 0;i<4;i++){
            int cur_x = x+direction[i];
            int cur_y = y+direction[i+1];
            if(cur_x>=0&&cur_x<heights.size()&&cur_y>=0&&cur_y<heights[0].size()&&heights[cur_x][cur_y]>=heights[x][y])
                dfs(heights,can_reach,cur_x,cur_y);
        }
        
    
    }
};

257.二叉树的遍历
返回二叉树所有从根节点到叶节点的路径。

解法:使用dfs实现
注意中间一个重要的点:这里面的字符串的回溯感觉会比较麻烦,因此在往下一层传递参数的时候,就只是值传递,因此回到上一层的时候就不需要再从字符串中把东西抽出来。
在确保了下一层会有东西的时候,在函数中添加->

class Solution {
public:
    vector<string> binaryTreePaths(TreeNode* root) {
        vector<string> ans;
        string sol;
        if(root==nullptr)return ans;
        dfs(root,ans,sol);
        return ans;

    }

    void dfs(TreeNode* cur,vector<string>& ans,string sol){
        //特判
        //如果为叶子节点,就将当前路径添加到结果中
        sol+=to_string(cur->val);
        if(cur->left==nullptr&&cur->right==nullptr){
            ans.push_back(sol);
        }
        //否则的话,继续dfs下面一层
        if(cur->left!=nullptr){
            dfs(cur->left,ans,sol+"->");
        }
        if(cur->right!=nullptr){
            dfs(cur->right,ans,sol+"->");
        }
        return;
    }
};

//Line 1034: Char 34: runtime error: addition of unsigned offset to 0x610000000040 overflowed to 0x610000000028 (stl_vector.h)
// SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior /usr/bin/…/lib/gcc/x86_64-linux-gnu/9/…/…/…/…/include/c++/9/bits/stl_vector.h:1043:34
//出现了下标-1

注意⚠️
出现问题

  • Line 1034: Char 34: runtime error: addition of unsigned offset to 0x610000000040 overflowed to 0x610000000028 (stl_vector.h)
    SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior /usr/bin/…/lib/gcc/x86_64-linux-gnu/9/…/…/…/…/include/c++/9/bits/stl_vector.h:1043:34
  • 一般是因为出现了下标-1
回溯法
  • 回溯法是优先搜索的特殊情况,需要记录节点状态的深度优先搜索。
  • 使用条件
    排列、组合、选择
  • 深度搜索 vs 回溯
    深度搜索:修改当前节点-递归求自节点
    回溯:修改当前节点-递归求子节点-回改当前节点状态
    深度遍历是不需要完全的从每个点出发、搜索每一种可能的。
    但是回溯基本上都是要使用一遍

79、单词搜索
给定一个由单词组成的矩阵,去遍历这个矩阵,如果字母都与其上下左右四个方向上的字母相连。给定一个字符串,看她能不能在字母矩阵中找到。

其中回溯体现的点在于使用了一个used数组,每次使用一个元素将其设置为used(每个元素只能用一遍,避免下一层调用时候再使用一遍)、使用完后将它的使用撤回

class Solution {
private:
    vector<int> direction{1,0,-1,0,1};
public:
    bool exist(vector<vector<char>>& board, string word) {
        //使用回溯的方法实现,来找从每个节点出发是否可以从深度遍历走到想要的字符串
        int m = board.size();
        int n = board[0].size();

        //特判
        if(m==0||n==0)return false;
        vector<vector<bool>> used(m,vector<bool>(n,false));
        for(int i = 0;i<board.size();i++){
            for(int j = 0;j<board[0].size();j++){
                if(traceback(board,used,word,0,i,j))
                    return true;
                
            }
        }
        return false;


    }

    bool traceback(vector<vector<char>>& board,vector<vector<bool>>& used,string word,int cur_p,int x,int y){
        //只有当前指针值相同时,才可以进入该函数
        //特判
        if(x<0||x>=board.size()||y<0||y>=board[0].size()||used[x][y]==true||word[cur_p]!=board[x][y]){
            return false;
        }
        if(cur_p+1==word.length())return true;
        //从当前节点出发,然后按照上下左右的方向去找是否能构成
        used[x][y] =true;
        for(int i=0;i<4;i++){
            int cur_x = x + direction[i];
            int cur_y = y+direction[i+1];
            if(traceback(board,used,word,cur_p+1,cur_x,cur_y)==true)
                return true;
        }
        used[x][y] = false;
        return false;
    }
};

51、N皇后
给了一个N*N的棋盘,往上放N个皇后,两个皇后不能在同一行,同一列,同一斜线。求有多少种算法。

解法:使用回溯算法,遍历所有的可能。
注意:其中希望能高效的求出是否有两个元素会在同一条斜线上,因此需要使用一个集合set来记录哪一些斜线上面已经有值了。

class Solution {
private:
    vector<vector<string>> ans;
public:
    vector<vector<string>> solveNQueens(int n) {
        //特判
        if(n==0)return {};
        //使用回溯算法

        //定义已经使用向量
    
        vector<bool> used(n,false);
        vector<string> sol(n,string(n,'.'));
        //标明两个斜线上面的数组
        auto diagonals1 = unordered_set<int>();
        auto diagonals2 = unordered_set<int>();

        //定义返回列表
        traceback(n,0,used,sol,diagonals1,diagonals2);
        return ans;
            


    }

    void traceback(int n,int level, vector<bool>& used,vector<string> sol,unordered_set<int>& diagonals1,unordered_set<int>& diagonals2){
        //特判
        if(level==n){
            ans.push_back(sol);
            return;
        }
        for(int j = 0;j<n;j++){
            //否则的化为当前行寻找合适的位置
            if(used[j]==false){
                if(diagonals1.find(level-j)!=diagonals1.end())continue;
                if(diagonals2.find(level+j)!=diagonals2.end())continue;
                diagonals1.insert(level-j);
                diagonals2.insert(level+j);
                used[j]=true;
                sol[level][j] = 'Q';
                traceback(n,level+1,used,sol,diagonals1,diagonals2);
                sol[level][j] = '.';
                used[j]=false;
                diagonals1.erase(level-j);
                diagonals2.erase(level+j);
            }
        }
    }
};

使用了unordered_set,其基本操作有find、insert、erase等。
find如果找不到元素,会返回set的end。

广度优先算法

使用队列实现。
适用于找最短路径等。
与深度优先都可以实现可达性问题,即一个点能否到达另一个点。

934、最短的桥
给了一个由01组成的矩阵,其中0表示水,1表示陆地。其中有且仅有两块陆地,求最少需要填多少块陆地来使得两个岛屿联通。

解答:实际上就是求两个岛屿之间的最短的路径长度。
使用DFS先求出一整个岛屿,将其放进队列中去,然后再从这个队列出发,使用BFS一直遍历到另外一个岛屿,其中的步数就是最短路径长度。

注意:其中很巧妙的将已经遍历过的岛屿和水都使用2来标记,使得在DFS寻找整个岛屿(类似于沉岛的思想)和使用BFS来找另一个岛屿的时候都不会出现有回头路的可能。而且在BFS找最短路的时,由于如果有岛屿遍历到了为2的位置,说明前面已经有人走过这了,此时遍历这个的人已经是慢一步的,因此不需要继续了,可以剪枝。

class Solution {

private:
    vector<int> direction{-1,0,1,0,-1};
    int m,n;
public:
    int shortestBridge(vector<vector<int>>& grid) {
        //找两座岛之间的最短距离

        m = grid.size();
        n = grid[0].size();
        //先使用DFS方法找到一个岛,把它的坐标存入到队列里面
        queue<pair<int,int>> island1;
        bool flag = false;
        for(int i = 0;i<m&&flag==false;i++){
            for(int j = 0;j<n&&flag==false;j++){
                if(grid[i][j]==1){
                    getisland1(grid,island1,i,j);
                    flag = true;
                }
            }
        }
        
        //使用BFS的方法,去找两座岛之间的最小距离
        //从islands1出发,BFS搜索,直至第一个找到值为1的格子
        int distance =0;
        while(!island1.empty()){
            distance++;
            int num = island1.size();
            for(int i = 0;i<num;i++){
                auto [x,y]= island1.front();
                island1.pop();
                //朝四个方向找
                for(int j=0;j<4;j++){
                    int cur_x = x+direction[j];
                    int cur_y = y+direction[j+1];
                    if(cur_x>=0&&cur_x<m&&cur_y>=0&&cur_y<n){
                        if(grid[cur_x][cur_y]==1)return distance-1;
                        if(grid[cur_x][cur_y]==2)continue;
                        island1.push({cur_x,cur_y});
                        grid[cur_x][cur_y]=2;
                    }
                }
            }
        }
        return 0;

        

    }

    void getisland1(vector<vector<int>>& grid,queue<pair<int,int>>& island1,int x,int y){
        //特判
        //如果定位已经超出了边界||不是陆地,就返回
        if(x<0||x>=m||y<0||y>=n||grid[x][y]==0||grid[x][y]==2)return;
        //否则将其加入到队列中,并且递归判断其四周的点是否为小岛
        island1.push({x,y});
        grid[x][y] = 2;
        for(int i = 0;i<4;i++){
            int cur_x = x+direction[i];
            int cur_y = y+direction[i+1];
            getisland1(grid,island1,cur_x,cur_y);
        }
        return;
    }
};

310

class Solution {
public:
    vector<int> findMinHeightTrees(int n, vector<vector<int>>& edges) {
        if(n==2)return edges[0];
        vector<vector<int>> edges_pic(n,vector<int>(n,0));
        for(int i = 0;i<edges.size();i++){
            int x= edges[i][0];
            int y = edges[i][1];
            edges_pic[x][y] = 1;
            edges_pic[y][x] = 1;
        }
        //给一棵树的图,求这棵树的最小高度
        auto ans = unordered_map<int,vector<int>>();
        //从每个节点出发,找到每个根节点对应的高度
        for(int i= 0;i<n;i++){
            queue<int> queue;
            queue.push(i);
            unordered_set<int> set;
            set.insert(i);
            int level = bfs(n,edges_pic,set,queue,i);
            ans[level].push_back(i);
        }
        for(int i =0;i<n;i++){
            //遍历所有,找到最小的n值
            if(ans.count(i)!=0)return ans[i];
        }
        return vector<int>{0};


    }
    int bfs(int n, vector<vector<int>>& edges,unordered_set<int> set,queue<int>& queue,int cur_i){
        cout<<"进入函数,cur_i="<<cur_i<<endl;
        //按照bfs的方式按层搜索节点,直至队列中没有元素
        int level = 0;
        while(!queue.empty()){
            level++;
            if(set.size()==n)break;

            
            // cout<<"level="<<level<<endl;
            //还有元素时,就批量处理这一组元素
            int queue_n = queue.size();
            // cout<<"queue_n = "<<queue_n<<endl;
            for(int i =0;i<queue_n;i++){
                int point = queue.front();
                // cout<<"queue.front执行完,point ="<<point<<endl;
                queue.pop();
                // cout<<"queue.pop执行完"<<endl;
                //从这个元素出发,找与他相连但是没有被遍历过的界定啊
                for(int j= 0;j<n;j++){
                    // cout<<"j = "<<j<<endl;
                    // cout<<"edge  ="<<edges[point][j]<<endl;
                    if(edges[point][j]==1&&set.count(j)==0){
                        //相连,且这个节点不在set中
                        // cout<<"符合条件,要加入set的有"<<j<<endl;
                        queue.push(j);
                        
                        set.insert(j);
                        // cout<<"set.size = "<<set.size()<<endl;
                    }
                }
            }
        }
        // cout<<"level = "<<level<<endl;
        return level;
    }
};

方法应该没问题,但是43个用例时超时了

修改
注意到越在这个图中央的点约有可能是拥有最小高度的树的root节点,因此类似剥洋葱的方法,其中有出度为1的节点为当前的最外面一层的节点。需要把这个节点剥掉,一层一层找到最里面的节点,那么从这个/这些节点出发,往外面走的最长路径最小,整棵树的高度越小。

class Solution {
public:
    vector<int> findMinHeightTrees(int n, vector<vector<int>>& edges) {
        //使用剥洋葱的方法,其中出度为1的节点表示洋葱中的最外一层。
        //首先处理边节点,然后找出所有当前的出度为1的节点


        //邻接节点map
        unordered_map<int,vector<int>> edge_map;
        //每个节点的出度
        vector<int> degree(n,0);

        //特判
        if(n==1){
            return vector<int>{0};
        }
        for(vector<int> edge:edges){
            int v0 = edge[0];
            int v1 = edge[1];
            edge_map[v0].push_back(v1);
            edge_map[v1].push_back(v0);
            degree[v0]++;
            degree[v1]++;
        }
        //所有出度为1的节点组成的队列
        queue<int> edge_queue;
        //找出所有出度为1的节点
        for(int i =0;i<n;i++){
            if(edge_map[i].size()==1){
                edge_queue.push(i);
            }
        }
        //从当前这些节点出发,进行bfs的搜索,其中最后一层剩下的就是
        return bfs(edge_queue,degree,edge_map);
    }
    vector<int> bfs(queue<int> edge_queue,vector<int> degree,unordered_map<int,vector<int>> edge_map){
        vector<int> ans;
        while(!edge_queue.empty()){
            //只要它不为空就可以
            int num = edge_queue.size();
            cout<<"num = "<<num<<endl;
            //将里面当前的所有元素加入到一个vector中去,可能是返回值
            ans.clear();
            for(int i =0;i<num;i++){
                //取当前队列的头
                int cur_v = edge_queue.front();
                // cout<<"cur_v ="<<cur_v<<endl;
                edge_queue.pop();
                degree[cur_v]--;
                ans.push_back(cur_v);
                //找与它相邻的节点,将他们的度--;
                for(int v:edge_map[cur_v]){
                    degree[v]--;
                    if(degree[v]==1)
                    edge_queue.push(v);
                }
            }
        }
        return ans;
    }

};


//queue的操作是push
//set的插入是insert

各种集合的操作整理

//queue的操作是push
//set的插入是insert

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值