LeetCode - Medium - 216. Combination Sum III

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)));
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值