这道题的核心思路是分情况讨论 + 中位数贪心(货舱选址问题)。

题目思路

有两种操作方式:
1. 操作1:把 Dylan 旁边的一个 `0` 变成 `1` 再拾取,代价为 2
2. 操作2:把一个已有的 `1` 通过相邻交换移动到 Dylan 的位置,代价为 距离

关键观察:
- 距离 Dylan 为 1 的位置(`i-1` 和 `i+1`)上的 `1` 可以直接交换过来,代价为 1
- 最多有 3 个位置(`i-1, i, i+1`)可以用代价 ≤1 的方式获取
- 如果 `maxChanges` 足够大,其余 `k-c` 个 `1` 都可以用操作1(代价2)获得

情况1:`maxChanges` 很大
如果 `maxChanges >= k - c`(`c` 是连续1的最大长度,最多为3),那么:
- 连续的 `c` 个 `1` 中,当前位置代价0,旁边两个代价各1
- 答案 = `max(c-1, 0) + (k-c) * 2`

情况2:`maxChanges` 不够
需要从远处的 `1` 通过交换移动过来。这转化为货舱选址问题:
- 收集 `size = k - maxChanges` 个已有的 `1`
- 最优位置是这些 `1` 的中位数
- 用前缀和快速计算距离之和

Java 实现

```java
class Solution {
    public long minimumMoves(int[] nums, int k, int maxChanges) {
        List<Integer> pos = new ArrayList<>(); // 记录所有1的位置
        
        int c = 0; // 连续1的最大长度
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] == 0) continue;
            pos.add(i);
            c = Math.max(c, 1);
            if (i > 0 && nums[i - 1] == 1) {
                if (i > 1 && nums[i - 2] == 1) {
                    c = 3; // 三个连续1
                } else {
                    c = Math.max(c, 2); // 两个连续1
                }
            }
        }
        
        c = Math.min(c, k);
        // 情况1:maxChanges足够大,其余k-c个1全部用操作1(代价2)
        if (maxChanges >= k - c) {
            return Math.max(c - 1, 0) + (long)(k - c) * 2;
        }
        
        int n = pos.size();
        // 前缀和,sum[i]表示前i个1的位置之和
        long[] sum = new long[n + 1];
        for (int i = 0; i < n; i++) {
            sum[i + 1] = sum[i] + pos.get(i);
        }
        
        long res = Long.MAX_VALUE;
        // 需要用操作2(交换)收集的1的个数
        int size = k - maxChanges;
        
        // 滑动窗口:在pos中选择size个1,用中位数贪心求最小距离和
        for (int right = size; right <= n; right++) {
            int left = right - size;
            int mid = left + size / 2;
            long index = pos.get(mid);
            
            // 计算[left, right)区间内所有pos到index的距离之和
            // 左半部分:index * (mid - left) - (sum[mid] - sum[left])
            long s1 = index * (mid - left) - (sum[mid] - sum[left]);
            // 右半部分:(sum[right] - sum[mid]) - index * (right - mid)
            long s2 = (sum[right] - sum[mid]) - index * (right - mid);
            
            res = Math.min(res, s1 + s2);
        }
        
        // 加上maxChanges次操作1的代价
        return res + (long)maxChanges * 2;
    }
}
```

复杂度分析

指标    复杂度    
时间    O(n),其中 n 是 `nums` 的长度    
空间    O(n),用于存储1的位置和前缀和    

关键点总结

1. 连续1的处理:最多3个连续1可以直接利用(代价0或1),这是 `c` 的上限
2. 中位数贪心:对于需要移动收集的1,选择中位数位置作为目标点,距离和最小(经典货舱选址问题)
3. 前缀和优化:用前缀和在 O(1) 时间内计算滑动窗口内的距离和
4. 操作1 vs 操作2的权衡:当 `maxChanges` 足够时,优先用操作1;否则需要精确计算操作2的最小代价

 

更多推荐