目录
- 77. 组合
- 题目描述
- 题解
- 216. 组合总和 III
- 题目描述
- 题解
- 17. 电话号码的字母组合
- 题目描述
- 题解
77. 组合
点此跳转题目链接
题目描述
给定两个整数 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]]
提示:
1 <= n <= 20
1 <= k <= n
题解
参考:代码随想录-77.组合 ,讲得非常细了。
算是回溯算法的入门题目,核心要理解回溯的树形结构,以及其中的横向和纵向遍历逻辑:
然后,考虑回溯三部曲 ✨
1️⃣ 处理
本题就是很基础的求组合,所以每次往当前组合 path
添加新数字就好了:
path.push_back(i);
当前组合名称取为
path
,旨在呼应回溯树形结构图中的纵向“探索”路线(先取一个数x,再取一个数y)
2️⃣ 递归
递归出口是经典的——当前组合大小达到目标组合大小,则将其加入结果集:
if (path.size() == k)
{
res.push_back(path);
return;
}
否则,当前位置数字确定后,递归下一个位置的数来做组合:
backTracking(i + 1, end, k)
3️⃣ 回溯
弹出当前组合的最后一个值,以便探索该位置的其他可能值:
path.pop_back();
整体代码如下:
C++
class Solution
{
private:
vector<int> path;
vector<vector<int>> res;
public:
void backTracking(int start, int end, int k)
{
// 回溯出口:子结果path已满(纵向遍历)
if (path.size() == k)
{
res.push_back(path);
return;
}
// 横向遍历
for (int i = start; i <= end; i++)
{
path.push_back(i); // 处理
backTracking(i + 1, end, k); // 递归
path.pop_back(); // 回溯
}
}
vector<vector<int>> combine(int n, int k)
{
backTracking(1, n, k);
return res;
}
};
Go
type Helper struct {
path []int
res [][]int
}
func (helper *Helper) backTracking(start int, end int, k int) {
// 递归出口
if len(helper.path) == k {
// newPath := make([]int, len(helper.path))
// copy(newPath, helper.path)
// helper.res = append(helper.res, newPath)
helper.res = append(helper.res, append([]int(nil), helper.path...))
return
}
// 横向遍历
for i := start; i <= end; i++ {
helper.path = append(helper.path, i) // 处理
helper.backTracking(i+1, end, k) // 递归
helper.path = helper.path[:len(helper.path)-1] // 回溯
}
}
func combine(n int, k int) [][]int {
helper := Helper{}
helper.backTracking(1, n, k)
return helper.res
}
216. 组合总和 III
点此跳转题目链接
题目描述
找出所有相加之和为 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,没有有效的组合。
提示:
2 <= k <= 9
1 <= n <= 60
题解
参考:代码随想录-216
回溯算法解决。首先想清楚回溯的树形结构图:
然后走回溯三部曲 ✨
-
处理: 将当前处理的数字加入当前组合
path
,并求此时组合中的数字和进行这一步之前可以剪枝:由于是从1到9(从小到大)横向遍历,如果 目标和 与 当前和 的差小于即将加入的数字,说明再加数字必将导致组合总和过大,故没必要在此基础上往后遍历处理了。
-
递归: 递归地尝试将后面的数字加入组合;递归出口:
path
的大小达到目标大小k
,且其中数字和等于目标和,则将path
加入结果集 -
回溯: 弹出当前组合的最后一个数,以便探索该位置放其他数的可能
代码如下:
class Solution
{
private:
vector<int> path;
vector<vector<int>> res;
public:
void backTracking(int start, int end, int maxPathSize, int targetSum, int curSum)
{
// 递归出口(纵向遍历)
if (path.size() == maxPathSize)
{
if (curSum == targetSum)
res.push_back(path);
return;
}
// 剪枝
if (targetSum - curSum < start)
return;
// 横向遍历
for (int i = start; i <= end; i++)
{
curSum += i;
path.push_back(i); // 处理
backTracking(i + 1, end, maxPathSize, targetSum, curSum); // 递归
curSum -= i;
path.pop_back(); // 回溯
}
}
vector<vector<int>> combinationSum3(int k, int n)
{
backTracking(1, 9, k, n, 0);
return res;
}
};
17. 电话号码的字母组合
点此跳转题目链接
题目描述
给定一个仅包含数字 2-9
的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
示例 1:
输入:digits = "23"
输出:["ad","ae","af","bd","be","bf","cd","ce","cf"]
示例 2:
输入:digits = ""
输出:[]
示例 3:
输入:digits = "2"
输出:["a","b","c"]
提示:
0 <= digits.length <= 4
digits[i]
是范围['2', '9']
的一个数字。
题解
参考:代码随想录-17
回溯算法解决。首先想清楚回溯的树形结构图:
然后走回溯三部曲 ✨
-
处理: 从当前处理的数字对应的字母串中,取一个字母加入当前组合(字符串)
path
-
递归: 递归地尝试将后面的数字对应的可能字母加入组合;递归出口:
path
的长度与所给数字串digits
相同,则将path
加入结果集 -
回溯: 弹出当前组合的最后一个字符,以便探索该位置放其他字母的可能
c++代码如下:
class Solution
{
private:
string path = "";
vector<string> res;
unordered_map<char, string> dict = {
{'2', "abc"},
{'3', "def"},
{'4', "ghi"},
{'5', "jkl"},
{'6', "mno"},
{'7', "pqrs"},
{'8', "tuv"},
{'9', "wxyz"}};
public:
void backTracking(const string &digits, int start)
{
// 递归出口
if (path.length() == digits.length())
{
res.push_back(path);
return;
}
char digit = digits[start]; // 当前要处理的数字
string letters = dict[digit]; // 当前处理数字对应的字母
// 横向遍历
for (int i = 0; i < letters.length(); i++)
{
path.push_back(letters[i]); // 处理
backTracking(digits, start + 1); // 递归
path.pop_back(); // 回溯
}
}
vector<string> letterCombinations(string digits)
{
if (digits == "")
return res;
backTracking(digits, 0);
return res;
}
};
顺便再熟悉下golang:
type Helper struct {
path string
res []string
dict map[byte]string
}
func newHelper() *Helper {
return &Helper{
dict: map[byte]string{
'2': "abc",
'3': "def",
'4': "ghi",
'5': "jkl",
'6': "mno",
'7': "pqrs",
'8': "tuv",
'9': "wxyz",
},
}
}
func (helper *Helper) backTracking(digits string, start int) {
// 递归出口
if len(helper.path) == len(digits) {
helper.res = append(helper.res, helper.path)
return
}
letters := helper.dict[digits[start]]
for _, letter := range letters {
helper.path = helper.path + string(letter) // 处理
helper.backTracking(digits, start+1) // 递归
helper.path = helper.path[:len(helper.path)-1] // 回溯
}
}
func letterCombinations(digits string) []string {
helper := newHelper()
if len(digits) == 0 {
return helper.res
}
helper.backTracking(digits, 0)
return helper.res
}