Topic
- Array
- Backtracking
Description
https://leetcode.com/problems/combination-sum-iii/
Find all valid combinations of k
numbers that sum up to n
such that the following conditions are true:
- Only numbers 1 through 9 are used.
- Each number is used at most once.
Return a list of all possible valid combinations. The list must not contain the same combination twice, and the combinations may be returned in any order.
Example 1:
Input: k = 3, n = 7
Output: [[1,2,4]]
Explanation:
1 + 2 + 4 = 7
There are no other valid combinations.
Example 2:
Input: k = 3, n = 9
Output: [[1,2,6],[1,3,5],[2,3,4]]
Explanation:
1 + 2 + 6 = 9
1 + 3 + 5 = 9
2 + 3 + 4 = 9
There are no other valid combinations.
Example 3:
Input: k = 4, n = 1
Output: []
Explanation: There are no valid combinations. [1,2,1] is not valid because 1 is used twice.
Example 4:
Input: k = 3, n = 2
Output: []
Explanation: There are no valid combinations.
Example 5:
Input: k = 9, n = 45
Output: [[1,2,3,4,5,6,7,8,9]]
Explanation:
1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 = 45
There are no other valid combinations.
Constraints:
- 2 <=
k
<= 9 - 1 <=
n
<= 60
Analysis
开始分析
本题就是在[1,2,3,4,5,6,7,8,9]这个集合中找到和为n的k个数的组合。组合问题,我们尝试用回溯算法解决。
在概论谈论到,回溯法解决的问题都可以抽象为树形结构。
本题k相当于了树的深度,9(因为整个集合[1,2,3,4,5,6,7,8,9]就是9个数)就是树的宽度。
例如 k = 2,n = 4的话,就是在集合[1,2,3,4,5,6,7,8,9]中求 k(个数) = 2, n(和) = 4的组合。
选取过程如图:
图中,可以看出,只有最后取到集合(1,3)和为4 符合条件。
回溯三弄
函数签名
函数参数如下:
-
List<Integer> path
:用来存放符合条件的结果。 -
List<List<Integer>> result
:用来存放结果集。 -
int targetSize
:就是题目中要求k个数的集合。 -
int targetSum
:目标和,也就是题目中的n。 -
int currentSum
:为已经收集的元素的总和,也就是path里元素的总和。 -
int startIndex
:为下一层for循环搜索的起始位置。
所以函数签名如下:
private void backtracking(List<Integer> path, int targetSize, int targetSum, //
int currentSum, int startIndex, List<List<Integer>> result) {}
其实这里currentSum这个参数也可以省略,每次targetSum减去选取的元素数值,然后判断如果targetSum为0了,说明收集到符合条件的结果了,这里为了直观便于理解,还是加一个currentSum参数。
这里再次,回溯法中递归函数参数很难一次性确定下来,一般先写逻辑,参数按需添加。
终止条件
在上文已经说了,k(也就是参数targetSize)其实就已经限制树的深度,因为就取k个元素,树再往下深了没有意义,所以如果path.size() 和 k相等了,就终止。
如果此时path里收集到的元素和(参数currentSum) 和targetSum(就是题目描述的n)相同了,就用result收集当前的结果。
所以终止代码如下:
if (path.size() == targetSize) {
if (currentSum == targetSum)
result.add(new ArrayList<>(path));
return;
}
遍历过程
如图所示:
处理过程就是 path收集每次选取的元素,相当于树型结构里的边,currentSum来统计path里元素的总和。
代码如下:
for (int i = startIndex; i <= 9; i++) {
path.add(i);
currentSum += i;
backtracking(path, targetSize, targetSum, currentSum, i + 1, result);
path.remove(path.size() - 1);
currentSum -= i;
}
别忘了处理过程 和 回溯过程是一一对应的,处理有加,回溯就要有减!
剪枝优化
如图所示:
已选元素总和如果已经大于n(图中数值为4)了,那么往后遍历就没有意义了,直接剪掉。
那么剪枝的地方一定是在递归终止的地方剪,剪枝代码如下:
// 剪枝
if (currentSum > targetSum)
return;
最终代码
请看下文的Submission章节。
参考资料
Submission
import java.util.ArrayList;
import java.util.List;
public class CombinationSumIII {
public List<List<Integer>> combinationSum3(int size, int targetSum) {
List<List<Integer>> result = new ArrayList<>();
List<Integer> path = new ArrayList<>();
backtracking(path, size, targetSum, 0, 1, result);
return result;
}
private void backtracking(List<Integer> path, int size, int targetSum, //
int currentSum, int startIndex, List<List<Integer>> result) {
// 剪枝
if (targetSum < currentSum)
return;
if (path.size() == size) {
if (targetSum == currentSum)
result.add(new ArrayList<>(path));
return;
}
for (int i = startIndex; i <= 9; i++) {
path.add(i);
currentSum += i;
backtracking(path, size, targetSum, currentSum, i + 1, result);
path.remove(path.size() - 1);
currentSum -= i;
}
}
}
Test
import static org.hamcrest.collection.IsIterableContainingInAnyOrder.containsInAnyOrder;
import static org.junit.Assert.*;
import java.util.Arrays;
import org.hamcrest.collection.IsEmptyCollection;
import org.junit.Test;
public class CombinationSumIIITest {
@Test
@SuppressWarnings("unchecked")
public void test() {
CombinationSumIII obj = new CombinationSumIII();
assertThat(obj.combinationSum3(3, 7), containsInAnyOrder(Arrays.asList(1, 2, 4)));
assertThat(obj.combinationSum3(3, 9), containsInAnyOrder(Arrays.asList(1, 2, 6), //
Arrays.asList(1, 3, 5), Arrays.asList(2, 3, 4)));
assertThat(obj.combinationSum3(4, 1), IsEmptyCollection.empty());
assertThat(obj.combinationSum3(3, 2), IsEmptyCollection.empty());
assertThat(obj.combinationSum3(9, 45), containsInAnyOrder(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9)));
}
}