39. 组合总和,40.组合总和II,131.分割回文串

简介: 1. **组合总和**(LeetCode 39):从无重复元素的整数数组中选取任意数量的数字,使其和等于目标值。允许重复选取同一数字,通过递归与回溯实现。2. **组合总和 II**(LeetCode 40):在含有重复元素的数组中选取数字,满足和为目标值,每个数字只能使用一次,需去重。3. **分割回文串**(LeetCode 131):将字符串分割为多个子串,要求每个子串都是回文串,返回所有可能的分割方案。代码实现中运用了深度优先搜索(DFS)、回溯法以及剪枝技巧,确保高效解决问题。

 题目:39. 组合总和

给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。

candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。  

对于给定的输入,保证和为 target 的不同组合数少于 150 个。

示例 1:

输入:candidates =  

[2,3,6,7],  

target =  

7

输出:[[2,2,3],[7]]

解释:

2 和 3 可以形成一组候选,2 + 2 + 3 = 7 。注意 2 可以使用多次。

7 也是一个候选, 7 = 7 。

仅有这两种组合。

示例 2:

输入: candidates = [2,3,5]

,  

target = 8

输出: [[2,2,2,2],[2,3,3],[3,5]]

示例 3:

输入: candidates =  

[2],  

target = 1

输出: []

提示:

1 <= candidates.length <= 30

2 <= candidates[i] <= 40

candidates 的所有元素 互不相同

1 <= target <= 40

思考历程与知识点:  

题目中的无限制重复被选取,吓得我赶紧想想 出现0 可咋办,然后看到下面提示:1 <= candidates[i] <= 200,我就放心了。

本题和77.组合  、216.组合总和III 的区别是:本题没有数量要求,可以无限重复,但是有总和的限制,所以间接的也是有个数的限制。

单层for循环依然是从startIndex开始,搜索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) {
            return;
        }
        if (sum == target) {
            result.push_back(path);
            return;
        }
 
        for (int i = startIndex; i < candidates.size(); i++) {
            sum += candidates[i];
            path.push_back(candidates[i]);
            backtracking(candidates, target, sum, i); // 不用i+1了,表示可以重复读取当前的数
            sum -= candidates[i];
            path.pop_back();
        }
    }
public:
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        result.clear();
        path.clear();
        backtracking(candidates, target, 0, 0);
        return result;
    }
};

 题目:40. 组合总和 II

给定一个候选人编号的集合 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的每个数字在每个组合中只能使用 一次 。

注意:解集不能包含重复的组合。  

示例 1:

输入: candidates =  

[10,1,2,7,6,1,5]

, target =  

8

,

输出:

[

[1,1,6],

[1,2,5],

[1,7],

[2,6]

]

示例 2:

输入: candidates = [2,5,2,1,2], target = 5,

输出:

[

[1,2,2],

[5]

]

提示:

1 <= candidates.length <= 100

1 <= candidates[i] <= 50

1 <= target <= 30

思考历程与知识点:  

如果candidates[i] == candidates[i - 1] 并且 used[i - 1] == false,就说明:前一个树枝,使用了candidates[i - 1],也就是说同一树层使用过candidates[i - 1]。

此时for循环里就应该做continue的操作。

题解:

class Solution {
private:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(vector<int>& candidates, int target, int sum, int startIndex, vector<bool>& used) {
        if (sum == target) {
            result.push_back(path);
            return;
        }
        for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i++) {
            // used[i - 1] == true,说明同一树枝candidates[i - 1]使用过
            // used[i - 1] == false,说明同一树层candidates[i - 1]使用过
            // 要对同一树层使用过的元素进行跳过
            if (i > 0 && candidates[i] == candidates[i - 1] && used[i - 1] == false) {
                continue;
            }
            sum += candidates[i];
            path.push_back(candidates[i]);
            used[i] = true;
            backtracking(candidates, target, sum, i + 1, used); // 和39.组合总和的区别1,这里是i+1,每个数字在每个组合中只能使用一次
            used[i] = false;
            sum -= candidates[i];
            path.pop_back();
        }
    }
 
public:
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        vector<bool> used(candidates.size(), false);
        path.clear();
        result.clear();
        // 首先把给candidates排序,让其相同的元素都挨在一起。
        sort(candidates.begin(), candidates.end());
        backtracking(candidates, target, 0, 0, used);
        return result;
    }
};

 题目:131. 分割回文串

给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是 回文串 。返回 s 所有可能的分割方案。

回文串 是正着读和反着读都一样的字符串。

示例 1:

输入:s = "aab"

输出:[["a","a","b"],["aa","b"]]

示例 2:

输入:s = "a"

输出:[["a"]]

提示:

1 <= s.length <= 16

s 仅由小写英文字母组成

思考历程与知识点:  

本题这涉及到两个关键问题:

切割问题,有不同的切割方式

判断回文

全局变量数组path存放切割后回文的子串,二维数组result存放结果集。 (这两个参数可以放到函数参数里)

本题递归函数参数还需要startIndex,因为切割过的地方,不能重复切割,和组合问题也是保持一致的。

题解:

class Solution {
private:
    vector<vector<string>> result;
    vector<string> path; // 放已经回文的子串
    void backtracking (const string& s, int startIndex) {
        // 如果起始位置已经大于s的大小,说明已经找到了一组分割方案了
        if (startIndex >= s.size()) {
            result.push_back(path);
            return;
        }
        for (int i = startIndex; i < s.size(); i++) {
            if (isPalindrome(s, startIndex, i)) {   // 是回文子串
                // 获取[startIndex,i]在s中的子串
                string str = s.substr(startIndex, i - startIndex + 1);
                path.push_back(str);
            } else {                                // 不是回文,跳过
                continue;
            }
            backtracking(s, i + 1); // 寻找i+1为起始位置的子串
            path.pop_back(); // 回溯过程,弹出本次已经填在的子串
        }
    }
    bool isPalindrome(const string& s, int start, int end) {
        for (int i = start, j = end; i < j; i++, j--) {
            if (s[i] != s[j]) {
                return false;
            }
        }
        return true;
    }
public:
    vector<vector<string>> partition(string s) {
        result.clear();
        path.clear();
        backtracking(s, 0);
        return result;
    }
};


相关文章
|
3月前
|
存储 C++
144.二叉树的前序遍历,145.二叉树的后序遍历,94.二叉树的中序遍历
本内容涵盖了二叉树的三种遍历方法:前序遍历(144题)、中序遍历(94题)和后序遍历(145题)。每种遍历方式通过递归实现,分别遵循“中左右”、“左中右”和“左右中”的访问顺序。题目提供了多个示例,涵盖空树、单节点树及普通二叉树的情况,节点值范围为[-100, 100],节点数量不超过100。代码实现均采用C++,定义递归函数完成遍历,并将结果存储在向量中返回。
|
3月前
|
算法 开发者 容器
239. 滑动窗口最大值,347.前 K 个高频元素
**简介:** 本文介绍了两道经典算法题的解法与思路。第一题“滑动窗口最大值”通过单调队列实现高效求解,避免了暴力方法的时间复杂度问题,利用双端队列维护窗口内的最大值。第二题“前 K 个高频元素”则结合哈希表统计频率与优先级队列(小顶堆)进行排序,筛选出频率最高的元素。两者均展示了数据结构在优化算法性能中的重要作用,适合学习滑动窗口与堆排序相关知识的开发者参考。
|
3月前
|
算法
56. 合并区间,738.单调递增的数字
**简介:** 本文介绍了两道经典的算法题及其解法,分别是“合并区间”和“单调递增的数字”。 - **合并区间**:通过排序与遍历,将重叠的区间合并为不重叠的区间集合。核心思想是按区间左边界排序,判断当前区间的左边界是否小于等于前一区间的右边界,若满足则更新右边界。时间复杂度为O(nlogn)。 - **单调递增的数字**:使用贪心算法解决,从后向前遍历数字字符串,遇到非单调递增的情况时,将前一位减1,并将后续位全部置为9,以确保结果最大且满足单调性。此方法高效且易于实现。 两题均通过清晰的逻辑与代码示例展示了算法设计思路,适合初学者理解排序、贪心等基础算法的应用场景。
|
3月前
|
机器学习/深度学习 算法
332.重新安排行程 , 51. N皇后 ,37. 解数独
本文介绍了三道经典的回溯算法问题及其解决方案。第一题“重新安排行程”要求根据给定的航班列表,从 JFK 出发规划行程,并按字典序返回最小的行程组合。通过构建映射关系并使用深度优先搜索(DFS)与回溯法解决 第二题“N 皇后”探讨如何在 n×n 的棋盘上放置 n 个皇后,使彼此不互相攻击。采用递归和剪枝技术验证每一步的合法性,确保行、列及对角线无冲突。 第三题“解数独”需填充空格以满足数独规则:每行、每列及每个 3x3 宫内的数字 1-9 不重复。利用双重循环遍历棋盘,递归尝试每个空格的可能数字,结合有效性检查完成求解。 以上题目均通过回溯法实现,体现了该算法在复杂约束条件下的强大适用性。
|
3月前
|
机器学习/深度学习 算法 NoSQL
344.反转字符串, 541. 反转字符串II,剑指Offer 05.替换空格,151.翻转字符串里的单词
1. **344. 反转字符串**:通过双指针法实现字符串的原地反转,时间复杂度为O(n),空间复杂度为O(1)。 2. **541. 反转字符串 II**:在特定规则下对字符串进行部分反转,需注意边界条件处理。 3. **剑指 Offer 05. 替换空格**:将字符串中的空格替换为"%20",采用双指针从后向前填充以节省空间。 4. **151. 反转字符串中的单词**:先去除多余空格,再整体反转和局部反转单词,最终实现单词顺序颠倒的效果。
|
3月前
|
XML 缓存 Java
一文讲透程序编排的核心方式:从表达式语言到并行化实践
高德的poi数据来源多种多样,处理流程也多种多样,但因流程相对固定,因此使用了流程化配置简化开发,使用表达式语言保证灵活性。为了加深对平台的理解,并帮助大家对编排有一定的了解,本文会以影响范围的视角去总结当前编排的方案。
182 13
一文讲透程序编排的核心方式:从表达式语言到并行化实践
|
3月前
|
IDE 开发工具 Python
lingma IDE无法使用很多微软官方插件,代码无法点击跳转
当前环境存在以下问题:1. 无法使用微软官方插件 IntelliCode,影响代码智能补全与开发效率;2. 代码中变量点击后无法跳转定义位置(如图所示,Python导入模块无法跳转),此为重大缺陷,请尽快修复,以提升开发体验。这些问题导致的功能缺失,使当前环境与理想开发需求存在一定差距。
|
3月前
|
存储 JSON 前端开发
菜鸟之路Day39一一登录
本文介绍了登录功能的实现及其相关技术细节,包括会话管理、令牌认证和异常处理等内容。作者通过 Java 实现了一个基于用户名和密码的登录接口,调用服务层和数据库层完成用户验证。同时,文章深入探讨了三种会话跟踪技术:Cookie、Session 和 JWT 令牌。 在 JWT 部分,详细讲解了其生成与校验流程,实现了登录成功后返回 JWT 令牌的功能。此外,文章还介绍了过滤器(Filter)和拦截器(Interceptor)的概念及应用,演示了如何利用它们实现登录校验。 最后,为解决前后端交互中异常响应不统一的问题,定义了一个全局异常处理器 将系统异常以统一的 JSON 格式返回给前端。
110 0
|
编解码 Go 文件存储
【YOLOv8改进 - 特征融合NECK】 DAMO-YOLO之RepGFPN :实时目标检测的创新型特征金字塔网络
【YOLOv8改进 - 特征融合NECK】 DAMO-YOLO之RepGFPN :实时目标检测的创新型特征金字塔网络
|
前端开发 数据可视化
前端轮询问题之使用setInterval进行轮询时遇到问题如何解决
前端轮询问题之使用setInterval进行轮询时遇到问题如何解决
316 0