leetcode
leetcode 301 ~ 350
最大整除子集

最大整除子集

难度:

标签:

题目描述

给你一个由 无重复 正整数组成的集合 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]

 

提示:

  • 1 <= nums.length <= 1000
  • 1 <= nums[i] <= 2 * 109
  • nums 中的所有整数 互不相同

代码结果

运行时间: 171 ms, 内存: 0.0 MB


// 使用Java Stream的题解代码和注释
// 题目思路:
// 1. 对数组nums进行排序
// 2. 使用动态规划的方式记录以每个元素为结尾的最大整除子集的长度和前驱元素
// 3. 最终从最大长度的子集开始回溯找到完整的最大整除子集
 
import java.util.*;
import java.util.stream.Collectors;
 
public class Solution {
    public List<Integer> largestDivisibleSubset(int[] nums) {
        Arrays.sort(nums); // 对nums进行排序
        int[] dp = new int[nums.length]; // dp[i]表示以nums[i]为结尾的最大整除子集的长度
        int[] prev = new int[nums.length]; // prev[i]表示在nums[i]前一个元素的索引
        int maxSize = 0; // 最大子集的大小
        int maxIndex = -1; // 最大子集的最后一个元素的索引
        
        // 初始化
        Arrays.fill(dp, 1); // 每个元素自身可以构成一个子集
        Arrays.fill(prev, -1); // 初始化前驱数组
        
        // 动态规划计算每个元素的最大子集大小
        for (int i = 1; i < nums.length; i++) {
            for (int j = 0; j < i; j++) {
                if (nums[i] % nums[j] == 0 && dp[i] < dp[j] + 1) {
                    dp[i] = dp[j] + 1;
                    prev[i] = j;
                }
            }
            // 更新最大子集的大小和最后一个元素的索引
            if (dp[i] > maxSize) {
                maxSize = dp[i];
                maxIndex = i;
            }
        }
        
        // 回溯最大整除子集
        List<Integer> result = new ArrayList<>();
        for (int i = maxIndex; i >= 0; i = prev[i]) {
            result.add(nums[i]);
            if (prev[i] == -1) break;
        }
        
        return result.stream().collect(Collectors.toList());
    }
}

解释

方法:

该题解采用了拓扑排序的思想。首先将数组按照升序排序,然后遍历排序后的数组,对于每个元素,找到满足整除条件的前驱元素,并将当前元素添加到相应的拓扑层级中。同时,使用一个字典 route 记录每个元素的前驱元素。最后,从拓扑层级的最后一层开始,通过 route 字典回溯得到最大整除子集。

时间复杂度:

O(n^2)

空间复杂度:

O(n^2)

代码细节讲解

🦆
为什么在解决最大整除子集问题时,选择先对数组进行排序?排序的目的是什么?
对数组进行排序的目的是为了方便找到元素的前驱元素,保证子集中的每个元素都可以被后面的元素整除。因为如果一个较小的数不能整除当前的数,那么比它大的数也不可能整除当前的数。排序使得算法可以顺序检查每个元素,并尝试将其添加到已存在的符合条件的子集中,这样可以有效地构建出满足条件的最大子集。
🦆
在题解中提到的拓扑排序思想,具体是如何应用在这个问题上的?拓扑排序通常用于解决什么类型的问题?
题解中所说的拓扑排序思想主要体现在通过层级结构组织数据,每个元素根据其前驱元素的条件(整除关系)被分配到不同的层级。这里的“层级”概念类似于拓扑排序中处理依赖关系的层次。拓扑排序通常用于解决具有依赖关系的任务安排问题,如项目中的任务执行顺序,编译依赖等,确保每个任务只在其依赖的任务完成后才开始。
🦆
题解中使用了两个字典`topo`和`route`,能否详细解释这两个字典各自的作用及其在算法中的重要性?
`topo`字典用于存储不同层级的元素,其中每个层级包含能被该层级之前所有元素整除的所有元素。`route`字典记录每个元素的直接前驱元素,这是为了在构建完所有层级后能够回溯找到形成最大整除子集的路径。这两个字典对算法至关重要,`topo`确保了元素可以按照依赖关系(整除条件)正确分层,而`route`使得最终能够从结果集中恢复出最大的整除子集。
🦆
在构建拓扑层级时,如果当前元素n没有找到任何满足整除条件的前驱元素,为何选择将其添加到拓扑层级的第0层?这样做有什么特别的意义或优势?
将没有找到满足整除条件的前驱元素的当前元素n添加到第0层,意味着它不能被之前的任何元素整除,因此它本身可以作为一个新的子集的开始。这样做的优势在于,它保证了算法可以捕捉到所有可能的开始点,从而不遗漏任何潜在的最大整除子集。这也有助于在没有任何其他元素可以整除某个元素时,仍然能够将其考虑进最终的解集中。

相关问题