import java.util.*;
/**
* NC385 划分等和序列
* @author d3y1
*/
public class Solution {
private int avg;
private int N;
private boolean[] visited;
private boolean[] memo;
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param nums int整型ArrayList
* @param k int整型
* @return bool布尔型
*/
public boolean candivide (ArrayList<Integer> nums, int k) {
// return solution1(nums, k);
// return solution2(nums, k);
return solution3(nums, k);
// return solution4(nums, k);
}
/**
* 动态规划: 01背包
*
* dp[j]表示背包大小为j时能够组成的最大的和(数组中的元素和)[数组中每个元素仅用一次]
*
* dp[j] = Math.max(dp[j], dp[j-num] + num)
*
* @param nums
* @param k
* @return
*/
private boolean solution1(ArrayList<Integer> nums, int k){
int n = nums.size();
int sum = 0;
for(int i=0; i<n; i++){
sum += nums.get(i);
}
if(sum%k != 0){
return false;
}
avg = sum/k;
int[] dp = new int[sum+1];
int num;
for(int i=0; i<n; i++){
num = nums.get(i);
if(num > avg){
return false;
}
// 数组中每个元素仅用一次 -> 降序
for(int j=sum; j>=num; j--){
dp[j] = Math.max(dp[j], dp[j-num] + num);
}
}
// avg的倍数 均要判断
for(int i=1; i<=k; i++){
// 恰好装满 -> 恰好组成下一个元素和avg
if(dp[avg*i] != avg*i){
return false;
}
}
return true;
}
/**
* dfs: 遍历解空间
* @param nums
* @param k
* @return
*/
private boolean solution2(ArrayList<Integer> nums, int k){
int n = nums.size();
visited = new boolean[n];
int sum = 0;
for(int i=0; i<n; i++){
sum += nums.get(i);
}
if(sum%k != 0){
return false;
}
int avg = sum/k;
return dfs(nums, k, avg);
}
/**
* dfs: 递归
* @param nums
* @param k
* @param target
* @return
*/
private boolean dfs(ArrayList<Integer> nums, int k, int target){
if(k == 0){
return true;
}
if(target == 0){
if(dfs(nums, k-1, avg)){
return true;
}
}else if(target < 0){
return false;
}else{
for(int i=0; i<nums.size(); i++){
if(!visited[i]){
visited[i] = true;
if(dfs(nums, k, target-nums.get(i))){
return true;
}
visited[i] = false;
}
}
}
return false;
}
/**
* 状态压缩(位运算) + 记忆化搜索
*
* 给定长度为n的数组nums和整数k, 我们需要判断是否能将数组分成k个总和相等的非空子集.
* 首先计算数组的和sum, 如果sum不是k的倍数, 那么不可能能有合法方案, 此时直接返回False.
* 否则我们需要得到k个和为avg=sum/k的集合, 那么我们每次尝试选择一个还在数组中的数, 若选择后当前已选数字和等于avg则说明得到了一个集合,
* 而已选数字和大于avg时, 不可能形成一个集合从而停止继续往下选择新的数字.
*
* 又因为n满足1≤n≤15, 所以我们可以用一个整数status来表示当前可用的数字集合: 从低位到高位, 第i位为1则表示数字nums[i]可以使用, 否则表示nums[i]已被使用.
*
* 为了避免相同状态的重复计算, 我们用memo[status]来表示在可用的数字状态为status的情况下是否可行, 初始全部状态为记录为可行状态True.
* 这样我们就可以通过记忆化搜索这种「自顶向下」的方式来进行求解原始状态的可行性, 而当状态集合中不存在任何数字时, 即status=0时, 表示原始数组可以按照题目要求来进行分配, 此时返回True即可.
*
* @param nums
* @param k
* @return
*/
private boolean solution3(ArrayList<Integer> nums, int k){
N = nums.size();
int sum = 0;
for(int i=0; i<N; i++){
sum += nums.get(i);
}
if(sum%k != 0){
return false;
}
avg = sum/k;
// 升序
Collections.sort(nums);
if(nums.get(N-1) > avg){
return false;
}
// 记忆化搜索
memo = new boolean[1<<N];
Arrays.fill(memo, true);
return dfs(nums, avg, (1<<N)-1, 0);
}
/**
* 递归
* @param nums
* @param avg
* @param status
* @param remain
* @return
*/
private boolean dfs(ArrayList<Integer> nums, int avg, int status, int remain){
if(status == 0){
return true;
}
// 记忆化搜索
if(!memo[status]){
return false;
}
memo[status] = false;
for(int i=0; i<N; i++){
// nums事先排序 升序
if(remain+nums.get(i) > avg){
break;
}
// nums[i]可选(即未被选择)
if(((status>>i)&1) == 1){
// 选择nums[i] 且将nums[i]置为不可选 <- status^(1<<i)
if(dfs(nums, avg, status^(1<<i), (remain+nums.get(i))%avg)){
return true;
}
}
}
return false;
}
/**
* 状态压缩(位运算) + 动态规划
*
* 也可以用「动态规划」这种「自底向上」的方法来求解是否存在可行方案:
* 同样我们用一个整数status来表示当前可用的数字集合: 从低位到高位, 第i位为0则表示数字nums[i]可以使用, 否则表示nums[i]已被使用.
*
* 然后我们用dp[status]来表示在可用的数字状态为status的情况下是否可能可行, 初始全部状态为记录为不可行状态False, 只记dp[0]=True为可行状态。
*
* 同样我们每次对于当前状态下从可用的数字中选择一个数字, 若此时选择全部数字取模后小于等于avg, 则说明选择该数字后的状态再继续往下添加数字是可能满足题意的, 并且此时标记状为可能可行状态, 否则就一定不能达到满足.
*
* 最终dp[U]即可, 其中U表示全部数字使用的集合状态(即status全1状态).
*
* @param nums
* @param k
* @return
*/
private boolean solution4(ArrayList<Integer> nums, int k){
N = nums.size();
int sum = 0;
for(int i=0; i<N; i++){
sum += nums.get(i);
}
if(sum%k != 0){
return false;
}
avg = sum/k;
// 升序
Collections.sort(nums);
if(nums.get(N-1) > avg){
return false;
}
// 动态规划
boolean[] dp = new boolean[1<<N];
int[] remain = new int[1<<N];
dp[0] = true;
int status;
for(int i=0; i<(1<<N); i++){
if(!dp[i]){
continue;
}
for(int j=0; j<N; j++){
if(remain[i]+nums.get(j) > avg){
break;
}
// nums[i]可选(即未被选择)
if(((i>>j)&1) == 0){
// 选择nums[i] 且将nums[i]置为不可选 <- i|(1<<j)
status = i|(1<<j);
if(!dp[status]){
remain[status] = (remain[i]+nums.get(j))%avg;
dp[status] = true;
}
}
}
}
return dp[(1<<N)-1];
}
}