递归与回溯算法整理(一)

递归 与 回溯

回溯其实是一种暴力解决问题的方式, 对于问题规模大于20以上的数据,个人计算机将不能处理。

下面基于几个问题分析体会一下

在这里插入图片描述

问题分析 :

对于 0 1 * # 不用考虑

对于 2~ 9 每个数字都有3中情况。 那么对于一个数字串 ,将面临多种组合现象 。

我们可以 画树形图。

树形图会很容易发现这是一个递归问题。
对于每个问题,又形成一个和原问题类似的问题。(不存在重叠)

那么 我们可以递归来处理

class Solution {
public:
    vector<string> table = {"abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
   //原问题入口
   vector<string> letterCombinations(string digits) {
        vector<string> res;
        func(res, "", digits, 0);
        return res;
    }

 // 递归处理
    void func(vector<string> &res, string str, string &digits, int i){
        if(i == digits.size()){
            if(str.size() > 0) res.push_back(str);
            return;
        }
        
        else{//进进出出
        					// -2 是因为不考虑  0 和 1 
            string s = table[digits[i] - '2'];//拿到当前数字对应字母可能
            for(int j = 0; j < s.size(); ++j){
                str.push_back(s[j]);//放进去
                func(res, str, digits, i+1);
                str.pop_back();//取出来
            }
        }
    }
};

上边这个看着有点凌乱,来来来,整理下代码

来看个精简版的

class Solution {
private:
    vector<string> letter={"abc","def","ghi","jkl", "mno","pqrs", "tuv", "wxyz"};

private:
    //index  代表当前处理到第几个字符了
    void subQuestion(vector<string>& res, string& digits,int index, const string& dest)
    {
        if (index == digits.size())
        {
            res.push_back(dest);
            return;
        }
        //获取当前字符串 , 下标由digits[index] -'2'决定
        string tmp = letter[digits[index] - '2'];
        for(int i=0; i<tmp.size(); ++i)
            subQuestion(res, digits, index+1, dest+tmp[i]);
    }

public:
    vector<string> letterCombinations(string digits) {
    vector<string> res;//保存返回
    int n =digits.size();
     if(n == 0) return  res;  
         subQuestion(res, digits, 0, "");
     return res;
    }
};

再想想思路 ,其实 回溯法就是一种暴力,我们完全可以用多重循环来解决。

有的时候没思路,就只能用暴力 ,那么考虑下回溯。

回溯的时间复杂度是 O (2^N)

全排列问题:

	力扣  46 题  :给定一个 没有重复 数字的序列,返回其所有可能的全排列。

示例:

输入: [1,2,3]
输出:
[
[1,2,3],
[1,3,2],
[2,1,3],
[2,3,1],
[3,1,2],
[3,2,1]
]

思路 还是 画 树型图 的办法:

在这里插入图片描述

画图之后一目了然。

//下面这个题 的解法就很好的体现了回溯的想法。 虽然递归本来就有回溯的意思 ,但是, 有必要时,我们还是得注意变量的回溯。

因为排列中的元素是不能冲突的, 所以,我们还是得回溯标记状态。

class Solution {
private: 
    vector<bool> memo;

private:
    void subQuestion(vector<vector<int>> & res, vector<int>nums, vector<int>& sub, int n)    
    {
        if(n == nums.size())  
        {
            res.push_back(sub);
            return ;
        }    

        //注意,这里每一层都会从这里开始,那么我们得避免重复排列
        //换句话说: 每来到一个数字前,判断这个数字是在本排列出现过
        //那么我们可以使用一趟遍历来查找。可是导致时间复杂变高
        //我们采用辅助数组标记已经用过的数字。
        for(int i=0; i < nums.size(); ++i)
        if(!memo[i])
        {
            memo[i] = true;
            sub.push_back(nums[i]);
            subQuestion(res, nums, sub, n+1);
            sub.pop_back();//回退(换个数字排列)
            memo[i] = false;
        }
    }
public:
    vector<vector<int>> permute(vector<int>& nums) {
        vector<vector<int>> res;
        int n = nums.size();
        if(n == 0)  return res;
        
        vector<int> sub;
        memo = vector<bool>(n,false);
        subQuestion(res, nums, sub, 0);
        return res;
    }
};
这道题还是和 之前的题不相同的。因为这里涉及到了吞下与吐出的考虑。
最后再体会一下辅助数组的妙处。

回溯法 --组合问题

给定两个整数 n 和 k,返回 1 … n 中所有可能的 k 个数的组合。

在这里插入图片描述

为什么2以后的数字不考虑前边的数字呢?
因为: 前别的100%重复。

class Solution {
private:
    vector<vector<int>>res;
private:
    //start  只考虑之后
    //tmp  存当前一种情况存的元素
    void subQuestion(int n, int k,int start, vector<int>& tmp)
    {
        //一种组合完成
        if(tmp.size() == k) 
        {
            res.push_back(tmp);
            return;
        }
        //这离别搞成 i <= n-k  因为虽然第一层不能选n-k以后的数
        //但其他层可以选,  即使第一层选了 ,那么后边也会因为不够k个
        //不会放入最终结果,但却一定程度上影响性能,
        for(int i=start; i <= n; ++i)
        {
            tmp.push_back(i);
            subQuestion(n, k, i+1, tmp);
            tmp.pop_back();
        }
    }

public:
    vector<vector<int>> combine(int n, int k) {
        res.clear();
        if(n <= 0  || k<=0 || k > n)   return res;
        
        vector<int> tmp;
        subQuestion(n, k, 1, tmp);
        return res;
    }
};

下面进行剪枝:

剪枝 :控制下循环条件

class Solution {
private:
    vector<vector<int>>res;
private:
    //start  只考虑之后
    //tmp  存当前一种情况存的元素
    void subQuestion(int n, int k,int start, vector<int>& tmp)
    {
        //一种组合完成
        if(tmp.size() == k) 
        {
            res.push_back(tmp);
            return;
        }
        //这里其实可以剪枝
        //  k-tmp.size()  目前还需要的元素数量
        //  i取[start, n] 之间必须满足够 k-tmp.size()个元素
        // 【start, n】前闭后闭 所以 + 1
        for(int i=start; i <= (n-(k-tmp.size()))+1; ++i)
        {
            tmp.push_back(i);
            subQuestion(n, k, i+1, tmp);
            tmp.pop_back();
        }
    }

public:
    vector<vector<int>> combine(int n, int k) {
        res.clear();
        if(n <= 0  || k<=0 || k > n)   return res;
        
        vector<int> tmp;
        subQuestion(n, k, 1, tmp);
        return res;
    }
};

力扣相关题号:
40
216
78
90

题目描述:Word Search

给定一个二维网格和一个单词,找出该单词是否存在于网格中。

单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。

class Solution {
private:
    int next[4][2] = {{-1,0}, {0,1}, {1,0}, {0,-1}};
    vector<vector<bool>>memo;
    int n, m;

private:
    bool InArea(int x, int y)
    {
         return x>=0 && x < n && y>=0 && y<m;
    }

    bool Search(vector<vector<char>>&board, string& word, int index, int i, int j)
    {
        //长度够了
        if(index == word.size()-1)
             return word[index] == board[i][j];
        //长度未够,判断是否是word上的字母
        if(word[index] == board[i][j])
        {
        //是的话,上下左右找下一个word字母
        memo[i][j] = true;
        for(int k=0; k<4; ++k)
        {
            int newx = i + next[k][0];
            int newy = j + next[k][1];
            if( InArea(newx, newy) && !memo[newx][newy] &&Search(board,word,index+1,newx,newy) )
                return true;
        }
         memo[i][j] = false;
    }
        return false;
    }

public:
    bool exist(vector<vector<char>>& board, string word) {
       n= board.size();
       if (n==0) return false;
       m = board[0].size();
       
       //保存走过的路径
       memo =vector<vector<bool>>(n,vector<bool>(m,false));
       
       for(int i=0; i<n; ++i)
        for(int j=0; j<m; ++j)
           if ( Search(board, word, 0, i, j) )
              return true;
        
        return false;
    }
};

深搜: 完全被水包围的岛屿个数。

class Solution {
private: 
    int ret =0;
    int l1,l2;
    vector<vector<bool>>memo;
    int next[4][2] ={{-1,0}, {0,1}, {1,0}, {0, -1}};
private:   
    bool inArea(int a, int b){
        //完全被包围意味着 不能在边界, 那么暂时不判断边界是否是合理
        return a>=0 && a <l1 && b>=0 && b<l2;
    }
private:
    bool touchEdge(int a, int b){
        return a <=0 ||a >=l1-1 || b <=0 ||b >=l2-1;
    }

private:
    void dfs(vector<vector<int>>& grid, int x, int y, bool& flag){
         //是否触碰边界
        if(touchEdge(x,y)) {
            flag = true;
            return ;
        }
        memo[x][y] = true;//访问过了
        for(int i=0; i<4; ++i){
            int newx = x + next[i][0];
            int newy = y + next[i][1];
            //不再这里判断 flag第二次值 是为了保证生深度优先搜索走到底
            if(inArea(newx, newy) && !grid[newx][newy] && !memo[newx][newy]){
                dfs(grid, newx, newy, flag);
        }
    }
}

public:
    int closedIsland(vector<vector<int>>& grid) {
        l1 = grid.size();
        if(l1==0) return 0;
        l2 = grid[0].size(); 
        memo=vector<vector<bool>>(l1,vector<bool>(l2,false)); 
        for(int i=0; i<l1; ++i)
            for(int j=0; j<l2; ++j)
            if(!grid[i][j] && !memo[i][j]){
                bool flag = false;
                dfs(grid, i, j, flag);
                if(!flag) 
                   ++ret;
            }
        return ret;
    }   
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值