算法学习|回溯

学习材料声明

所有知识点都来自互联网,进行总结和梳理,侵权必删。
引用来源:基本参考代码随想录的刷题顺序和讲解。
带你学透回溯算法(理论篇)| 回溯法精讲!
1|组合

理论基础

回溯纯暴力的方法(靠剪枝进行一些优化)。是一个递归的过程。
回溯的是当前的解空间。

1.回溯与模板

可以抽象为树形结构!(数的宽度 解空间for循环;树的深度 递归的深度;叶子结点收集结果)
在这里插入图片描述

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

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

2.解决什么问题?

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

2.1组合问题

分为个数限制,和限制,个数和加和双限制。candidates有重复元素(used限制层级不重复),无重复元素。

2.2切割问题|重点要关注字符串的相关方法

回文串和复原ip地址,怎么说呢,要写一个相关问题,startIdx开始,获取下一个分割点,分别判断分割点是否合法,不合法continue,合法回溯寻找下一个分割点。

2.3子集问题

与组合问题一致,只是需要再每个节点都收集path。

2.4排列问题

不使用startIndex,但是要使用used限制解空间大小。其他的重复与否之类的和以上问题一直。同层重复(序列中有重复元素)用set。

习题

1|77. 组合|要求组合元素为k个。|不可出现重复元素。

class Solution {
     List<Integer> path  = new ArrayList<Integer>();
    List<List<Integer>> ans = new ArrayList<List<Integer>>();

    public void backtracking(int n, int k, int startidx) {
    if (path.size()== k) {
        ans.add(new ArrayList<Integer>(path));
        System.out.print(path);
        return;
    }

    for (int j=startidx; j<=n-k+path.size()+1; j++) {//剪枝n-(k-path.size())+1
        path.add(j);
        backtracking(n, k, j+1);//这里是j+1,否则出现重复组合。
        path.remove(path.size() - 1);
    }
}
    public List<List<Integer>> combine(int n, int k) {
    
        backtracking(n, k, 1);
        return ans;

    }
}

剪枝操作!

----------------------------------------------------------------------------2023年10月23日----------------------------------------------------------

2|216. 组合总和 III|找出所有相加之和为 n 的 k 个数的组合|每个数字 最多使用一次

与组合那题没什么区别,只要搞清楚n和k的含义就好。

class Solution {
    List<List<Integer>> ans = new ArrayList<List<Integer>>();
    List<Integer> path  = new ArrayList<Integer>();
    int sum = 0;
    public void backtracking(int n, int k, int startidx) {
        if(sum > n){
            return;
        }
        if (path.size()== k){
            if(sum == n){
                ans.add(new ArrayList<Integer>(path));
                System.out.print(path);
                return;   
            }                     
        }
       
        for (int j=startidx; j<=9-(k-path.size())+1; j++) {//剪枝n-(k-path.size())+1
            sum += j;
            if(sum > n){
                sum -= j;
                return;
            }
            path.add(j);
            backtracking(n, k, j+1);//这里是j+1,否则出现重复组合。
            path.remove(path.size() - 1);
            sum -= j;
        }
    }
    public List<List<Integer>> combinationSum3(int k, int n) {
        backtracking(n, k, 1);
        return ans;
    }
}

3|17. 电话号码的字母组合

这题曾经看过灵茶山艾府的教学视频,试一试可不可以一下就写出来。主要还是在对String的处理上!

class Solution {
public:
    vector<string> ans;
    string path = "";
    const string nums[12] = {" ", " ", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
    void backtracing(string digits, int n, int k){
        if(path.size() == n){
            ans.push_back(path);
            return;
        }
        int num = int(digits[k]-'0');
        for(int i=0; i<nums[num].size(); i++){
            path.push_back(nums[num][i]);
            backtracing(digits, n, k+1);
            path.pop_back();
        }

    }
    vector<string> letterCombinations(string digits) {
        int n = digits.length();
        if(n == 0)
         return ans;
        backtracing(digits, n, 0);
        return ans;
    }
};

----------------------------------------------------------------------------2023年10月24日----------------------------------------------------------

4|39. 组合总和|可重复利用元素

class Solution {
public:
    vector<vector<int>> ans;
    vector<int> path;
    void backtracing(vector<int>& candidates, int target, int sum, int startidx){
        if(sum > target){
            return;
        }
        if(sum == target){
            ans.push_back(path);
            return;
        }
        for(int i=startidx; i<candidates.size(); i++){
            path.push_back(candidates[i]);
            sum += candidates[i];
            backtracing(candidates, target, sum, i);
            path.pop_back();
            sum -= candidates[i];
        }
    }
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        backtracing(candidates, target, 0, 0);
        return ans;
        
    }
};

代码随想录
优化思路是排序candidates+剪枝,以下代码来自代码随想录

class Solution {
private:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(vector<int>& candidates, int target, int sum, int startIndex) {
        if (sum == target) {
            result.push_back(path);
            return;
        }

        // 如果 sum + candidates[i] > target 就终止遍历
        for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i++) {
            sum += candidates[i];
            path.push_back(candidates[i]);
            backtracking(candidates, target, sum, i);
            sum -= candidates[i];
            path.pop_back();

        }
    }
public:
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        result.clear();
        path.clear();
        sort(candidates.begin(), candidates.end()); // 需要排序
        backtracking(candidates, target, 0, 0);
        return result;
    }
};

5|40. 组合总和 II|每个字母至多使用一次但candidates里面有重复数字。(这也是处理难点)|设计一个used,同一树层和同一树枝的概念。即对同一树层,只使用相同值的一个。

class Solution {
public:
    vector<vector<int>> ans;
    vector<int> path;
    void backtracing(vector<int>& candidates, int target, int sum, int startidx, vector<bool> used){
        if(sum > target){
            return;
        }
        if(sum == target){
            ans.push_back(path);
            return;
        }
        for(int i=startidx; i<candidates.size() && sum + candidates[i] <= target; i++){
            if (i > 0 && candidates[i] == candidates[i - 1] && used[i - 1] == false) {
                continue;
            }
            path.push_back(candidates[i]);
            sum += candidates[i];
            used[i] = true;
            backtracing(candidates, target, sum, i+1, used);
            path.pop_back();
            sum -= candidates[i];
            used[i] = false;
        }
    }
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        vector<bool> used(candidates.size(), false);
        path.clear();
        ans.clear();
        sort(candidates.begin(), candidates.end());
        backtracing(candidates, target, 0, 0, used);
        return ans;
        
    }
};

代码随想录

6|131. 分割回文串

主要问题,一用到String就各种方法水土不服。双指针判断回文串不要忘记。

class Solution {
    List<List<String>> ans =  new ArrayList<List<String>>();
    List<String> path = new ArrayList<String>();

    public boolean isPalindrome(String s, int start, int end){
     for (int i = start, j = end; i < j; i++, j--) {
         if (s.charAt(i) != s.charAt(j)) {
             return false;
         }
     }
     return true;
    }
    public void backTracing(String s, int startidx){
        if(startidx == s.length()){
            ans.add(new ArrayList<String>(path));
            return;
        }

        for(int i = startidx; i<s.length(); i++){
            if(isPalindrome(s, startidx, i)){
                String str = s.substring(startidx, i + 1);
                path.add(str);
            }else{
                continue;
            }
            backTracing(s, i+1);
            path.remove(path.size() - 1);
        }

    }
    public List<List<String>> partition(String s) {
        backTracing(s, 0);
        return ans;
    }
}

----------------------------------------------------------------------------2023年10月25日----------------------------------------------------------

7|93. 复原 IP 地址

写不出来,主要在回溯出问题了。—>原因在于for外面的一个判断,path,没有复原,导致出现无法回溯的问题。

class Solution {
    List<String> ans = new ArrayList<String>();
    List<String> path = new ArrayList<String>();
    int[] nums = {1, 10, 100};
    public boolean isCorrect(String s, int start, int end){
        if(end - start + 1 > 3){
            return false;
        }
        int intNum = 0;     
        String temp = s.substring(start, end+1);
        for(int i=end, j=0; i>=start; i--,j++){
            intNum += (s.charAt(i) - '0')*nums[j];
        }
        if(intNum<10 && start == end){
            return true;
        }
        if(intNum>9 && intNum<100 && end-start == 1){
            return true;
        }
        if(intNum > 99 && intNum<=255 && end-start == 2 ){
            return true;
        }
        return false;      
    }
    public void backTracing(String s, int startIdx){
        if(path.size() == 3){
            if(isCorrect(s, startIdx, s.length()-1)){
                path.add(s.substring(startIdx, s.length()));
                ans.add(path.get(0)+"."+path.get(1)+"."+path.get(2)+"."+path.get(3));
                path.remove(path.size() - 1);//这里!!!!!记住要复原!!!
            }           
            return;           
        }
        for(int i=startIdx; i<=startIdx+2 && i<s.length(); i++){
            if(isCorrect(s, startIdx, i)){
                String str = s.substring(startIdx, i+1);
                path.add(str);
                System.out.print(path+"\n");
                backTracing(s, i+1);
                path.remove(path.size() - 1);
            }else{
                break;
            }
            
        }

    }
    public List<String> restoreIpAddresses(String s) {
        backTracing(s, 0);
        return ans;
    }
}

8|78. 子集

class Solution {
public:
    vector<vector<int>> ans;
    vector<int> path;
    void backTracing(vector<int>& nums, int startIndex){
     	ans.push_back(path);//每个节点都收集
        if(startIndex == nums.size()){
            return;
        }
        for(int i=startIndex; i<nums.size(); i++){
            path.push_back(nums[i]);
           
            backTracing(nums, i+1);
            path.pop_back();
        }
    }
    vector<vector<int>> subsets(vector<int>& nums) {
        backTracing(nums, 0);
        return ans;

    }
};

9|90. 子集 II |与组合问题中candidates里面有重复元素一样,要排序后跳跃,或者用used。

class Solution {
public:
    vector<vector<int>> ans;    
    vector<int> path;
    void backTracing(vector<int>& nums, int startIndex){
     	ans.push_back(path);//每个节点都收集
        if(startIndex == nums.size()){
            return;
        }
        for(int i=startIndex; i<nums.size(); i++){
            if(i > startIndex && nums[i - 1] == nums[i]){
                continue;
            }
            path.push_back(nums[i]);         
            backTracing(nums, i+1);
            path.pop_back();
        }
    }
    vector<vector<int>> subsetsWithDup(vector<int>& nums) {
        sort(nums.begin(), nums.end());
        backTracing(nums, 0);
        return ans;
    }
};

----------------------------------------------------------------------------2023年10月27日----------------------------------------------------------

10|491.递增子序列

这一题感觉非常简单呀,就是排序,然后过滤掉一些节点就好了。(错误思路,不能排序)
啊啊啊,好想用动态规划来做。同层不能用同样的,但是怎么确定捏?
还是画树比较容易理解。
很明显一个元素不能重复使用,所以需要startIndex。
回溯三步(参数设计,终止条件,单层搜索逻辑)。
又学到一个知识点,用set标记。

class Solution {
public:
    vector<vector<int>> ans;    
    vector<int> path;
    void backTracing(vector<int>& nums, int startIndex){
        if(path.size()>=2){
     	    ans.push_back(path);
        }//每个节点都收集
        if(startIndex == nums.size()){
            return;
        }
        unordered_set<int> uset; // 使用set对本层元素进行去重
        for(int i=startIndex; i<nums.size(); i++){
            if((path.size()!=0 && nums[i] < path.back()) || uset.find(nums[i]) != uset.end()){
                continue;
            }       
           	uset.insert(nums[i]);    
            path.push_back(nums[i]);       
            backTracing(nums, i+1);
            path.pop_back();

        }
    }
    vector<vector<int>> findSubsequences(vector<int>& nums) {
        path.clear();
        ans.clear();
        backTracing(nums, 0);
        return ans;
    }
};

11|46. 全排列

排列问题不使用startIndex。
每一个节点的拓展宽度受父节点影响,所以记录一条树枝上的used情况。

class Solution {
public:
    vector<vector<int>> ans;
    vector<int> path;
    void backTracing(vector<bool> used, vector<int> nums, int n){
        if(n==nums.size()){
            ans.push_back(path);
            return;
        }
        for(int i=0; i<nums.size(); i++){
            if(used[i]==false){
                path.push_back(nums[i]);
                used[i]=true;
                backTracing(used, nums, n+1);
                path.pop_back();
                used[i]=false;
            }
        }
    }

    vector<vector<int>> permute(vector<int>& nums) {
        vector<bool> used(nums.size(), false);
        ans.clear();
        path.clear();
        backTracing(used, nums, 0);
        return ans;

    }
};

12|47.全排列 II

全排列1+同层去重。

class Solution {
public:
    vector<vector<int>> ans;
    vector<int> path;
    void backTracing(vector<bool> used, vector<int> nums){
        if(path.size()==nums.size()){
            ans.push_back(path);
            return;
        }
        unordered_set<int> uset; // 使用set对本层元素进行去重
        for(int i=0; i<nums.size(); i++){
            if(used[i]==false&&uset.find(nums[i]) == uset.end()){//uset中没有这个元素。
                uset.insert(nums[i]);  
                path.push_back(nums[i]);
                used[i]=true;
                backTracing(used, nums);
                path.pop_back();
                used[i]=false;
            }
        }
    }
    vector<vector<int>> permuteUnique(vector<int>& nums) {
        vector<bool> used(nums.size(), false);
        ans.clear();
        path.clear();
        backTracing(used, nums);
        return ans;
    }
};

13|494. 目标和

class Solution {
public:
    int count = 0;

    int findTargetSumWays(vector<int>& nums, int target) {
        backtrack(nums, target, 0, 0);
        return count;
    }

    void backtrack(vector<int>& nums, int target, int index, int sum) {
        if (index == nums.size()) {
            if (sum == target) {
                count++;
            }
        } else {
            backtrack(nums, target, index + 1, sum + nums[index]);
            backtrack(nums, target, index + 1, sum - nums[index]);
        }
    }
};

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值