题目
分析
看上去是很复杂的题目,需要去搜索。搜索的复杂度就是指数级的了,如果数很多就不现实了。换种思路,根据整除的传递性,比如4可以整除以2,8可以整除以4,所以8自然可以整除以2,我们就可以简化这个问题为一个dp问题。但是需要先对数组排序。
dp[i] 表示数组中第i个元素和之前的数能组成的最多的整除集合的数量,初始值显然是1,就是本身一个元素作为集合。
然后对每个数,遍历它之前的所有数,由于数字已经排好序了,所以后面的数只需要检查前面的每一个数,如果能够整除某个数nums[j],那就比较一下dp[j] + 1和dp[i]的大小,保留较大的。由于题目是要找最大的集合,所以为了省去最后的遍历,再用个变量存一下最大的集合数量值。
题目还要求我们把这个子集给找出来,所以我们需要额外开辟一个数组pre,pre[i]保存的是nums[i]组成的最大整除数组中的上一个被整除的元素的位置,初始化为-1。比如说1 2 4 8,那么pre的值就分别是-1,0,1,2,这样最后我们只需要根据pre,递归回溯找到这个最大子集从而解决问题。下面来看代码
代码
class Solution {
public:
vector<int> largestDivisibleSubset(vector<int>& nums) {
int n = nums.size();
if (n <= 1) return nums;
sort(nums.begin(), nums.end());
vector<int>dp(n, 1), pre(n, -1);
// 最大子集长度maxlen和这个子集的最大元素的下标maxpos
int maxlen = 0, maxpos = -1;
for (int i = 1; i < nums.size(); ++i) {
for (int j = 0; j < i; ++j) {
if (nums[i] % nums[j] != 0) continue;
else {
// 更新每个位置的dp和pre值
if (dp[j] + 1 > dp[i]) {
dp[i] = dp[j] + 1;
pre[i] = j;
}
// 找到更大的子集了 更新maxlen maxpos
if (dp[i] > maxlen) {
maxlen = dp[i];
maxpos = i;
}
}
}
}
vector<int>ans;
// 边界情况,如果所有元素互质,那只需要随便返回数组中一个元素即可,不妨返回第一个
if (maxpos == -1) {
ans.push_back(nums.front());
return ans;
}
// 一般情况,递归找到所有的集合元素,最后一个pre肯定是-1
while (maxpos != -1) {
ans.push_back(nums[maxpos]);
maxpos = pre[maxpos];
}
return ans;
}
};