368. Largest Divisible Subset (Medium)
Given a set of distinct positive integers, find the largest subset such that every pair $$(S_i, S_j)$$ of elements in this subset satisfies: $$Si % Sj = 0$$ or $$Sj % Si = 0$$.
If there are multiple solutions, return any subset is fine.
Example 1:
nums: [1,2,3]
Result: [1,2] (of course, [1,3] will also be ok)
Example 2:
nums: [1,2,4,8]
Result: [1,2,4,8]
Solution: DP
Time Complexity: $$O(n^2)$$
- Explanation 1:
The key concept here is:
Given a set of integers that satisfies the property that each pair of integers inside the set are mutually divisible, for a new integer S, S can be placed into the set as long as it can divide the smallest number of the set or is divisible by the largest number of the set.
For example, let's say we have a set P = { 4, 8, 16 }
, P satisfies the divisible condition. Now consider a new number 2, it can divide the smallest number 4, so it can be placed into the set; similarly, 32 can be divided by 16, the biggest number in P, it can also placed into P.
Next, let's define:
EDIT: For clarification, the following definitions try to enlarge candidate solutions by appending a larger element at the end of each potential set, while my implementation below is prefixing a smaller element at the front of a set. Conceptually they are equivalent but by adding smaller elements at the front saves the trouble for keeping the correct increasing order for the final answer. Please refer to comments in code for more details.
For an increasingly sorted array of integers a[1 .. n]
T[n] = the length of the largest divisible subset whose largest number is a[n]
T[n+1] = max{ 1 + T[i] if a[n+1] mod a[i] == 0 else 1 }
Now, deducting T[n] becomes straight forward with a DP trick. For the final result we will need to maintain a backtrace array for the answer.
- Explanation 2:
这道题给了我们一个数组,让我们求这样一个子集合,集合中的任意两个数相互取余均为0,而且提示中说明了要使用DP来解。那么我们考虑,较小数对较大数取余一定为0,那么问题就变成了看较大数能不能整除这个较小数。那么如果数组是无序的,处理起来就比较麻烦,所以我们首先可以先给数组排序,这样我们每次就只要看后面的数字能否整除前面的数字。定义一个动态数组dp,其中dp[i]表示到数字nums[i]位置最大可整除的子集合的长度,还需要一个一维数组parent,来保存上一个能整除的数字的位置,两个整型变量mx和mx_idx分别表示最大子集合的长度和起始数字的位置,我们可以从后往前遍历数组,对于某个数字再遍历到末尾,在这个过程中,如果nums[j]能整除nums[i], 且dp[i] < dp[j] + 1的话,更新dp[i]和parent[i],如果dp[i]大于mx了,还要更新mx
和mx_idx
,最后循环结束后,我们来填res数字,根据parent数组来找到每一个数字,参见代码如下:
version 1: loop from end to front, answer is from small to big (53ms)
class Solution {
public:
vector<int> largestDivisibleSubset(vector<int>& nums) {
sort(nums.begin(), nums.end());
int mx = 0, mx_idx = 0, n = nums.size();
vector<int> dp(n, 0), parent(n, 0), res;
for (int i = n-1; i >= 0; --i) {
for (int j = i; j < n; ++j) { // j is after i
if (nums[j]%nums[i] == 0 && dp[i] < dp[j]+1) {
dp[i] = dp[j]+1;
parent[i] = j;
if (mx < dp[i]) {
mx = dp[i];
mx_idx = i;
}
}
}
}
for (int i = 0; i < mx; ++i) {
res.push_back(nums[mx_idx]);
mx_idx = parent[mx_idx];
}
return res;
}
};
version 2: loop from front to end, answer is from big to small (49ms)
class Solution {
public:
vector<int> largestDivisibleSubset(vector<int>& nums) {
sort(nums.begin(), nums.end());
int mx = 0, mx_idx = 0, n = nums.size();
vector<int> dp(n, 0), parent(n, 0), res;
for (int i = 0; i < n; ++i) {
for (int j = i; j >= 0; --j) { // j is before i
if (nums[i]%nums[j] == 0 && dp[i] < dp[j]+1) {
dp[i] = dp[j]+1;
parent[i] = j;
if (mx < dp[i]) {
mx = dp[i];
mx_idx = i;
}
}
}
}
for (int i = 0; i < mx; ++i) {
res.push_back(nums[mx_idx]);
mx_idx = parent[mx_idx];
}
return res;
}
};