-
216.组合总和III
剪枝优化如下:
-
已经选择的元素个数:path.size();
-
所需需要的元素个数为: k - path.size();
-
列表中剩余元素(n-i) >= 所需需要的元素个数(k - path.size())
-
在集合n中至多要从该起始位置 : i <= n - (k - path.size()) + 1,开始遍历
题目:
找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。
说明:
- 所有数字都是正整数。
- 解集不能包含重复的组合。
示例 1: 输入: k = 3, n = 7 输出: [[1,2,4]]
示例 2: 输入: k = 3, n = 9 输出: [[1,2,6], [1,3,5], [2,3,4]]
思路:
确定参数值:n, k, start(开始标志)
确定终止条件:若sum大于目标和, 或者path.size()大于k,都可以return进行剪枝,若满足要求则将path存入result,再return。
单层搜索过程:用for循环,将值存入path中,并计算sum,然后递归,再pop,特别注意的是pop后,sum还要减去当前的值。
class Solution {
private:
vector<vector<int>> result;
int sum = 0;
vector<int> path;
void backtracking(int k, int n, int start) {
// 结束条件
if (sum > n || path.size() > k) return;
if (sum == n && path.size() == k) {
result.push_back(path);
return;
}
// 递归过程
for (int i = start; i <= 9; i++) {
path.push_back(i);
sum += i; // 更新sum
backtracking(k, n, i + 1); // 注意:不修改k的值,并且从i+1开始下一层递归
sum -= i;
path.pop_back(); // 回溯
}
}
public:
vector<vector<int>> combinationSum3(int k, int n) {
result.clear();
path.clear();
backtracking(k, n, 1);
return result;
}
};
-
17.电话号码的字母组合
题目:
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
示例:
- 输入:"23"
- 输出:["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"].
说明:尽管上面的答案是按字典序排列的,但是你可以任意选择答案输出的顺序。
思路:
可以使用map或者定义一个二维数组,例如:string letterMap[10],来做映射.
二维数组:
const string letterMap[10] = {
"", // 0
"", // 1
"abc", // 2
"def", // 3
"ghi", // 4
"jkl", // 5
"mno", // 6
"pqrs", // 7
"tuv", // 8
"wxyz", // 9
};
map:
unordered_map<char, string> phoneMap{
{'2', "abc"},
{'3', "def"},
{'4', "ghi"},
{'5', "jkl"},
{'6', "mno"},
{'7', "pqrs"},
{'8', "tuv"},
{'9', "wxyz"}
};
class Solution {
private:
vector<string> result; // 存储最终结果
string path; // 存储当前路径
const string letterMap[10] = {
"", // 0
"", // 1
"abc", // 2
"def", // 3
"ghi", // 4
"jkl", // 5
"mno", // 6
"pqrs", // 7
"tuv", // 8
"wxyz" // 9
};
void backtracking(string& digits, int cur) {
if (cur == digits.size()) {
result.push_back(path);
return;
}
int digit = digits[cur] - '0'; // 将字符转化为数字
string letters = letterMap[digit]; // 取数字对应的字符集
for (char letter : letters) {
path.push_back(letter); // 添加当前字母到路径
backtracking(digits, cur + 1); // 递归处理下一个数字
path.pop_back(); // 回溯
}
}
public:
vector<string> letterCombinations(string digits) {
if (digits.empty()) return {}; // 如果输入为空,直接返回空结果
backtracking(digits, 0); // 开始回溯
return result; // 返回结果
}
};
39. 组合总和
给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的数字可以无限制重复被选取。
说明:
- 所有数字(包括 target)都是正整数。
- 解集不能包含重复的组合。
示例 1:
- 输入:candidates = [2,3,6,7], target = 7,
- 所求解集为: [ [7], [2,2,3] ]
示例 2:
- 输入:candidates = [2,3,5], target = 8,
- 所求解集为: [ [2,2,2,2], [2,3,3], [3,5] ]
思路:
回溯法回溯三部曲,需要注意 backtracking 函数中的 candidates 参数为传引用(vector<int>& candidates),以避免每次递归时复制整个数组。result
、path
和 sum
被定义为类的成员变量,以便在整个类中共享。
class Solution {
private:
vector<int> path;
vector<vector<int>> result;
int sum = 0;
void backtracking(vector<int>& candidates, int target, int start) {
if (sum > target) return;
if (sum == target) {
result.push_back(path);
return;
}
for (int i = start; i < candidates.size(); i++) {
sum += candidates[i];
path.push_back(candidates[i]);
backtracking(candidates, target, i);
sum -= candidates[i];
path.pop_back();
}
}
public:
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
backtracking(candidates, target, 0);
return result;
}
};
40.组合总和II
给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用一次。
说明: 所有数字(包括目标数)都是正整数。解集不能包含重复的组合。
- 示例 1:
- 输入: candidates = [10,1,2,7,6,1,5], target = 8,
- 所求解集为:
[
[1, 7],
[1, 2, 5],
[2, 6],
[1, 1, 6]
]
思路:
该题比上一题多了个去重的操作,所以先给数组排序,然后在横向循环时跳过重复的元素。但需要注意:
- 避免越界检查:在循环中对
candidates[i - 1]
进行访问时,应该在访问之前加上i > start
的检查,以避免越界访问。
class Solution {
private:
vector<vector<int>> result;
vector<int> path;
int sum = 0;
void backtracking(vector<int>& candidates, int target, int start) {
if (sum > target) return;
if (sum == target) {
result.push_back(path);
return;
}
for (int i = start; i < candidates.size(); i++) {
if (i > start && candidates[i] == candidates[i - 1]) continue;
sum += candidates[i];
path.push_back(candidates[i]);
backtracking(candidates, target, i + 1);
sum -= candidates[i];
path.pop_back();
}
}
public:
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
sort(candidates.begin(), candidates.end());
backtracking(candidates, target, 0);
return result;
}
};
131.分割回文串
给定一个字符串 s,将 s 分割成一些子串,使每个子串都是回文串。
返回 s 所有可能的分割方案。
示例: 输入: "aab" 输出: [ ["aa","b"], ["a","a","b"] ]
思路:
回溯法,针对横向for循环,选择的第一个区间内含有的字符数依次增大, 纵向的递归则是区间位置依次后移。其中有许多细节需要注意。
1,需要建立一个辅助函数判断字符串是否“回文”。
2,终止条件为startindex == s.size()(不用减1,因为在backtracking(s, i + 1);的过程中startindex已经变成了s.size())。
3, string str = s.substr(startindex, i - startindex + 1); 再赋值给path时,先定义子字符串。语法:s.substr(startIndex, length)
4, 若字符串不回文, 一定要用“else continue” 跳过。
class Solution {
private:
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;
}
vector<vector<string>> result;
vector<string> path;
void backtracking(string s, int startindex) {
if (startindex == s.size()) {
result.push_back(path);
return;
}
for (int i = startindex; i < s.size(); i++) {
if (isPalindrome(s, startindex, i)) {
string str = s.substr(startindex, i - startindex + 1);
path.push_back(str);
}
else continue;
backtracking(s, i + 1);
path.pop_back();
}
}
public:
vector<vector<string>> partition(string s) {
backtracking(s, 0);
return result;
}
};
93.复原IP地址
给定一个只包含数字的字符串,复原它并返回所有可能的 IP 地址格式。
有效的 IP 地址 正好由四个整数(每个整数位于 0 到 255 之间组成,且不能含有前导 0),整数之间用 '.' 分隔。
例如:"0.1.2.201" 和 "192.168.1.1" 是 有效的 IP 地址,但是 "0.011.255.245"、"192.168.1.312" 和 "192.168@1.1" 是 无效的 IP 地址。
示例 1:
- 输入:s = "25525511135"
- 输出:["255.255.11.135","255.255.111.35"]
示例 2:
- 输入:s = "0000"
- 输出:["0.0.0.0"]
思路:
这道题目和“分割回文串”的解题思路相似,只是多了许多剪枝操作,
path.size()>4, 可以直接return;
因为数字最多三位数,所以i < startindex + 3;
避免前导零时,注意要保证其是非一位数。
然后,用stoi()函数将字符转换为数字。在主函数中,只处理字符串位数在[4, 12]之间的.
class Solution {
private:
vector<string> result;
vector<string> path;
void backtracking(string s, int startindex) {
if (startindex == s.size() && path.size() == 4) {
result.push_back(path[0] + '.' + path[1] + '.' + path[2] + '.' + path[3]);
return;
}
if (path.size() > 4) {
return;
}
for (int i = startindex; i < s.size() && i < startindex + 3; i++) {
if (s[startindex] == '0' && i > startindex) continue;
// 避免前导零,需要保证非一位数
string str = s.substr(startindex, i - startindex + 1);
int num = stoi(str);
if (num > 255) continue; // 检查范围
path.push_back(str);
backtracking(s, i + 1);
path.pop_back();
}
}
public:
vector<string> restoreIpAddresses(string s) {
result.clear();
path.clear();
if (s.size() >= 4 && s.size() <= 12) {
backtracking(s, 0);
}
return result;
}
};
78.子集
给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
说明:解集不能包含重复的子集。
示例: 输入: nums = [1,2,3] 输出: [ [3], [1], [2], [1,2,3], [1,3], [2,3], [1,2], [] ]
思路:
回溯三部曲,只是result收集的是每个子节点,所以result收集结果放在终止条件以前,其中因为i<nums.size()已经限制了循环,所以上面的终止条件可以不写。
class Solution {
private:
vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int>& nums, int startIndex) {
result.push_back(path); // 收集子集,要放在终止添加的上面,否则会漏掉自己
if (startIndex >= nums.size()) { // 终止条件可以不加
return;
}
for (int i = startIndex; i < nums.size(); i++) {
path.push_back(nums[i]);
backtracking(nums, i + 1);
path.pop_back();
}
}
public:
vector<vector<int>> subsets(vector<int>& nums) {
result.clear();
path.clear();
backtracking(nums, 0);
return result;
}
};
90.子集II
给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
说明:解集不能包含重复的子集。
示例:
- 输入: [1,2,2]
- 输出: [ [2], [1], [1,2,2], [2,2], [1,2], [] ]
思路:
该题相较于上题多了一个去重的操作,所以需要保证在横向for循环(即同一层级)跳过重复元素。
注意:
只跳过同一层级的重复元素,而不是整个数组的。为此,需要确保在检查重复元素时,判断是否是同一层级的元素。将 if (i > 0 && nums[i] == nums[i - 1])
改为 if (i > startindex && nums[i] == nums[i - 1])
,这样可以跳过当前层级的重复元素。
class Solution {
private:
vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int>& nums, int startindex) {
result.push_back(path);
for (int i = startindex; i < nums.size(); i++) {
if (i > startindex && nums[i] == nums[i - 1]) continue;
// 只跳过当前层级的重复元素
path.push_back(nums[i]);
backtracking(nums, i + 1);
path.pop_back();
}
}
public:
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
sort(nums.begin(), nums.end()); // 排序以便检测重复元素
result.clear();
path.clear();
backtracking(nums, 0);
return result;
}
};
491.递增子序列
给定一个整型数组, 你的任务是找到所有该数组的递增子序列,递增子序列的长度至少是2。
示例:
- 输入: [4, 6, 7, 7]
- 输出: [[4, 6], [4, 7], [4, 6, 7], [4, 6, 7, 7], [6, 7], [6, 7, 7], [7,7], [4,7,7]]
说明:
- 给定数组的长度不会超过15。
- 数组中的整数范围是 [-100,100]。
- 给定数组中可能包含重复数字,相等的数字应该被视为递增的一种情况。
思路:
这道题需要两个元素以上,给个if语句判断,再收集结果。然后就是需要确保递增顺序:
if (path.empty() || nums[i] >= path.back())。注意判断是否为空。
class Solution {
private:
vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int>& nums, int startindex) {
if (path.size() > 1) result.push_back(path);
for (int i = startindex; i < nums.size(); i++) {
if (i > startindex && nums[i] == nums[i - 1]) continue; // 跳过重复元素
if (path.empty() || nums[i] >= path.back()) { // 确保递增顺序
path.push_back(nums[i]);
backtracking(nums, i + 1);
path.pop_back();
}
}
}
public:
vector<vector<int>> findSubsequences(vector<int>& nums) {
result.clear();
path.clear();
backtracking(nums, 0);
return result;
}
};