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]; } }