LeetCode 368----Largest Divisible Subset

原题链接: 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, or
  • answer[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 == 0a 后,就立即将 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正确。

综上所述,应用规则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]
	return ans
}

运行结果

posted @ 2021-11-15 20:34  HorseShoe2016  阅读(42)  评论(0编辑  收藏  举报