for (int index =0; index <nums.length; index++) {System.out.println(nums[index]);}
递归遍历数组你会不会?其实也很简单:
voidtraverse(int[] nums,int index) {if (index ==nums.length) {return; }System.out.println(nums[index]);traverse(nums, index +1);}
只要调用 traverse(nums, 0),和 for 循环的效果是完全一样的。
那么回到这道题,以数字的视角,选择 k 个桶,用 for 循环写出来是下面这样:
// k 个桶(集合),记录每个桶装的数字之和int[] bucket =newint[k];// 穷举 nums 中的每个数字for (int index =0; index <nums.length; index++) {// 穷举每个桶for (int i =0; i < k; i++) {// nums[index] 选择是否要进入第 i 个桶// ... }}
如果改成递归的形式,就是下面这段代码逻辑:
// k 个桶(集合),记录每个桶装的数字之和int[] bucket =newint[k];// 穷举 nums 中的每个数字voidbacktrack(int[] nums,int index) {// base caseif (index ==nums.length) {return; }// 穷举每个桶for (int i =0; i <bucket.length; i++) {// 选择装进第 i 个桶 bucket[i] += nums[index];// 递归穷举下一个数字的选择backtrack(nums, index +1);// 撤销选择 bucket[i] -= nums[index]; }}
虽然上述代码仅仅是穷举逻辑,还不能解决我们的问题,但是只要略加完善即可:
// 主函数booleancanPartitionKSubsets(int[] nums,int k) {// 排除一些基本情况if (k >nums.length) returnfalse;int sum =0;for (int v : nums) sum += v;if (sum % k !=0) returnfalse;// k 个桶(集合),记录每个桶装的数字之和int[] bucket =newint[k];// 理论上每个桶(集合)中数字的和int target = sum / k;// 穷举,看看 nums 是否能划分成 k 个和为 target 的子集returnbacktrack(nums,0, bucket, target);}// 递归穷举 nums 中的每个数字booleanbacktrack(int[] nums,int index,int[] bucket,int target) {if (index ==nums.length) {// 检查所有桶的数字之和是否都是 targetfor (int i =0; i <bucket.length; i++) {if (bucket[i] != target) {returnfalse; } }// nums 成功平分成 k 个子集returntrue; }// 穷举 nums[index] 可能装入的桶for (int i =0; i <bucket.length; i++) {// 剪枝,桶装装满了if (bucket[i] + nums[index] > target) {continue; }// 将 nums[index] 装入 bucket[i] bucket[i] += nums[index];// 递归穷举下一个数字的选择if (backtrack(nums, index +1, bucket, target)) {returntrue; }// 撤销选择 bucket[i] -= nums[index]; }// nums[index] 装入哪个桶都不行returnfalse;}