递归,回溯总结——算法(一)

回溯经典例题
全排列
非常经典的例题,用回溯方法帮助我们解决排列问题,回溯主要注意三个方面,终止条件,添加选择,去除选择

class Solution {
    vector<vector<int>> res;
    void backTrack(vector<int> nums, vector<int> temp, vector<bool>& visit){
        if (temp.size() == nums.size()){//满足的条件
            res.push_back(temp);
            return;
        }
        for (int i=0; i<nums.size(); i++){
            if (visit[i])//是否使用过
                continue;
            visit[i] = true;
            temp.push_back(nums[i]);//添加选择
            backTrack(nums, temp, visit);
            visit[i] = false;
            temp.pop_back();//去除选择
        }
    }
public:
    vector<vector<int>> permute(vector<int>& nums) {
        if (nums.empty()) return {};
        vector<bool> visit(nums.size(), false);
        backTrack(nums, {}, visit);
        return res;
    }
};

存在重复元素的排列

class Solution {
    vector<vector<int>> res;
    void backTrack(vector<int> nums, vector<int> temp, vector<bool>& visit){
        if (temp.size() == nums.size()){//满足的条件
            res.push_back(temp);
            return;
        }
        for (int i=0; i<nums.size(); i++){
            if (visit[i])//是否使用过
                continue;
            //判断重复元素
            if (i>0 && visit[i-1] && nums[i]==nums[i-1])
            	continue;
            visit[i] = true;
            temp.push_back(nums[i]);//添加选择
            backTrack(nums, temp, visit);
            visit[i] = false;
            temp.pop_back();//去除选择
        }
    }
public:
    vector<vector<int>> permute(vector<int>& nums) {
        if (nums.empty()) return {};
        vector<bool> visit(nums.size(), false);
        sort(nums.begin(), nums.endl());//注意先排序
        backTrack(nums, {}, visit);
        return res;
    }
};

组合总数
candidates = {2,3,5,7} target = 7
这是非常经典的组合问题,按照以下模板。分为元素可重复使用和不可重复使用

class Solution {
public:
    vector<vector<int>> res;
    void backTrack(int now, int sum, int target, vector<int> temp, vector<int>& candidates){
        if(sum > target) return;//剪枝
        if(sum == target){//终止条件
            res.push_back(temp);
            return;
        }
        //注意这里的now,不是0,因为这是组合问题,不是排列
        for(int i=now; i<candidates.size(); i++){
            sum += candidates[i];//添加选择
            temp.push_back(candidates[i]);
            //这里是允许元素重复使用的,如[5,5,7]
            backTrack(i, sum, target, temp, candidates);
            sum -= candidates[i];
            temp.pop_back();//去除选择
        }
    }
    vector<vector<int>> combinationSum(vector<int>& candidates, int target){
        vector<int> temp;
        backTrack(0, 0, target, temp, candidates);
        return res;
    }
};

go 语言版本,注意二维slice的值拷贝问题

func combinationSum(candidates []int, target int) [][]int {
    res := make([][]int, 0)
    temp := make([]int, 0)
    backTrack(0, candidates, target, &res, temp) // 注意res一定是指针类型,函数内部会发生append
    return res
}

func backTrack(now int, candidates []int,  target int, res *[][]int, temp []int) {
    if target < 0 {
        return
    }

    if target == 0 {
        track := make([]int, len(temp))
        copy(track, temp)  // 注意,二维slice添加时,一定要值拷贝
        *res = append(*res, track)
        return
    }

    for i := now; i < len(candidates); i++ {
        temp = append(temp, candidates[i])
        backTrack(i, candidates, target-candidates[i], res, temp)
        temp = temp[ : len(temp)-1]
    }
}
class Solution {
public:
    vector<vector<int>> res;
    void backTrack(int now, int sum, int target, vector<int> temp, vector<int>& candidates){
        if(sum > target) return;//剪枝
        if(sum == target){//终止条件
            res.push_back(temp);
            return;
        }
        //注意这里的now,不是0,因为这是组合问题,不是排列
        for(int i=now; i<candidates.size(); i++){
            sum += candidates[i];//添加选择
            temp.push_back(candidates[i]);
            //这里是不允许元素重复使用的,如[2,3,5,7]
            backTrack(i+1, sum, target, temp, candidates);
            sum -= candidates[i];
            temp.pop_back();//去除选择
        }
    }
    vector<vector<int>> combinationSum(vector<int>& candidates, int target){
        vector<int> temp;
        backTrack(0, 0, target, temp, candidates);
        return res;
    }
};

存在重复元素的组合
acab
output
a aa aab aabc aac ab abc ac b bc c

#include <bits/stdc++.h>
using namespace std;
vector<string> res;
void backTrack(int now, string str, string temp, vector<bool> visit, int k){
    if (temp.length() > k){//剪枝
        return;
    }
    res.push_back(temp);
    for (int i=now; i<str.length(); i++){
        if (i>0 && !visit[i-1] && str[i]==str[i-1])//剪枝,去除重复元素
            continue;
        visit[i] = true;
        temp += str[i]; //这里进行了修改,那么后面一定有对应的还原回去
        backTrack(i+1, str, temp, visit, k);
        visit[i] = false;
        temp = temp.substr(0, temp.length()-1);//这里还原
    }
}
int main(){
    string str; int k;
    while (cin >> str >> k){
        sort(str.begin(), str.end());
        vector<bool> visit(str.length(), false);
        backTrack(0, str, "", visit, k);
        for (int i=0; i<res.size(); i++)
            cout << res[i] << ' ';
    }
    return 0;
}

24游戏
给出4个数,运用加减乘除凑齐24
经典解法,递归,递归从头到尾只跑一遍,中间递归,不同于回溯,回溯从任意地方开始递归。

#include <bits/stdc++.h>
using namespace std;
char sign[4] = {'+', '-', '*', '/'};
bool res = false;
void backTrack(int i, vector<int> arr, vector<bool>& visit, double sum, int count){
    if (count == 4 && (int)sum == 24){
        res = true;
        return;
    }
    if (i >= arr.size())
        return;
    if (!visit[i]){
        for (int j=0; j<4; j++){
            switch (sign[j]){//选择
                case '+': sum+=arr[i]*1.0; break;
                case '-': sum-=arr[i]*1.0; break;
                case '*': sum*=arr[i]*1.0; break;
                case '/': if (arr[i]!=0) sum/=arr[i]*1.0; break;
                default: break;
            }
            backTrack(i+1, arr, visit, sum, count+1);//选择
        }
    }
    else
        backTrack(i+1, arr, visit, sum, count);
}
int main(){
    vector<int> arr; int i;
    while (cin >> i){
        arr.push_back(i);
    }
    if (arr.size() != 4){
        cout << "input error!" << endl;
        return 0;
    }
    for (int i=0; i<arr.size(); i++){
        vector<bool> visit(arr.size(), false); visit[i] = true;
        backTrack(0, arr, visit, (double)arr[i], 1);
    }
    cout << res << endl;
    return 0;
}

括号生成 中等
递归,从头到尾,然后中间就是选择

class Solution {
    vector<string> res;
    void helper(string s, int ln, int rn, int n){
        if (ln < 0 || rn < 0) return;
        if (s.length() == n && ln == rn) res.push_back(s);
        helper(s+'(', ln-1, rn, n);//选择
        if (ln < rn)
            helper(s+')', ln, rn-1, n);//选择
    }
public:
    vector<string> generateParenthesis(int n) {
        helper("", n, n, 2*n);
        return res;
    }
};

电话号码组合问题

回溯思路,但是只走一遍,所以注意return是在for里面。

func letterCombinations(digits string) []string {
    if len(digits) == 0 {
        return []string{}
    }
    s := []string {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"}
    res := make([]string, 1)
    backTrack(0, s, digits, &res)  // 注意res一定是指针类型,函数内部会发生append
    
    return res
}

func backTrack(now int, s []string, digits string, res *[]string) {
    for i := now;  i < len(digits); i++ {
        if len((*res)[0]) >= len(digits) { // 不是组合问题,只走一遍循环,所以判断要放for里面
            return
        }
        num := digits[i] - '0'
        temp := make([]string, 0)
        for m := 0; m < len(*res); m++ {
            for n := 0; n < len(s[num]); n++ {
                str := (*res)[m] + string(s[num][n])
                temp = append(temp, str)
            }
        }
        (*res) = temp
        backTrack(i+1, s, digits, res)
    }
}

最近做笔试题,很多题基本是回溯方法,但是就是会超时,不是回溯本身的问题,组合排列本身不存在重复,所以方法应该是没问题的,用迭代也是一样的时间复杂度。所以唯一能做的就是剪枝或者完全应该是另外解题思路。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

华为云计算搬砖工

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值