算法题(程序员面试宝典)
解题思路主要来源于leetcode官方与《程序员面试宝典》&labuladong
698. 划分为k个相等的子集
给定一个整数数组 nums 和一个正整数 k,找出是否有可能把这个数组分成 k 个非空子集,其总和都相等。
示例 1:
输入: nums = [4, 3, 2, 3, 5, 2, 1], k = 4
输出: True
说明: 有可能将其分成 4 个子集(5),(1,4),(2,3),(2,3)等于总和。
提示:
1 <= k <= len(nums) <= 16
0 < nums[i] < 10000
解题方法
解题思路1
从数字的视角
class Solution {
public boolean canPartitionKSubsets(int[] nums, int k) {
//回溯算法 路径 结束条件 选择列表
//排除不合法情况
if(k>nums.length)
return false;
int sum = 0;
for(int i=0;i<nums.length;i++){
sum +=nums[i];
}
//总和不能整除 说明不能合理分配给k个桶
if((sum%k)!=0)
return false;
int target = sum/k;
int[] bucket = new int[k];
return backtrack(nums,0,k,target,bucket);
}
public boolean backtrack(int[] nums,int index,int k,int target,int[] bucket){
//结束条件
if(index==nums.length){
//如果k个桶的值都等于目标值则返回true
for(int j=0;j<bucket.length;j++){
if(bucket[j]!=target)
return false;
}
return true;
}
for(int i=0;i<k;i++){
//跳过不合法的桶
if(bucket[i]+nums[index]>target)
continue;
//做出选择
bucket[i] += nums[index];
if(backtrack(nums,index+1,k,target,bucket)){
return true;
}
//撤销选择
bucket[i] -= nums[index];
}
//nums[index] 放在哪个桶中都不行
return false;
}
}
class Solution {
public boolean canPartitionKSubsets(int[] nums, int k) {
//回溯算法 路径 结束条件 选择列表
//排除不合法情况
if(k>nums.length)
return false;
int sum = 0;
for(int i=0;i<nums.length;i++){
sum +=nums[i];
}
//总和不能整除 说明不能合理分配给k个桶
if((sum%k)!=0)
return false;
int target = sum/k;
int[] bucket = new int[k];
//对数组进行降序排列,对减枝进行排除
Arrays.sort(nums);
for(int i=0,j=nums.length-1;i<=j;i++,j--){
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
return backtrack(nums,0,k,target,bucket);
}
public boolean backtrack(int[] nums,int index,int k,int target,int[] bucket){
//结束条件
if(index==nums.length){
//如果k个桶的值都等于目标值则返回true
for(int j=0;j<bucket.length;j++){
if(bucket[j]!=target)
return false;
}
return true;
}
for(int i=0;i<k;i++){
//跳过不合法的桶
if(bucket[i]+nums[index]>target)
continue;
//做出选择
bucket[i] += nums[index];
if(backtrack(nums,index+1,k,target,bucket)){
return true;
}
//撤销选择
bucket[i] -= nums[index];
}
//nums[index] 放在哪个桶中都不行
return false;
}
}
解题思路2
从桶的角度
class Solution {
public boolean canPartitionKSubsets(int[] nums, int k) {
//从桶的视角
//回溯算法 路径 结束条件 选择列表
//排除不合法情况
if(k>nums.length)
return false;
int sum = 0;
for(int i=0;i<nums.length;i++){
sum +=nums[i];
}
//总和不能整除 说明不能合理分配给k个桶
if((sum%k)!=0)
return false;
int target = sum/k;
//true: used[i] 已装入桶中 false: used[i] 未装入桶中
boolean[] used = new boolean[nums.length];
return backtrack(nums,0,k,0,target,used);
}
public boolean backtrack(int[] nums,int start,int k,int bucket,int target,boolean[] used){
//结束条件
//每个桶都装满了
if(k==0){
return true;
}
//当前桶装满了 换下一个桶
if(bucket==target){
return backtrack(nums,0,k-1,0,target,used);
}
for(int i=start;i<nums.length;i++){
//跳过已加入桶的元素
if(used[i])
continue;
//当前第k个桶装不下num[i]
if(nums[i]+bucket>target)
continue;
//做出选择
used[i]=true;
bucket += nums[i];
if(backtrack(nums,i+1,k,bucket,target,used)){
return true;
}
//撤销选择
used[i] = false;
bucket -= nums[i];
}
//nums[index] 放在哪个桶中都不行
return false;
}
}