友情支持

如果您觉得这个笔记对您有所帮助,看在D瓜哥码这么多字的辛苦上,请友情支持一下,D瓜哥感激不尽,😜

支付宝

微信

有些打赏的朋友希望可以加个好友,欢迎关注D 瓜哥的微信公众号,这样就可以通过公众号的回复直接给我发信息。

wx jikerizhi

公众号的微信号是: jikerizhi因为众所周知的原因,有时图片加载不出来。 如果图片加载不出来可以直接通过搜索微信号来查找我的公众号。

698. Partition to K Equal Sum Subsets

Given an array of integers nums and a positive integer k, find whether it’s possible to divide this array into k non-empty subsets whose sums are all equal.

Example 1:

Input: nums = [4, 3, 2, 3, 5, 2, 1], k = 4
Output: True
Explanation: It's possible to divide it into 4 subsets (5), (1, 4), (2,3), (2,3) with equal sums.

Note:

  • 1 ⇐ k ⇐ len(nums) ⇐ 16.

  • 0 < nums[i] < 10000.

思路分析

球视角

0698 01
  • 一刷(超时)

  • 一刷(优化)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/**
 * 第一版:超时,通过 150/163 个测试用例
 *
 * @author D瓜哥 · https://www.diguage.com
 * @since 2024-07-10 17:23:46
 */
public boolean canPartitionKSubsets(int[] nums, int k) {
  if (nums == null || nums.length == 0 || nums.length < k) {
    return false;
  }
  // 总和不能整除,肯定无法均分
  int sum = Arrays.stream(nums).sum();
  if (sum % k != 0) {
    return false;
  }
  boolean[] used = new boolean[nums.length];
  int[] bucket = new int[k];
  int target = sum / k;
  return backtrack(nums, used, 0, bucket, target);
}

private boolean backtrack(int[] nums, boolean[] used, int start, int[] bucket, int target) {
  // 遍历完了所有数组
  if (start == nums.length) {
    return true;
  }
  for (int i = 0; i < bucket.length; i++) {
    // 剪枝:如果该桶总和大于平均值,则跳过
    if (target < nums[start] + bucket[i]) {
      continue;
    }
    if (used[start]) {
      continue;
    }
    used[start] = true;
    bucket[i] += nums[start];
    if (backtrack(nums, used, start + 1, bucket, target)) {
      return true;
    }
    bucket[i] -= nums[start];
    used[start] = false;
  }
  return false;
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
/**
 * 第一版:超时,通过 150/163 个测试用例
 *
 * @author D瓜哥 · https://www.diguage.com
 * @since 2024-07-10 17:53:39
 */
public boolean canPartitionKSubsets(int[] nums, int k) {
  if (nums == null || nums.length == 0 || nums.length < k) {
    return false;
  }
  // 总和不能整除,肯定无法均分
  int sum = Arrays.stream(nums).sum();
  if (sum % k != 0) {
    return false;
  }
  int target = sum / k;
  // 排序:逆序先放大元素,这样减少回溯的可能和次数
  Arrays.sort(nums);
  for (int l = 0, r = nums.length - 1; l < r; l++, r--) {
    int temp = nums[l];
    nums[l] = nums[r];
    nums[r] = temp;
  }
  // 最大元素大于平均值,也不可能平分,直接返回
  if (target < nums[0]) {
    return false;
  }
  int[] bucket = new int[k];
  return backtrack(nums, 0, bucket, target);
}

private boolean backtrack(int[] nums, int start, int[] bucket, int target) {
  // 遍历完数组内所有的数字,那么就成功
  if (start == nums.length) {
    return true;
  }
  for (int i = 0; i < bucket.length; i++) {
    // 剪枝:如果两个桶的值一样,只要做一个尝试即可
    // 这里也包含了:第一个数字放在第一个桶的优化点
    // 解释:最初始时,所有的桶都是0,在执行完第一个桶后,
    //      由于回溯一加一减,所有的桶还是0,后面的桶就直接跳过了。
    if (0 < i && bucket[i - 1] == bucket[i]) {
      continue;
    }
    // 剪枝:如果该桶总和大于平均值,则跳过
    if (target < nums[start] + bucket[i]) {
      continue;
    }
    bucket[i] += nums[start];
    if (backtrack(nums, start + 1, bucket, target)) {
      return true;
    }
    bucket[i] -= nums[start];
  }
  return false;
}

桶视角

回头尝试一下桶视角的解法。

0698 02