代码随想录算法训练营第二十二天| 回溯算法理论基础 77. 组合 216.组合总和III 17.电话号码的字母组合


一、回溯算法理论基础

回溯法:回溯法是一种将问题遍历的结构抽象为树形结构,在解空间树种采取深度优先策略,系统地搜索问题的所有解。

 简单地来说,回溯法是一种在解空间树内暴力搜索的算法。可通过限界函数进行剪枝来提升算法效率。

 回溯法在问题没有更优解法的时候,适用于求解答案是向量的问题,如搜索问题、排序问题、优化问题等。

 回溯法的搜索空间是由问题的解构成的解空间树,常见的类型有:子集树、排列树、搜索树。

二、LeetCode 77. 组合

题目链接:LeetCode 77. 组合

文章讲解:代码随想录
视频讲解:带你学透回溯算法-组合问题(对应力扣题目:77.组合)| 回溯法精讲!

思路

 一招鲜,吃遍天。组合问题是回溯法的经典题目,也可以作为所有回溯法(子集树)的一个代码模板来写。

 要求在 1 ∼ n 1\sim n 1n 中取出所有可能的组合,涉及到我们每一步选择哪个数加入组合的问题。在每一步(每一层)选择每个可能的数(遍历孩子结点)就可以抽象为一个子集树的遍历过程,于是可以使用回溯算法进行遍历求解。
在这里插入图片描述
 在回溯过程中,重要的部分在于如何表述每一层中选择不同孩子的逻辑,以及遍历子集树时进入下一层遍历的传参问题。如上图,在本问题中,我们将当前位置选择哪一个数作为不同的孩子结点,那么我们可以写一个for循环来完成不同孩子结点之间的遍历,用递归实现进入下一层:

for(int i = pre + 1; i <= n; i++){  //递归构建子集树
    set[t] = i;
    backtrack(t+1, i, n, k, set);
}

set使用全局变量的话可以这样写:

for (int i = startIndex; i <= n; i++) { // 控制树的横向遍历
    path.push_back(i); // 处理节点
    backtracking(n, k, i + 1); // 递归:控制树的纵向遍历,注意下一层搜索要从i+1开始
    path.pop_back(); // 回溯,撤销处理的节点
}

 这样更为规范,可读性也更好一些。

 终止条件设置为set中元素数量等于k即可。

 在上述代码的基础上,可以做一定的剪枝提高时间效率:我们可以发现,在前一位数选择了n-(k-set.size())+1时,即前面数选择以后,后面的数不够分,达不到k个的数目,那么这些分支实际上都是无效遍历;也就是说,在组合中,每一位上选择的数是有一个右边界的,我们通过找规律不难发现,这个右边界就是n-(k-set.size())+1
在这里插入图片描述
 那么遍历代码可以优化为:

for (int i = startIndex; i <= n - (k - set.size()) + 1; i++) // i为本次搜索的起始位置

C++代码

class Solution {
public:
    vector<vector<int>> subsets;

    void backtrack(int t, int pre, int n, int k, vector<int> set){
        if(t == k){
            subsets.push_back(set);
            return;
        } 
        for(int i = pre + 1; i <= n-(k-t)+1; i++){  //递归构建子集树
            set[t] = i;
            backtrack(t+1, i, n, k, set);
        }
    }

    vector<vector<int>> combine(int n, int k) {
        vector<int> set(k);
        backtrack(0, 0, n, k, set);
        return subsets;
    }
};

二、LeetCode 216.组合总和III

题目链接:LeetCode 216.组合总和III

文章讲解:代码随想录
视频讲解:和组合问题有啥区别?回溯算法如何剪枝?| LeetCode:216.组合总和III

思路

 本题与上题较为相似,不同之处只在于使用数字变为一到九,并增加一个总和条件。我们只调节可选取的数字,并增加一个总和相等的终止条件即可。

C++代码

class Solution {
private:
    vector<vector<int>> sumsets;
    vector<int> set;

    void backtrack(int pre, int sum, int k, int n){
        if(sum > n) return; //剪枝操作:总和大于要求值返回
        if(set.size() == k){ //终止条件
            if(sum == n){
                sumsets.push_back(set);
            }
            return;
        }
        for(int i = pre + 1; i <= 9-(k-set.size())+1; i++){ //数字选取1--9
            set.push_back(i);
            sum += i;
            backtrack(i, sum, k, n);
            sum -= i;
            set.pop_back();
        }
    }
public:
    vector<vector<int>> combinationSum3(int k, int n) {
        backtrack(0, 0, k, n);
        return sumsets;
    }
};

二、LeetCode 17.电话号码的字母组合

题目链接:LeetCode 17.电话号码的字母组合

文章讲解:代码随想录
视频讲解:还得用回溯算法!| LeetCode:17.电话号码的字母组合

思路

 本题也是很简单的组合问题,主要解决的点在于电话号码上字母的映射,可以选择建立一个全局变量数组存储每个数字对应的字母,笔者采用 A S C I I ASCII ASCII 码转换的方法进行映射。其余操作与基本的回溯法相同。

C++代码

class Solution {
private:
    vector<string> combinations;
    string letters;

    void backtrack(string digits, int index){
        if(index == digits.size()){
            combinations.push_back(letters);
            return;
        }

        int del = 3;
        int be;

        if(digits[index] > '1' && digits[index] < '7'){
            be = 43 + int(digits[index]-'0') * 2;
        }
        else if(digits[index] == '7'){
            be = 57;
            del = 4;
        }
        else if(digits[index] == '8'){
            be = 60;
        }
        else if(digits[index] == '9'){
            be = 62;
            del = 4;
        } 

        for(int i = be; i < be+del; i++){
            letters.push_back(digits[index] + i);
            backtrack(digits, index+1);
            letters.pop_back();
        }
    }
public:
    vector<string> letterCombinations(string digits) {
        if(digits.empty()) return combinations;
        backtrack(digits, 0);
        return combinations;
    }
};


总结

 回溯法章节开始,逐渐开始正式复习算法设计课的知识。


文章图片来源:代码随想录 (https://programmercarl.com/)

  • 14
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
第二十二算法训练营主要涵盖了Leetcode题目中的三道题目,分别是Leetcode 28 "Find the Index of the First Occurrence in a String",Leetcode 977 "有序数组的平方",和Leetcode 209 "长度最小的子数组"。 首先是Leetcode 28题,题目要求在给定的字符串中找到第一个出现的字符的索引。思路是使用双指针来遍历字符串,一个指向字符串的开头,另一个指向字符串的结尾。通过比较两个指针所指向的字符是否相等来判断是否找到了第一个出现的字符。具体实现的代码如下: ```python def findIndex(self, s: str) -> int: left = 0 right = len(s) - 1 while left <= right: if s[left == s[right]: return left left += 1 right -= 1 return -1 ``` 接下来是Leetcode 977题,题目要求对给定的有序数组中的元素进行平方,并按照非递减的顺序返回结果。这里由于数组已经是有序的,所以可以使用双指针的方法来解决问题。一个指针指向数组的开头,另一个指针指向数组的末尾。通过比较两个指针所指向的元素的绝对值的大小来确定哪个元素的平方应该放在结果数组的末尾。具体实现的代码如下: ```python def sortedSquares(self, nums: List[int]) -> List[int]: left = 0 right = len(nums) - 1 ans = [] while left <= right: if abs(nums[left]) >= abs(nums[right]): ans.append(nums[left ** 2) left += 1 else: ans.append(nums[right ** 2) right -= 1 return ans[::-1] ``` 最后是Leetcode 209题,题目要求在给定的数组中找到长度最小的子数组,使得子数组的和大于等于给定的目标值。这里可以使用滑动窗口的方法来解决问题。使用两个指针来表示滑动窗口的左边界和右边界,通过移动指针来调整滑动窗口的大小,使得滑动窗口中的元素的和满足题目要求。具体实现的代码如下: ```python def minSubArrayLen(self, target: int, nums: List[int]) -> int: left = 0 right = 0 ans = float('inf') total = 0 while right < len(nums): total += nums[right] while total >= target: ans = min(ans, right - left + 1) total -= nums[left] left += 1 right += 1 return ans if ans != float('inf') else 0 ``` 以上就是第二十二算法训练营的内容。通过这些题目的练习,可以提升对双指针和滑动窗口等算法的理解和应用能力。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值