力扣题之回溯算法

什么是回溯法

回溯和递归相辅相成,递归函数的下面是回溯的过程。回溯法:纯暴力的搜索,抽象成树形结构,回溯法抽象为树形结构后,遍历过程就是:for循环横向遍历,递归纵向遍历,回溯不断调整结果集。
回溯算法模板框架:

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

回溯算法能解决的问题

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

组合问题

给定两个整数 n 和 k,返回 1 … n 中所有可能的 k 个数的组合。
示例: 输入: n = 4, k = 2 输出: [ [2,4], [3,4], [2,3], [1,2], [1,3], [1,4], ]
解决k层for循环嵌套的问题,回溯法三部曲

class Solution {
public:
    vector<vector<int>> result;//存放符合条件的结果集合
    vector<int> path;//符合条件的结果
    void backtracking(int n, int k, int index){
        if(path.size() == k){//终止条件
            result.push_back(path);
            return;
        }
        
        for(int i = index; i <= n; i++){//控制横向遍历
            path.push_back(i);//处理节点
            backtracking(n, k, i+1);//递归,下一层搜索从i+1开始
            path.pop_back();//回溯,撤销处理结果
        }
    }
    
    vector<vector<int>> combine(int n, int k) {
        backtracking(n, k, 1);
        return result;
    }
};

组合优化

剪枝优化

class Solution {
public:
    vector<vector<int>> result;//存放符合条件的结果集合
    vector<int> path;//符合条件的结果
    void backtracking(int n, int k, int index){
        if(path.size() == k){//终止条件
            result.push_back(path);
            return;
        }
        
        for(int i = index; i <= n - (k - path.size()) + 1; i++){//控制横向遍历,优化
            path.push_back(i);//处理节点
            backtracking(n, k, i+1);//递归,下一层搜索从i+1开始
            path.pop_back();//回溯,撤销处理结果
        }
    }
    
    vector<vector<int>> combine(int n, int k) {
        backtracking(n, k, 1);
        return result;
    }
};

组合总和|||

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

电话号码的字母组合

string letterMap[10]={
	"",//0
	"",//1
	"abc",//2
	"def",//3
	"ghi",//4
	"jkl",//5
	"mno",//6
	"pqrs",//7
	"tuv",//8
	"wxyz",//9
};
string s;
vector<string> result;
//index是输入数字组合的下标
void backtracking(string digits, int index){
	if(index==digits.size()){
		return.push_back(s);
		return;
	}
	int digit = digits[index]-'0';
	string letter = letterMap[digit]
	for(int i=0;i<letter.size();i++){
		s.push_back(letter[i]);
		backtracking(digits,index+1);
		s.pop_back();
	}
}

完整代码:

class Solution {
public:
    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(string digits, int index){
        if(index==digits.size()){
            result.push_back(s);
            return;
        }
        int digit = digits[index] - '0';//index指向的数字转成int类型
        string letter = letterMap[digit];
        for(int i=0; i<letter.size(); i++){
            s.push_back(letter[i]);//处理
            backtracking(digits, index+1);//递归,处理下一个数字index+1
            s.pop_back()//回溯
        }
    }
    vector<string> letterCombinations(string digits) {
        if(digits.size()==0) return result;
        backtracking(digits, 0);
        return result;
    }
};

组合总和

**题目:**给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。candidates 中的数字可以无限制重复被选取。
所有数字(包括 target)都是正整数,解集不能包含重复的组合。
示例:
输入:candidates = [2,3,6,7], target = 7,
所求解集为: [ [7], [2,2,3] ]

组合没有数量要求;元素可以重复提取

class Solution {
public:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(vector<int>& candidates, int target, int sum, int startindex){
    	//终止条件
        if(sum>target) return;
        if(sum==target){
            result.push_back(path);
            return;
        }
        //单层搜索逻辑
        for(int i=startindex; i<candidates.size(); i++){
            sum+=candidates[i];
            path.push_back(candidates[i]);
            backtracking(candidates, target, sum, i);//i,元素可重复使用
            sum-=candidates[i];//回溯
            path.pop_back();//回溯
        }
    }
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        backtracking(candidates, target, 0, 0);
        return result;
    }
};

组合总和||

题目:给定一个候选人编号的集合 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用 一次 。
注意:解集不能包含重复的组合。
代码:

//排序,去重
//树层去重,树枝去重
class Solution {
public:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(vector<int>& nums, int sum, int startindex, int target, vector<bool> used){
        if(sum > target) return;
        if(sum == target){
            result.push_back(path);
            return;
        }
        for(int i = startindex; i < nums.size(); i++){
            if(i>0 && nums[i] == nums[i-1] && used[i-1] == 0) continue;
            path.push_back(nums[i]);
            sum += nums[i];
            used[i] = true;
            backtracking(nums, sum, i+1, target, used);
            sum -= nums[i];//回溯
            used[i] = false;
            path.pop_back();
        }

    }
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        int n = candidates.size();
        vector<bool> used(n, false);
        sort(candidates.begin(), candidates.end());
        backtracking(candidates, 0, 0, target, used);
        return result;
    }
};

分割回文串

题目:给定一个字符串 s,将 s 分割成一些子串,使每个子串都是回文串。
返回 s 所有可能的分割方案。
示例: 输入: “aab” 输出: [ [“aa”,“b”], [“a”,“a”,“b”] ]

class Solution {
public:
    vector<vector<string>> result;//所有结果集
    vector<string> path;//回文的子串
    //判断是否是回文串,双指针
    bool isHuiwen(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(string s, int startIndex){
    	//起始位置大于s的大小,找到一组分割方案
        if(startIndex >= s.size()){
            result.push_back(path);
            return;
        }
        for(int i = startIndex; i < s.size(); i++){
            if(isHuiwen(s,startIndex,i)){
                //是回文,获取[startIndex,i]的子串
                string str = s.substr(startIndex,i-startIndex+1);
                path.push_back(str);
            }else{//不是回文串,跳过
                continue;
            }
            backtracking(s, i + 1);//寻找下一个子串,从i+1开始
            path.pop_back();//回溯
        }
        
    }
    vector<vector<string>> partition(string s) {
        backtracking(s, 0);
        return result;
    }
};

复原IP地址

**题目:**给定一个只包含数字的字符串,复原它并返回所有可能的 IP 地址格式。
有效的 IP 地址 正好由四个整数(每个整数位于 0 到 255 之间组成,且不能含有前导 0),整数之间用 ‘.’ 分隔。
例如:“0.1.2.201” 和 “192.168.1.1” 是 有效的 IP 地址,但是 “0.011.255.245”、“192.168.1.312” 和 “192.168@1.1” 是 无效的 IP 地址。
示例 1:
输入:s = “25525511135”
输出:[“255.255.11.135”,“255.255.111.35”]
0 <= s.length <= 3000
s 仅由数字组成
思路: 分割、合法性判断

vector<string> result;
void backtracking(s, startIndex, pointSum){
	if(pointSum == 3){//逗点
		if(isValid(s, startIndex, s.size()-1)){
			result.push_back(s);
			return;
		}
	}
	for(i = startIndex; i < s.size(); i++){
		if(isValid(s,startIndex,i)){//[stratIndex,i]
			s.insert(s.begin() + i + 1, '.');//插入逗点
			pointSum += 1;
			backtracking(s, i+2, pointSum);
			s.erase(s.begin() + i + 1);//删掉逗点
			pointSum -= 1;//回溯
		}
	}
}
class Solution {
public:
    vector<string> result;//存储合法的IP字符串
    //判断是否合法
    bool isValid(string s, int start, int end){
        if(start > end) return false;
        if(s[start] == '0' && start != end) return false;//0开头的数字不合法
        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;//数字大于255
        }
        return true;
    }
    void backtracking(string s, int starIndex, int pointNum){
        if(pointNum == 3){
            if(isValid(s, starIndex, s.size()-1)){
                result.push_back(s);
            }
            return;
        }
        for(int i = starIndex; i < s.size(); i++){
            if(isValid(s, starIndex, i)){
                s.insert(s.begin() + i + 1, '.');//插入逗点
                pointNum++;//逗点数量加1
                backtracking(s, i + 2, pointNum);
                pointNum--;//逗点数量减1 回溯
                s.erase(s.begin() + i + 1);//删掉逗点
            }else break;
        }
    }
    vector<string> restoreIpAddresses(string s) {
        if(s.size() > 12 || s.size() < 4) return result;
        backtracking(s, 0, 0);
        return result;
    }   
};

子集

给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
说明:解集不能包含重复的子集。
示例: 输入: nums = [1,2,3] 输出: [ [3], [1], [2], [1,2,3], [1,3], [2,3], [1,2], [] ]

path
result
void backtracking(nums, 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();
	}
	return;
}

完整代码:

class Solution {
public:
    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();//回溯
        }
        return;
    }
    vector<vector<int>> subsets(vector<int>& nums) {
        backtracking(nums, 0);
        return result;
    }
};

子集 II

给你一个整数数组 nums,其中可能包含重复元素,请你返回该数组所有可能的
子集(幂集)。
解集不能包含重复的子集。返回的解集中,子集可以按 任意顺序排列。

class Solution {
public:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(vector<int> nums, int startIndex, vector<bool> used){
        result.push_back(path);
        for(int i = startIndex; i < nums.size(); i++){
            if(i>0 && nums[i]==nums[i-1] && used[i-1]==false){
                continue;
            }
            path.push_back(nums[i]);
            used[i] = true;
            backtracking(nums, i+1, used);
            used[i] = false;
            path.pop_back();
        }
        return;
    }
    vector<vector<int>> subsetsWithDup(vector<int>& nums) {
        vector<bool> used(nums.size(), false);
        sort(nums.begin(), nums.end());//去重排序
        backtracking(nums, 0, used);
        return result;
    }
};

491.递增子序列

给定一个整型数组, 你的任务是找到所有该数组的递增子序列,递增子序列的长度至少是2。
示例:
输入: [4, 6, 7, 7]
输出: [[4, 6], [4, 7], [4, 6, 7], [4, 6, 7, 7], [6, 7], [6, 7, 7], [7,7], [4,7,7]]
说明:
给定数组的长度不会超过15。
数组中的整数范围是 [-100,100]。
给定数组中可能包含重复数字,相等的数字应该被视为递增的一种情况。

class Solution {
public:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(vector<int> nums, int startIndex){
        unordered_set<int> uset;//对本层元素去重
        if(path.size()>1) result.push_back(path);
        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();
        }
    }
    vector<vector<int>> findSubsequences(vector<int>& nums) {
        backtracking(nums, 0);
        return result;
    }
};

46. 全排列

class Solution {
public:
    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(used[i]==true) continue;
            used[i]=true;
            path.push_back(nums[i]);
            backtracking(nums, used);
            used[i]=false;
            path.pop_back();
        }
    }
    vector<vector<int>> permute(vector<int>& nums) {
        vector<bool> used(nums.size(), false);
        backtracking(nums, used);
        return result;
    }
};

47. 全排列 II

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

class Solution {
public:
    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]==false){
                continue;
            }
            if(used[i]==true) continue;
            used[i] = true;
            path.push_back(nums[i]);
            backtracking(nums, used);
            path.pop_back();
            used[i] = false;
        }
    }
    vector<vector<int>> permuteUnique(vector<int>& nums) {
        vector<bool> used(nums.size(), false);
        sort(nums.begin(), nums.end());
        backtracking(nums, used);
        return result;
    }
};

重新安排行程

给定一个机票的字符串二维数组 [from, to],子数组中的两个成员分别表示飞机出发和降落的机场地点,对该行程进行重新规划排序。所有这些机票都属于一个从 JFK(肯尼迪国际机场)出发的先生,所以该行程必须从 JFK 开始。
示例 1:
输入:[[“MUC”, “LHR”], [“JFK”, “MUC”], [“SFO”, “SJC”], [“LHR”, “SFO”]]
输出:[“JFK”, “MUC”, “LHR”, “SFO”, “SJC”]
这道题很难,看的题解敲的

class Solution {
public:
    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;
    }
    vector<string> findItinerary(vector<vector<string>>& tickets) {
        vector<string> result;
        for(const vector<string> vec : tickets){
            targets[vec[0]][vec[1]]++;
        }
        result.push_back("JFK");
        backtracking(tickets.size(), result);
        return result;
    }
};

N皇后

n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。
给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。
每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 ‘Q’ 和 ‘.’ 分别代表了皇后和空位。
输入:n = 4
输出:[[“.Q…”,“…Q”,“Q…”,“…Q.”],[“…Q.”,“Q…”,“…Q”,“.Q…”]]
解释:如上图所示,4 皇后问题存在两个不同的解法。
思路
约束条件:不能同行、不能同列、不能同斜线
棋盘的宽度就是for循环的长度,递归的深度就是棋盘的高度

class Solution {
public:
    vector<vector<string>> result;
    void backtracking(int row, int n, vector<string>& chessboard){
        if(row==n){
            result.push_back(chessboard);
            return;
        }
        for(int col=0; col<n; col++){
            if(isValid(row, col, chessboard, n)){
                chessboard[row][col]='Q';
                backtracking(row+1,n,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--){
            if(chessboard[i][j]=='Q') return false;
        }
        for(int i=row-1, j=col+1; i>=0&&j<n; i--, j++){
            if(chessboard[i][j]=='Q') return false;
        }
        return true;
    }
    vector<vector<string>> solveNQueens(int n) {
        std::vector<std::string> chessboard(n,std::string(n,'.');
        backtracking(0, n, chessboard);
        return result;
    }
};

数独

二维递归

class Solution {
public:
    bool backtracking(vector<vector<char>>& board){
        for(int i = 0; i < board.size(); i++){//遍历行
            for(int j = 0; j < board[0].size(); j++){//遍历列
                if(board[i][j]=='.'){
                    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;//9个数试完了,还是不行,返回false
                }

            }
        }
        return true;
    }
    bool isValid(int row, int col, char k, vector<vector<char>>& board){
        for(int i=0; i<9; i++){//是否行重复
            if(board[row][i]==k) return false;
        }
        for(int j=0; j<9; j++){//是否列重复
            if(board[j][col]==k) return false;
        }
        int startRow=(row/3)*3;
        int startCol=(col/3)*3;
        for(int i=startRow; i<startRow+3; i++){//九宫格是否重复
            for(int j=startCol; j<startCol+3; j++){
                if(board[i][j]==k) return false;
            }
        }
        return true;
    }
    void solveSudoku(vector<vector<char>>& board) {
        backtracking(board);
    }
};
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值