2111-使数组 K 递增的最少操作次数

Raphael Liu Lv10

给你一个下标从 0 开始包含 n 个正整数的数组 arr ,和一个正整数 k

如果对于每个满足 k <= i <= n-1 的下标 i ,都有 arr[i-k] <= arr[i] ,那么我们称 arrK
递增 的。

  • 比方说,arr = [4, 1, 5, 2, 6, 2] 对于 k = 2 是 K 递增的,因为:
    • arr[0] <= arr[2] (4 <= 5)
    • arr[1] <= arr[3] (1 <= 2)
    • arr[2] <= arr[4] (5 <= 6)
    • arr[3] <= arr[5] (2 <= 2)
  • 但是,相同的数组 arr 对于 k = 1 不是 K 递增的(因为 arr[0] > arr[1]),对于 k = 3 也不是 K 递增的(因为 arr[0] > arr[3] )。

每一次 操作 中,你可以选择一个下标 i 并将 arr[i] **改成任意 **正整数。

请你返回对于给定的 k ,使数组变成 K 递增的 最少操作次数

示例 1:

**输入:** arr = [5,4,3,2,1], k = 1
**输出:** 4
**解释:** 对于 k = 1 ,数组最终必须变成非递减的。
可行的 K 递增结果数组为 [5, _ **6**_ , _ **7**_ , _ **8**_ , _ **9**_ ],[ _ **1**_ , _ **1**_ , _ **1**_ , _ **1**_ ,1],[ _ **2**_ , _ **2**_ ,3, _ **4**_ , _ **4**_ ] 。它们都需要 4 次操作。
次优解是将数组变成比方说 [ _ **6**_ , _ **7**_ , _ **8**_ , _ **9**_ , _ **10**_ ] ,因为需要 5 次操作。
显然我们无法使用少于 4 次操作将数组变成 K 递增的。

示例 2:

**输入:** arr = [4,1,5,2,6,2], k = 2
**输出:** 0
**解释:**
这是题目描述中的例子。
对于每个满足 2 <= i <= 5 的下标 i ,有 arr[i-2] <= **** arr[i] 。
由于给定数组已经是 K 递增的,我们不需要进行任何操作。

示例 3:

**输入:** arr = [4,1,5,2,6,2], k = 3
**输出:** 2
**解释:**
下标 3 和 5 是仅有的 3 <= i <= 5 且不满足 arr[i-3] <= arr[i] 的下标。
将数组变成 K 递增的方法之一是将 arr[3] 变为 4 ,且将 arr[5] 变成 5 。
数组变为 [4,1,5, _ **4**_ ,6, _ **5**_ ] 。
可能有其他方法将数组变为 K 递增的,但没有任何一种方法需要的操作次数小于 2 次。

提示:

  • 1 <= arr.length <= 105
  • 1 <= arr[i], k <= arr.length

方法一:动态规划

提示 1

题目的要求本质上是对于每一个 i~(0 \leq i < k),数组 arr 的子序列:

\textit{arr}[i], \textit{arr}[i + k], \textit{arr}[i + 2k], \cdots

是单调递增的。

提示 1 解释

由于 arr}[i - k] \leq \textit{arr}[i],它也可以写成 arr}[i] \leq \textit{arr}[i + k]。如果我们对于 arr}[i + k] 也套用这个不等式,就可以得到 arr}[i + k] \leq \textit{arr}[i + 2k]。以此类推,我们就可以得到:

\textit{arr}[i] \leq \textit{arr}[i + k] \leq \textit{arr}[i + 2k] \leq \cdots

也就是说,我们将数组 arr 中的每个元素根据其下标对 k 取模的值,分成了 k 个子序列(即下标对 k 取模的值分别为 0, 1, \cdots, k-1)。这 k-1 个子序列分别都是单调递增的。

提示 2

对于给定的一个序列,如果我们希望通过修改最少的元素,使得它单调递增,那么最少需要修改的元素个数,就是「序列的长度」减去「序列的最长递增子序列」的长度。

提示 2 解释

我们可以这样想:对于序列中那些需要被修改的元素,由于我们可以把它们修改成任意元素,因此它们修改之前的值并不重要,我们可以忽略它们。

而对于序列中那些没有被修改的元素,由于最终的序列是单调递增的,因此这些没有被修改的元素组成的子序列也一定是单调递增的。要想修改的元素越少,这个子序列就要越长。因此我们的目标就是求出序列的最长递增的子序列。

思路与算法

我们对于每一个满足 0 \leq i < k 的 i,抽取出数组 arr 的子序列:

\textit{arr}[i], \textit{arr}[i + k], \textit{arr}[i + 2k], \cdots

设其长度为 length,最长递增的子序列的长度为 f,那么最少需要修改的元素个数即为 length} - f。

最终的答案即为所有的 length} - f 之和。

细节

求解最长递增子序列可以参考「300. 最长递增子序列」的官方题解 的方法二,这里不再赘述。

代码

[sol1-C++]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution {
public:
int kIncreasing(vector<int>& arr, int k) {
int n = arr.size();
int ans = 0;
for (int i = 0; i < k; ++i) {
vector<int> f;
int length = 0;
for (int j = i; j < n; j += k) {
++length;
auto it = upper_bound(f.begin(), f.end(), arr[j]);
if (it == f.end()) {
f.push_back(arr[j]);
}
else {
*it = arr[j];
}
}
ans += length - f.size();
}
return ans;
}
};
[sol1-Python3]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution:
def kIncreasing(self, arr: List[int], k: int) -> int:
n = len(arr)
ans = 0
for i in range(k):
f = list()
j, length = i, 0
while j < n:
length += 1
it = bisect_right(f, arr[j])
if it == len(f):
f.append(arr[j])
else:
f[it] = arr[j]
j += k
ans += length - len(f)
return ans

复杂度分析

  • 时间复杂度:O(n \log \dfrac{n}{k})。每个序列的长度为 O(\dfrac{n}{k}),寻找其最长递增子序列需要的时间为 O(\dfrac{n}{k} \log \dfrac{n}{k}),而一共有 k 个序列,因此总时间复杂度为 O(n \log \dfrac{n}{k})。

  • 空间复杂度:O(\dfrac{n}{k}),即为寻找单个序列的最长递增子序列时,数组 f 需要的空间。

 Comments
On this page
2111-使数组 K 递增的最少操作次数