2963. 统计好分割方案的数目

前言

「力扣题解」系列致力于分享有价值的题目、探讨更优秀的解法。

这是本系列的第 11 篇题解,更多题解关注 Somnia1337@力扣。

本篇介绍 @2023.12.10 第 375 场周赛的最后一题,有人说难度只能算中等,不过赛场上还是憋了挺久才拿下 AK🤣

题目描述

难度:🔴困难

标签:#数学哈希表

给你一个下标从 0 开始、由正整数组成的数组 nums

将数组分割成一个或多个连续子数组,如果不存在包含了相同数字的两个子数组,则认为是一种好分割方案

返回 nums 的好分割方案的数目

由于答案可能很大,请返回答案对 10^9 + 7 取余的结果。

输入: nums = [1,2,1,3]
输出: 2
解释: 有 2 种好分割方案: ([1,2,1], [3]),([1,2,1,3])
输入: nums = [1,1,1,1]
输出: 1
解释: 只有 1 种好分割方案: ([1,1,1,1])

解题思路

先考虑没有限制条件时的方案数:相邻元素之间的空位上可以放隔板,加 / 不加是 种选择,则 个元素有 种方案。

加限制条件相当于禁用其中一些位置(不能放隔板)。

假设某元素重复出现的位置列表为 [p0,...,pk],由于它必须全部出现在一个子数组,则 p0pk 间的所有位置都不能放隔板。

boolean[n - 1] 标记每个位置是否被禁用,哈希表记录 <元素, 下标[最左, 最右]>,对于重复出现的元素,禁用其出现的两端之间的所有位置。最后,计数没有被禁用的位置,用带模快速幂计算

复杂度

  • 时间复杂度:
  • 空间复杂度:

代码

Java
class Solution {
    public int numberOfGoodPartitions(int[] nums) {
        boolean[] ban = new boolean[nums.length - 1]; // 禁用的位置
        Map<Integer, int[]> pos = new HashMap<>(); // <元素, 下标[最左, 最右]>
        for (int i = 0; i < nums.length; i++) {
            if (pos.containsKey(nums[i])) pos.get(nums[i])[1] = i; // 重复出现, 最右位置 i
            else pos.put(nums[i], new int[]{i, -1}); // 首次出现, 最左位置 i
        }
        
        for (int[] p : pos.values()) {
            if (p[1] > 0) { // 重复出现
                Arrays.fill(ban, p[0], p[1], true); // 禁用两端之间的位置
            }
        }
        
        int cnt = 0; // 未禁用的位置计数
        for (boolean b : ban) if (!b) cnt++;
        return pow(2, cnt, 1000000007); // 带模快速幂
    }
	
    private int pow(long x, int p, int MOD) {
        long ret = 1;
        while (p > 0) {
            if (p % 2 == 1) ret = ((ret % MOD) * (x % MOD)) % MOD;
            x = ((x % MOD) * (x % MOD)) % MOD;
            p >>= 1;
        }
        return (int) ret;
    }
}
Python

Python 内置带模快速幂。

class Solution:
    def numberOfGoodPartitions(self, nums):
        ban = [False] * (len(nums) - 1)  # 禁用的位置
        pos = {}  # <元素, 下标[最左, 最右]>
        for i in range(len(nums)):
            if nums[i] in pos:
                pos[nums[i]][1] = i  # 重复出现, 最右位置 i
            else:
                pos[nums[i]] = [i, -1]  # 首次出现, 最左位置 i
		
        for p in pos.values():
            if p[1] > 0:  # 重复出现
                ban[p[0] : p[1]] = [True] * (p[1] - p[0])  # 禁用两端之间的位置
		
        return pow(2, sum(1 for b in ban if not b), 1000000007)  # 带模快速幂

最后

更多系列「外源推文」&「生活分享」关注公众号: