1959. K 次调整数组大小浪费的最小总空间

💻 动态数组最小浪费空间 —— 动态规划经典问题解析

📌 题目描述

在实现动态数组时,我们常常面临空间与性能的权衡。某一时刻数组的大小必须足够容纳所有元素,但也不能浪费太多内存。本题模拟的就是这样一个真实场景:

给你一个整数数组 nums,其中 nums[i] 表示第 i 时刻数组中元素的数量。

此外,允许你最多调整数组大小 k(每次可以调整成任意大小,但大小必须 ≥ 当前元素数量)。

每个时刻 t 的数组空间大小必须 ≥ nums[t]
浪费空间 定义为该时刻数组大小减去实际元素数量:size_t - nums[t]

你的任务是:在最多调整数组大小 k 次的前提下,最小化整个过程中的总浪费空间

输入输出示例

示例 1:
输入: nums = [10, 20], k = 0
输出: 10
解释: 初始大小为20,总浪费空间为(20-10) + (20-20) = 10。

示例 2:
输入: nums = [10,20,30], k = 1
输出: 10
解释: 初始大小为20,在时刻2调整为30,总浪费为10。

示例 3:
输入: nums = [10,20,15,30,20], k = 2
输出: 15
解释: 尽量每一段固定大小,总浪费为15。

约束条件

  • 1 <= nums.length <= 200
  • 1 <= nums[i] <= 10^6
  • 0 <= k <= nums.length - 1

🧠 解题思路分析

核心思想:分段 + 动态规划

数组最多可以调整大小 k 次,因此可以将 nums 分为最多 k + 1连续的段。每一段中,我们选用一个固定的数组大小,必须 ≥ 段内所有元素的最大值。

在某一段 [i..j] 中:

  • 最优的数组大小是这段中的最大值 max(nums[i..j])
  • 这段的总浪费 = 段长度 * 段最大值 - 段内元素和

转化为 DP 问题

我们定义:

  • dp[i][j]:前 i 个时刻,用 j 段(即调整 j-1 次)时的最小浪费空间。
  • 初始化:dp[0][0] = 0(0 个元素,0 段,浪费为 0)

状态转移:

dp[i][j] = min(dp[t][j-1] + cost[t][i-1])  for all j-1 ≤ t < i

其中 cost[t][i-1] 表示将 nums[t..i-1] 当作一段时的浪费空间(已预处理)。


🧮 解题方法(Python 实现)

from typing import List
import math

class Solution:
    def minSpaceWastedKResizing(self, nums: List[int], k: int) -> int:
        n = len(nums)
        
        # 前缀和:用于快速求区间和
        prefix = [0] * (n + 1)
        for i in range(n):
            prefix[i + 1] = prefix[i] + nums[i]

        # cost[i][j]: 将 nums[i..j] 作为一个段的最小浪费
        cost = [[0] * n for _ in range(n)]
        for i in range(n):
            max_val = nums[i]
            for j in range(i, n):
                max_val = max(max_val, nums[j])
                cost[i][j] = max_val * (j - i + 1) - (prefix[j + 1] - prefix[i])

        # dp[i][j]: 前 i 个数,用 j 段的最小浪费
        dp = [[math.inf] * (k + 2) for _ in range(n + 1)]
        dp[0][0] = 0

        for i in range(1, n + 1):  # 前 i 个数
            dp[i][1] = cost[0][i - 1]  # 只用一段的情况
            max_parts = min(k + 1, i)
            for j in range(2, max_parts + 1):  # 分成 j 段
                for t in range(j - 1, i):  # 分段点 t
                    dp[i][j] = min(dp[i][j], dp[t][j - 1] + cost[t][i - 1])

        return dp[n][k + 1]

🔍 示例说明与过程还原

以输入 nums = [10,20,15,30,20], k = 2 为例:

我们希望将数组分为 3 段(最多调整 2 次):

一种最优划分方案:

  • 段 1: [10] 大小为10,浪费为0
  • 段 2: [20,15] 大小为20,浪费 = (20-20) + (20-15) = 5
  • 段 3: [30,20] 大小为30,浪费 = (30-30) + (30-20) = 10

总浪费 = 0 + 5 + 10 = 15


⏱ 复杂度分析

时间复杂度

  • O(n^2):构造 cost[i][j]
  • O(n^2 * k):三重循环进行状态转移

总复杂度为 O(n^2 * k),在 n ≤ 200 时完全可以接受。

空间复杂度

  • O(n^2):用于存储 cost
  • O(nk):用于 DP 状态表

📈 分析与比较

方法时间复杂度空间复杂度适用范围
暴力递归指数级(不可用)不适合大数据
本文 DP 方法O(n^2 * k)O(n^2)n ≤ 200
的场景
进一步优化(如单调队列优化)O(nk)
(理想)
中等面试挑战题可考虑

🧾 总结

这道题结合了「动态规划」、「分段最优化」、「前缀和」与「区间最大值预处理」等多个算法思想,是一道很适合锻炼 DP 思维的题目。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值