[Mdfs] lc698. 划分为k个相等的子集(剪枝优化+经典题+好题)

33 篇文章 3 订阅

1. 题目来源

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

拔高、证明:

2. 题目解析

本题十分经典、重要。4 大剪枝方法缺一不可。具体的剪枝方法正确性的证明可以去看看 [dfs] aw167. 木棒(dfs剪枝与优化+分类讨论+思维+好题)

剪枝说明:

  • 剪枝1:搜索顺序剪枝。常用的是从大到小搜索。降低搜索深度。
  • 剪枝2:重复元素剪枝。如果当前元素搜索失败,那么下一个元素和我相等的话,也必然失败。所以可以跳过和我相等的下一个元素。注意:这里是跳过,而非整个分支失败。
  • 剪枝3:第一个元素剪枝。如果当前元素是第一个元素,且搜索失败了。那么整个 搜索分支也将失败。因为这里是从大到小按顺序枚举的,第一个元素在任何组内都将是第一个元素。如果 ai 能成功的话,那么说明在其他组这个元素也排在第一位,那么只需要交换这两个组,第一个元素就成功了,此时就与假设产生矛盾。故,第一个元素失败的话,必然当前的整个方案都是无解的。无需回溯,直接剪枝即可。
  • 剪枝4:最后一个元素剪枝。如果当前元素是最后一个元素,且搜索失败了。那么整个搜索分支也将失败。因为如果当前组的最后一个元素失败了,且最终还能搜索成功的话,最后一个元素所占用的长度必然被其他元素代替,那么只需要将这些木棒和其他组的最后一个元素所在位置替换位置,就能将最后一个元素替换过去,还能成功。此时就与假设产生矛盾。

注意:这里设计到一些关键点的理解

  • if (dfs(i + 1, cur + nums[i], cnt)) return true; 时,这里是判断我当前选择 nums[i] 是否能找到答案。如果可以的话,那么就已经找到了答案,一路返回 true 即可。
  • 否则,那就说明当前的这个 nums[i] 不是合法方案。
  • 此时,当 cur == 0 时,就说明我初始的时候是 0,而 0+nums[i] 失败了,也就是第一个数失败了。此时剪枝。
  • 同理,当 cur+nums[i]==x 时,走到这里就说明失败了,那么也就是最后一个数失败了。此时剪枝。

  • 时间复杂度:此类 dfs 和剪枝,不必进行分析
  • 空间复杂度:此类 dfs 和剪枝,不必进行分析

class Solution {
public:
    bool canPartitionKSubsets(vector<int>& nums, int k) {
        int n = nums.size();

        int sum = 0;
        for (int x : nums) sum += x;
        int x = sum / k;
        if (x * k != sum) return false;
        sort(nums.begin(), nums.end(), greater<int>()); // 剪枝1:从大到小进行搜索

        vector<bool> st(n, false);
        function<bool(int, int, int)> dfs = [&](int u, int cur, int cnt) {
            if (cnt == k) return true;
            if (cur == x) return dfs(0, 0, cnt + 1);

            for (int i = u; i < n; i ++ ) { // 从大到小搜索
                if (st[i]) continue;
                if (cur + nums[i] > x) continue;

                // 尝试搜索当前位置
                st[i] = true;
                if (dfs(i + 1, cur + nums[i], cnt)) return true;

                // 搜索当前位置失败的话
                st[i] = false;
                while (i + 1 < n && nums[i] == nums[i + 1]) i ++ ; // 剪枝2:搜索的相同元素失败
                if (!cur || cur + nums[i] == x) return false; // 剪枝3、4:如果搜索的第一个、最后一个失败
            }

            return false;
        };


        return dfs(0, 0, 0);
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ypuyu

如果帮助到你,可以请作者喝水~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值