也算是一道比较经典的DP题了,需要找到一个最大的子集,使得其中元素两两的余数为0。
我们假设有一个集合为{a1, a2, a3, …, an}, {a1 < a2 < … < an)
如果 a2 % a1 = 0, a3 % a2 == 0, 那么a3 % a1 一定也等于0。也就是说a2是a1的倍数,a3是a2的倍数,那么a3一定也是a1的倍数。所以我们并不需要测试任意两个元素之间的余数为0,我们只需要检测a[i]是否为a[i-1]的倍数即可,如果成立,则可以确保a[i]也是a[i-2], a[i-3], … a[1]的倍数。这样就可以大大降低问题的复杂度。
接下来就需要dp就发挥威力了。我们将原数组排序,用dp[i]来表示以a[i]结尾(集合中的最大值),能够构建的子集的最大长度。
转移方程:dp[j] = max(dp[j], dp[i] + 1) if nums[j] % nums[i] = 0.
这表示,如果nums[j] 是 nums[i]的倍数,那么我们可以把nums[j]加入以nums[i]结尾的最大子集中,构建一个新的最大子集,长度+1。当然对于j,可能有好多个满足条件的i,我们需要找一个最大的。
举个例子:
nums = {2, 3, 4, 12}
以3结尾的最大子集{3},长度为1。由于12%3==0,那么我们可以把12追加到{3} 中,构成{3,12}。
以4结尾的最大子集是{2,4},长度为2。由于12%4==0,那么我们可以把12追加到{2,4} 中,构成{2,4,12} 。得到最优解。
这样我们只需要双重循环,枚举i,再枚举j (0 <= i < j)。时间复杂度:O(n^2)。空间复杂度:O(n)。
最后需要输出一个最大子集,如果不记录递推过程,则需要稍微判断一下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
class Solution { public: vector<int> largestDivisibleSubset(vector<int>& nums) { const int n = nums.size(); sort(begin(nums), end(nums)); // dp[i] = max size of subset ends with nums[i] vector<int> dp(n); for (int i = 0; i < n; ++i) for (int j = i + 1; j < n; ++j) if (nums[j] % nums[i] == 0) dp[j] = max(dp[j], dp[i] + 1); int s = *max_element(begin(dp), end(dp)); vector<int> ans; for (int i = n - 1; i >= 0; --i) { if (dp[i] == s && (ans.empty() || ans.back() % nums[i] == 0)) { ans.push_back(nums[i]); --s; } } return ans; } }; |