好的,这道题我们先理解一下题意,然后再给出 Java 实现。

题目理解

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

好分割的定义:
将数组分割成若干个连续的非空子数组,使得每个数字在所有的子数组中最多出现一次。
换句话说,对于一个数字,它在原数组中出现的所有位置,必须属于同一个子数组,不能跨两个不同的子数组。

---

思路

1. 记录每个数字的最后出现位置
   · 遍历一次数组,用 lastPos[x] 记录数字 x 最后一次出现的下标。
2. 贪心扫描划分点
   · 从左到右遍历数组,维护当前段需要覆盖到的最大右边界 currEnd。
   · 当遍历到下标 i,更新 currEnd = max(currEnd, lastPos[nums[i]])。
   · 如果 i == currEnd,说明当前位置可以作为一个分割点(这个段的最后一个位置)。
   · 每找到一个分割点,可能的分段方式就会翻倍(因为可以在这些分割点处选择切或不切)。
3. 计算方案数
   · 如果有 k 个可分割的位置(不包括数组末尾),那么方案数为 2^(k)。
   · 注意:整个数组必须作为一个完整的分割,所以 k 指的是中间可切开的位置数。
   · 用快速幂或逐次乘 2 来避免溢出。

---

举例

示例:
nums = [1, 1, 2, 2, 3, 3]

· lastPos:
    1 → 1
    2 → 3
    3 → 5

遍历:

· i=0, currEnd = lastPos[1] = 1 → i != currEnd
· i=1, currEnd = max(1, lastPos[1]=1) = 1, i == currEnd → 切分点 +1
· i=2, currEnd = max(1, lastPos[2]=3) = 3
· i=3, currEnd = max(3, lastPos[2]=3) = 3, i == currEnd → 切分点 +1
· i=4, currEnd = max(3, lastPos[3]=5) = 5
· i=5, currEnd = max(5, lastPos[3]=5) = 5, i == currEnd → 切分点 +1

切分点位置:1, 3, 5(3个)
分段数 = 2^3 = 8

---

Java 实现

```java
class Solution {
    public int numberOfGoodPartitions(int[] nums) {
        Map<Integer, Integer> lastPos = new HashMap<>();
        int n = nums.length;
        
        // 记录每个数字最后出现的位置
        for (int i = 0; i < n; i++) {
            lastPos.put(nums[i], i);
        }
        
        int segments = 0;
        int currEnd = 0;
        
        for (int i = 0; i < n; i++) {
            currEnd = Math.max(currEnd, lastPos.get(nums[i]));
            if (i == currEnd) {
                segments++;
            }
        }
        
        // 有 segments 段,中间有 segments-1 个切割点,每个切割点可以切或不切
        // 所以总方案数 = 2^(segments-1)
        long ans = 1;
        int MOD = 1_000_000_007;
        for (int i = 1; i < segments; i++) {
            ans = (ans * 2) % MOD;
        }
        return (int) ans;
    }
}
```

---

复杂度分析

· 时间复杂度:O(n)
· 空间复杂度:O(n),存储每个数字最后出现的位置

---

总结

核心是找到所有必须合并在一起形成的“块”,块的数量决定了中间有多少个可选的切割点,每个切割点独立决定切或不切,因此方案数为 2^(块数-1)。

 

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐