leetcode
leetcode 2201 ~ 2250
拆分数组的最小代价

拆分数组的最小代价

难度:

标签:

题目描述

You are given an integer array nums and an integer k.

Split the array into some number of non-empty subarrays. The cost of a split is the sum of the importance value of each subarray in the split.

Let trimmed(subarray) be the version of the subarray where all numbers which appear only once are removed.

  • For example, trimmed([3,1,2,4,3,4]) = [3,4,3,4].

The importance value of a subarray is k + trimmed(subarray).length.

  • For example, if a subarray is [1,2,3,3,3,4,4], then trimmed([1,2,3,3,3,4,4]) = [3,3,3,4,4].The importance value of this subarray will be k + 5.

Return the minimum possible cost of a split of nums.

A subarray is a contiguous non-empty sequence of elements within an array.

 

Example 1:

Input: nums = [1,2,1,2,1,3,3], k = 2
Output: 8
Explanation: We split nums to have two subarrays: [1,2], [1,2,1,3,3].
The importance value of [1,2] is 2 + (0) = 2.
The importance value of [1,2,1,3,3] is 2 + (2 + 2) = 6.
The cost of the split is 2 + 6 = 8. It can be shown that this is the minimum possible cost among all the possible splits.

Example 2:

Input: nums = [1,2,1,2,1], k = 2
Output: 6
Explanation: We split nums to have two subarrays: [1,2], [1,2,1].
The importance value of [1,2] is 2 + (0) = 2.
The importance value of [1,2,1] is 2 + (2) = 4.
The cost of the split is 2 + 4 = 6. It can be shown that this is the minimum possible cost among all the possible splits.

Example 3:

Input: nums = [1,2,1,2,1], k = 5
Output: 10
Explanation: We split nums to have one subarray: [1,2,1,2,1].
The importance value of [1,2,1,2,1] is 5 + (3 + 2) = 10.
The cost of the split is 10. It can be shown that this is the minimum possible cost among all the possible splits.

 

Constraints:

  • 1 <= nums.length <= 1000
  • 0 <= nums[i] < nums.length
  • 1 <= k <= 109

 

代码结果

运行时间: 332 ms, 内存: 18.3 MB


/*
 * 思路:
 * 1. 使用动态规划来解决问题。
 * 2. dp[i]表示将nums的前i个元素拆分的最小代价。
 * 3. 使用一个哈希图来计算trimmed子数组的重要性。
 * 4. 遍历每一个可能的拆分点,计算并更新dp数组。
 * 5. 使用Java Stream API来简化部分计算。
 */
import java.util.*;
import java.util.stream.Collectors;

public class Solution {
    public int minCost(int[] nums, int k) {
        int n = nums.length;
        int[] dp = new int[n + 1];
        Arrays.fill(dp, Integer.MAX_VALUE);
        dp[0] = 0;

        for (int i = 1; i <= n; i++) {
            Map<Integer, Long> countMap = new HashMap<>();
            int[] trimmedLength = {0};
            List<Integer> sublist = Arrays.stream(nums, 0, i).boxed().collect(Collectors.toList());
            for (int j = i; j > 0; j--) {
                int num = sublist.get(j - 1);
                countMap.put(num, countMap.getOrDefault(num, 0L) + 1);
                if (countMap.get(num) == 2) {
                    trimmedLength[0] += 2;
                } else if (countMap.get(num) > 2) {
                    trimmedLength[0] += 1;
                }
                dp[i] = Math.min(dp[i], dp[j - 1] + k + trimmedLength[0]);
            }
        }
        return dp[n];
    }
}

解释

方法:

这个题解使用了线段树来优化动态规划的过程。具体思路如下: 1. 定义状态 f[i] 表示将数组 nums[:i] 拆分成若干个非空子数组的最小代价。 2. 状态转移时,对于每个 i,枚举上一个拆分点 j,计算 [j+1,i] 这一段的重要性,加上 f[j] 作为一种拆分方案,取所有方案的最小值作为 f[i] 的值。 3. 朴素的状态转移复杂度为 O(n^2),因此使用线段树优化,将每次查询 f[j] 的最小值的复杂度降至 O(log n)。 4. 线段树中维护区间加的懒标记,同时在更新答案时,需要对每个数字 x 维护其出现的上一个位置和上上个位置,方便线段树对区间进行修改。

时间复杂度:

O(n log n)

空间复杂度:

O(n)

代码细节讲解

🦆
在解法中,如何通过线段树优化动态规划的查询和更新操作?具体是如何实现的?
在这个解法中,线段树用于优化动态规划的查询和更新操作,主要目的是降低时间复杂度。每个节点在线段树中代表一个区间的最小值,以及一个懒标记用于记录该区间所有元素需要增加的值。通过线段树,我们可以在O(log n)时间内完成以下操作:1. 更新操作:当数字x的上一个位置和上上个位置发生变化时,我们需要更新线段树中对应区间的值,这通过递归地分割问题区间,直到达到完全覆盖的叶节点,然后应用懒标记进行快速更新。2. 查询操作:查询操作用于获取当前动态规划状态f[i]的最小值,这同样通过递归地访问涉及的区间,结合懒标记快速得到最小值。这种方法能够有效地减少重复的计算,从而提高整体的执行效率。
🦆
为什么在更新线段树时,需要维护数字的上一个位置和上上个位置?这与算法的哪一部分逻辑直接相关?
在更新线段树时,维护数字的上一个位置和上上个位置是因为这些信息直接关联到动态规划中状态转移的计算。特别是在本题中,数组中的每个数字的出现位置影响计算其对应的代价。具体来说,当一个数字的新位置被发现时,它的上一个位置和上上个位置的信息被用来调整线段树中的区间值,如重要性的调整(代价的改变)。这些位置信息帮助算法在保持正确性的同时,通过线段树快速更新和查询相关的动态规划状态,实现效率的提升。
🦆
在实现线段树时,为什么选择使用懒惰传播(lazy propagation)?这种方法在本题中有什么特别的优势?
在实现线段树时,选择使用懒惰传播(lazy propagation)主要是为了优化区间更新操作的效率,避免对每个单独的元素进行更新,从而减少不必要的计算量。懒惰传播允许我们将一个更新操作延迟到实际需要这个数据时再进行计算。在本题中,这种方法具有特别的优势,因为它使得复杂度高的区间更新操作变得高效,特别是在频繁调整线段树中多个区间值的场景下。通过懒惰传播,我们可以在O(log n)的时间内完成这些区间更新,相比朴素的更新方法大大提高了执行效率。

相关问题