原题链接: 368. Largest Divisible Subset
Given a set of distinct positive integers nums
, return the largest subset answer
such that every pair (answer[i], answer[j])
of elements in this subset satisfies:
answer[i] % answer[j] == 0
, oranswer[j] % answer[i] == 0
If there are multiple solutions, return any of them.
Example 1:
Input: nums = [1,2,3]
Output: [1,2]
Explanation: [1,3] is also accepted.
Example 2:
Input: nums = [1,2,4,8]
Output: [1,2,4,8]
Constraints:
1 <= nums.length <= 1000
1 <= nums[i] <= 2 * 109
- All the integers in
nums
are unique.
算法分析
首先想到要对数组进行升序排序
从小向大遍历数组。假设数字 b % a == 0
,那么可以考虑将数字b
加入到包含a
的链表里,b
的前缀就是a
;最终,我们挑选出长度最大的那个链表,将其中的全部数字放入到一个数组中,即可得到答案。
但我们并不是找到任意一个满足 b % a == 0
的 a
后,就立即将 b
加入到对应的链表里,而是应该选择 b
的全部约数中的最长的链表,将 b
加入该链表尾部。
直接使用链表,不方便在O(1)的时间内直接得到链表的长度,所以我们可以创建一个数组 linkLen,linkLen[i] 即表示以 nums[i] 作为尾节点的链表的长度。另外,我们创建一个数组 parent,parent[i] 即表示 nums[i] 所在链表的前缀节点在 nums 数组的下标。如果 parent[i] == i,那么表示 nums[i] 是这个数组的头结点 ---- 没有前缀节点了。
这里需要使用数学归纳法证明一下,我们将 b
加入到其所有约数中长度最大的链表中的正确性:
记 将数字nums[i]加入到其全部约数中长度最大的链表尾部
为规则x
初始令全部 linkLen[i] = 0
- (1) 对于 nums 中的第一个nums[0],其没有前缀数字,所以作为链表头结点,有 parent[0] = 0,linkLen[0] = 1; 此时,最长链表即为 linkLen 中数字最大的元素对应的下标i,即 i = 0;
规则x
正确; - (2) 假设当 i = k (k > 0),
规则x
得到的 parent、linkLen 数组满足我们的要求 ---- 即 linkLen 数组中最大元素的下标,就是最长链表的尾节点元素在 nums 中的下标,假设该下标值为 index_k(至少有 linkLen[index_k] >= 1)。
那么当 i = k + 1 时,遍历 j = 0 ~ k:- (2.1) 如果全部 nums[j] 均不为 nums[i] 的约数,那么应用
规则x
,nums[i] 将作为链表头结点,有 parent[i] = i, linkLen[i] = 1; 此时linkLen数组最大值依然为 linkLen[index_k];规则x
正确。 - (2.2) 如果某个 nums[j] 为 nums[i] 的约数,我们令 parent[i] = j, linkLen[i] = linkLen[j] + 1,那么:
- (2.2.1) 如果 j == index_k,那么 linkLen[i] = linkLen[index_k] + 1,此时 linkLen[i] 为 linkLen 数组中的最大元素;
规则x
正确。 - (2.2.2) 如果 j != index_k,那么 nums[index_k] 就不是 nums[i] 的约数,nums[i] 对以 nums[index_k] 为尾结点的链表无影响。linkLen[i] 或 linkLen[index_k] 为数组 linkLen 中的最大元素;
规则x
正确。
- (2.2.1) 如果 j == index_k,那么 linkLen[i] = linkLen[index_k] + 1,此时 linkLen[i] 为 linkLen 数组中的最大元素;
- (2.1) 如果全部 nums[j] 均不为 nums[i] 的约数,那么应用
综上所述,应用规则x
,我们得到的 linkLen 数组,其最大元素对应的下标为i,那么以 nums[i] 为尾结点的链表即为最长链表。
算法实现 Golang
func largestDivisibleSubset(nums []int) []int {
sort.Sort(sort.IntSlice(nums))
N := len(nums) // 元素个数
parent := make([]int, N) // 使用类似并查集的算法,但是不对该并查集进行压缩
linkLen := make([]int, N)
maxLen := 0 // 最大链表长度
maxIndex := -1 // 最大链表尾结点元素在 nums 数组中的下标
for i, val := range nums {
parent[i] = i // 默认当前元素没有约数,则其前缀节点即为自身
linkLen[i] = 1 // 当前元素默认作为头结点,链表长度为 1
for j := 0; j < i && (val/nums[j] >= 2); j++ { // 因为 nums 数组是升序的,所以对于 val/nums[j] < 2 的 j 就无需再遍历了
if val%nums[j] == 0 {
if linkLen[j]+1 > linkLen[i] {
parent[i] = j
linkLen[i] = linkLen[j] + 1
}
}
}
// 内层 for 循环结束,nums[i] 即挂在到了正确的链表尾部
if linkLen[i] > maxLen {
// 记录全部链表中的最长链表长度和其尾元素的下标
maxLen = linkLen[i]
maxIndex = i
}
}
cursor := maxIndex
ans := make([]int, maxLen)
pos := linkLen[cursor] - 1
for parent[cursor] != cursor { // 如果 parent[cursor] == cursor,那么 nums[cursor] 即为链表头结点
ans[pos] = nums[cursor]
pos--
cursor = parent[cursor]
}
ans[pos] = nums[cursor] // 此时实际上 cursor = 0
return ans
}
运行结果