代码随想录算法训练营day22 | 77. 组合、216.组合总和III 、17.电话号码的字母组合

碎碎念:加油
参考:代码随想录

回溯算法理论基础

回溯和递归是相辅相成的,只要有递归,就会有回溯。回溯通常在递归函数的下面。
回溯搜索到法的效率: 它其实是纯暴力的做法,不是一个高效的算法。
回溯法能解决的问题: 组合问题(不强调元素的顺序)、切割问题、子集问题、排列问题(强调元素的顺序)、棋盘问题(N皇后)
如何理解回溯法: 所有回溯法都可以抽象为一个树行结构。一般来说树的宽度是回溯法中处理的集合的大小,树的深度就是递归的深度。
回溯法的模板:

void backtracking(参数){
    if (终止条件) {
        收集结果;
        return;
    }
    for (遍历集合的每一个元素){
        处理节点;
        递归函数;
        回溯操作; // 撤销处理节点
    }
    return;
}

77. 组合

题目链接

77. 组合

思想

看到这题我们自然而然想到暴力做法,如果k是2,那么就用两层for循环,但是k稍微大点,这么做就显然做不出来了,时间复杂度太大了。
回溯法是通过递归来控制有多少层for的。
上面提到过,回溯法做题可以抽象出一个树形图,本题抽象出来的树形图如图。
在这里插入图片描述

叶子节点就是我们要求的所有组合。通过维护一个参数startindex来控制搜索的起始位置。
定义一个一维数组path,定义一个二维数组result
回溯三部曲:

  1. 递归函数的参数和返回值:一般返回值都是void,参数n,k,startindex
  2. 终止条件:path的大小等于k,就要收割结果。
  3. 单层搜索的逻辑:从startindex开始遍历后面的元素,用path收集路径上的元素,递归下一层,回溯(把之前收集到的一个元素pop出去)。

剪枝:
在这里插入图片描述
优化上面单层搜索的逻辑:
其实图里每个节点都是for循环。那么我们要做的优化就在于for循环里i的范围。
path存放着已经选取到的元素,还剩k-path.size()需要选取,这些元素至多要从n-(k-path.size())+1的位置开始。
[x, n]的数组长度起码应该是k-path.size(),这才有继续搜索的必要, 有 n-x+1 = k-path.size() ,得 x = n+1 - (k-path.size()), 而且这个x是可以作为起点往下搜的,也就是for(i = s; i<=x; i++) 这里的x是可以取到的。
体现在代码里,修改为i <= n-(k-path.size())+1即可。

题解

// cpp
class Solution {
private:
    vector<vector<int>> result;
    vector<int> path;
    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();
        }
    }
public:
    vector<vector<int>> combine(int n, int k) {
        backtracking(n, k, 1);
        return result;
    }
};

# python 剪枝优化
class Solution:
    def __init__(self):
        self.result = []
        self.path = []
    
    def backtracking(self, n, k, startIndex):
        if len(self.path) == k:
            self.result.append(self.path[:])
            return
        
        for i in range(startIndex, n - (k - len(self.path)) + 2):
            self.path.append(i)
            self.backtracking(n, k, i + 1)
            self.path.pop()

    def combine(self, n: int, k: int) -> List[List[int]]:
        self.backtracking(n, k, 1)
        return self.result

反思

注意组合是无序的,组合中的元素只能使用一次。

216.组合总和III

题目链接

216.组合总和III

思想

和上一题的区别在于多了一个关于和的限制。
注意本题不强调元素的顺序,比如126和216是同一个组合。
树形结构:
在这里插入图片描述
定义一个一维数组path,定义一个二维数组result
回溯三部曲:

  1. 参数和返回值:返回值为void,参数targetSum,k,sum,startIndex。
  2. 终止条件:path的size==k,判断sum和targetSum是否相等,相等就把path加入result,否则直接返回。
  3. 单层递归逻辑:从startIndex开始遍历,取数加入path,计算sum,下一层递归(startIndex传入i+1),pop出刚加入的数,修改sum。

剪枝优化:
如果sum>targetSum,直接返回;
在for循环那里也可以做剪枝,体现在代码里,修改为i <= 9-(k-path.size())+1即可。

题解

// cpp
class Solution {
private:
    vector<int> path;
    vector<vector<int>> result;
    void backtracking(int targetSum, int k, int sum, int startIndex) {
        if (path.size() == k) {
            if (sum == targetSum) result.push_back(path);
            return;
        }
        for (int i = startIndex; i <= 9; i ++) {
            sum += i;
            path.push_back(i);
            backtracking(targetSum, k, sum, i + 1);
            sum -= i;
            path.pop_back();
        }
    }
public:
    vector<vector<int>> combinationSum3(int k, int n) {
        result.clear();
        path.clear();
        backtracking(n, k, 0, 1);
        return result;
    }
};


# python 剪枝优化
class Solution:
    def __init__(self):
        self.path = []
        self.result = []
    
    def backtracking(self, targetSum, k, currentSum, startIndex):
        if currentSum > targetSum: # 剪枝
            return
        if len(self.path) == k:
            if currentSum == targetSum:
                self.result.append(self.path[:])
            return
        for i in range(startIndex, 9 - (k - len(self.path)) + 2):
            currentSum += i
            self.path.append(i)
            self.backtracking(targetSum, k,currentSum, i + 1)
            currentSum -= i
            self.path.pop()
        
    def combinationSum3(self, k: int, n: int) -> List[List[int]]:
        self.backtracking(n, k, 0, 1)
        return self.result

反思

注意组合是无序的,组合中的元素只能使用一次。

17.电话号码的字母组合

题目链接

17.电话号码的字母组合

思想

树形图如图:
在这里插入图片描述

回溯三部曲:

  1. 参数和返回值:返回值是void,参数是digits,index(当前遍历到哪个数字了)
  2. 终止条件:index==digits.size() 收获结果
  3. 单层递归逻辑:用index取数字,用数字找字符串,接下来遍历得到的字符串,把字母放入s,调用递归函数(index+1),再把s取出来(回溯)。

回溯过程也可以隐藏在参数里。

题解

class Solution {
private:
    const string letterMap[10] = {
        "", // 0
        "", // 1
        "abc", // 2
        "def", // 3
        "ghi", // 4
        "jkl", // 5
        "mno", // 6
        "pqrs", // 7
        "tuv", // 8
        "wxyz", // 9
    };
public:
    vector<string> result;
    string s;
    void backtracking(const string& digits, int index) {
        if (index == digits.size()) {
            result.push_back(s);
            return;
        }
        string letters = letterMap[digits[index] - '0'];
        for (int i = 0; i < letters.size(); i ++) {
            s.push_back(letters[i]);
            backtracking(digits, index + 1);
            s.pop_back();
        }
    }
    vector<string> letterCombinations(string digits) {
        if (digits.size() == 0) return result;
        backtracking(digits, 0);
        return result;
    }
};
class Solution:
    def __init__(self):
        self.letterMap = [
            "",     # 0
            "",     # 1
            "abc",  # 2
            "def",  # 3
            "ghi",  # 4
            "jkl",  # 5
            "mno",  # 6
            "pqrs", # 7
            "tuv",  # 8
            "wxyz"  # 9
        ]
        self.result = []
        self.s = ""
    
    def backtracking(self, digits, index):
        if index == len(digits):
            self.result.append(self.s)
            return
        letters = self.letterMap[int(digits[index])]
        for i in range(len(letters)):
            self.s += letters[i]
            self.backtracking(digits, index + 1)
            self.s = self.s[:-1]

    
    def letterCombinations(self, digits: str) -> List[str]:
        if len(digits) == 0:
            return self.result
        self.backtracking(digits, 0)
        return self.result
        

反思

前两题都是在一个集合里来求组合,所以需要用到startIndex,本题是在不同的集合里,不需要用参数来控制之前遍历的元素。

  • 17
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
代码随想录算法训练是一个优质的学习和讨论平台,提供了丰富的算法训练内容和讨论交流机会。在训练中,学员们可以通过观看视频讲解来学习算法知识,并根据讲解内容进行刷题练习。此外,训练还提供了刷题建议,例如先看视频、了解自己所使用的编程语言、使用日志等方法来提高刷题效果和语言掌握程度。 训练中的讨论内容非常丰富,涵盖了各种算法知识点和解题方法。例如,在第14天的训练中,讲解了二叉树的理论基础、递归遍历、迭代遍历和统一遍历的内容。此外,在讨论中还分享了相关的博客文章和配图,帮助学员更好地理解和掌握二叉树的遍历方法。 训练还提供了每日的讨论知识点,例如在第15天的讨论中,介绍了层序遍历的方法和使用队列来模拟一层一层遍历的效果。在第16天的讨论中,重点讨论了如何进行调试(debug)的方法,认为掌握调试技巧可以帮助学员更好地解决问题和写出正确的算法代码。 总之,代码随想录算法训练是一个提供优质学习和讨论环境的平台,可以帮助学员系统地学习算法知识,并提供了丰富的讨论内容和刷题建议来提高算法编程能力。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [代码随想录算法训练每日精华](https://blog.csdn.net/weixin_38556197/article/details/128462133)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值