|
|
package class_2023_05_3_week;
|
|
|
|
|
|
// 来自小红书
|
|
|
// 给你一个整数数组 nums 和一个整数 k 。 nums 仅包含 0 和 1
|
|
|
// 每一次移动,你可以选择 相邻 两个数字并将它们交换
|
|
|
// 请你返回使 nums 中包含 k 个 连续 1 的 最少 交换次数
|
|
|
// 测试链接 : https://leetcode.cn/problems/minimum-adjacent-swaps-for-k-consecutive-ones/
|
|
|
public class Code04_MinimumAdjacentSwapsForConsecutiveOnes {
|
|
|
|
|
|
// . . . 1 0 1 0 0 | 0 0 X X |
|
|
|
// 3 4 5 6 7 8 9
|
|
|
// 假设3位置的1,和5位置的1,移动到8、9位置
|
|
|
// 最优移动是 :
|
|
|
// 5位置的1,移动到9位置
|
|
|
// 3位置的1,移动到8位置
|
|
|
// 距离和 = 9 - 5 + 8 - 3
|
|
|
// 距离和 = 9 + 8 - (5 + 3)
|
|
|
// 推广 :
|
|
|
// 假设最优移动是 :
|
|
|
// a位置的1,向右移动到c位置
|
|
|
// b位置的1,向右移动到d位置
|
|
|
// 距离和 = c + d - (a + b)
|
|
|
// 这是对所有1向右移动来说的
|
|
|
// 那么所有1向左移动是同理的
|
|
|
// . . . | X X 0 0 | 0 1 0 0 1
|
|
|
// c d 5 a 7 8 b
|
|
|
// 假设最优移动是 :
|
|
|
// a位置的1,向左移动到c位置
|
|
|
// b位置的1,向左移动到d位置
|
|
|
// 距离和 = a - c + b - d = c + d - (a + b) 整体的绝对值
|
|
|
// 综上,不管向左还是向右
|
|
|
// 假设最优移动是 :
|
|
|
// a位置的1,移动到c位置
|
|
|
// b位置的1,移动到d位置
|
|
|
// 距离和 = | c + d - (a + b) |
|
|
|
// 枚举所有K长度的窗口 :
|
|
|
// 对窗口的左半边来说,就是再左边的1向右移动
|
|
|
// 对窗口的右半边来说,就是再右边的1向左移动
|
|
|
// 举个例子
|
|
|
// 0 1 1 0 1 1 1 | 0 0 0 0 | 1 0 1 1
|
|
|
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
|
|
// 假设K = 4
|
|
|
// 当前窗口来到7~10位置的区域
|
|
|
// 那么,这个窗口的7位置、8位置是左半边
|
|
|
// 这个窗口的9位置、10位置是右半边
|
|
|
// 此时的最优策略为 :
|
|
|
// 对左半边来说,
|
|
|
// 6位置的1,应该去8位置
|
|
|
// 5位置的1,应该去7位置
|
|
|
// 对右半边来说,
|
|
|
// 11位置的1,应该去9位置
|
|
|
// 13位置的1,应该去10位置
|
|
|
// 上面就是某一个长度为K的窗口,最优的移动策略
|
|
|
// 那么枚举每一个长度为K的窗口,选出代价最好的时候,就是答案
|
|
|
// 当然求每一个窗口的代价,就是上文给你说的公式
|
|
|
public static int minMoves(int[] nums, int k) {
|
|
|
if (k == 1) {
|
|
|
return 0;
|
|
|
}
|
|
|
int n = nums.length;
|
|
|
int x = (k - 1) / 2;
|
|
|
int leftAimIndiesSum = x * (x + 1) / 2;
|
|
|
int rightAimIndiesSum = (int) ((long) (k - 1) * k / 2 - leftAimIndiesSum);
|
|
|
int ans = Integer.MAX_VALUE;
|
|
|
int l = 0;
|
|
|
int m = (k - 1) / 2;
|
|
|
int r = k - 1;
|
|
|
int leftNeedOnes = m + 1;
|
|
|
int leftWindowL = 0;
|
|
|
int leftWindowOnes = 0;
|
|
|
int leftWindowOnesIndiesSum = 0;
|
|
|
for (int i = 0; i < m; i++) {
|
|
|
if (nums[i] == 1) {
|
|
|
leftWindowOnes++;
|
|
|
leftWindowOnesIndiesSum += i;
|
|
|
}
|
|
|
}
|
|
|
int rightNeedOnes = k - leftNeedOnes;
|
|
|
int rightWindowR = m;
|
|
|
int rightWindowOnes = nums[m];
|
|
|
int rightWindowOnesIndiesSum = nums[m] == 1 ? m : 0;
|
|
|
for (; r < n; l++, m++, r++) {
|
|
|
if (nums[m] == 1) {
|
|
|
leftWindowOnes++;
|
|
|
leftWindowOnesIndiesSum += m;
|
|
|
rightWindowOnes--;
|
|
|
rightWindowOnesIndiesSum -= m;
|
|
|
}
|
|
|
while (leftWindowOnes > leftNeedOnes) {
|
|
|
if (nums[leftWindowL] == 1) {
|
|
|
leftWindowOnes--;
|
|
|
leftWindowOnesIndiesSum -= leftWindowL;
|
|
|
}
|
|
|
leftWindowL++;
|
|
|
}
|
|
|
while (rightWindowOnes < rightNeedOnes && rightWindowR + 1 < n) {
|
|
|
if (nums[rightWindowR + 1] == 1) {
|
|
|
rightWindowOnes++;
|
|
|
rightWindowOnesIndiesSum += rightWindowR + 1;
|
|
|
}
|
|
|
rightWindowR++;
|
|
|
}
|
|
|
if (leftWindowOnes == leftNeedOnes && rightWindowOnes == rightNeedOnes) {
|
|
|
ans = Math.min(ans,
|
|
|
leftAimIndiesSum - leftWindowOnesIndiesSum + rightWindowOnesIndiesSum - rightAimIndiesSum);
|
|
|
}
|
|
|
leftAimIndiesSum += m + 1 - l;
|
|
|
rightAimIndiesSum += r - m;
|
|
|
}
|
|
|
return ans;
|
|
|
}
|
|
|
|
|
|
}
|