Leetcode——划分为k个相等的子集(目标和)
Posted Yawn,
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Leetcode——划分为k个相等的子集(目标和)相关的知识,希望对你有一定的参考价值。
1. 划分为k个相等的子集
(1)回溯,剪枝,贪心
- 将数组划分为k个组,使得每个组的和均相等。
- 求出数组总和sum,如果sum除以k不为整数,肯定就不满足每个组的和相等,直接return false.
- 拿到sum/k的值target后,target就代表每个组的和需要达到的值(超过或不足都不行)
- 同时,如果数组的最大值>target也无法满足题意,return false.
剪枝:
- 找到k-1个满足题意的组合后,就可以return true了。因为剩下未选的元素的和肯定为数组总和sum-(k-1)*target=target。
- 当组合中的元素之和超过了目标值时,就没必要再往组合中添加元素了,直接回溯。
- 如果某个元素无法使得组合达到目标值,那么跟它值相同的元素也不需要添加了
- 举个例子,数组[1,3,3,3],k=2.可以计算出target=5.【1】——此时组合的和为1,小于目标值5,继续往组合中添加元素。添加第一个3,【1,3】,小于5,继续添加。我们发现随后继续添加两个3都超过了5,回溯到【1,3】这个状态。数组遍历完,依然找不到和为5的组合,继续回溯——【1】。此时,我们发现下一个要添加的元素还是3,但在之前我们已经试过了3无法使得当前组合达到目标值,所以就没必要再添加3了,直接continue.
- 从大到小遍历,这有点类似贪心的思想
class Solution {
public boolean canPartitionKSubsets(int[] nums, int k) {
int sum=0;
int len = nums.length;
boolean[] used = new boolean[len];
Arrays.sort(nums);
//求数组总和
for (int i = 0; i < nums.length; i++) {
sum += nums[i];
}
//如果sum除以k不为整数,肯定就不满足每个组的和相等,直接return false.
//不能整除则子集分不出k份,直接false。
if (sum % k != 0)
return false;
//拿到sum/k的值target后,target就代表每个组的和需要达到的值(超过或不足都不行)
int target = sum / k;
//如果数组的最大值>target也无法满足题意,return false.
if (nums[nums.length - 1] > target)
return false;
return dfs(nums, nums.length-1, target, 0, k, used);
}
public static boolean dfs(int[] nums,int begin,int target,int curSum,int k,boolean[] used) {
//剪枝1
if (k == 1)
return true;
if (curSum == target)
return dfs(nums, nums.length-1, target, 0, k-1, used);//找到了一个组合,还有k-1个.
//剪枝4
for(int i = begin; i >= 0; i--) {
//使用过的元素就不能再使用了
if (used[i])
continue;
//剪枝2
if (curSum+nums[i] > target)
continue;
//添加元素nums[i]
used[i]=true;
//如果添加这个元素后,完成了题目要求,就return true.
if (dfs(nums,i-1,target,curSum+nums[i],k,used))
return true;
//回溯
used[i]=false;
//剪枝3
while (i > 0 && nums[i-1] == nums[i])
i--;
}
return false;
}
}
(2)桶
先算出子集的和是多少,并抽象成k个桶,每个桶的值是子集的和。然后尝试所有不同的组合(即放数到桶中),如果存在一种组合可以使每个桶都正好放下,那么返回可以。如果不存在,返回不可以。
class Solution {
public boolean canPartitionKSubsets(int[] nums, int k) {
//因为题目限制条件不用担心溢出
int sum = 0;
for(int i = 0; i < nums.length; i++){
sum += nums[i];
}
if(sum % k != 0){
return false;
}
//求出子集的和
sum = sum / k;
//排序 小的放最前面大的放最后面
Arrays.sort(nums);
//如果子集的和小于数组最大的直接返回false
if(nums[nums.length - 1] > sum){
return false;
}
//建立一个长度为k的桶
int[] arr = new int[k];
//桶的每一个值都是子集的和
Arrays.fill(arr, sum);
//从数组最后一个数开始进行递归
return help(nums, nums.length - 1, arr, k);
}
boolean help(int[] nums, int cur, int[] arr, int k){
//已经遍历到了-1说明前面的所有数都正好可以放入桶里,那所有桶的值此时都为0,说明找到了结果,返回true
if(cur < 0){
return true;
}
//遍历k个桶
for(int i = 0; i < k; i++){
//如果正好能放下当前的数或者放下当前的数后,还有机会继续放前面的数(剪枝)
if(arr[i] == nums[cur] || (cur > 0 && arr[i] - nums[cur] >= nums[0])){
//放当前的数到桶i里
arr[i] -= nums[cur];
//开始放下一个数
if(help(nums, cur - 1, arr, k)){
return true;
}
//这个数不该放在桶i中
//从桶中拿回当前的数
arr[i] += nums[cur];
}
}
return false;
}
}
以上是关于Leetcode——划分为k个相等的子集(目标和)的主要内容,如果未能解决你的问题,请参考以下文章
LeetCode 698. 划分为k个相等的子集(回溯算法解决)
LeetCode 1624. 两个相同字符之间的最长子字符串 / 698. 划分为k个相等的子集 / 面试题 01.08. 零矩阵 / 1694. 重新格式化电话号码
LeetCode 1624. 两个相同字符之间的最长子字符串 / 698. 划分为k个相等的子集 / 面试题 01.08. 零矩阵 / 1694. 重新格式化电话号码