LeetCode 698

LeetCode 2022.9.20 的打卡题目

698 划分为k个相等的子集 [https://leetcode.cn/problems/partition-to-k-equal-sum-subsets]

给定一个整数数组  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 搜索剪枝

回溯法,枚举每个数字是否放进某个组里。
但是直接搜索会超时。
剪枝思路:

  1. 从大到小对数字进行排序,这样有比较大的概率在 (targets[i] >= nums[current])这一步进行跳过;
  2. 对于已经处理过的同样大小的组,如果当前数没有成功放进去,那它就不需要再次进行计算了,这一步剪枝可以使搜索算法通过此题。这里用unordered_set存储了一下已经废掉的解;网上题解多是使用targets[i] == targets[i - 1]来判断,好像会更快一些。
class Solution {
public:
    bool dfs(vector<int>& nums, vector<int>& targets, int current) {
        if (current == nums.size()) {
            for (int i = 0; i < targets.size(); ++i) {
                if (targets[i]) {
                    return false;
                }
            }
            return true;
        }

        unordered_set<int> unsolved;

        for (int i = 0; i < targets.size(); ++i) {
            if ((solved.find(targets[i]) == unsolved.end()) && targets[i] >= nums[current]) {
                targets[i] -= nums[current];
                if (dfs(nums, targets, current + 1)) {
                    return true;
                }
                targets[i] += nums[current];
                unsolved.insert(targets[i]);
            }
        }
        return false;
    }

    bool canPartitionKSubsets(vector<int>& nums, int k) {
        int sum = accumulate(begin(nums), end(nums), 0);
        if (sum % k != 0) {
            return false;
        }

        sort(begin(nums), end(nums), greater<int>());

        vector<int> targets(k, sum / k);
        return dfs(nums, targets, 0);
    }
};

解法2:状态压缩DP

由于数组最大长度是16,用一个2^16的整数正好可以表达所有的可能性。
从低位向高位枚举,dp[state]表示当前状态下所被计算过的值之和(模去target表示一组已经满足)。
如果dp[state] + nums[j] > target说明这种情况下是不能往里填数的,这组解不能使用。
最终只要能够一直沿着合法路线达到 (1 << nums.size()) - 1 这个状态,说明问题是可解的。

class Solution {
public:
	bool canPartitionKSubsets(vector<int>& nums, int k) {
		int sum = accumulate(begin(nums), end(nums), 0);
		if (sum % k != 0) {
			return false;
		}

		sum /= k;

		vector<int> dp(1 << 16, -1);
		dp[0] = 0;

		sort(begin(nums), end(nums), greater<int>());

		for (int i = 0; i < (1 << nums.size()); ++i) {
			if (dp[i] == -1) {
				continue;
			}
			for (int j = 0; j < nums.size(); ++j) {
				if (dp[i] + nums[j] > sum) {
					continue;
				}
				if (!(i & (1 << j))) {
					int next = i | (1 << j);
					dp[next] = (dp[i] + nums[j]) % sum;
				}
			}
		}

		return dp[(1 << nums.size()) - 1] == 0;
	}
};

解法3:启发式搜索

从状压DP看来,实际上没有做DP,只是递推,通过状态压缩的方式在状态图上找了一条路径而已。
结合回溯的计算目标是所有的组合,由于每个组合都可以用位图来表示,用BFS的方式对位图进行扩张直到它能到达最终点。
这里直接BFS是可行的,但是会比较慢(不知道为什么比状压递推的方式还慢),用一个启发来做了优化,即优先考虑与最终目标位数差别较小的状态来搜索。

class Solution {
public:
	typedef unsigned int Bits;

	bool canPartitionKSubsets(vector<int>& nums, int k) {
		int sum = accumulate(begin(nums), end(nums), 0);
		if (sum % k != 0) {
			return false;
		}
        sum /= k;

		sort(begin(nums), end(nums), greater<int>());

		const auto count_1 = [](int val) {
			int count = 0;
			while (val) {
				count += val & 1;
				val >>= 1;
			}
			return count;
		};

		const auto cmp = [&count_1](const auto& l, const auto& r) {
			return count_1(l.first) < count_1(r.first);
		};

		priority_queue<pair<Bits, int>, vector<pair<Bits, int> >, decltype(cmp)> pq(cmp);
		unordered_set<Bits> visited;

		pq.emplace(Bits(0), 0);
		visited.insert(Bits(0));

		while (!pq.empty()) {
			auto pr = pq.top();
			pq.pop();
			if (pr.first == (1 << nums.size()) - 1) {
				return pr.second == 0;
			}
			for (int i = 0; i < nums.size(); ++i) {
				Bits next = pr.first | (1 << i);
				if (pr.second + nums[i] <= sum && visited.find(next) == visited.end()) {
					pq.emplace(next, (pr.second + nums[i]) % sum);
					visited.insert(next);
				}
			}
		}

		return false;
	}
};
posted @   玉扉一乐  阅读(94)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示