一、组合总和
1.题目
Leetcode:第 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
输出: []
2.解题思路
使用回溯算法来解决组合求和问题。backtracking 函数是一个递归函数,它尝试将每个可能的数字添加到当前路径中,并递归地继续添加下一个数字,直到找到所有可能的组合。每次递归调用时,都会检查当前路径是否满足条件(即当前和是否等于目标和),如果满足,则将其添加到结果中。combinationSum 函数是公共接口,它初始化结果和路径,然后开始递归过程。
3.实现代码
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;// 一、组合总和
class Solution1 {
public: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; // 递归返回,不再继续扩展当前路径}// 从startIndex开始遍历候选数字数组for (int i = startIndex; i < candidates.size(); i++) {sum += candidates[i]; // 将当前数字添加到当前和中path.push_back(candidates[i]); // 将当前数字添加到当前路径中// 递归调用backtracking函数,尝试添加下一个数字backtracking(candidates, target, sum, i);// 回溯,从当前和中移除最后一个数字,并从当前路径中移除最后一个数字sum -= candidates[i];path.pop_back();}}// 主函数,用于生成给定目标和的所有可能组合vector<vector<int>> combinationSum(vector<int>& candidates, int target) {result.clear(); // 清空之前的组合结果集path.clear(); // 清空当前路径backtracking(candidates, target, 0, 0); // 调用递归函数,从索引0开始生成组合return result; // 返回所有可能的组合结果集}
};// 二、组合总和(剪枝优化)
class Solution2 {
public:vector<vector<int>> result; // 用于存储所有可能组合的结果集vector<int> path; // 用于记录当前组合的路径// 递归函数,用于生成所有可能的组合void backtracking(vector<int>& candidates, int target, int sum, int startIndex) {if (sum == target) { // 如果当前和等于目标和,表示找到了一个有效的组合result.push_back(path); // 将当前路径添加到结果集中return; // 递归返回,不再继续扩展当前路径}// 遍历候选数字数组,从startIndex开始,直到不能形成有效组合为止for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i++) {//剪枝优化sum += candidates[i]; // 将当前数字添加到当前和中path.push_back(candidates[i]); // 将当前数字添加到当前路径中// 递归调用backtracking函数,尝试添加下一个数字backtracking(candidates, target, sum, i);// 回溯,从当前和中移除最后一个数字,并从当前路径中移除最后一个数字sum -= candidates[i];path.pop_back();}}// 主函数,用于生成给定目标和的所有可能组合vector<vector<int>> combinationSum(vector<int>& candidates, int target) {result.clear(); // 清空之前的组合结果集path.clear(); // 清空当前路径sort(candidates.begin(), candidates.end()); // 对候选数字数组进行排序,以便使用剪枝backtracking(candidates, target, 0, 0); // 调用递归函数,从索引0开始生成组合return result; // 返回所有可能的组合结果集}
};//测试
int main()
{Solution1 s;vector<vector<int>> result;vector<int>candidates = { 2, 3, 6, 7 };int target = 7;result = s.combinationSum(candidates, target);cout << "所有的组合有:" << endl;for (auto & ans :result) {for (auto& i : ans) {cout << i << " ";}cout << endl;}cout << endl;return 0;
}
二、组合Ⅱ
1.题目
Leetcode:第 40 题
给定一个候选人编号的集合 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] ]
2.解题思路
使用回溯算法来解决组合求和问题。combinationSum2
首先对候选数字进行排序,然后初始化路径和结果集,并开始回溯过程。backtracking
函数是回溯算法的核心,它尝试将每个候选数字添加到当前路径中,并递归地继续寻找满足条件的组合。为了避免重复使用相同的数字,它会跳过与前一个元素相同的候选数字。当找到一个满足条件的组合时,它会将其添加到结果集中。通过这种方式,backtracking
函数能够找到所有可能的组合。
3.实现代码
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;// 一、组合总和II
class Solution {
public:// 定义一个二维数组result,用于存储所有满足条件的组合vector<vector<int>> result;// 定义一个一维数组path,用于存储当前的组合路径vector<int> path;// 定义backtracking函数,用于实现回溯算法void backtracking(vector<int>& candidates, int target, int sum, int startIndex) {// 如果当前和sum等于目标值target,则将当前路径path添加到结果集result中if (sum == target) {result.push_back(path);return;}// 遍历候选数字candidates,从startIndex开始,直到遍历完或者当前和超过目标值for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i++) {// 如果当前元素与前一个元素相同,跳过当前循环,避免重复使用相同的数字if (i > startIndex && candidates[i] == candidates[i - 1]) {continue;}// 将当前候选数字加入到当前和sum中,并将其加入到当前路径path中sum += candidates[i];path.push_back(candidates[i]);// 递归调用backtracking函数,尝试使用下一个候选数字backtracking(candidates, target, sum, i + 1);// 回溯:从当前和中减去当前候选数字,并从当前路径中移除它sum -= candidates[i];path.pop_back();}}// 定义combinationSum2函数,用于求解组合求和问题vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {// 清空当前路径path和结果集resultpath.clear();result.clear();// 将候选数字candidates按升序排序,以便在回溯过程中可以跳过重复的数字sort(candidates.begin(), candidates.end());backtracking(candidates, target, 0, 0); // 调用backtracking函数,开始回溯过程return result;// 返回结果集result,其中包含了所有满足条件的组合}
};//测试
int main()
{Solution s;vector<vector<int>> result;vector<int>candidates = { 10,1,2,7,6,1,5 };int target = 8;result = s.combinationSum2(candidates, target);cout << "所有的组合有:" << endl;for (auto& ans : result) {for (auto& i : ans) {cout << i << " ";}cout << endl;}cout << endl;return 0;
}
三、分割回文串
1.题目
Leetcode:第 131 题
给你一个字符串 s
,请你将 s
分割成一些子串,使每个子串都是
回文串
。返回 s
所有可能的分割方案。
示例 1:
输入:s = "aab" 输出:[["a","a","b"],["aa","b"]]
示例 2:
输入:s = "a" 输出:[["a"]]
2.解题思路
使用回溯算法来解决分割回文串问题。partition
是主函数,它初始化路径和结果集,并开始回溯过程。backtracking
函数是回溯算法的核心,它尝试将字符串分割为多个子字符串,并递归地继续寻找满足条件的分区。isPalindrome
函数用于检查一个子字符串是否是回文串。通过这种方式,backtracking
函数能够找到所有可能的满足条件的分区。
3.实现代码
#include <iostream>
#include <vector>
using namespace std;// 一、分割回文串
class Solution1 {
public:// 定义一个二维字符串数组result,用于存储所有满足条件的分区结果vector<vector<string>> result;// 定义一个一维字符串数组path,用于存储当前的分区路径vector<string> path;// 定义backtracking函数,用于实现回溯算法void backtracking(const string& s, int startIndex) {// 如果当前起始索引startIndex已经到达字符串s的末尾,说明已经处理完字符串if (startIndex >= s.size()) {result.push_back(path);// 将当前路径path添加到结果集result中return;}// 遍历字符串s,从startIndex开始,直到遍历完字符串for (int i = startIndex; i < s.size(); i++) {// 检查从startIndex到i的子字符串是否是回文串if (isPalindrome(s, startIndex, i)) {// 从字符串s中截取从startIndex到i的子字符串,并保存到局部变量str中string str = s.substr(startIndex, i - startIndex + 1);path.push_back(str);// 将子字符串str添加到当前路径path中}else {continue;}backtracking(s, i + 1);// 递归调用backtracking函数,尝试使用下一个分区path.pop_back(); // 回溯:从当前路径中移除最后一个元素}}// 定义isPalindrome函数,用于检查一个子字符串是否是回文串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;// 如果所有字符都相等,则该子字符串是回文串}// 定义partition函数,用于求解字符串分区问题vector<vector<string>> partition(string s) {// 清空当前路径path和结果集resultresult.clear();path.clear();backtracking(s, 0); // 调用backtracking函数,开始回溯过程return result; // 返回结果集result,其中包含了所有满足条件的分区结果}
};// 二、分割回文串(优化)
class Solution2 {
public:// 定义一个二维字符串数组result,用于存储所有满足条件的分区结果vector<vector<string>> result;// 定义一个一维字符串数组path,用于存储当前的分区路径,即已经找到的回文子串vector<string> path;// 定义一个二维布尔数组isPalindrome,用于存储字符串中每个子串是否是回文串的预先计算结果vector<vector<bool>> isPalindrome;// 定义backtracking函数,用于实现回溯算法void backtracking(const string& s, int startIndex) {// 如果当前起始索引startIndex已经到达或超过字符串s的末尾,说明已经找到了一组分区方案if (startIndex >= s.size()) {// 将当前路径path添加到结果集result中result.push_back(path);return;}// 遍历字符串s,从startIndex开始,直到遍历完字符串for (int i = startIndex; i < s.size(); i++) {// 如果从startIndex到i的子串是回文串(根据预先计算的isPalindrome向量)if (isPalindrome[startIndex][i]) {// 从字符串s中截取从startIndex到i的子串,并保存到局部变量str中string str = s.substr(startIndex, i - startIndex + 1);// 将子串str添加到当前路径path中path.push_back(str);} else {continue;}backtracking(s, i + 1);// 递归调用backtracking函数,尝试使用下一个起始位置i+1进行分区path.pop_back();// 回溯过程,从当前路径中移除最后一个元素}}// 定义computePalindrome函数,用于预先计算字符串中所有子串是否是回文串void computePalindrome(const string& s) {// 根据字符串s的大小,初始化isPalindrome二维向量,所有元素初始为falseisPalindrome.resize(s.size(), vector<bool>(s.size(), false));// 从后向前遍历字符串s,计算每个子串是否是回文串for (int i = s.size() - 1; i >= 0; i--) {// 由于是检查子串,所以需要从后向前计算,以确保在计算i行时,i+1行已经计算好了for (int j = i; j < s.size(); j++) {// 如果子串的长度为1,则它是回文串if (j == i) {isPalindrome[i][j] = true;}// 如果子串的长度为2,则检查两个字符是否相等else if (j - i == 1) {isPalindrome[i][j] = (s[i] == s[j]);}// 如果子串的长度大于2,则检查首尾字符是否相等,并且中间子串是否是回文串else {isPalindrome[i][j] = (s[i] == s[j] && isPalindrome[i + 1][j - 1]);}}}}// 定义partition函数,用于求解字符串分区问题vector<vector<string>> partition(string s) {// 清空当前路径path和结果集resultresult.clear();path.clear();computePalindrome(s);// 预先计算字符串s中所有子串是否是回文串backtracking(s, 0); // 调用backtracking函数,开始回溯过程,以0作为起始位置return result;// 返回结果集result,其中包含了所有满足条件的分区结果}
};//测试
int main()
{Solution1 p;vector<vector<string>> result;string s = "aab";result = p.partition(s);cout << "所有的组合有:" << endl;for (auto& ans : result) {for (auto& i : ans) {cout << i << " ";}cout << endl;}cout << endl;return 0;
}
ps:以上皆是本人在探索算法旅途中的浅薄见解,诚挚地希望得到各位的宝贵意见与悉心指导,若有不足或谬误之处,还请多多指教。