leetcode
leetcode 2251 ~ 2300
美丽子集的数目

美丽子集的数目

难度:

标签:

题目描述

You are given an array nums of positive integers and a positive integer k.

A subset of nums is beautiful if it does not contain two integers with an absolute difference equal to k.

Return the number of non-empty beautiful subsets of the array nums.

A subset of nums is an array that can be obtained by deleting some (possibly none) elements from nums. Two subsets are different if and only if the chosen indices to delete are different.

 

Example 1:

Input: nums = [2,4,6], k = 2
Output: 4
Explanation: The beautiful subsets of the array nums are: [2], [4], [6], [2, 6].
It can be proved that there are only 4 beautiful subsets in the array [2,4,6].

Example 2:

Input: nums = [1], k = 1
Output: 1
Explanation: The beautiful subset of the array nums is [1].
It can be proved that there is only 1 beautiful subset in the array [1].

 

Constraints:

  • 1 <= nums.length <= 20
  • 1 <= nums[i], k <= 1000

代码结果

运行时间: 45 ms, 内存: 16.0 MB


/*
 * 思路:
 * 1. 使用Java Stream的组合生成所有子集。
 * 2. 过滤掉不满足美丽子集条件的子集。
 * 3. 计算剩余子集的数量。
 */
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class BeautifulSubsetsStream {
    public static int countBeautifulSubsets(int[] nums, int k) {
        int n = nums.length;
        // 生成所有非空子集并判断是否为美丽子集
        return (int) IntStream.range(1, 1 << n)
                .mapToObj(i -> IntStream.range(0, n)
                        .filter(j -> (i & (1 << j)) != 0)
                        .mapToObj(j -> nums[j])
                        .collect(Collectors.toList()))
                .filter(subset -> isBeautifulSubset(subset, k))
                .count();
    }

    private static boolean isBeautifulSubset(List<Integer> subset, int k) {
        for (int i = 0; i < subset.size(); i++) {
            for (int j = i + 1; j < subset.size(); j++) {
                if (Math.abs(subset.get(i) - subset.get(j)) == k) {
                    return false;
                }
            }
        }
        return true;
    }

    public static void main(String[] args) {
        int[] nums1 = {2, 4, 6};
        int k1 = 2;
        System.out.println(countBeautifulSubsets(nums1, k1)); // 输出:4

        int[] nums2 = {1};
        int k2 = 1;
        System.out.println(countBeautifulSubsets(nums2, k2)); // 输出:1
    }
}

解释

方法:

题解的核心思想是通过模 k 的分组来识别可能的冲突,即当两个数的差为 k 时,它们在同一个模 k 的组内的位置之间相差 1。算法首先通过模 k 对 nums 中的数字进行分组,并使用 Counter 来统计同一组内每个数出现的次数。对每个组内的数字进行排序,然后使用动态规划计算每组内可以形成的美丽子集数目。对于每组,如果两个数之间的差为 k,则在计算子集时需要考虑不选取前一个数的情况。最后,所有组的子集数目相乘(去除空集的情况)即为结果。

时间复杂度:

O(n^2)

空间复杂度:

O(n)

代码细节讲解

🦆
算法中提到的`模 k 的分组`是如何帮助识别可能冲突的?具体是如何通过模 k 的值来分组的?
在这个算法中,将数组 `nums` 中的每个数 `x` 按照 `x % k` 的结果进行分组,这是因为任何两个数 `a` 和 `b`,如果它们满足 `a % k == b % k`,则 `a` 和 `b` 的差是 `k` 的倍数。通过这种分组,我们将所有可能相互冲突的数字(即差值为 `k` 的倍数的数字)放入同一个组中。这样,每个组内部的数字都是在模 `k` 意义下相同的,这有助于我们在后续的处理中更容易地识别并处理这些冲突,特别是当需要考虑组合子集时。
🦆
题解中提到使用动态规划来计算每个组的美丽子集数目,能否详细解释这个动态规划过程中状态转移的逻辑?特别是`f[i+1] = f[i] + f[i-1] * (2**g[i][1]-1)`这一步。
动态规划数组 `f` 用于存储计算到当前元素为止的美丽子集数目。`f[i+1]` 表示考虑到第 `i` 个元素时所有可能的子集数目。如果当前元素与前一个元素的差值为 `k`,那么为了形成美丽子集,我们不能同时选择这两个元素。因此,状态转移方程分为两部分: 1. `f[i]` 直接继承,表示不选择当前元素。 2. `f[i-1] * (2**g[i][1]-1)` 表示选择当前元素,但不选择前一个元素,并且当前元素可以以多种方式(由于可能的重复)组成子集。`2**g[i][1]` 表示当前元素所有可能的选择(包括不选),减去1是排除全部不选择的情况。最终,`f[i+1]` 是这两种情况的和,即包括了选择和不选择当前元素的所有有效子集组合。
🦆
在计算子集时,为什么要特别处理当两个数之间的差为 k 的情况?这种处理方式是如何避免选择导致非美丽子集的数的?
当两个数之间的差为 `k`,这意味着这两个数不能同时出现在一个美丽子集中。因为根据题目定义,美丽子集中任意两个数 `a` 和 `b` 的差值不应该是 `k` 的倍数。因此,在计算可能的子集组合时,如果发现当前元素和前一个元素的差是 `k`,我们必须特别处理以避免这两个元素同时被选取。这种处理方式通过动态规划中的状态转移实现,确保我们在计算子集数时考虑到了不选择具有冲突差值的前一个元素的情况,从而避免产生非美丽子集。

相关问题