LC 1703. 得到连续 K 个 1 的最少相邻交换次数
https://leetcode-cn.com/problems/minimum-adjacent-swaps-for-k-consecutive-ones/
这题首先要证明,将1往中间靠是最优解,然后就是如何优化计算。
假设有k个不连续的1,我们假设在某个点上,左边有m个,右边则有k-m-1个。首先我们想象,要将这些1全部聚到这个点附近,需要X步骤。如果这个点右移一个单位,那么左边距离会增加m, 而右边距离则会减少(k-m-1),就是 `X+2m-k+1`. 为了确保距离会继续增加,就需要假设 `X=(X+2m-k+1)`, 所以 `m=(k-1)/2`. 而这个中间点的左边就是 `(k-1)/2` (以0为下标,在python函数里面就是 `k//2` )
一旦确定要往中点靠近,接着就是确定每个中点的移动代价了。这个代价其实可以分为两个部分:
- 每个点到这个点的距离之和
- 每个点因为顺序原因少移动的距离。
关于(2)可以举个例子, 假设 010101 -> 000111, 最左边的1到最右边的1距离是4,不过因为它是最外面的一个1,所以只需要移动4-2=2。将(2)单独分离出来计算可以减少逻辑复杂度,并且(2)这个距离是不变。下面是计算(2)这个值得代码
saved = 0 for i in range(mid): saved += (mid - i) for i in range(mid+1, k): saved += (i - mid)
然后每次移动中点,有4个部分会变化:
- 中点所有左边的点需要增加 `(a[mid+1]-a[mid])`
- 中点所有右边的点需要减少 `(a[mid+1]-a[mid])`
- 减去最左边的点 `(a[mid+1]-a[i-k])`
- 增加最右边的点 `(a[i]-a[mid+1])`
下面是完整代码
class Solution: def minMoves(self, nums: List[int], k: int) -> int: arr = [] for i in range(len(nums)): if nums[i] == 1: arr.append(i) if k == 1: return 0 # 这题首先要证明往中间靠是最优解 # 之后采用类似滑动窗口办法 # mid = (k-1) / 2 是最优解 # initialize cost. half = mid = k // 2 cost = 0 for i in range(k): p0 = arr[mid] p1 = arr[i] cost += abs(p0 - p1) # note: move all around 1 to mid. saved = 0 for i in range(mid): saved += (mid - i) for i in range(mid+1, k): saved += (i - mid) ans = cost # print(cost) for i in range(k, len(arr)): # mid -> mid + 1 it = arr[mid+1] - arr[mid] a = (half + 1) * it b = (k - half - 1) * it # remove (i-k-1) item. c = arr[mid+1] - arr[i-k] # add (i) item d = arr[i] - arr[mid+1] cost += (a - b - c + d) # print(it, a, b, c, d, cost) ans = min(ans, cost) mid = mid + 1 # adjust final cost. ans -= saved return ans