目录
一、理论基础
1. 相关题目
2. 遍历过程
3. 代码框架
二、LeetCode 题目
1. 组合
2. 组合总和III
3. 电话号码的字母组合
4. 组合总和
5. 组合总和II
6. 分割回文串
7. 复原IP地址
8. 子集
一、理论基础
1. 相关题目
2. 遍历过程
3. 代码框架
void backtracking(参数) {
if (终止条件) {
存放结果;
return;
}
for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
处理节点;
backtracking(路径,选择列表); // 递归
回溯,撤销处理结果
}
}
二、LeetCode 题目
1. 组合
https://leetcode.cn/problems/combinations/description/https://leetcode.cn/problems/combinations/description/
给定两个整数
n
和k
,返回范围[1, n]
中所有可能的k
个数的组合。你可以按 任何顺序 返回答案。
示例 1:
输入:n = 4, k = 2
输出:
[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]
示例 2:
输入:n = 1, k = 1
输出:[[1]]
class Solution {
public:
vector<vector<int>> result;
vector<int> path;
void backtracking(int n, int k, int startIndex) {
// 满足条件插入
if (path.size() == k) {
result.push_back(path);
return;
}
// 如果后面的数组满足不了能取出 k - path.size() 个数组,则不用遍历了
for (int i = startIndex; i <= n - (k - path.size()) + 1; i++) {
path.push_back(i);
backtracking(n, k, i + 1);
path.pop_back();
}
return;
}
vector<vector<int>> combine(int n, int k) {
backtracking(n, k, 1);
return result;
}
};
2. 组合总和III
https://leetcode.cn/problems/combination-sum-iii/description/https://leetcode.cn/problems/combination-sum-iii/description/
找出所有相加之和为
n
的k
个数的组合,且满足下列条件:
- 只使用数字 1 到 9
- 每个数字 最多使用一次
返回 所有可能的有效组合的列表 。该列表不能包含相同的组合两次,组合可以以任何顺序返回。
示例 1:
输入: k = 3, n = 7
输出: [[1,2,4]]
解释:
1 + 2 + 4 = 7
没有其他符合的组合了。
示例 2:
输入: k = 3, n = 9
输出: [[1,2,6], [1,3,5], [2,3,4]]
解释:
1 + 2 + 6 = 9
1 + 3 + 5 = 9
2 + 3 + 4 = 9
没有其他符合的组合了。
示例 3:
输入: k = 4, n = 1
输出: []
解释: 不存在有效的组合。
在[1,9]范围内使用4个不同的数字,我们可以得到的最小和是1+2+3+4 = 10,因为10 > 1,没有有效的组合。
class Solution {
public:
vector<vector<int>> result;
vector<int> path;
void backtracking(int targetSum, int k, int sum, int startIndex) {
if (path.size() == k) {
if (targetSum == sum) result.push_back(path);
return;
}
// 剪枝
for (int i = startIndex; i <= 9 - (k - path.size()) + 1; i++) {
sum += i;
path.push_back(i);
backtracking(targetSum, k, sum, i + 1);
sum -= i;
path.pop_back();
}
return;
}
vector<vector<int>> combinationSum3(int k, int n) {
backtracking(n, k, 0, 1);
return result;
}
};
3. 电话号码的字母组合
https://leetcode.cn/problems/letter-combinations-of-a-phone-number/description/https://leetcode.cn/problems/letter-combinations-of-a-phone-number/description/
给定一个仅包含数字
2-9
的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
示例 1:
输入:digits = "23"
输出:["ad","ae","af","bd","be","bf","cd","ce","cf"]
示例 2:
输入:digits = ""
输出:[]
示例 3:
输入:digits = "2"
输出:["a","b","c"]
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;
}
int digit = digits[index] - '0'; // 将index指向的数字转为int
string letters = letterMap[digit]; // 取数字对应的字符集
for (int i = 0; i < letters.size(); i++) {
s.push_back(letters[i]); // 处理
backtracking(digits, index + 1); // 递归,注意index+1,一下层要处理下一个数字了
s.pop_back(); // 回溯
}
}
vector<string> letterCombinations(string digits) {
s.clear();
result.clear();
if (digits.size() == 0) {
return result;
}
backtracking(digits, 0);
return result;
}
};
4. 组合总和
https://leetcode.cn/problems/combination-sum/description/https://leetcode.cn/problems/combination-sum/description/
给你一个 无重复元素 的整数数组
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
输出: []
class Solution {
public:
vector<vector<int>> result;
vector<int> record;
void backtracking(vector<int>& candidates, int targetnum, int sum, int startIndex) {
if (sum == targetnum) {
result.push_back(record);
return;
}
for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= targetnum; i++) {
record.push_back(candidates[i]);
backtracking(candidates, targetnum, sum + candidates[i], i);
record.pop_back();
}
return;
}
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
sort(candidates.begin(), candidates.end()); // 需要排序
backtracking(candidates, target, 0, 0);
return result;
}
};
5. 组合总和II
https://leetcode.cn/problems/combination-sum-ii/description/https://leetcode.cn/problems/combination-sum-ii/description/
给定一个候选人编号的集合
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]
]
// 方法一:
class Solution {
public:
vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int>& candidates, int startIndex, int target) {
if (target == 0) {
result.push_back(path);
return;
}
for (int i = startIndex; i < candidates.size() && target - candidates[i] >= 0; i++) {
if (i > startIndex && candidates[i] == candidates[i - 1]) {
continue;
}
path.push_back(candidates[i]);
backtracking(candidates, i + 1, target - candidates[i]);
path.pop_back();
}
return;
}
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
sort(candidates.begin(), candidates.end());
backtracking(candidates, 0, target);
return result;
}
};
// 方法二:
class Solution {
public:
vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int>& candidates, int startIndex, int target) {
if (target == 0) {
result.push_back(path);
return;
}
unordered_set<int> uset;
for (int i = startIndex; i < candidates.size() && target - candidates[i] >= 0; i++) {
if (uset.find(candidates[i]) != uset.end()) {
continue;
}
uset.insert(candidates[i]);
path.push_back(candidates[i]);
backtracking(candidates, i + 1, target - candidates[i]);
path.pop_back();
}
return;
}
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
sort(candidates.begin(), candidates.end());
backtracking(candidates, 0, target);
return result;
}
};
6. 分割回文串
https://leetcode.cn/problems/palindrome-partitioning/description/https://leetcode.cn/problems/palindrome-partitioning/description/
给你一个字符串
s
,请你将s
分割成一些子串,使每个子串都是 回文串 。返回s
所有可能的分割方案。
示例 1:
输入:s = "aab"
输出:[["a","a","b"],["aa","b"]]
示例 2:
输入:s = "a"
输出:[["a"]]
class Solution {
public:
vector<vector<string>> result;
vector<string> st;
void backtracking(const string s, int beginIndex) {
// 找到一种切割方法
if (beginIndex == s.size()) {
result.push_back(st);
return;
}
for (int i = beginIndex; i < s.size(); i++) {
if (isPalindrome(s, beginIndex, i)) {
// 是回文子串,保存
st.push_back(s.substr(beginIndex, i - beginIndex + 1));
}
else {
continue;
}
backtracking(s, i + 1);
st.pop_back();
}
return;
}
bool isPalindrome(const string s, int begin, int end) {
for (int i = begin, j = end; i < j; i++, j--) {
if (s[i] != s[j]) return false;
}
return true;
}
vector<vector<string>> partition(string s) {
backtracking(s, 0);
return result;
}
};
7. 复原IP地址
https://leetcode.cn/problems/restore-ip-addresses/description/https://leetcode.cn/problems/restore-ip-addresses/description/
有效 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 地址。给定一个只包含数字的字符串
s
,用以表示一个 IP 地址,返回所有可能的有效 IP 地址,这些地址可以通过在s
中插入'.'
来形成。你 不能 重新排序或删除s
中的任何数字。你可以按 任何 顺序返回答案。
示例 1:
输入:s = "25525511135"
输出:["255.255.11.135","255.255.111.35"]
示例 2:
输入:s = "0000"
输出:["0.0.0.0"]
示例 3:
输入:s = "101023"
输出:["1.0.10.23","1.0.102.3","10.1.0.23","10.10.2.3","101.0.2.3"]
// 版本一:
class Solution {
public:
vector<string> result;
vector<string> st;
void backtracking(const string s, int beginIndex) {
if (beginIndex == s.size()) {
if (st.size() == 4) {
string buff = st.front();
for (int j = 1; j < 4; j++) buff = buff + "." + st[j];
result.push_back(buff);
return;
}
else {
return;
}
}
for (int i = beginIndex; i < s.size(); i++) {
if (judgeNum(s, beginIndex, i)) {
// 在范围内
st.push_back(s.substr(beginIndex, i - beginIndex + 1));
backtracking(s, i + 1);
st.pop_back();
}
else {
break;
}
}
return;
}
bool judgeNum(const string& s, int start, int end) {
if (start > end) {
return false;
}
if (s[start] == '0' && start != end) { // 0开头的数字不合法
return false;
}
int num = 0;
for (int i = start; i <= end; i++) {
if (s[i] > '9' || s[i] < '0') { // 遇到非数字字符不合法
return false;
}
num = num * 10 + (s[i] - '0');
if (num > 255) { // 如果大于255了不合法
return false;
}
}
return true;
}
vector<string> restoreIpAddresses(string s) {
backtracking(s, 0);
return result;
}
};
// 版本二:
class Solution {
private:
vector<string> result; // 记录结果
// startIndex: 搜索的起始位置,pointNum:添加逗点的数量
void backtracking(string& s, int startIndex, int pointNum) {
if (pointNum == 3) { // 逗点数量为3时,分隔结束
// 判断第四段子字符串是否合法,如果合法就放进result中
if (isValid(s, startIndex, s.size() - 1)) {
result.push_back(s);
}
return;
}
for (int i = startIndex; i < s.size(); i++) {
if (isValid(s, startIndex, i)) { // 判断 [startIndex, i] 这个区间的子串是否合法
s.insert(s.begin() + i + 1 , '.'); // 在i的后面插入一个逗点
pointNum++;
backtracking(s, i + 2, pointNum); // 插入逗点之后下一个子串的起始位置为i+2
pointNum--; // 回溯
s.erase(s.begin() + i + 1); // 回溯删掉逗点
} else {
break; // 不合法,直接结束本层循环
}
}
}
// [start, end]
bool isValid(const string& s, int start, int end) {
if (start > end) {
return false;
}
if (s[start] == '0' && start != end) { // 0开头的数字不合法
return false;
}
int num = 0;
for (int i = start; i <= end; i++) {
if (s[i] > '9' || s[i] < '0') { // 遇到非数字字符不合法
return false;
}
num = num * 10 + (s[i] - '0');
if (num > 255) { // 如果大于255了不合法
return false;
}
}
return true;
}
public:
vector<string> restoreIpAddresses(string s) {
result.clear();
if (s.size() < 4 || s.size() > 12) return result; // 算是剪枝了
backtracking(s, 0, 0);
return result;
}
};
8. 子集
https://leetcode.cn/problems/subsets/description/https://leetcode.cn/problems/subsets/description/
给你一个整数数组
nums
,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
示例 1:
输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
示例 2:
输入:nums = [0]
输出:[[],[0]]
class Solution {
public:
vector<vector<int>> result;
vector<int> vec;
void backtracking(vector<int>& nums, int beginIndex) {
result.push_back(vec);
if (beginIndex >= nums.size()) {
return;
}
for (int i = beginIndex; i < nums.size(); i++) {
vec.push_back(nums[i]);
backtracking(nums, i + 1);
vec.pop_back();
}
return;
}
vector<vector<int>> subsets(vector<int>& nums) {
if (nums.size() == 0) return result;
backtracking(nums, 0);
return result;
}
};