Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Leetcode 2518. Number of Great Partitions #166

Open
Woodyiiiiiii opened this issue Jan 5, 2023 · 0 comments
Open

Leetcode 2518. Number of Great Partitions #166

Woodyiiiiiii opened this issue Jan 5, 2023 · 0 comments

Comments

@Woodyiiiiiii
Copy link
Owner

Woodyiiiiiii commented Jan 5, 2023

这道题是周赛第四题,我并没有什么思路。

我一开始的想法是用DFS找到选中数之和大于等于k的组合,很显然这样超过时间复杂度了,因为是O(2^n)。

class Solution {
    int ans = 0;

    final int MOD = 1000000007;

    public int countPartitions(int[] nums, int k) {
        int sum = Arrays.stream(nums).sum();
        boolean[] visited = new boolean[nums.length];

        for (int i = 0; i < nums.length; ++i) {
            visited[i] = true;
            dfs(nums, visited, i, nums[i], sum, k);
            visited[i] = false;
        }

        return ans;
    }

    private void dfs(int[] nums, boolean[] visited, int start, int curSum, int sum, int k) {
        if (curSum >= k && sum - curSum >= k) {
            ans++;
            ans %= MOD;
        }

        for (int i = start + 1; i < nums.length; ++i) {
            if (visited[i]) continue;
            visited[i] = true;
            dfs(nums, visited, i,curSum + nums[i], sum, k);
            visited[i] = false;
        }
    }
}

那有什么方法,能够在O(nlgn)及以下的时间复杂度中找到满足条件的个数呢?

因为正向思维中找到大于等于k的这个条件太大了,行不通,我们不妨用反向思维,找到小于k的组合,然后用总组合相减。

首先观察用例,可以知道总组合的计算条件是2^(n-1),或者说是2^n-2,因为n太大了,会爆栈超过integer的限制,所以我们用循环,每次*2后取%。顺便用long计算下和值,做一下先决判断。

接着,问题变成如何取数计算和小于k的subsequence的个数了。这里自然地想到coin change II题目的变型。coin change II题目中,因为硬币可以取无限个,所以是在coin循环里面从小到大,而这题是只能取一次,所以循环里从大到小。

这实际上是个 Knapsack(背包) 问题。

class Solution {
    public int countPartitions(int[] nums, int k) {
        final int MOD = (int) (1e9 + 7);
        //        int ans = (int) (Math.pow(2, n) - 2); // exceed integer limit
        int ans = 1;
        long total = 0;
        for (int j : nums) {
            ans = ans * 2 % MOD;
            total += j;
        }
        ans -= 2;
        if (total < 2L * k) return 0;

        long[] dp = new long[k];
        dp[0] = 1L;
        for (int num : nums) {
            for (int i = k - 1; i - num >= 0; --i) {
                dp[i] = (dp[i] + dp[i - num]) % MOD;
            }
        }

        for (int i = 1; i < k; ++i) {
            ans -= dp[i] * 2;
            ans = (ans % MOD + MOD) % MOD;
        }
        return ans;
    }
}

最后,循环DP数组,减去2*dp[i]即可。

注意这里还有一个细节,以为要在减后取余,因为会出现a-b<-MOD的情况,所以减法的取余是((a-b)%MOD+MOD)%MOD。正常情况下如果在[-MOD,MOD]区间内就不用这么麻烦了。所以要弄清楚这个公式的由来。

可以看出,hard难度的题目其实是思维转换+medium难度算法的结合。

tips:

  1. 第二次刷的时候,发现先处理判断total<2*k还有另一层深意:避免重复计算。比如初始条件为[1,3,4]和5,dp[4]为2,但其实这两种情况是同一种。所以为了避免这种情况,另一个集合的和要保证大于k。
  2. 不要用BigInteger了,尝试了下感觉没意义
  3. 三刷,其实观察大小,可以发现答案时间复杂度跟n和k有关,可以猜测是O(nk)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant