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

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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;
}
}