Hard
题目描述
给你一个长度为 n 的环形整数数组 nums。
如果索引 i 的值严格大于其相邻元素,则索引 i 是一个峰值:
i的前一个邻居是nums[i - 1](如果i > 0),否则是nums[n - 1]。i的后一个邻居是nums[i + 1](如果i < n - 1),否则是nums[0]。
你可以执行以下操作任意次数:
- 选择任意索引
i并将nums[i]增加 1。
返回使数组包含至少 k 个峰值所需的最少操作次数。如果不可能,返回 -1。
示例 1:
输入:nums = [2,1,2], k = 1
输出:1
解释:
为了实现至少 k = 1 个峰值,我们可以将 nums[2] = 2 增加到 3。
此操作后,nums[2] = 3 严格大于其邻居 nums[0] = 2 和 nums[1] = 1。
因此,所需的最少操作次数是 1。
示例 2:
输入:nums = [4,5,3,6], k = 2
输出:0
解释:
数组已经包含至少 k = 2 个峰值,无需任何操作。
索引 1:nums[1] = 5 严格大于其邻居 nums[0] = 4 和 nums[2] = 3。
索引 3:nums[3] = 6 严格大于其邻居 nums[2] = 3 和 nums[0] = 4。
因此,所需的最少操作次数是 0。
示例 3:
输入:nums = [3,7,3], k = 2
输出:-1
解释:
在这个数组中不可能有至少 k = 2 个峰值。因此,答案是 -1。
约束条件:
2 <= n == nums.length <= 5000-10^5 <= nums[i] <= 10^50 <= k <= n
解题思路
如果选择下标 i 作为峰值,只需把它增加到严格大于两个原始邻居,代价为:
cost[i] = max(0, max(nums[prev], nums[next]) + 1 - nums[i])
两个相邻下标不可能同时成为峰值;反过来,对任意一组互不相邻的下标,分别支付上述代价后都能成为峰值。因此问题等价于:在一个环上选择恰好 k 个互不相邻的点,使权重和最小。
将环拆成两种互斥情况:
- 不选择下标 0,在路径
[1, n - 1]中选择k个点。 - 选择下标 0,支付
cost[0],排除下标 1 和n - 1,再在路径[2, n - 2]中选择k - 1个点。
路径 DP 维护“前一个位置未选”和“前一个位置已选”两组状态,并使用滚动数组把空间压缩到 O(k)。环上最多只能选择 floor(n / 2) 个互不相邻点,超过时直接返回 -1。
代码实现
class Solution {
public:
int minOperations(vector<int>& nums, int k) {
int n = nums.size();
if (k == 0) return 0;
if (k > n / 2) return -1;
vector<int> cost(n);
for (int i = 0; i < n; i++) {
int previous = nums[(i - 1 + n) % n];
int next = nums[(i + 1) % n];
cost[i] = max(0, max(previous, next) + 1 - nums[i]);
}
const long long INF = 1LL << 60;
auto solvePath = [&](int left, int right, int need) {
if (need == 0) return 0LL;
if (left > right) return INF;
vector<long long> notSelected(need + 1, INF);
vector<long long> selected(need + 1, INF);
notSelected[0] = 0;
for (int index = left; index <= right; index++) {
vector<long long> nextNotSelected(need + 1, INF);
vector<long long> nextSelected(need + 1, INF);
for (int count = 0; count <= need; count++) {
nextNotSelected[count] = min(notSelected[count], selected[count]);
if (count > 0 && notSelected[count - 1] != INF) {
nextSelected[count] = notSelected[count - 1] + cost[index];
}
}
notSelected.swap(nextNotSelected);
selected.swap(nextSelected);
}
return min(notSelected[need], selected[need]);
};
long long answer = solvePath(1, n - 1, k);
answer = min(answer, cost[0] + solvePath(2, n - 2, k - 1));
return answer == INF ? -1 : static_cast<int>(answer);
}
};
class Solution:
def minOperations(self, nums: list[int], k: int) -> int:
n = len(nums)
if k == 0:
return 0
if k > n // 2:
return -1
cost = [0] * n
for i in range(n):
previous = nums[(i - 1) % n]
following = nums[(i + 1) % n]
cost[i] = max(0, max(previous, following) + 1 - nums[i])
inf = 10**30
def solve_path(left, right, need):
if need == 0:
return 0
if left > right:
return inf
not_selected = [inf] * (need + 1)
selected = [inf] * (need + 1)
not_selected[0] = 0
for index in range(left, right + 1):
next_not_selected = [inf] * (need + 1)
next_selected = [inf] * (need + 1)
for count in range(need + 1):
next_not_selected[count] = min(
not_selected[count], selected[count]
)
if count > 0 and not_selected[count - 1] != inf:
next_selected[count] = (
not_selected[count - 1] + cost[index]
)
not_selected = next_not_selected
selected = next_selected
return min(not_selected[need], selected[need])
answer = solve_path(1, n - 1, k)
answer = min(answer, cost[0] + solve_path(2, n - 2, k - 1))
return -1 if answer == inf else answer
public class Solution {
public int MinOperations(int[] nums, int k) {
int n = nums.Length;
if (k == 0) return 0;
if (k > n / 2) return -1;
int[] cost = new int[n];
for (int i = 0; i < n; i++) {
int previous = nums[(i - 1 + n) % n];
int following = nums[(i + 1) % n];
cost[i] = Math.Max(0, Math.Max(previous, following) + 1 - nums[i]);
}
const long Inf = long.MaxValue / 4;
long SolvePath(int left, int right, int need) {
if (need == 0) return 0;
if (left > right) return Inf;
long[] notSelected = new long[need + 1];
long[] selected = new long[need + 1];
for (int count = 0; count <= need; count++) {
notSelected[count] = Inf;
selected[count] = Inf;
}
notSelected[0] = 0;
for (int index = left; index <= right; index++) {
long[] nextNotSelected = new long[need + 1];
long[] nextSelected = new long[need + 1];
for (int count = 0; count <= need; count++) {
nextNotSelected[count] = Math.Min(
notSelected[count], selected[count]
);
nextSelected[count] = Inf;
if (count > 0 && notSelected[count - 1] != Inf) {
nextSelected[count] = notSelected[count - 1] + cost[index];
}
}
notSelected = nextNotSelected;
selected = nextSelected;
}
return Math.Min(notSelected[need], selected[need]);
}
long answer = SolvePath(1, n - 1, k);
answer = Math.Min(answer, cost[0] + SolvePath(2, n - 2, k - 1));
return answer == Inf ? -1 : (int)answer;
}
}
var minOperations = function(nums, k) {
const n = nums.length;
if (k === 0) return 0;
if (k > Math.floor(n / 2)) return -1;
const cost = new Array(n).fill(0);
for (let i = 0; i < n; i++) {
const previous = nums[(i - 1 + n) % n];
const following = nums[(i + 1) % n];
cost[i] = Math.max(0, Math.max(previous, following) + 1 - nums[i]);
}
const solvePath = (left, right, need) => {
if (need === 0) return 0;
if (left > right) return Infinity;
let notSelected = new Array(need + 1).fill(Infinity);
let selected = new Array(need + 1).fill(Infinity);
notSelected[0] = 0;
for (let index = left; index <= right; index++) {
const nextNotSelected = new Array(need + 1).fill(Infinity);
const nextSelected = new Array(need + 1).fill(Infinity);
for (let count = 0; count <= need; count++) {
nextNotSelected[count] = Math.min(
notSelected[count], selected[count]
);
if (count > 0 && notSelected[count - 1] !== Infinity) {
nextSelected[count] = notSelected[count - 1] + cost[index];
}
}
notSelected = nextNotSelected;
selected = nextSelected;
}
return Math.min(notSelected[need], selected[need]);
};
let answer = solvePath(1, n - 1, k);
answer = Math.min(answer, cost[0] + solvePath(2, n - 2, k - 1));
return answer === Infinity ? -1 : answer;
};
复杂度分析
| 指标 | 复杂度 |
|---|---|
| 时间 | O(n × k) |
| 空间 | O(k) |