这道题的核心思路是动态规划 + 记忆化搜索。我们定义 `dfs(i)` 为从下标 `i` 开始拆分数组的最小代价,答案即为 `dfs(0)`。

关键观察

子数组的重要性 = `k + trimmed(subarray).length`。其中 `trimmed` 操作会移除子数组中只出现一次的数字。

如果我们用 `cnt[x]` 记录数字 `x` 在当前子数组中的出现次数,用 `one` 记录出现次数恰好为 1 的数字个数,那么:
- `trimmed(subarray).length = 子数组总长度 - one`
- 重要性 = `k + (j - i + 1) - one`

算法设计

在 `dfs(i)` 中,枚举子数组的右端点 `j`(从 `i` 到 `n-1`),同时维护一个计数数组和 `one` 变量:
- 当 `nums[j]` 第一次出现时,`one++`
- 当 `nums[j]` 第二次出现时,它从"唯一"变成"重复",所以 `one--`
- 第三次及以上出现,`one` 不变

转移方程:

```
dfs(i) = min{ k + (j - i + 1 - one) + dfs(j + 1) }  对所有 j ≥ i
```

使用记忆化数组 `f[i]` 避免重复计算。

C++ 实现

```cpp
class Solution {
public:
    int minCost(vector<int>& nums, int k) {
        int n = nums.size();
        // f[i] 表示从下标 i 开始拆分的最小代价,0 表示未计算
        // 注意:题目保证答案为正,所以可以用 0 作为未访问标记
        vector<int> f(n, 0);
        
        function<int(int)> dfs = [&](int i) -> int {
            if (i >= n) return 0;
            if (f[i] != 0) return f[i];
            
            // cnt[x] 记录子数组中数字 x 的出现次数
            // 由于 nums[i] < n,可以用数组代替哈希表
            vector<int> cnt(n, 0);
            int one = 0;  // 出现次数为 1 的数字个数
            int ans = INT_MAX;
            
            for (int j = i; j < n; ++j) {
                int x = ++cnt[nums[j]];
                if (x == 1) {
                    ++one;      // 新出现的唯一数字
                } else if (x == 2) {
                    --one;      // 从唯一变成重复
                }
                // x >= 3 时,one 不变(已经是重复数字)
                
                // 当前子数组 [i, j] 的重要性:k + 长度 - one
                int cost = k + (j - i + 1 - one) + dfs(j + 1);
                ans = min(ans, cost);
            }
            
            f[i] = ans;
            return ans;
        };
        
        return dfs(0);
    }
};
```

复杂度分析

- 时间复杂度:O(n^2)。每个状态 `dfs(i)` 最多计算一次,每次计算需要 O(n) 的枚举。
- 空间复杂度:O(n)。记忆化数组和递归栈的空间。

示例验证

以 `nums = [1,2,1,2,1,3,3], k = 2` 为例:
- 拆分为 `[1,2]` 和 `[1,2,1,3,3]`
- 第一个子数组:2 + (2 - 2) = 2(1 和 2 都只出现一次,trimmed 后长度为 0)
- 第二个子数组:2 + (5 - 3) = 6(1 出现 3 次,2 出现 1 次,3 出现 2 次;仅出现一次的是 2,所以 trimmed 长度为 4?不对,重新算:1 出现 3 次保留,2 出现 1 次移除,3 出现 2 次保留,trimmed = [1,1,3,3],长度为 4,重要性 = 2 + 4 = 6)
- 总代价 = 2 + 6 = 8 ✓

参考实现来自 LeetCode 官方题解和 Doocs 开源社区 。

 

更多推荐