博客园  :: 首页  :: 新随笔  :: 管理

6.递归/回溯

Posted on 2021-03-29 21:39  wsg_blog  阅读(234)  评论(0编辑  收藏  举报

Index LeetCode

递归应该已经不陌生了,在前面的二叉树中,我们大量使用了递归,回溯是递归的“副产品”,只要有递归的过程就会有对应的回溯过程,递归和回溯的本质都为穷举,我们通常称为“暴搜”,回溯会有撤回之前操作的功能。
回溯可以解决的问题:

  1. 组合问题:如何按照一定规则在N个数中找出k个数的集合?
  2. 切割问题:一个字符串按照一定的规则切割,有几种切割方式?
  3. 自己问题:一个N个数的集合中有多少符合条件的子集?
  4. 排列问题:N个数按一定的规则全排列,有几种排列方式?
  5. 期盼问题:N皇后、数独等问题。

BM55.没有重复项数字的全排列(46.全排列)[medium]

输入:nums=[1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
说明:给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。

vector<vector<int>> permute(vector<int>& nums){
  vector<vector<int>> res;
  vector<int> path;
  vector<bool> used(nums.size(), false);
  backtracking(nums, path, used, res);
  return res;
}
void backtracking(const vector<int>& nums, vector<int>& path, vector<boo>& used, vector<vector<int>>& result){
  if(path.size() == nums.size()){
    result.push_back(path);
    return;
  }
  for(int i=0; i<nums.size(); i++){
    if(used[i]) continue;
    path.push_back(nums[i]);
    used[i]==true;
    backtracking(nums, path, used, result);
    path.pop_back();
    used[i]=false;
  }
}
BM56.有重复项数字的全排列(47.全排列II)[medium]

输入:nums=[1,1,2]
输出:[[1,1,2],[1,2,1],[2,1,1]]
说明:给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。

vector<vector<int>> permuteUnique(vector<int>& nums){
  vector<vector<int>> res;
  vector<int> path;
  vector<bool> used(nums.size(), false);
  sort(nums.begin(), nums.end());
  backtracking(nums, path, used, res);
  return res;
}
void backtracking(const vector<int>& nums, vector<int>& path, vector<bool>& used, vector<vector<int>>& result){
  if(path.size() == nums.size()){
    result.push_back(path);
    return;
  }
  for(int i=0; i<nums.size(); i++){
    if(used[i]) contine;
    if(i>0 && nums[i]==nums[i-1] && used[i-1]==false) continue;  //去重
    path.push_back(nums[i]);
    used[i]=true;
    backtracking(nums, path, used, result);
    path.pop_back();
    used[i]=false;
  }
}
BM57.岛屿数量(200.岛屿数量)[medium]

输入:grid = [
["1","1","1","1","0"],
["1","1","0","1","0"],
["1","1","0","0","0"],
["0","0","0","0","0"]]
输出:1
dfs+沉没此块陆地

int numIslands(vector<vector<char>>& grid){
  if(grid.size() ==0 ) return 0;
  int num=0;
  for(int i=0; i<grid.size(); i++)
    for(int j=0; j<grid[0].size(); j++){
      if(grid[i][j] == '1'){
        num++;
        dfs(grid, i, j);
      }
    }
  return num;
}
void dfs(vector<vector<char>>&& grid, int i, int j){
  grid[i][j]='0';  //沉没此块陆地
  if(i>0 && grid[i-1][j]=='1') dfs(grid, i-1, j);
  if(i<grid.size()-1 && grid[i+1][j]=='1') dfs(grid, i+1, j);
  if(j>0 && grid[i][j-1]=='1') dfs(grid, i, j-1);
  if(j<grid[0].size()-1 && grid[i][j+1]=='1') dfs(grid, i, j+1);
}
BM58.字符串的排列(剑指Offer38.字符串的排列)[medium]

输入:s="abc"
输出:[["abc"],["acb"],["bac"],["bca"],["cab"],["cba"]]
全排列II:排序+回溯,去重

vector<string> permutation(string s){
  vector<string> res;
  string path;
  vector<bool> used(s.size(), false);
  sort(s.begin(), s.end());
  backtracking(s, path, used, res);
  return res;
}
void backtracking(const string& s, string & path, vector<bool>& used, vector<string>& res){
  if(path.size()==s.size()){
    res.push_back(path);
    return;
  }
  for(int i=0; i<s.size(); i++){
    if(used[i]) continue;
    if(i>0 && s[i]==s[i-1] && !used[i-1]) continue;
    path.push_back(s[i]);
    used[i]=true;
    backtracking(s, path, used, res);
    path.pop_back();
    used[i]=false;
  }
}
BM59.N皇后问题(51.N皇后)[hard]

输入:n=4
输出:[[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]]
解释:皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子;如上图所示,4 皇后问题存在两个不同的解法; 1<=n<=9

vector<vector<string>> result;
//n为输入的棋盘大小
//row是当前递归到棋盘的第几行了
vector<vector<string>> solveNQueens(int n){
  vector<string> chessboard(n, string(n, '.'));
  backtracking(n, 0, chessboard);
  return result;
}
void backtracking(int n, int row, vector<string>& chessboard){
  if(row == n){
    result.push_back(chessboard);
    return;
  }
  for(int col=0; col<n; i++){
    if(isVaild(row, col, chessboard, n)){  //验证合法性
      chessboard[row][col]='Q';  //放置皇后
      backtracking(n, row+1, chessboard);
      chessboard[row][col]='.';  //回溯,撤销皇后
    }
  }
}
bool isValid(int row, int col, vector<string>& chessboard, int n){
  for(int i=0; i< row; i++){   //检查列,由于行在递归是按行进行,每行中只有一个皇后,无需检查
    if(chessboard[i][col] == 'Q') return false;
  }
  for(int i=row-1, j=col-1; i>=0 && j>=0; i--,j--){ //检查45度角是否有皇后
    if(chessboard[i][j] == 'Q') return false;
  }
  for(int i=row-1, j=col+1; i>=0 && j<n; i--,j++){  //检查135度角是否有皇后
    if(chessboard[i][j] == 'Q') return false;
  }
  return true;
}
BM60.括号生成(22.括号生成)[medium]

输入:n=3
输出:["((()))","(()())","(())()","()(())","()()()"]
说明:数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。
递归

vector<string> generateParentesis(int n){
  vector<string> res;
  dfs(0, 0, n, "", res);
  return res;
}
void dfs(int left, int right, int n, string str, vector<string>& res){
  if(left>n || right>n || left<right) return;
  if(left == n && right ==n){
    res.push_back(str);
    return;
  }
  dfs(left+1, right, n, str+'(', res);
  dfs(left, right+1, n, str+')', res);
}
BM61.矩阵最长递增路径(329.矩阵中的最长递增路径)[hard]

输入:matrix = [[9,9,4],[6,6,8],[2,1,1]]
输出:4
解释:最长递增路径为 [1, 2, 6, 9]。
说明:定一个 m x n 整数矩阵 matrix,找出其中最长递增路径 的长度。对于每个单元格,你可以往上,下,左,右四个方向移动。你不能在对角线方向上移动或移动到边界外。
深度优先搜索

int dirs[4][2]={{-1,0},{1,0},{0,-1},{0,1}};  //记录四个方向,**注意这里是int**
int m,n;
//深度优先搜索,返回最大单元格数
int dfs(vector<vector<int>> &matrix, vector<vector<int>>& dp, int i, int j){
  if(dp[i][j] != 0)  //这里也很重要
    return dp[i][j];
  dp[i][j]++;
  for(int k=0; k<4; k++){
    int nexti=i+dirs[k][0];
    int nextj=j+dirs[k][1];
    //判断条件
    if(nexti>=0 && nexti<m && nextj>=0 && nextj<n && matrix[nexti][nextj] > matrix[i][j])
      dp[i][j]=max(dp[i][j], dfs(matrix, dp, nexti, nextj)+1);
  }
  return dp[i][j];
}
int longestIncreasingPath(vector<vector<int>>& matrix){
  if(matrix.size()==0 || matrix[0].size()==0)  //矩阵为空
    return 0;
  int res=0;
  m=matrix.size();
  n=matrix[0].size();
  vector<vector<int>> dp(m, vector<int> (n, 0));  //从i,j处的单元格拥有的最长递增路径
  for(int i=0; i<m; i++){
    for(int j=0; j<n; j++){
      res=max(res, dfs(matrix, dp, i, j));  //更新最大值
    }
  }
  return res;
}
BM74.数字字符串转化成IP地址(93.复原IP地址)[medium]

输入:s = "25525511135"
输出:["255.255.11.135","255.255.111.35"]
回溯

vector<string> result;  //记录结果
//startIndex:搜索的起始位置,pointNum:添加逗点的数量
void backtracking(string& s, int startIndex, int pointNum){
  if(pointNum == 3){  //逗点数量为3,分割结束
    //判断第四段子字符串是否合法,如果合法就放进result中
    if(isVaild(s, startIndex, s.size()-1)){
      result.push_back(s);
    }
    return;
  }
  for(int i=startIndex; i<s.size(); i++){
    if(isVaild(s, startIndex, i)){  //判断[startIndex, i]这个区间的子串是否合法
      s.insert(s.begin()+i+1,  '.');  //在i后面插入一个逗点
      pointNum++;
      backtracking(s, i+2, pointNum);  //插入逗点之后下一个子串的起始位置为i+2
      pointNum--;    //回溯
      s.erase(s.begin()+i+1);    //回溯删掉逗点
    }else break;  //不合法,直接结束本层循环
  }
}
//判断字符串s在左闭右闭区间[start, end]所组成的数字是否合法
bool isVaild(const string& s, int start, int end){
  if(start > end) return false;
  if(s[start] == '0' && start != end)  //0开头的数字不合法
    return false;
  int num=0;
  for(int i=start; i<=end; i++){
    if(s[i] > '9' || s[i] < '0')  //遇到非数字字符不合法
      return false;
    num = num*10 + (s[i]-'0');
    if(num > 255) return false;
  }
  return true;
}
vector<string> restoreIpAddresses(string s){
  if(s.size()>12) return result;
  bactracking(s, 0, 0);
  return result;
}