You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

114 lines
3.7 KiB

1 year ago
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;
}
}