题目:
给你一个长度为 n
、下标从 0 开始的整数数组 nums
,nums[i]
表示收集位于下标 i
处的巧克力成本。每个巧克力都对应一个不同的类型,最初,位于下标 i
的巧克力就对应第 i
个类型。
在一步操作中,你可以用成本 x
执行下述行为:
- 同时修改所有巧克力的类型,将巧克力的类型
ith
修改为类型((i + 1) mod n)th
。
假设你可以执行任意次操作,请返回收集所有类型巧克力所需的最小成本。
示例 1:
输入:nums = [20,1,15], x = 5 输出:13 解释:最开始,巧克力的类型分别是 [0,1,2] 。我们可以用成本 1 购买第 1 个类型的巧克力。 接着,我们用成本 5 执行一次操作,巧克力的类型变更为 [1,2,0] 。我们可以用成本 1 购买第 2 个类型的巧克力。 然后,我们用成本 5 执行一次操作,巧克力的类型变更为 [2,0,1] 。我们可以用成本 1 购买第 0 个类型的巧克力。 因此,收集所有类型的巧克力需要的总成本是 (1 + 5 + 1 + 5 + 1) = 13 。可以证明这是一种最优方案。
示例 2:
输入:nums = [1,2,3], x = 4 输出:6 解释:我们将会按最初的成本收集全部三个类型的巧克力,而不需执行任何操作。因此,收集所有类型的巧克力需要的总成本是 1 + 2 + 3 = 6 。
提示:
1 <= nums.length <= 1000
1 <= nums[i] <= 109
1 <= x <= 109
题解
初始化和基本思路
我们需要考虑是否应该执行“修改所有巧克力的类型”的操作。如果我们不进行任何操作,那么总成本就是收集所有巧克力的原始成本,即数组 nums
中所有元素的和。
但是,有时进行一些操作可以降低总成本,比如当某些巧克力的收集成本较高,而通过操作将这些成本分布到费用较低的巧克力上,我们可能会减少总成本。
动态规划思想
为了实现最低成本,我们需要模拟将所有种类的巧克力进行最多 n
次操作,其中 n
是 nums
数组的长度。每次操作的成本是 x
,并且在每次操作之后,我们需要重新评估收集各类巧克力的成本。
在进行每次操作后,对于每一类,我们计算其变为所有其他类型的成本,并取其中的最小值。
具体实现步骤
- 初始化一个长度为
n
的数组dp
,其中dp[i]
表示收集第i
类巧克力的最小花费。 - 将
nums
的初始值赋给dp
。 - 对
n
次操作进行循环,每次更新dp
数组。 - 在每次更新过程中,计算操作的累积成本,并更新
dp
数组。 - 最后,计算经过最多
n
次操作之后,收集所有种类巧克力的最小总成本。
流程图说明:
+---------------------------------------+
| 开始 |
+---------------------------------------+
|1. 初始化 dp 数组,与 nums 长度相同, |
| 并将 dp[i] 初始化为 nums[i]。 |
+---------------------------------------+
|2. 对于最多 n 次操作(即 0 到 n-1 次操作),|
| 进行以下流程: |
| |
| +-----------------------------------+|
| |3. 计算当前 dp 数组中每种类型的巧克力 |
| | 在经过 j(当前操作次数)次操作后 |
| | 的最小收集成本: |
| | a. 计算当前操作的花费 cost = j × x |
| | b. 对于每个巧克力类型 i,计算操作后 |
| | 变成为其他类型的成本并更新 dp。 |
| +-----------------------------------+|
+---------------------------------------+
|4. 计算所有操作后收集所有类型巧克力的最小 |
| 总成本,将 dp 数组中的所有元素相加。 |
+---------------------------------------+
|5. 结束 |
+---------------------------------------+
具体步骤解释:
- 开始:流程图从此处开始。
- 初始化
dp
数组:创建与nums
数组长度相同的dp
数组,并将dp[i]
初始化为nums[i]
。dp[i]
表示收集第i
类型巧克力的最小花费。 - 进行
n
次操作:- 对于每次操作:
- 计算当前操作的总花费
cost = j × x
,其中j
是当前操作的次数。 - 对于每种巧克力类型
i
:- 计算巧克力类型
i
变为其他类型的成本。 - 更新
dp[i]
为当前操作后收集到第i
类型巧克力的最小成本。
- 计算巧克力类型
- 计算当前操作的总花费
- 对于每次操作:
- 计算最小总成本:经过所有操作后,计算收集所有类型巧克力的最小总成本,即
dp
数组中所有元素的和。 - 结束:流程图结束。
代码实现
class Solution {
public long minCost(int[] nums, int x) {
int n = nums.length;
long[] dp = new long[n];
for (int i = 0; i < n; i++) {
dp[i] = nums[i]; // 初始化dp数组,每个位置存储最初成本
}
long minTotalCost = 0;
for (int cost : dp) {
minTotalCost += cost; // 初始的最小总成本就是不进行任何操作的情况
}
long currentTotalCost;
for (int k = 1; k < n; k++) {
// 计算一次操作后的累计成本
for (int i = 0; i < n; i++) {
dp[i] = Math.min(dp[i], nums[(i + k) % n] + k * x);
}
currentTotalCost = 0;
for (long cost : dp) {
currentTotalCost += cost;
}
// 更新最小总成本
minTotalCost = Math.min(minTotalCost, currentTotalCost);
}
return minTotalCost;
}
}
解释代码的核心部分:
- 初始化
dp
数组:dp[i]
表示收集第i
类巧克力的初始成本。 - 初始化最小总成本:将
nums
数组所有元素的和赋给minTotalCost
。 - 模拟 n 次操作循环:对于每次操作,更新
dp
数组以记录最小成本。 - 更新
dp
数组:每次操作后,更新每一类巧克力的收集成本,考虑当前操作后的累积成本。 - 比较和更新最小总成本:每次操作后计算新的总成本,并与当前最小总成本比较,取最小值。
知识点解析:
知识点 | 代码行数 | 解释 |
---|---|---|
数组声明与初始化 | 3, 4 | 定义并初始化数组 dp 以存储每种巧克力的最小成本。 |
初始总成本计算 | 9-11 | 计算不进行任何操作时收集所有巧克力的总成本。 |
for循环 | 13-23 | 使用 for 循环模拟最多 n 次操作。 |
条件判断与更新 | 16-18 | 在每次循环内更新 dp 数组以记录当前最小成本。 |
方法调用 | - | 使用辅助求解方法完成最优解。 |
贪心与动态规划思想 | 整体代码 | 通过贪心与动态规划的方法最小化收集所有巧克力的总成本。 |
2024.5.22