深度优先搜索+回溯法

本文详细讲解了如何使用深度优先搜索算法解决C++编程中的三个问题:1. 岛屿最大面积的计算,2. 社交圈的朋友数量查找,以及3. 太平洋大西洋水流问题。通过递归和栈的原理,展示了DFS在图和树遍历中的关键作用。

深度优先搜索

C++题解中的:

深度优先搜索在搜索到一个新的节点时,立即对该新节点进行遍历;因此遍历需要用先入后出的栈来实现,也可以通过与栈等价的递归来实现。对于树结构而言,由于总是对新节点调用遍历,因此看起来是向着“深”的方向前进。

1.岛屿的最大面积

class Solution {
public:
int dfs(vector<vector<int>>& grid,int i,int j){//返回的是以i,j出发找到的岛屿的最大面积
    if(i<0 || i>=grid.size() || j<0 || j>=grid[0].size() || grid[i][j]==0){
        return 0;
    }
   grid[i][j]=0;//后面再不能算在计数里了,这里其实最好显示地写上if(grid[i][j]==1然后执行后面两句
   return 1+dfs(grid,i-1,j)+dfs(grid,i+1,j)+dfs(grid,i,j-1)+dfs(grid,i,j+1);

}
    int maxAreaOfIsland(vector<vector<int>>& grid) {
        int ans=0;
        for(int i=0;i<grid.size();++i){
            for(int j=0;j<grid[0].size();++j){
                ans=max(ans,dfs(grid,i,j));
            }
        }
        return ans;
    }
};

2.“朋友圈”的个数

省份的数量,输入一个二维数组

这里实际上有n个节点,对于图来说。我们需要一个visited数组来记录节点是否被访问过了,初始状态是都未被访问过。

 

class Solution {
public:
void dfs(vector<vector<int>>& isConnected,int i,vector<bool>& visited ){//用这个函数来修改visited,从节点i开始能访问到的visited都置为true
visited[i]==true;
for(int k=0;k<isConnected.size();++k){
    if(isConnected[i][k]==1 && visited[i]==false){
        dfs(isConnected,k,visited);
    }
}
}
    int findCircleNum(vector<vector<int>>& isConnected) {
        if(isConnected.empty() || isConnected[0].empty()) return 0;
        vector<bool> visited(isConnected.size());

        int n=isConnected.size();
        int count=0;
        for(int i=0;i<n;++i){
            if(visited[i]==false) count++;
            dfs(isConnected,i,visited);
        }
        return count;
    }
};

3.太平洋大西洋水流问题

class Solution2 {
public:
    vector<vector<int>> P, A, ans;//P里面放的是能流到太平洋的,A放的是能流到大西洋的节点()
    int n, m;//n行 m列的二维数组
    vector<vector<int>> pacificAtlantic(vector<vector<int>>& heights) {
        //左右两边加上下两边出发深搜
        n=heights.size();
        m=heights[0].size();
        P=vector<vector<int>> (n,vector<int>(m,0));//能流向太平洋的初始化都为0
        A=P;//能流向大西洋的初始化都为0
        for(int i=0;i<n;++i){
            dfs(heights,P,i,0);
            dfs(heights,A,i,m-1);
        }
        for(int j=0;j<m;++j){
            dfs(heights,P,0,j);
            dfs(heights,A,n-1,j);
        }
        return ans;

    }
    void dfs(vector<vector<int>>& heights, vector<vector<int>>& visited, int i, int j){

        if(visited[i][j]) return;
        visited[i][j]=1;

        if(P[i][j] && A[i][j]) ans.push_back({i,j});
        //上下左右深搜
        if(i-1>=0 && heights[i-1][j]>=heights[i][j]) dfs(heights,visited,i-1,j);
        if(i+1<n && heights[i+1][j]>=heights[i][j]) dfs(heights,visited,i+1,j);
        if(j-1>=0 && heights[i][j-1]>=heights[i][j]) dfs(heights,visited,i,j-1);
        if(j+1<m && heights[i][j+1]>=heights[i][j]) dfs(heights,visited,i,j+1);

    }
};

回溯法:这个太重要了

回溯法,一般可以解决如下几种问题:

组合无序,排列有序

  • 组合问题:N个数里面按一定规则找出k个数的集合
  • 切割问题:一个字符串按一定规则有几种切割方式
  • 子集问题:一个N个数的集合里有多少符合条件的子集
  • 排列问题:N个数按一定规则全排列,有几种排列方式  (比如全排列,字符串的排列
  • 棋盘问题:N皇后,解数独等等
  • 模板:

 组合:

class Solution {
public:

    vector<vector<int>> combine(int n, int k) {
        vector<vector<int>> result;
        vector<int> path;
        if(n==1) return {{1}};
        backtracking(n,k,1,result,path);
        cout<<"path="<<endl;
        for(auto i:path){//最后的path里没有元素
            cout<<i<<endl;
        }
        return result;
    }
    //组合回溯的是是否把当前的数字 加入到结果
   // 需要startIndex来记录下一层递归,搜索的起始位置。??重要!
   //一个用来存放符合条件单一结果,一个用来存放符合条件结果的集合。
    void backtracking(int n,int k,int statrIndex, vector<vector<int>>& result,vector<int>& path){
        if(path.size()==k){
            result.push_back(path);
            return;
        }
        for(int i=statrIndex;i<=n;++i){
            path.push_back(i);
            backtracking(n,k,i+1,result,path);//递归// 递归:控制树的纵向遍历,注意下一层搜索要从i+1开始!
            path.pop_back();//回溯 撤销处理结果
        }
    }
};

1.全排列问题

全排列问题:给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。 

状态:每一个结点表示了求解问题的不同阶段

状态重置:回到上一层节点时需要状态重置,这也是“回溯”的含义

class Solution {
public:
    vector<vector<int>> permute(vector<int>& nums) {
        vector<vector<int>> ans;
        if(nums.empty()) return ans;
        backtracking(nums,0,ans);
        return ans;
    }
    //对于每一个当前位置 i,我们可以将其于之后的任意位置交换,然后继续处理位置 i+1,直到处理到最后一位。为了防止我们每此遍历时都要新建一个子数组储
    //存位置 i 之前已经交换好的数字,我们可以利用回溯法,只对原数组进行修改,在递归完成后再修改回
    void backtracking(vector<int>& nums,int depth,vector<vector<int>> &ans){//按引用传递状态
       if(depth==nums.size()-1) {
           ans.push_back(nums);
           return;
       }
       for(int i=depth;i<nums.size();++i){
           swap(nums[i],nums[depth]);//修改当前结点状态
             backtracking(nums,depth+1,ans);//递归
             //递归后才能回改状态
             swap(nums[i],nums[depth]);
       }
    }

};

字符串的全排列(如果是有重复字符的情况,那么可以使用set来去重)

class Solution {
public:
    vector<string> permutation(string s) {
set<string> ans;
vector<string> final_ans;
    if(s.empty()) return final_ans;
    backtrack(s,0,ans);
    for(auto i:ans){
        final_ans.push_back(i);
    }
    return final_ans;
    }
   
void backtrack(string& s,int depth,set<string>& ans){
    if(depth==s.size()-1){
        ans.insert(s);
        return;
    }
    for(int i=depth;i<s.size();++i){
        swap(s[depth],s[i]);
        backtrack(s,depth+1,ans);
        swap(s[depth],s[i]);
    }
}
};

2.组合

给定一个整数 n 和一个整数 k,求在 1 到 n 中选取 k 个数字的所有组合方法
参考代码随想录:

剪枝优化:如果for循环选择的其实位置之后的元素个数已经不足,那么就没有必要在进行搜索了

例如n=4 k=4的情况下

图中每一个结点 就代表本层的一个for循环

class Solution {
public:

    vector<vector<int>> combine(int n, int k) {
        vector<vector<int>> result;
        vector<int> path;
        if(n==1) return {{1}};
        backtracking(n,k,1,result,path);
        cout<<"path="<<endl;
        for(auto i:path){//最后的path里没有元素
            cout<<i<<endl;
        }
        return result;
    }
    //组合回溯的是是否把当前的数字 加入到结果
   // 需要startIndex来记录下一层递归,搜索的起始位置。??重要!
   //一个用来存放符合条件单一结果,一个用来存放符合条件结果的集合。
    void backtracking(int n,int k,int statrIndex, vector<vector<int>>& result,vector<int>& path){
        if(path.size()==k){
            result.push_back(path);
            return;
        }
        for(int i=statrIndex;i<=n;++i){
            path.push_back(i);
            backtracking(n,k,i+1,result,path);//递归// 递归:控制树的纵向遍历,注意下一层搜索要从i+1开始!
            path.pop_back();//回溯 撤销处理结果
        }
    }
};

3.组合总数

这道题目我又不知道是怎么写对的了,,

给定一个无重复元素的正整数数组 candidates 和一个正整数 target ,找出 candidates 中所有可以使数字和为目标数 target 的唯一组合。

candidates 中的数字可以无限制重复被选取。如果至少一个所选数字数量不同,则两种组合是唯一的。 

对于给定的输入,保证和为 target 的唯一组合数少于 150 个。

class Solution {
public:
//回到本题,我们定义递归函数 dfs(target, combine, idx) 表示当前在 candidates 数组的第 idx 位,还剩 target 要组合,已经组合的列表为 combine。递归的终止条件为 target <= 0 或者 candidates 数组被全部用完。那么在当前的函数中,每次我们可以选择跳过不用第 idx 个数,即执行 dfs(target, combine, idx + 1)。也可以选择使用第 idx 个数,即执行 dfs(target - candidates[idx], combine, idx),注意到每个数字可以被无限制重复选取,因此搜索的下标仍为 idx。
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        vector<vector<int>> result;
        vector<int> combine;
        dfs(candidates,target,0,result,combine);
        return result;
    }
    void dfs(vector<int>& candidates,int target,  int index, vector<vector<int>>& result, vector<int>& combine){

if(index==candidates.size()){
    return;
}
if(target==0){
    result.push_back(combine);
    return;
}
//直接跳过
dfs(candidates,target,index+1,result,combine);
//选择当前数
if(target-candidates[index]>=0){//记住三个步骤:处理节点 递归 回溯,撤销处理结果
combine.push_back(candidates[index]);
    dfs(candidates,target-candidates[index],index,result,combine);
    combine.pop_back();
}
    }
};

4.求幂集

这个题目是之前面试过程中被问到的,现在终于会写了

/幂集的这道题目 比如集合{1,2,3}
void dfs(vector<int>& nums,vector<vector<int>>& result,vector<int>& path,int index);
 vector<vector<int>> miji(vector<int>& nums){
vector<vector<int>> result;//或者设为一个全局的变量
vector<int> path;
if(nums.empty()) return result;
dfs(nums,result,path,0);
return result;
 }
 void dfs(vector<int>& nums,vector<vector<int>>& result,vector<int>& path,int index){
     if(index==nums.size()){
        result.push_back(path);
        return;
     }
     //for选择的是本层中元素
     dfs(nums,result,path,index+1);

     path.push_back(nums[index]);
     dfs(nums,result,path,index+1);
     path.pop_back();
 }
int main()
{
    vector<int> nums={1,2,3};
    vector<vector<int>> result=miji(nums);
    for(auto i:result){
        for(auto j:i){
            cout<<j<<" ";
        }
        cout<<endl;
    }
    cout << "Hello world!" << endl;
    return 0;
}

5.单词搜索

同剑指12题:矩阵中的路径

需要记下思路:首先,在矩阵中任选一个格子作为路径的起点。假设路径中某个格子的字符为ch,并且这个格子将对应于路径上的第i个字符。 ...   那么到相邻的格子寻找路径上的第i+1个字符。

pos在函数递归地调用自己往深走的时候就会++,所有不用写什么++pos!

记住此题的解法

class Solution {
public:
    bool exist(vector<vector<char>>& board, string word) {
bool find=false;
int m=board.size();
int n=board[0].size();

vector<vector<int>> visited(m,vector<int>(n,0));//初始化为0
for(int i=0;i<m;++i){
    for(int j=0;j<n;++j){
         dfs(board,word,visited,find,i,j,0);
    }
}
return find;
}


    void dfs(vector<vector<char>>& board, string& word,vector<vector<int>>& visited, bool& find,int i,int j,int pos){
        if(i<0 || i>=board.size() || j<0 || j>=board[0].size() ){
            return;
        }
       if(find || visited[i][j] || word[pos]!=board[i][j]){
           return;
       }
       if(pos==word.size()-1){//注意此处
           find=true;
           return;
       }
       //处理结点
       visited[i][j]=1;
       //++pos;也没有这一句,
       dfs(board,word,visited,find,i-1,j,pos+1);
       dfs(board,word,visited,find,i+1,j,pos+1);
       dfs(board,word,visited,find,i,j-1,pos+1);
       dfs(board,word,visited,find,i,j+1,pos+1);
        visited[i][j]=0;
        //--pos  注意没有这一句
          
    }
};

6.二叉树中和为某一值的路径

类似先序遍历??

在程序设计中,有相当一类求一组解,或求全部解,或求最优解的问题,是利用试探和回溯的搜索技术求解。回溯法也是设计递归过程的一种重要法则,它的求解过程实质上是一个先序遍历一棵状态树的过程,

class Solution {
public:
//枚举每一条从根节点到叶子节点的路径。当我们遍历到叶子节点,且此时路径和恰为目标和时,我们就找到了一条满足条件的路径。
vector<vector<int>> result;
vector<int> path;
void dfs(TreeNode* root,int target){
    if(root==NULL){
        return;
    }
    path.push_back(root->val);
    target=target-root->val;
   if(target==0 && root->left==NULL && root->right==NULL){
       result.push_back(path);
       //return; 不能写这个return,这里return的话实际上path里的没有被pop_back ,target的值也没有被更新
   }
   dfs(root->left,target);
   dfs(root->right,target);
   path.pop_back();
}
    vector<vector<int>> pathSum(TreeNode* root, int target) {
        if(root==NULL) return result;
        dfs(root,target);
        return result;
    }
};

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值