代码随想录 回溯部分

什么情况下 需要用回溯:

1.当你需要循环的次数不确定时(即根据输入的k来确定循环的次数时)

2.

 

回溯算法能解决如下问题:

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

 有困难题目:

1.LC491  递增子序列

2.LC332  重新安排行程

3.

回溯模板

void backtracking(参数) {
    if (终止条件) {
        存放结果;
        return;
    }

    for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
        处理节点;
        backtracking(路径,选择列表); // 递归
        回溯,撤销处理结果
    }
}

代码随想录
 组合问题:
 LC77
LC216 组合总和

找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。

说明:

  • 所有数字都是正整数。
  • 解集不能包含重复的组合。

示例 1: 输入: k = 3, n = 7 输出: [[1,2,4]]

示例 2: 输入: k = 3, n = 9 输出: [[1,2,6], [1,3,5], [2,3,4]]

首先确定两个参数 vector<vector<int>> result;  vector<int> path;  其中result存放最终结果,path存放一条路径;

接着用backtracking 参数没确定不要紧,往下写发现需要 k startIndex 和targetSum(加减 到零即符合条件)

然后套用模板

记得回溯前的操作回溯之后都要做!! 

class Solution {
private:
vector<vector<int>> result;
vector<int> path;
void backtracking(int k,int targetSum,int startIndex){
    if(path.size()==k){
        if(targetSum==0) result.push_back(path);
        return;
    }
    for(int i=startIndex;i<=9;i++)
    {
        path.push_back(i);
        targetSum-=i;
       
//
if(targetSum<0){
            targetSum+=i;
            path.pop_back();
            return ;
        }
//
        backtracking(k,targetSum,i+1);
        targetSum+=i;
        path.pop_back();
    }
}
public:
    vector<vector<int>> combinationSum3(int k, int n) {
        backtracking(k,n,1);
        return result;
    }
};

 

 
优化:
剪枝:1.targetSum小于零即可返回
if(targetSum<0){
            targetSum+=i;
            path.pop_back();
            return ;
        }
  2.for的范围限定:
for(int i=startIndex;i<=(9-(k-path.size())+1);i++)  具体题目分析是否要+1;
 
 
LC17 电话号码的字母组合:

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。

给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。

基础:1.map映射

    2.字符串的基本处理(size() ;push_back(i)  ;pop_back())

思路:先设置映射表  再进行回溯

注意现在的path即为一维的string 所以result即为一维的vector

对于处理第几个数字:用参数中的index表示  然后对应的for 需要遍历letters的长度次数!!注意这个

 

 

class Solution {
private:
const string letterMap[10] = {
    "", // 0
    "", // 1
    "abc", // 2
    "def", // 3
    "ghi", // 4
    "jkl", // 5
    "mno", // 6
    "pqrs", // 7
    "tuv", // 8
    "wxyz", // 9
};
vector<string> result;
string s;
void backtracking(const string& digits,int index){
    if(index==(digits.size()))
    {
        result.push_back(s);
        return;
    }
    int digit=digits[index]-'0';//将index 指向的char转化为数字
    string letters=letterMap[digit];
    for(int i=0;i<letters.size();i++)//
    {
        s.push_back(letters[i]);
        backtracking(digits,index+1);
        s.pop_back();
    }
}
public:
    vector<string> letterCombinations(string digits) {
        s.clear();
        result.clear();
        if(digits.size()==0)
        return result;
        backtracking(digits,0);
        return result;
    }
};

 

LC39 组合总和

给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。

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

思路:注意这个是可以重复选的,所以对于startindex来说,下次的递归不需要+1,而是从当前开始(注意参数是i 而非index!!!!)

区别于LC17 LC17中的index独立循环,这个的循环开始点应该是i

代码:

class Solution {
private:
vector<vector<int>> result;
vector<int>path;
void backtracking(int target,vector<int>& candidates,int startIndex){
    if(target<0) return;
    if(target==0){
        result.push_back(path);
        return;
    }
    for(int i=startIndex;i<candidates.size();i++)
    {
        target-=candidates[i];
        path.push_back(candidates[i]);
        backtracking(target,candidates,i);//参数应该为i 而非index
        target+=candidates[i];
        path.pop_back();
    }
}
public:
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        backtracking(target,candidates,0);
        return result;
    }
};

 组合总和2

LC40

主要需要sort(vector.begin(),vector.end());判重:vector[startIndex]!=vector[startindex-1];

 

分割回文串:

LC131

给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是 回文串 。返回 s 所有可能的分割方案。

回文串 是正着读和反着读都一样的字符串。

思路:

1.判断回文串的函数---双指针

2.如何分割--startindex处开始,然后到i=string.size()结束

3.substr的用法:substr(int 指定的位置,复制的长度);

class Solution {
private:
vector<vector<string>> result;
vector<string> path;
bool isHuiWen(const string& s,int start,int end){

    for(int i=start,j=end;i<j;i++,j--)
    {
        if(s[i]!=s[j])
        return false;
    }
    return true;
}
void backtracking(const string& s ,int startIndex){
if(startIndex>=s.size())
{
    result.push_back(path);
    return;
}
for(int i=startIndex;i<s.size();i++)
{
    if(isHuiWen(s,startIndex,i)){
        string str=s.substr(startIndex,i-startIndex+1);//i-startindex+1?
        path.push_back(str);
    }
    else{
        continue;
    }
    backtracking(s,i+1);
    path.pop_back();
}
}
public:
    vector<vector<string>> partition(string s) {
        result.clear();
        path.clear();
        backtracking(s,0);
        return result;
    }
};

LC93 复原ip地址

 

注意回溯的参数s不能是const 因为要添加逗号

需要理解,判断字串是否合法的函数

class Solution {
private:
vector<string> result;

bool isValid(const string&s,int start,int end){
    if(start>end)return false;
    if(s[start]=='0'&&start!=end)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;
}
void backtracking( string& s,int startindex,int pointnum){
    if(pointnum==3)
    {
        if(isValid(s,startindex,s.size()-1))
        {result.push_back(s);}
        return ;
    }
    for(int i=startindex;i<s.size();i++)
    {
        string str=s.substr(startindex,i-startindex+1);
        if (isValid(s,startindex,i)){
            char c='.';
            s.insert(s.begin()+i+1 , c);
            pointnum++;
            backtracking(s,i+2,pointnum);
            pointnum--;
            s.erase(s.begin()+i+1);
        }
       else{
           break;
       }
       
    }
}
public:
    vector<string> restoreIpAddresses(string s) {
        backtracking(s,0,0);
        return result;
    }
};

 

 LC78 子集问题

给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。

解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。、

思路:

此题于一般的回溯题目不同,这个要收集的是全部子集,即这棵树的全部节点,所以result.push_back(path)放在每次递归的开头

同时使用startIndex 来记录每次的开始位置,防止出现重读

class Solution {
private:
vector<vector<int>> result;
vector<int>path;
void backtracking(vector<int>&nums,int startIndex){
    result.push_back(path);
if(startIndex>=nums.size()){
    return;
}
for(int i=startIndex;i<nums.size();i++)
{
    path.push_back(nums[i]);
    backtracking(nums,i+1);
    path.pop_back();
}
}
public:
    vector<vector<int>> subsets(vector<int>& nums) {
        path.clear();
        result.clear();
        backtracking(nums,0);
        return result;
    }
};

LC90 子集2

思路同上,只是多一个去重,参考组合总和2

疑问:为什么

if(nums[i]==nums[i-1]&& i>startIndex)不能过
而if(i>startIndex&&nums[i]==nums[i-1]) 能过?
 
class Solution {
private:
vector<vector<int>>result;
vector<int>path;
void backtracking(vector<int>& nums,int startIndex)
{
    result.push_back(path);
    
    for(int i=startIndex;i<nums.size();i++)
    {
        if(nums[i]==nums[i-1]&& i>startIndex){continue;}
        path.push_back(nums[i]);
        backtracking(nums,i+1);
        path.pop_back();
        
    }
}
public:
    vector<vector<int>> subsetsWithDup(vector<int>& nums) {
        result.clear();
        path.clear();
        sort(nums.begin(),nums.end());
        backtracking(nums,0);
        return result;
    }
};

LC419

难 需要再回顾

class Solution {
private:
vector<vector<int>> result;
vector<int> path;

void backtracking(vector<int>& nums,int startIndex){
   if(path.size()>1){result.push_back(path);}
   unordered_set<int> uset;//使用set对本层元素去重
   for(int i=startIndex;i<nums.size();i++){
       if((!path.empty()&&nums[i]<path.back())|| uset.find(nums[i])!=uset.end()){
           continue;
       }
       uset.insert(nums[i]);
       path.push_back(nums[i]);
       backtracking(nums,i+1);
       path.pop_back();
   }
}
public:
    vector<vector<int>> findSubsequences(vector<int>& nums) {
        result.clear();
        path.clear();
        
        backtracking(nums,0);
        return result;
    }
};

 LC46 全排列

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

思路:正常做,多加一个used数组,记得在主函数里面使用

class Solution {
private:
vector<vector<int>> result;
vector<int> path;
vector<bool>used;
void backtraking(vector<int>&nums,vector<bool>used){
if(path.size()==nums.size()){
    result.push_back(path);
    return;
}
for(int i=0;i<nums.size();i++)
{
    if(used[i])continue;
    if(!used[i]){
        path.push_back(nums[i]);
        used[i]=true;
        backtraking(nums,used);
        path.pop_back();
        used[i]=false;
    }
}
}
public:
    vector<vector<int>> permute(vector<int>& nums) {
        result.clear();
        path.clear();
        vector<bool> used(nums.size(),false);
        backtraking(nums,used);
        return result;
    }
};

LC 47 全排列2

给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。

 注意 这里的判断条件,什么时候需要剪纸,第一次就是漏了 &&used[i-1]==true)  导致没通过

 if((i>0&&nums[i]==nums[i-1]&&used[i-1]==true)||used[i]==true)continue;
class Solution {
private:
vector<vector<int>>result;
vector<int>path;
void backtracking(vector<int>& nums,vector<bool>used){
    if(path.size()==nums.size())
    {result.push_back(path);
    return;
    }
    for(int i=0;i<nums.size();i++)
    {
        if((i>0&&nums[i]==nums[i-1]&&used[i-1]==true)||used[i]==true)continue;
        path.push_back(nums[i]);
        used[i]=true;
        backtracking(nums,used);
        path.pop_back();
        used[i]=false;

    }
}
public:
    vector<vector<int>> permuteUnique(vector<int>& nums) {
        result.clear();
        path.clear();
        vector<bool>used(nums.size(),false);
        sort(nums.begin(),nums.end());
        backtracking(nums,used);
        return result;
    }
};

 LC 332 重新安排行程

问题很大

对于map set的不理解和不熟悉用法  需要重做理解

 for(pair<const string,int>& target : targets[result[result.size()-1]])  对这句代码也不理解

给你一份航线列表 tickets ,其中 tickets[i] = [fromi, toi] 表示飞机出发和降落的机场地点。请你对该行程进行重新规划排序。

所有这些机票都属于一个从 JFK(肯尼迪国际机场)出发的先生,所以该行程必须从 JFK 开始。如果存在多种有效的行程,请你按字典排序返回最小的行程组合。

  • 例如,行程 ["JFK", "LGA"] 与 ["JFK", "LGB"] 相比就更小,排序更靠前。

假定所有机票至少存在一种合理的行程。且所有的机票 必须都用一次 且 只能用一次。

 

class Solution {
private:
unordered_map<string,map<string,int>> targets;
bool backtracking(int ticketNum,vector<string>& result){
    if(result.size()==ticketNum+1){
        return true;
    }
    for(pair<const string,int>& target : targets[result[result.size()-1]]){
        if(target.second>0){
            result.push_back(target.first);
            target.second--;
            if(backtracking(ticketNum,result)) return true;
            result.pop_back();
            target.second++;
        }
    }
    return false;
}
public:
    vector<string> findItinerary(vector<vector<string>>& tickets) {
        targets.clear();
        vector<string> result;
        for(const vector<string>& vec:tickets){
            targets[vec[0]][vec[1]]++;
        }
        result.push_back("JFK");
        backtracking(tickets.size(),result);
        return result;
    }
};

LC51 N皇后

按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。

n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。

给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。

每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 'Q' 和 '.' 分别代表了皇后和空位。

 

思路:

1. 要有一个判断是否有效的函数 isValid

2.选择回溯的原因:能够画出树状,并进行剪枝

3.backtraking(row+1,n,chessboard);  注意要row+1

4.vector<string> chessboard(n, string(n, '.'));//主函数中,需要初始化

class Solution {
private:
bool isValid(int row,int col,int n,vector<string>& chessboard){
    //列检查
    for(int i=0;i<row;i++){
        if(chessboard[i][col]=='Q') return false;
    }
    //斜角都只往上查,因为是按行来遍历,只要上方没有交叉的就行
    //45°检查 
    for(int i=row-1,j=col-1;i>=0&&j>=0;i--,j--)
    {
        if(chessboard[i][j]=='Q') return false;
    }
    //135°检查
    for(int i=row-1,j=col+1;i>=0&&j>=0;i--,j++)
    {
        if(chessboard[i][j]=='Q') return false;
    }
    return true;
}
vector<vector<string>>result;
void backtraking(int row,int n,vector<string>& chessboard){
    if(row==n){
        result.push_back(chessboard);
        return;
    }
    for(int col=0;col<n;col++){
       // if(!isValid()) break;
        if(isValid(row,col,n,chessboard)){
            chessboard[row][col]='Q';
            backtraking(row+1,n,chessboard);
            chessboard[row][col]='.';
        }
    }
}
public:
    vector<vector<string>> solveNQueens(int n) {
        result.clear();
      vector<string> chessboard(n, string(n, '.'));//
        backtraking(0,n,chessboard);
        return result;
    }
};


LC 37 解数独

需要重刷 

对于二重递归不熟练。

需要写isValid;

class Solution {
private:
vector<vector<char>>result;
bool isValid(int row, int col, char val, vector<vector<char>>& board) {
    for (int i = 0; i < 9; i++) { // 判断行里是否重复
        if (board[row][i] == val) {
            return false;
        }
    }
    for (int j = 0; j < 9; j++) { // 判断列里是否重复
        if (board[j][col] == val) {
            return false;
        }
    }
    int startRow = (row / 3) * 3;
    int startCol = (col / 3) * 3;
    for (int i = startRow; i < startRow + 3; i++) { // 判断9方格里是否重复
        for (int j = startCol; j < startCol + 3; j++) {
            if (board[i][j] == val ) {
                return false;
            }
        }
    }
    return true;
}
bool backtracking(vector<vector<char>>& board){
    for(int i=0;i<9;i++){
            for(int j=0;j<9;j++){
            if(board[i][j]!='.')continue;
            for(char k='1';k<='9';k++)
            {
               
                if(isValid(i,j,k,board)){
                 board[i][j]=k;
                if(backtracking(board))return true;
                board[i][j]='.';
                }
            }
        
    return false;            
        }//这两层的目的是找到一个空格
    }
    return true;

}
public:
    void solveSudoku(vector<vector<char>>& board) {
        backtracking(board);
    }
};

 

posted @ 2024-02-28 19:51  Fredddy  阅读(4)  评论(0编辑  收藏  举报