8.算法之搜索

200. 岛屿的个数

给定一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,计算岛屿的数量。一个岛被水包围,并且它是通过水平方向或垂直方向上相邻的陆地连接而成的。你可以假设网格的四个边均被水包围。
示例 1:
输入:
11110
11010
11000
00000
输出: 1
示例 2:
输入:
11000
11000
00100
00011
输出: 3
8.算法之搜索_第1张图片

DFS:递归
8.算法之搜索_第2张图片
8.算法之搜索_第3张图片
注意:图5搜完之后,会回退到图3!
所以先判断是否在地图内,然后判断是否已经搜索过,接着判断是否为陆地,再进行探索。

BFS:队列
8.算法之搜索_第4张图片
8.算法之搜索_第5张图片

求地图岛屿数量
8.算法之搜索_第6张图片

class Solution {
public:
    int numIslands(vector<vector<char>>& grid) {
        int island_num=0;
        vector<vector<int>> mark;
        for(int i=0;i<grid.size();i++){
            mark.push_back(vector<int>());
            for(int j=0;j<grid[i].size();j++){
                mark[i].push_back(0);
            }
        }
        
        for(int i=0;i<grid.size();++i){
            for(int j=0;j<grid[i].size();++j){
                if(mark[i][j]==0&&grid[i][j]=='1'){
                    BFS(mark,grid,i,j);//DFS(mark,grid,i,j);
                    island_num++;
                }
            }
        }
        return island_num;
    }
private:
    void DFS(vector<vector<int>>& mark,
             vector<vector<char>>& grid,
            int x,int y)
    {
        mark[x][y]=1;
        static const int dx[]={-1,1,0,0};
        static const int dy[]={0,0,-1,1};
        for(int i=0;i<4;i++)
        {
            int newx=x+dx[i];
            int newy=y+dy[i];
            if(newx<0||newx>=mark.size()||
              newy<0||newy>=mark[newx].size()){//超过数组边界
                continue;
            }
            if(mark[newx][newy]==0&&grid[newx][newy]=='1')
                BFS(mark,grid,newx,newy);
        }
    }
    
    void BFS(vector<vector<int>>& mark,
             vector<vector<char>>& grid,
            int x,int y){
        static const int dx[]={-1,1,0,0};
        static const int dy[]={0,0,-1,1};
        queue<pair<int,int>> Q;
        Q.push(make_pair(x,y));
        mark[x][y]=1;
        while(!Q.empty()){
            x=Q.front().first;
            y=Q.front().second;
            Q.pop();
            for(int i=0;i<4;i++){
                int newx=x+dx[i];
                int newy=y+dy[i];
                if(newx<0||newx>=mark.size()||
                    newy<0||newy>=mark[newx].size()){//超过数组边界
                    continue;
                }
                if(mark[newx][newy]==0&&grid[newx][newy]=='1'){
                   Q.push(make_pair(newx,newy));
                    mark[newx][newy]=1;
                }
            }
        }
    }
};

127. 单词接龙

给定两个单词(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” 不在字典中,所以无法进行转换。

模型:
8.算法之搜索_第7张图片
8.算法之搜索_第8张图片8.算法之搜索_第9张图片
8.算法之搜索_第10张图片
8.算法之搜索_第11张图片

class Solution {
public:
    int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
        map<string,vector<string>> graph;
        construct_graph(beginWord,wordList,graph);
        return BFS_graph(beginWord,endWord,graph);
    }
private:
    bool connect(const string &word1,const string &word2)
    {//记录word1和word2不相等字符个数
        int cnt=0;
        for(int i=0;i<word1.length();++i){
            if(word1[i]!=word2[i])
                cnt++;
        }
        return cnt==1;
    }
    
    void construct_graph(string &beginWord,
                        vector<string> &wordList,
                        map<string,vector<string>> &graph){
        wordList.push_back(beginWord);
        
        for(int i=0;i<wordList.size();++i){
            graph[wordList[i]]=vector<string>();
        }
        
        for(int i=0;i<wordList.size();++i){
            for(int j=i+1;j<wordList.size();++j){
                if(connect(wordList[i],wordList[j])){
                    graph[wordList[i]].push_back(wordList[j]);
                    graph[wordList[j]].push_back(wordList[i]);
                }
            }
        }
    }
    
    int BFS_graph(string &beginWord,string &endWord,
                  map<string,vector<string>>&graph){
        queue<pair<string,int>> Q;
        set<string> visit;
        Q.push(make_pair(beginWord,1)); //添加起始点,起始点步数为1
        visit.insert(beginWord);//标记起点已访问
        while(!Q.empty()){
            string node= Q.front().first;//取队列头部节点与步数
            int step=Q.front().second;
            Q.pop();
            if(node==endWord)//找到终点,返回步数
                return step;
            const vector<string> &neighbors=graph[node];//取node得全部邻接点
            for(int i=0;i<neighbors.size();i++){
                if(visit.find(neighbors[i])==visit.end()){
                    Q.push(make_pair(neighbors[i],step+1));
                    visit.insert(neighbors[i]);//标记neighbors[i]已添加到队列
                }
            }
        }
        return 0;
    }
};

126. 单词接龙 II

给定两个单词(beginWord 和 endWord)和一个字典 wordList,找出所有从 beginWord 到 endWord 的最短转换序列。转换需遵循如下规则:
每次转换只能改变一个字母。
转换过程中的中间单词必须是字典中的单词。
说明:
如果不存在这样的转换序列,返回一个空列表。
所有单词具有相同的长度。
所有单词只由小写字母组成。
字典中不存在重复的单词。
你可以假设 beginWord 和 endWord 是非空的,且二者不相同。
示例 1:
输入:
beginWord = “hit”,
endWord = “cog”,
wordList = [“hot”,“dot”,“dog”,“lot”,“log”,“cog”]
输出:
[
[“hit”,“hot”,“dot”,“dog”,“cog”],
[“hit”,“hot”,“lot”,“log”,“cog”]
]
示例 2:
输入:
beginWord = “hit”
endWord = “cog”
wordList = [“hot”,“dot”,“dog”,“lot”,“log”]
输出: []
解释: endWord “cog” 不在字典中,所以不存在符合要求的转换序列。

8.算法之搜索_第12张图片
8.算法之搜索_第13张图片
8.算法之搜索_第14张图片
在这里插入图片描述

struct Qitem{
    string node;//搜索结点
    int parent_pos;//前驱结点在队列中的位置
    int step;//到达当前节点的步数
    Qitem(string _node,int _parent_pos,int _step)
        :node(_node),parent_pos(_parent_pos),step(_step){}
};
class Solution {
public:
    vector<vector<string>> findLadders(string beginWord, string endWord, vector<string>& wordList) {
        map<string,vector<string>> graph;
        construct_graph(beginWord,wordList,graph);
        vector<Qitem> Q;
        vector<int> end_word_pos;
        BFS_graph(beginWord,endWord,graph,Q,end_word_pos);
        vector<vector<string>> result;
        for(int i=0;i<end_word_pos.size();i++){
            int pos=end_word_pos[i];
            vector<string> path;
            while(pos!=-1){
                path.push_back(Q[pos].node);
                pos=Q[pos].parent_pos;
            }
            result.push_back(vector<string>());
            for(int j=path.size()-1;j>=0;j--){
                result[i].push_back(path[j]);
            }
            
        }
        return result;
    }
    
private:
     bool connect(const string &word1,const string &word2)
    {//记录word1和word2不相等字符个数
        int cnt=0;
        for(int i=0;i<word1.length();++i){
            if(word1[i]!=word2[i])
                cnt++;
        }
        return cnt==1;
    }
    
    void construct_graph(string &beginWord,
                        vector<string> &wordList,
                        map<string,vector<string>> &graph){
        int has_begin_word=0;
        
        for(int i=0;i<wordList.size();++i){//由于wordList可能有beginWord,直接将beginWord push进入wordList,会出现重复结果
            if(wordList[i]==beginWord)
                has_begin_word=1;
            graph[wordList[i]]=vector<string>();
            
        }
        
        for(int i=0;i<wordList.size();++i){
            for(int j=i+1;j<wordList.size();++j){
                if(connect(wordList[i],wordList[j])){
                    graph[wordList[i]].push_back(wordList[j]);
                    graph[wordList[j]].push_back(wordList[i]);
                }
            }
             if(has_begin_word==0&&connect(beginWord,wordList[i])){
                    graph[beginWord].push_back(wordList[i]);
                }
        }
    }
    
    void BFS_graph(string &beginWord,string &endWord,
                  map<string,vector<string>> &graph,//使用vector实现队列,可保存所有信息
                  vector<Qitem>&Q,vector<int> &end_word_pos){//终点endWord所在队列得位置下标
        map<string,int> visit;//单词,步数
        int min_step=0;//到达endWord的最小步数
        Q.push_back(Qitem(beginWord.c_str(),-1,1));//起始单词的前驱为1
        visit[beginWord]=1;//标记起始单词的步数为1
        int front=0;//队列头指针,指向vector的队列头
        while(front!=Q.size()){//front指向Q.size()尾部时,队列为空
            const string &node=Q[front].node;
            int step=Q[front].step;
            if(min_step!=0&&step>min_step){//step>min_step,代表所有到终点的路径都搜索完成
                break;
            }
            if(node==endWord){//当搜索到结果时,记录到达终点的最小步数
                min_step=step;
                end_word_pos.push_back(front);
            }
            const vector<string> &neighbors=graph[node];
            for(int i=0;i<neighbors.size();i++){
                if(visit.find(neighbors[i])==visit.end()
                  ||(step+1)==visit[neighbors[i]]){//结点没被访问过,或者另一条最短路径
                    Q.push_back(Qitem(neighbors[i],front,step+1));
                    visit[neighbors[i]]=step+1;//标记到达邻接点neighbors[i]的最小步数
                }
            }
            front++;
        }
        
    }
};

回溯深搜: 当求最短时、当尝试、试探时
宽搜/深搜遍历所有的点、位置、遍历地图等,复杂度差不多

473. 火柴拼正方形

还记得童话《卖火柴的小女孩》吗?现在,你知道小女孩有多少根火柴,请找出一种能使用所有火柴拼成一个正方形的方法。不能折断火柴,可以把火柴连接起来,并且每根火柴都要用到。
输入为小女孩拥有火柴的数目,每根火柴用其长度表示。输出即为是否能用所有的火柴拼成正方形。
示例 1:
输入: [1,1,2,2,2]
输出: true
解释: 能拼成一个边长为2的正方形,每边两根火柴。
示例 2:
输入: [3,3,3,3,4]
输出: false
解释: 不能用所有火柴拼成一个正方形。
注意:
给定的火柴长度和在 0 到 10^9之间。
火柴数组的长度不超过15。
8.算法之搜索_第15张图片
8.算法之搜索_第16张图片

class Solution {
public:
    bool makesquare(vector<int>& nums) {
        if(nums.size()<4)//数量小于4,则无法摆正方形的四条边
           return false; 
        
        int sum=0;
        for(int i=0;i<nums.size();i++)
            sum+=nums[i];
        
        if(sum%4!=0)//总和若不是4的倍数也无法成功
            return false;
        
        sort(nums.rbegin(),nums.rend());//sort默认从小到大排序
        
        int bucket[4]={0};//装火柴的桶
        return generate(0,nums,sum/4,bucket);
    }
private:
    bool generate(int i,vector<int>& nums,int target,int bucket[])
    {
        if(i>=nums.size()){
            return bucket[0]==target&&bucket[1]==target
                &&bucket[2]==target&&bucket[3]==target;
        }
        for(int j=0;j<4;j++){//4个桶分别尝试
            if(bucket[j]+nums[i]>target)
                continue;
            bucket[j]+=nums[i];//放入j桶中
            if(generate(i+1,nums,target,bucket)){
                return true;
            }
            bucket[j]-=nums[i];
        }
        return false;
    }
};

8.算法之搜索_第17张图片

class Solution {
public:
    bool makesquare(vector<int>& nums) {
        if(nums.size()<4)//数量小于4,则无法摆正方形的四条边
           return false; 
    
        int sum=0;
        for(int i=0;i<nums.size();i++)
            sum+=nums[i];
        
        if(sum%4!=0)//总和若不是4的倍数也无法成功
            return false;
        
        int target=sum/4;
        vector<int> ok_subset;//所有满足条件的一个边代表的集合
        vector<int> ok_half;//所有满足条件的两个边代表的集合
        
        int all=1<<nums.size();//
        
        for(int i=0;i<all;++i){
            int sum=0;
            for(int j=0;j<nums.size();++j){
                if(i&(1<<j))//保证i和j不是同一个
                    sum+=nums[j];
            }
            if(sum==target)
                ok_subset.push_back(i);
        }
        
        for(int i=0;i<ok_subset.size();++i){
            for(int j=i+1;j<ok_subset.size();++j){
                if((ok_subset[i]&ok_subset[j])==0)//说明集合无交集
                    ok_half.push_back(ok_subset[i]|ok_subset[j]);
            }
        }//完成两条满足要求的边的组合
        
        for(int i=0;i<ok_half.size();++i){
            for(int j=i+1;j<ok_half.size();++j){
                if((ok_half[i]&ok_half[j])==0)
                    return true;
            }
        }//完成四条边(两条边组合)的边的组合

        return false;
    }
};

407. 接雨水 II

给定一个 m x n 的矩阵,其中的值均为正整数,代表二维高度图每个单元的高度,请计算图中形状最多能接多少体积的雨水。
说明:
m 和 n 都是小于110的整数。每一个单位的高度都大于0 且小于 20000。
示例:
给出如下 3x6 的高度图:
[
[1,4,3,1,3,2],
[3,2,1,3,2,4],
[2,3,3,2,3,1]
]
返回 4。
8.算法之搜索_第18张图片
如上图所示,这是下雨前的高度图[[1,4,3,1,3,2],[3,2,1,3,2,4],[2,3,3,2,3,1]] 的状态。
8.算法之搜索_第19张图片
下雨后,雨水将会被存储在这些方块中。总的接雨水量是4。

8.算法之搜索_第20张图片
8.算法之搜索_第21张图片
8.算法之搜索_第22张图片
8.算法之搜索_第23张图片
8.算法之搜索_第24张图片拿出一个点2,当水面为2,探索该点四周的点,当发现1比2小,就可以积水,因为2此时是最低的点,所以它不可能从1再流出去,升高1的位置的水面高度为2,修改其高度。
8.算法之搜索_第25张图片
8.算法之搜索_第26张图片
8.算法之搜索_第27张图片

struct Qitem
{
    int x;
    int y;
    int h;
    Qitem(int _x,int _y,int _h):x(_x),y(_y),h(_h){}
};
struct cmp{
    bool operator()(const Qitem &a,const Qitem &b){
        return a.h>b.h;
    }
};
class Solution {
public:
    int trapRainWater(vector<vector<int>>& heightMap) {
        priority_queue<Qitem,vector<Qitem>,cmp> Q;
        if(heightMap.size()<3||heightMap[0].size()<3)
            return 0;
        int row=heightMap.size();
        int column=heightMap[0].size();
        vector<vector<int>> mark;
        for(int i=0;i<row;++i){
            mark.push_back(vector<int>());
            for(int j=0;j<column;++j)
                mark[i].push_back(0);
        }
        for(int i=0;i<row;++i){
            Q.push(Qitem(i,0,heightMap[i][0]));
            mark[i][0]=1;
            Q.push(Qitem(i,column-1,heightMap[i][column-1]));
            mark[i][column-1]=1;
        }
        for(int i=1;i<column-1;++i){
            Q.push(Qitem(0,i,heightMap[0][i]));
            mark[0][i]=1;
            Q.push(Qitem(row-1,i,heightMap[row-1][i]));
            mark[row-1][i]=1;
        }
        
        static const int dx[]={-1,1,0,0};
        static const int dy[]={0,0,-1,1};
        int result=0;//最终积水量
        
        while(!Q.empty()){
            int x=Q.top().x;
            int y=Q.top().y;
            int h=Q.top().h;
            Q.pop();
            
            for(int i=0;i<4;i++){
                int newx=x+dx[i];
                int newy=y+dy[i];
                if(newx<0||newx>=row||
                  newy<0||newy>=column||mark[newx][newy]){
                    continue;//当新拓展的点超出边界或者已加入队列
                }
                if(h>heightMap[newx][newy]){//当前点的高度高于拓展点
                    result+=(h-heightMap[newx][newy]);
                    heightMap[newx][newy]=h;
                }
                Q.push(Qitem(newx,newy,heightMap[newx][newy]));
                mark[newx][newy]=1;
            }
        }
        return result;
    }
};

你可能感兴趣的:(算法)