「这是我参与2022首次更文挑战的第12天,活动详情查看:2022首次更文挑战」。
早上起床看窗外,发现有一些积雪,在当前疫情反复的背景下,也算是一种惊喜。跑到外面去拍了一些照片,回家继续刷leetcode。
题目
给你一个由 无重复 正整数组成的集合 nums ,请你找出并返回其中最大的整除子集 answer ,子集中每一元素对 (answer[i], answer[j]) 都应当满足:
answer[i] % answer[j] == 0 ,或answer[j] % answer[i] == 0
如果存在多个有效解子集,返回其中任何一个均可。
示例 1:
输入:nums = [1,2,3]
输出:[1,2]
解释:[1,3] 也会被视为正确答案。
示例 2:
输入:nums = [1,2,4,8]
输出:[1,2,4,8]
思路
动态规划的题目,关键在与定义状态和状态转移方程,这2个步骤是要一起考虑的。
本题需要绕一个弯,我们可以分成2个子问题:
- 求出最大集合的元素数量
- 求出最大集合的每个元素(有多个符合条件的集合的话,只要求解出其中一个)
求出最大集合的元素数量
我们先对原数组做从小到大排序,定义一个一维数组dp[],dp[n]代表以下标为n的数字作为集合最大元素时,集合的最大size。根据整除子集的定义,我们在求dp[n+1]的时候,就可以遍历0 ~ n,如果num[n+1]可以整除num[j],那么dp[n+1]的1种解是dp[j]+1,所以dp[n+1] = max(dp[j]+1),条件是num[n+1]%num[j]==0。那如果0~n任何一个数都不能被整除,那么这个子集只能包含num[n+1]自己,即dp[n+1] = 1。最后,我们遍历dp[0] ~ dp[len-1],求可以得到最大集合的元素数量max。
求出最大集合的每个元素
这一步是本题比较容易错误的一个点,上述求解最大集合的元素数量的过程,我们可以同时记录下最大集合的结束元素值maxValue和它的下标maxIndex。然后从后往前遍历去找它前一个元素,找到后把找到的元素作为maxValue,下标作为maxIndex,继续递归,直到找满max个元素即可。这里注意,找到一个可以被maxValue整数的值之后,不一定就是要找的循环,必须再判断1层dp[i] == maxSize - 1,否则可以出现错误,我们举个例子:
原数组为:[3,6,8,24]
dp值为:[1,2,1,3]
此时,maxValue = 24,maxIndex = 3,所以从下标为2开始从后往前找,找到了8是可以被24整除的,但是显然8不是我们结果集合应该有的元素。如果加上判断1层dp[i] == maxSize - 1,就可以避免这个错误。
Java版本代码
class Solution {
public List<Integer> largestDivisibleSubset(int[] nums) {
List<Integer> ans = new ArrayList<>();
int len = nums.length;
int[] dp = new int[len];
Arrays.sort(nums);
dp[0] = 1;
int max = dp[0];
int maxIndex = 0;
int maxVal = nums[0];
for (int i = 1; i < len; i++) {
int temp = 1;
for (int j = 0; j < i; j++) {
if (nums[i]%nums[j] == 0 && dp[j] + 1 > temp) {
temp = dp[j] + 1;
}
}
dp[i] = temp;
if (dp[i] > max) {
max = dp[i];
maxIndex = i;
maxVal = nums[i];
}
}
// 回查原数组,找到最大的集合
ans.add(nums[maxIndex]);
int maxSize = max;
while (ans.size() < max) {
for (int i = maxIndex-1; i >= 0; i--) {
if (maxVal%nums[i] == 0 && dp[i] == maxSize - 1) {
ans.add(nums[i]);
maxVal = nums[i];
maxIndex = i;
maxSize--;
break;
}
}
}
return ans;
}
}