LeetCode 剑指 Offer II 回溯(上) 专题总结

  • 📚 博客主页:⭐️这是一只小逸白的博客鸭~⭐️
  • 👉 欢迎 关注❤️点赞👍收藏⭐️评论📝
  • 😜 小逸白正在备战实习,经常更新面试题LeetCode题解,欢迎志同道合的朋友互相交流~
  • 💙 若有问题请指正,记得关注哦,感谢~

往期文章 :

回溯介绍:

回溯法(backtracking)是优先搜索的一种特殊情况,又称为试探法,常用于需要记录节点状态的深度优先搜索
顾名思义,回溯法的核心是回溯。在搜索到某一节点的时候,如果我们发现目前的节点(及其子节点)并不是需求目标时,我们回退到原来的节点继续搜索,并且把在目前节点修改的状态还原。这样的好处是我们可以始终只对图的总状态进行修改,而非每次遍历时新建一个图来储存状态。
在具体的写法上,它与普通的深度优先搜索一样,都有 [修改当前节点状态]→[递归子节点] 的步骤,
只是多了回溯的步骤,变成了 [修改当前节点状态]→[递归子节点]→[回改当前节点状态]

这六道题连续的两个都是同一类型的,涵盖了大部分回溯法的基础思路,推荐两个两个比对着看

079. 所有子集(模板题)

题目:

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

示例:

输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]

提示:

  • 1 <= nums.length <= 10
  • -10 <= nums[i] <= 10
  • nums中的所有元素 互不相同

思路:

不能回头重复搜索类型,一个数取一次
模板题,我给大家提供了两个模板,一个是带for,选了就加上,一个是选不选的思路

class Solution {
public:
    vector<vector<int>> ans;
    vector<int> nums;
    vector<int> cur;
    int n;
    vector<vector<int>> subsets(vector<int>& nums) {
        this->nums = nums;
        n = nums.size();
        dfs(0);
        return ans;
    }
    // 模板,不回头地搜索
    void dfs(int index) {
        ans.emplace_back(cur);
        for(int i = index; i < n; i++) {
            cur.emplace_back(nums[i]);
            dfs(i + 1);
            cur.pop_back();
        }
    }
    // 不用for 的模板
    void dfs2(int index) {
        if(index == nums.size()) {
            ans.emplace_back(cur);
            return ;
        }
        // 选
        cur.emplace_back(nums[index]);
        dfs(index + 1);
        cur.pop_back();
        // 不选
        dfs(index + 1);
    }
};

080. 含有 k 个元素的组合

题目:

给定两个整数 n 和 k,返回 1 … n 中所有可能的 k 个数的组合。

示例:

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

提示:

  • 1 <= n <= 20
  • 1 <= k <= n

思路:

比上一题 079. 所有子集 多了限制,原本不限制多少个数,取不同的组合
现在限制只能取两个数,所以只要参数加一个个数限制 k 判断一下就可以

class Solution {
public:
    vector<int> cur;
    vector<vector<int>> ans;
    int n;
    vector<vector<int>> combine(int n, int k) {
        this->n = n;
        dfs(1, k);
        return ans;
    }
    // 回溯跟上题79差不多,加了个个数限制
    void dfs(int num, int k) {
        if(k == 0) {
            ans.emplace_back(cur);
            return ;
        }
        for(int i = num; i <= n; i++) {
            cur.emplace_back(i);
            dfs(i + 1, k - 1);
            cur.pop_back();
        }
    }
};

081. 允许重复选择元素的组合

题目:

给定一个无重复元素的正整数数组 candidates 和一个正整数 target ,找出 candidates 中所有可以使数字和为目标数 target 的唯一组合。
candidates 中的数字可以无限制重复被选取。如果至少一个所选数字数量不同,则两种组合是唯一的。
对于给定的输入,保证和为 target 的唯一组合数少于 150 个。

示例:

输入: candidates = [2,3,6,7], target = 7
输出: [[7],[2,2,3]]

提示:

  • 1 <= candidates.length <= 30
  • 1 <= candidates[i] <= 200
  • candidate 中的每个元素都是独一无二的。
  • 1 <= target <= 500

思路:

本题有两个限制:允许重复选择同一元素;选择的元素和为target

  • 第一个限制:我们只需要从原来的从下一个数取变成从本身继续往下取
  • 第二个限制:我们每取一个元素就让和减去这个元素,为0的时候表示和刚好为target
  • 大于0时表示超过,不用继续搜索,返回,因为我是在传参时减去的,所以回溯时不需要再次加上该元素
class Solution {
public:
    vector<int> candidates;
    vector<vector<int>> ans;
    vector<int> cur;
    int n;
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        this->candidates = candidates;
        n = candidates.size();
        dfs(0, target);
        return ans;
    }
    // 跟前两题稍有不同,candidates 中的数字可以无限制重复被选取
    void dfs(int index, int target) {
        // 总和 > target 返回
        if(target < 0) return ;
        // 总和 == target时加入答案
        if(target == 0) {
            ans.emplace_back(cur);
            return ;
        }
        for(int i = index; i < n; i++) {
            cur.emplace_back(candidates[i]);
            // 因为可以重复使用,所以我们从本身位置继续遍历,直到和>=target返回,再加入下一个元素
            dfs(i, target - candidates[i]);
            cur.pop_back();
        }
    }
};

082. 含有重复元素集合的组合

题目:

给定一个可能有重复数字的整数数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用一次,解集不能包含重复的组合。

示例:

输入: candidates = [10,1,2,7,6,1,5], target = 8,
输出:
[
[1,1,6],
[1,2,5],
[1,7],
[2,6]
]

提示:

  • 1 <= candidates.length <= 100
  • 1 <= candidates[i] <= 50
  • 1 <= target <= 30

思路:

本题相较于 081. 允许重复选择元素的组合 有以下改变
一、不能重复取同一元素
二、不能存在重复组合

  • 第一个:每次取的都是这个元素的后面元素dfs(i + 1, target - candidates[i]);
  • 第二个:难点在于这个,我们将数组排序一下,方便查重,sort(candidates.begin(), candidates.end());
    在相邻一样的元素时选择跳过if(i - 1 >= index && candidates[i - 1] == candidates[i]) continue;

比如[1,2,2,2,5],选了第一个 2,变成 [1,2],它的下一选项也是 2,跳过它,因为如果选它,就还是 [1,2]

class Solution {
public:
    vector<vector<int>> ans;
    vector<int> candidates;
    vector<int> cur;
    int n;
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        this->n = candidates.size();
        // 需要排序,方便遇到相同元素时跳过
        sort(candidates.begin(), candidates.end());
        //quickSort(candidates, 0, candidates.size() - 1);
        this->candidates = candidates;
        dfs(0, target);
        return ans;
    }
    void dfs(int index, int target) {
        if(target < 0) return ;
        if(target == 0) {
            ans.emplace_back(cur);
            return;
        }
        for(int i = index; i < n; i++) {
            // 当前数跟前一个数一样就不选
            if(i - 1 >= index  && candidates[i - 1] == candidates[i]) continue;
            cur.emplace_back(candidates[i]);
            dfs(i + 1, target - candidates[i]);
            cur.pop_back();
        }
    }
    // 多写写而已,用了没有比sort函数更快,尴尬
    void quickSort(vector<int>& candidates, int l, int r) {
        if(l >= r) return ;
        int i = l, j = r, mid = candidates[((r - l) >> 1) + l];
        do{
            while(candidates[i] < mid) i++;
            while(candidates[j] > mid) j--;
            if(i <= j) swap(candidates[i++], candidates[j--]);
        }while(i <= j);
        quickSort(candidates, l, j);
        quickSort(candidates, i, r);
    }
};

083. 没有重复元素集合的全排列

题目:

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

示例:

输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

提示:

  • 1 <= nums.length <= 6
  • -10 <= nums[i] <= 10
  • nums 中的所有整数 互不相同

思路:

关键字:不含重复数字,所有可能的全排列
全排列就不能跟前几题一样只考虑后面元素,不考虑前面元素了

  • 所以我们需要一个标记数组vis标记访问过的元素防止重复访问
  • 每次都从头开始遍历,遍历过的vistrue跳过
class Solution {
public:
    vector<vector<int>> ans;
    vector<int> cur;
    vector<int> nums;
    int n;
    // 标记访问过的数字
    vector<bool> vis;
    vector<vector<int>> permute(vector<int>& nums) {
        this->nums = nums;
        this->n = nums.size();
        vis.resize(n, 0);
        dfs(0);
        return ans;
    }
    // 相比较于前几题,多了个标记数组vis,并且for都是从0开始,dfs传参不是下标,而是cur中的个数,这样看的话不用传参也行
    void dfs(int count) {
        if(count == n) {
            ans.emplace_back(cur);
            return ;
        }
        // 全排列从0开始,可以回头加数
        for(int i = 0; i < n; i++) {
            if(vis[i] == true) continue;
            cur.emplace_back(nums[i]);
            vis[i] = 1;
            dfs(count + 1);
            vis[i] = 0;
            cur.pop_back();
        }
    }
};

084. 含有重复元素集合的全排列

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

示例:
输入:nums = [1,1,2]
输出:
[[1,1,2],
[1,2,1],
[2,1,1]]

提示:

  • 1 <= nums.length <= 8
  • -10 <= nums[i] <= 10

思路:

关键字:包含重复数字,不重复的全排列。
如果数组中元素一样意味着就会出现重复的全排列,所以我们在剑指 Offer II 083. 没有重复元素集合的全排列基础上去重
剑指 Offer II 082. 含有重复元素集合的组合在这去重基础上加上vis标记为的判定
如果相邻两个元素一样,且前一个元素使用过了,代表这个排列使用过,就不必再继续往后搜索了
if((i - 1 >= 0 && nums[i - 1] == nums[i]) && vis[i-1] == 1) break;

class Solution {
public:
    vector<vector<int>> ans;
    vector<int> cur;
    vector<int> nums;
    int n;
    vector<bool> vis;
    vector<vector<int>> permuteUnique(vector<int>& nums) {
        // 排序方便查重
        sort(nums.begin(), nums.end());
        this->nums = nums;
        this->n = nums.size();
        vis.resize(n, 0);
        dfs();
        return ans;
    }
    // 跟82题很像,但因为这个会往回寻找,所以需要判定一下标记数组
    void dfs() {
        // 跟上一题一样传个count也行,这里不传了,对比一下
        if(cur.size() == n) {
            ans.emplace_back(cur);
            return ;
        }
        for(int i = 0; i < n; i++) {
            if(vis[i] == 1)
                continue;
            // 相比较82题,遇到前后两数相等且前一个数已经标记过了就表示后面都重复了
            if((i - 1 >= 0 && nums[i - 1] == nums[i]) && vis[i-1] == 1)
                break;
            cur.emplace_back(nums[i]);
            vis[i] = 1;
            dfs();
            vis[i] = 0;
            cur.pop_back();
        }
        
    }
};
  • 13
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 18
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

一只小逸白

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

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

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

打赏作者

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

抵扣说明:

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

余额充值