344,最大整除子集

想了解更多数据结构以及算法题,可以关注微信公众号“数据结构和算法”,每天一题为你精彩解答。也可以扫描下面的二维码关注
在这里插入图片描述

给出一个由无重复的正整数组成的集合,找出其中最大的整除子集,子集中任意一对 (Si,Sj) 都要满足:Si % Sj = 0 或 Sj %Si = 0。

如果有多个目标子集,返回其中任何一个均可。


示例 1:

输入: [1,2,3]
输出: [1,2] (当然, [1,3] 也正确)

示例 2:

输入: [1,2,4,8]
输出: [1,2,4,8]


答案:

public List<Integer> largestDivisibleSubset1(int[] nums) {
    int n = nums.length;
    int[] dp = new int[n];
    int[] pre = new int[n];
    Arrays.sort(nums);
    Arrays.fill(dp,1);
    Arrays.fill(pre,-1);
    int max = 0, index = -1;
    for (int i = 0; i < n; i++) {
        for (int j = i - 1; j >= 0; j--) {
            if (nums[i] % nums[j] == 0) {
                if (1 + dp[j] > dp[i]) {
                    dp[i] = dp[j] + 1;
                    pre[i] = j;
                }
            }
        }
        if (dp[i] > max) {
            max = dp[i];
            index = i;
        }
    }
    List<Integer> res = new ArrayList<>();
    while (index != -1) {
        res.add(nums[index]);
        index = pre[index];
    }
    return res;
}

解析:

这题实际上是求最长等比数列,我们可以通过动态规划来求解。dp[i]表示是数组中前i个元素组成的最大整除子集的个数,首先第5行对数组nums进行排序,如果nums[i]%nums[j]==0,则表示nums[i]能被nums[j]整除,所以dp[i]=max{dp[i],dp[j]+1},但这里求的不是最大整除子集的长度,而是把最大整除子集的元素全部列出来,所以这里要使用一个临时数组pre来存储最大整除子集元素的下标。代码中第8-17行主要是计算dp和pre的值,第18-21行主要是为了记录最大整除子集的最后一个元素的下标,然后在第24-27行通过最大整除子集的下标把元素找出来,存放到res集合中。当然我们还可以在换一种写法

public List<Integer> largestDivisibleSubset2(int[] nums) {
    List<Integer> res = new ArrayList<>();
    Arrays.sort(nums);
    List<Integer>[] lists = new List[nums.length];
    for (int i = 0; i < nums.length; i++) {
        lists[i] = new ArrayList<>();
    }
    for (int i = nums.length - 1; i >= 0; i--) {
        List<Integer> largest = new ArrayList<>();
        for (int j = i + 1; j < nums.length; j++) {
            if (nums[j] % nums[i] == 0) {
                if (largest.size() < lists[j].size())
                    largest = lists[j];
            }
        }
        lists[i].add(nums[i]);
        lists[i].addAll(largest);
        if (res.size() < lists[i].size())
            res = lists[i];
    }
    return res;
}

这种写法没有像第一种那样有一个临时数组存储最大整除子集的下标,他是每次计算找到最大的都会赋值给res。下面再来看最后一种解法,使用递归的方式解决

public List<Integer> largestDivisibleSubset(int[] nums) {
    Arrays.sort(nums);
    return helper(nums, 0, new HashMap<>());
}

private List<Integer> helper(int[] nums, int index, HashMap<Integer, List<Integer>> map) {
    if (map.containsKey(index))
        return map.get(index);
    List<Integer> maxLenLst = new ArrayList<>();
    int div = index == 0 ? 1 : nums[index - 1];//div除数
    for (int k = index; k < nums.length; k++) {
        if (nums[k] % div == 0) {
            List<Integer> lst = new ArrayList<>(helper(nums, k + 1, map));
            lst.add(nums[k]);
            if (lst.size() > maxLenLst.size())
                maxLenLst = lst;
        }
    }
    map.put(index, maxLenLst);
    return maxLenLst;
}

map只是为了减少重复计算而引入的,helper函数表示从数组下标的index到数组的最后一个元素所能构成的最大整除子集。


在这里插入图片描述

posted @ 2020-09-26 20:48  数据结构和算法  阅读(131)  评论(0编辑  收藏  举报