[每日一题] 698. 划分为k个相等的子集

126 阅读1分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

698. 划分为k个相等的子集

给定一个整数数组  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] 范围内

思路

  1. 假设有解,可分为k组,每一组是多少?

若是要达到有解的目的,那么 肯定满足这样的式子:

设每一组为x1ninum[i]=x×k设 每一组为 x \\ \sum^i_{1\thicksim n} num[i] = x \times k

那么,我们的第一个条件肯定是要满足不符合整除的直接丢弃

  1. 如何检查是否可以把这些元素划分到k组内呢?

若是要达到这一目的,我们最粗暴的方式是进行枚举,即:

对于k组,其编号分别为1,2,3,...,k;现在对于元素nums[i],我们设其编号为x对于每一个数的选择,应该有k种选择方案总的时间复杂度为nk的一个枚举 对于k组,其编号分别为 1, 2, 3, ..., k; \\ 现在对于元素 nums[i],我们设其编号为 x \\ 对于每一个数的选择,应该有 k 种选择方案 \\ 总的时间复杂度为 n^k 的一个枚举

看上去这个时间复杂度非常高,但是我们可以剪一剪枝

  1. 剪枝方法
  1. 可行性剪枝,我们把dfs分为k个阶段,上一阶段不满足就直接返回,不继续下去剪枝
  2. 顺序性优化,在可行性剪枝的条件下,我们可以发现,先选较大的,可以减少拼接次数发现合理与否 所以,我们可以对于原数组从大到小去选取

end

代码

class Solution {
public:
    bool canPartitionKSubsets(vector<int>& nums, int k) {
        int res = accumulate(begin(nums), end(nums), 0);
        if (res % k) return false;
        int flag = 0; res /= k;
        sort(nums.rbegin(), nums.rend());
        vector<int> vis(nums.size());
        function<void(int, int, int)> dfs = 
            [&](int cur, int cnt, int pre) -> void {
            if (flag) return;
            if (cnt == k) {
                flag = 1;
                return;
            }
            if (cur == res) return dfs(0, cnt + 1, 0);
            for (int i = pre; i < nums.size(); i ++) {
                if (vis[i] || nums[i] + cur > res) continue;
                vis[i] = 1;
                dfs(nums[i] + cur, cnt, i + 1);
                if (flag) return;
                vis[i] = 0;
                if (cur == 0) return;
            }
        };
        dfs(0, 0, 0);
        return flag;
    }
};