【Day30】代码随想录之回溯算法part6——回溯算法总结、重新安排行程、N皇后、解数独

本文详细介绍了回溯算法的理论概念,包括其原理、常见问题类型(如组合、排列、切割和子集等),并提供了代码模板和优化策略。重点回顾了N皇后和解数独问题,强调了在实际应用中的理解和练习策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

现在到了回溯算法的第6部分,今天的题目很难:

今天这三道题都非常难,那么这么难的题,为啥一天做三道?
因为 一刷 也不求大家能把这么难的问题解决,所以 大家一刷的时候,就了解一下题目的要求,了解一下解题思路,不求能直接写出代码,先大概熟悉一下这些题,二刷的时候,随着对回溯算法的深入理解,再去解决如下三题。

今天的任务其实是对回溯算法章节做一个总结就行。

今日任务

  • 回溯算法总结
  • 332.重新安排行程(可跳过)
  • 51.N皇后(可跳过)
  • 37.解数独(可跳过)

一、回溯算法总结

参考:【代码随想录之回溯总结篇】

1.1 回溯算法理论篇

1、回溯是递归的副产品,只要有递归就会有回溯,所以回溯也经常和二叉树遍历,深度优先搜索混在一起,因为这两种方式都用到了递归。

2、回溯算法就是暴力搜索,并不是什么高效的算法,最多再剪枝一下。

3、回溯算法可以解决的问题:

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

4、回溯代码的模板:


void backtracking(参数){
	if(终止条件){
		存放结果
		return;
	}	
	// 遍历节点中的元素(如果是排列问题的话,i从0开始)
	for(int  i = startIndex; i < ***; i++){
		处理节点;
		backtracking();
		回溯
	}
}

5、在这个模板中,for循环负责横向遍历,递归负责纵向遍历,回溯不断调整结果集

6、优化回溯算法只有剪枝一种方法。

1.2 组合问题:

1.2.1 普通的n个数中选k个数

在这里插入图片描述
对于组合问题,为了避免被重新选择,所以需要用一个startIndex来计数。


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

    vector<vector<int>> combine(int n, int k) {
        backtracking(n, k, 1);
        return result;
    }
};

如果对此过程进行剪枝优化,只需要在for循环中做调整即可:i至多从n-(k-path.size()) + 1开始,调整如下:

for(int i = 0; i < n-(k-path.size()) + 1)

1.2.2 组合总和(一)

在这里插入图片描述
此题的特点为:取得时候树枝上是可以重复的,所以要在递归处做文章,但是for循环处的不可以变,所以i还是从startIndex开始的。

在这里插入图片描述

终止条件为:元素的和 > target了
在递归时需要注意:因为元素是可以重复的,所以递归时传入的是i,而非 i + 1;
其次,剪枝操作通常都是在for循环中做文章的,然后 数组也需要先进行排序。
剪枝的操作(可以不进行剪枝):

for(int  i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i++)

1.2.3 组合总和(二)

在这里插入图片描述
此题的特点为:原数组中有重复元素,需要在树层上进行去重,所以做法是先排序,然后使用used数组进行去重(也可采用unordered_map这种哈希表进行去重)

终止条件:sum > target时 return; sum == target 存到result中,然后return;
在这里插入图片描述

class Solution {
public:

    vector<vector<int>> result;
    vector<int> path;

    void backtracing(vector<int>& candidates, int target, int sum, int startIndex, vector<bool>& used){
        if(sum > target) return;
        if(sum == target){
            result.push_back(path);
            return;
        }

        for(int i = startIndex; i < candidates.size(); i++){
            if(i>0 && candidates[i] == candidates[i-1] && used[i-1] == 0){
                continue;
            }
            path.push_back(candidates[i]);
            sum += candidates[i];
            used[i] = true;
            backtracing(candidates, target, sum, i+1,used);
            sum -= candidates[i];
            path.pop_back();
            used[i] = false;
        }
    }

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

1.2.4 组合总和(三)

题目:找出所有相加之和为n的k个数的组合。**==(每个数字只能用1次,只能使用数字1-9)==**
终止条件为元素的个数是否为k
还是原来组合的方法,然后只需要在存放到result的时候验证下path中元素的和是否为n即可。

在这里插入图片描述

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

    void backtraing(int n, int k, int startIndex){
        if(path.size() == k){
            int sum = 0;
            for(int i = 0; i < k; i++){
                sum += path[i];
            }
            if(sum == n){
                result.push_back(path);
            }
            return;
        }
        for(int i = startIndex; i <= 9 - (k - path.size()) + 1; i++){
            path.push_back(i);
            backtraing(n, k, i + 1);
            path.pop_back();
        }
        return;
    }

    vector<vector<int>> combinationSum3(int k, int n) {
        backtraing(n, k, 1);
        return result;
    }
};

1.2.5 多个集合求组合(电话号码)

在这里插入图片描述
此题的特点为:每个数字代表的是不同集合,在不同集合之间的组合。

非常巧妙地利用数组做了一个字符串映射。

class Solution {
public:
    vector<string> result;
    string s;
    string letter_map[10] = {"","", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};

    // 此处的index表示字符串的索引值

    void backTracking(string digits, int index){
        // 到达叶子节点位置
        if(digits.size() == index){
            result.push_back(s);
            return;
        }
        // 取出digits中的元素
        int digit = digits[index] - '0';
        string letter = letter_map[digit];

        for(int  i = 0; i < letter.size(); i++){
            s.push_back(letter[i]);
            backTracking(digits, index + 1);
            s.pop_back();
        }
        return ;
    }

    vector<string> letterCombinations(string digits) {
       if(digits.size() == 0){
           return result;
       }
        backTracking(digits, 0);
        return result;
    }
};

1.3 切割问题

此类题目就是要注意:startIndex就是切割的板子;切割出来地子串位于 [ startIndex, i ] 之间的。

1.3.1 分割回文串

参考:【代码随想录之分割回文串】

1.3.2 复原IP地址

参考:【代码随想录之复原IP地址】

1.4 子集问题

注意子集问题去重一定要先排序

1.4.1 子集

此类题目的特点为收集结果的时机是在每个节点都进行收集,所以模板的顺序需要调整下。
在这里插入图片描述


result.push_back(path); // 收集子集,要放在终止添加的上面,否则会漏掉结果
if (startIndex >= nums.size()) { // 终止条件可以不加
    return;
}

1.4.2 子集(二)

当然还有子集问题的去重,同样采用排序 + used数组:
在这里插入图片描述

1.4.3 递增子序列

在这里插入图片描述

1.5 排列问题

1.5.1 排列

它的遍历顺序跟其他的都不一样,尤其是在for循环那里,需要i从0开始,然后需要使用used数组记录path里面都存放了哪些元素。
在这里插入图片描述
只有没有使用过,才能取值

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

    vector<vector<int>> permute(vector<int>& nums) {
        vector<bool> used(nums.size(), false);
        backtracking(nums, used, 0);
        return result;
    }

};

1.5.2 排列(二)

在数组中有重复元素,因为排列的话,不可以对原数组进行排序,所以只能换一种去重的方法(采用set哈希表)

在这里插入图片描述

二、N皇后问题

待补充(二刷再说)

三、解数独

待补充(二刷再说)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值