1. 解题思路
这一题大的思路上不难想到就是一个动态规划的思路。我们分别考察每一个位置是否需要作为某一个子串的开始位置,然后考察对应子串要使之变为完全相同所需要的最小操作数,然后找出其总和的最小值即可。其总体的算法复杂度在不考虑求子串的最小操作数的情况下就会是 O ( N K ) O(NK) O(NK)。
但是,这复杂度还是很高的,因此,也就要求我们事实上对于上述子问题,即给定一个长度为x
的数组,求令其变得完全相同时所需的最小操作数,的算法复杂度要求就会非常高,至少不是一个滑动窗口遍历的
O
(
X
)
O(X)
O(X)可以处理的,我们需要将其压缩至
O
(
l
o
g
X
)
O(logX)
O(logX)甚至
O
(
1
)
O(1)
O(1)。
万幸的是,通过数学分析,我们可以知道对于一个给定一个长度为x
的数组,其所需的最小操作数一定就是将其全部变为其中位数时所需的操作数。而基于此,我们可以提前先算出每一个位置作为起始位置时其长度为x
的数组的最小操作数,此时我们可以通过前一个位置的结果进行调整,两者变动的元素至多只有一个,因此我们可以复用之前的结果从而省略掉排序的过程,整体的算法复杂度就会是
O
(
N
l
o
g
X
)
O(NlogX)
O(NlogX)。
此时,总的题目的算法复杂度就会是 O ( N K + N l o g X ) O(NK + NlogX) O(NK+NlogX),整体就还勉强在允许范围内了。
2. 代码实现
给出python代码实现如下:
class Solution:
def minOperations(self, nums: List[int], x: int, k: int) -> int:
n = len(nums)
min_ops = [math.inf for i in range(n-x+1)]
sub = sorted(nums[:x])
m = (x+1) // 2
left, ls = sub[:m], sum(sub[:m])
right, rs = sub[m:], sum(sub[m:])
min_ops[0] = (m*2-x) * left[-1] - ls + rs
for i in range(n-x):
if bisect.bisect_left(left, nums[i]) < m:
left.pop(bisect.bisect_left(left, nums[i]))
ls -= nums[i]
else:
right.pop(bisect.bisect_left(right, nums[i]))
rs -= nums[i]
bisect.insort(left, nums[i+x])
ls += nums[i+x]
while len(left) < m:
elem = right.pop(0)
rs -= elem
bisect.insort(left, elem)
ls += elem
while len(left) > m:
elem = left.pop(-1)
ls -= elem
bisect.insort(right, elem)
rs += elem
while left[-1] > right[0]:
l, r = left.pop(-1), right.pop(0)
bisect.insort(left, r)
bisect.insort(right, l)
ls = ls - l + r
rs = rs - r + l
min_ops[i+1] = (m*2-x) * left[-1] - ls + rs
@lru_cache(maxsize = 10000)
def dp(idx, k):
if k == 0:
return 0
if idx >= n:
return math.inf
if idx+k*x > n:
return math.inf
elif idx+k*x == n:
return min_ops[idx] + dp(idx+x, k-1)
return min(dp(idx+1, k), min_ops[idx] + dp(idx+x, k-1))
return dp(0, k)
提交代码评测得到:耗时4845ms,占用内存194MB。