目录

LC 698. 划分为 k 个相等的子集 (opens new window) (opens new window)

中等

# 问题描述

给定一个整数数组 nums 和一个正整数 k,找出是否有可能把这个数组分成 k 个非空子集,其总和都相等。

示例 1:

输入: nums = [4, 3, 2, 3, 5, 2, 1], k = 4
输出: True
说明: 有可能将其分成 4 个子集(5),(1,4),(2,3),(2,3)等于总和。

示例 2:

输入: nums = [1,2,3,4], k = 3
输出: false

提示:

  • 1 <= k <= len(nums) <= 16
  • 0 < nums[i] < 10000
  • 每个元素的频率在 [1,4] 范围内

# 回溯 + 剪枝

首先对数组进行一下预处理,计算是否有可能分成 kk 个和相等的非空子集,若不能则返回 falsefalse。其次是计算出平均值 avgavg 后,查看数组中是否存在比平均值大的值,若存在,则返回 falsefalse。然后创建出 kk 个容量大小为 avgavg 的桶,遍历数组,尝试将数字放入满足要求的桶中,若失败,则进行回溯,直到最后若所有数字都能够放入桶中则返回 truetrue, 否则返回 falsefalse。过程需要剪枝以减少时间消耗。

/**
 * @param {number[]} nums
 * @param {number} k
 * @return {boolean}
 */
var canPartitionKSubsets = function (nums, k) {
  const sum = nums.reduce((s, v) => s + v, 0)
  if (sum % k !== 0) return false
  const avg = sum / k
  // 剪枝一 排序
  nums.sort((a, b) => b - a)
  if (nums[0] > avg) return false
  const n = nums.length
  const buckets = new Array(n).fill(avg)
  const dfs = (idx) => {
    if (idx >= n) return true
    for (let i = 0; i < k; i++) {
      if (nums[idx] > buckets[i]) continue
      // 剪枝二 与上一个桶情况一样可以直接跳过
      if (i > 0 && buckets[i] === buckets[i - 1]) continue
      buckets[i] -= nums[idx]
      if (dfs(idx + 1)) return true
      buckets[i] += nums[idx]
    }
    return false
  }
  return dfs(0)
}
  • 时间复杂度:O(n×2n)O(n \times 2^n),其中 nn 为数组 numsnums 的长度,共有 2n2^n 个状态,每一个状态进行了 nn 次尝试。
  • 空间复杂度:O(n)O(n)
上次更新: 2023/01/31 19:48:05

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 , 转载请注明出处!