100227. 拾起 K 个 1 需要的最少行动次数
给你一个下标从 0 开始的二进制数组 nums
,其长度为 n
;另给你一个 正整数 k
以及一个 非负整数 maxChanges
。
灵茶山艾府在玩一个游戏,游戏的目标是让灵茶山艾府使用 最少 数量的 行动 次数从 nums
中拾起 k
个 1 。游戏开始时,灵茶山艾府可以选择数组 [0, n - 1]
范围内的任何索引index
站立。如果 nums[index] == 1
,灵茶山艾府就会拾起一个 1 ,并且 nums[index]
变成0
(这 不算 作一次行动)。之后,灵茶山艾府可以执行 任意数量 的 行动(包括零次),在每次行动中灵茶山艾府必须 恰好 执行以下动作之一:
j != index
且满足 nums[j] == 0
,然后将 nums[j]
设置为 1
。这个动作最多可以执行 maxChanges
次。x
和 y
(|x - y| == 1
)且满足 nums[x] == 1
, nums[y] == 0
,然后交换它们的值(将 nums[y] = 1
和 nums[x] = 0
)。如果 y == index
,在这次行动后灵茶山艾府拾起一个 1 ,并且 nums[y]
变成 0
。返回灵茶山艾府拾起 恰好 k
个 1 所需的 最少 行动次数。
示例 1:
输入:nums = [1,1,0,0,0,1,1,0,0,1], k = 3, maxChanges = 1
输出:3
解释:如果游戏开始时灵茶山艾府在 index == 1
的位置上,按照以下步骤执行每个动作,他可以利用 3
次行动拾取 3
个 1 :
nums[1]
变成了 0
。此时 nums
变为 [1,0,0,0,0,1,1,0,0,1]
。j == 2
并执行第一种类型的动作。nums
变为 [1,0,1,0,0,1,1,0,0,1]
x == 2
和 y == 1
,并执行第二种类型的动作。nums
变为 [1,1,0,0,0,1,1,0,0,1]
。由于 y == index
,灵茶山艾府拾取了一个 1 ,nums
变为 [1,0,0,0,0,1,1,0,0,1]
。x == 0
和 y == 1
,并执行第二种类型的动作。nums
变为 [0,1,0,0,0,1,1,0,0,1]
。由于 y == index
,灵茶山艾府拾取了一个 1 ,nums
变为 [0,0,0,0,0,1,1,0,0,1]
。请注意,灵茶山艾府也可能执行其他的 3
次行动序列达成拾取 3
个 1 。
示例 2:
输入:nums = [0,0,0,0], k = 2, maxChanges = 3
输出:4
解释:如果游戏开始时灵茶山艾府在 index == 0
的位置上,按照以下步骤执行每个动作,他可以利用 4
次行动拾取 2
个 1 :
j == 1
并执行第一种类型的动作。nums
变为 [0,1,0,0]
。x == 1
和 y == 0
,并执行第二种类型的动作。nums
变为 [1,0,0,0]
。由于 y == index
,灵茶山艾府拾起了一个 1 ,nums
变为 [0,0,0,0]
。j == 1
并执行第一种类型的动作。nums
变为 [0,1,0,0]
。x == 1
和 y == 0
,并执行第二种类型的动作。nums
变为 [1,0,0,0]
。由于y == index
,灵茶山艾府拾起了一个 1 ,nums
变为 [0,0,0,0]
。
提示:
2 <= n <= 105
0 <= nums[i] <= 1
1 <= k <= 105
0 <= maxChanges <= 105
maxChanges + sum(nums) >= k
原站题解
cpp 解法, 执行用时: 64 ms, 内存消耗: 83.4 MB, 提交时间: 2024-07-04 09:11:20
class Solution { public: long long minimumMoves(vector<int> &nums, int k, int maxChanges) { vector<int> pos; int c = 0; // nums 中连续的 1 长度 for (int i = 0; i < nums.size(); i++) { if (nums[i] == 0) continue; pos.push_back(i); // 记录 1 的位置 c = max(c, 1); if (i > 0 && nums[i - 1] == 1) { if (i > 1 && nums[i - 2] == 1) { c = 3; // 有 3 个连续的 1 } else { c = max(c, 2); // 有 2 个连续的 1 } } } c = min(c, k); if (maxChanges >= k - c) { // 其余 k-c 个 1 可以全部用两次操作得到 return max(c - 1, 0) + (k - c) * 2; } int n = pos.size(); vector<long long> sum(n + 1); for (int i = 0; i < n; i++) { sum[i + 1] = sum[i] + pos[i]; } long long ans = LLONG_MAX; // 除了 maxChanges 个数可以用两次操作得到,其余的 1 只能一步步移动到 pos[i] int size = k - maxChanges; for (int right = size; right <= n; right++) { // s1+s2 是 j 在 [left, right) 中的所有 pos[j] 到 index=pos[(left+right)/2] 的距离之和 int left = right - size; int i = left + size / 2; long long index = pos[i]; long long s1 = index * (i - left) - (sum[i] - sum[left]); long long s2 = sum[right] - sum[i] - index * (right - i); ans = min(ans, s1 + s2); } return ans + maxChanges * 2; } };
golang 解法, 执行用时: 57 ms, 内存消耗: 7.8 MB, 提交时间: 2024-03-20 09:33:30
func minimumMoves(nums []int, k, maxChanges int) int64 { pos := []int{} c := 0 // nums 中连续的 1 长度 for i, x := range nums { if x == 0 { continue } pos = append(pos, i) // 记录 1 的位置 c = max(c, 1) if i > 0 && nums[i-1] == 1 { if i > 1 && nums[i-2] == 1 { c = 3 // 有 3 个连续的 1 } else { c = max(c, 2) // 有 2 个连续的 1 } } } c = min(c, k) if maxChanges >= k-c { // 其余 k-c 个 1 可以全部用两次操作得到 return int64(max(c-1, 0) + (k-c)*2) } n := len(pos) sum := make([]int, n+1) for i, x := range pos { sum[i+1] = sum[i] + x } ans := math.MaxInt // 除了 maxChanges 个数可以用两次操作得到,其余的 1 只能一步步移动到 pos[i] size := k - maxChanges for right := size; right <= n; right++ { // s1+s2 是 j 在 [left, right) 中的所有 pos[j] 到 pos[(left+right)/2] 的距离之和 left := right - size i := left + size/2 s1 := pos[i]*(i-left) - (sum[i] - sum[left]) s2 := sum[right] - sum[i] - pos[i]*(right-i) ans = min(ans, s1+s2) } return int64(ans + maxChanges*2) }
java 解法, 执行用时: 6 ms, 内存消耗: 55.3 MB, 提交时间: 2024-03-20 09:33:16
class Solution { public long minimumMoves(int[] nums, int k, int maxChanges) { List<Integer> pos = new ArrayList<>(); int c = 0; // nums 中连续的 1 长度 for (int i = 0; i < nums.length; i++) { if (nums[i] == 0) continue; pos.add(i); // 记录 1 的位置 c = Math.max(c, 1); if (i > 0 && nums[i - 1] == 1) { if (i > 1 && nums[i - 2] == 1) { c = 3; // 有 3 个连续的 1 } else { c = Math.max(c, 2); // 有 2 个连续的 1 } } } c = Math.min(c, k); if (maxChanges >= k - c) { // 其余 k-c 个 1 可以全部用两次操作得到 return Math.max(c - 1, 0) + (k - c) * 2; } int n = pos.size(); long[] sum = new long[n + 1]; for (int i = 0; i < n; i++) { sum[i + 1] = sum[i] + pos.get(i); } long ans = Long.MAX_VALUE; // 除了 maxChanges 个数可以用两次操作得到,其余的 1 只能一步步移动到 pos[i] int size = k - maxChanges; for (int right = size; right <= n; right++) { // s1+s2 是 j 在 [left, right) 中的所有 pos[j] 到 index=pos[(left+right)/2] 的距离之和 int left = right - size; int i = left + size / 2; long index = pos.get(i); long s1 = index * (i - left) - (sum[i] - sum[left]); long s2 = sum[right] - sum[i] - index * (right - i); ans = Math.min(ans, s1 + s2); } return ans + maxChanges * 2; } }
python3 解法, 执行用时: 157 ms, 内存消耗: 27.8 MB, 提交时间: 2024-03-20 09:32:55
class Solution: def minimumMoves(self, nums: List[int], k: int, max_changes: int) -> int: pos = [] c = 0 # nums 中连续的 1 长度 for i, x in enumerate(nums): if x == 0: continue pos.append(i) # 记录 1 的位置 c = max(c, 1) if i > 0 and nums[i - 1] == 1: if i > 1 and nums[i - 2] == 1: c = 3 # 有 3 个连续的 1 else: c = max(c, 2) # 有 2 个连续的 1 c = min(c, k) if max_changes >= k - c: # 其余 k-c 个 1 可以全部用两次操作得到 return max(c - 1, 0) + (k - c) * 2 n = len(pos) pre_sum = list(accumulate(pos, initial=0)) ans = inf # 除了 max_changes 个数可以用两次操作得到,其余的 1 只能一步步移动到 pos[i] size = k - max_changes for right in range(size, n + 1): # s1+s2 是 j 在 [left, right) 中的所有 pos[j] 到 pos[(left+right)/2] 的距离之和 left = right - size i = left + size // 2 s1 = pos[i] * (i - left) - (pre_sum[i] - pre_sum[left]) s2 = pre_sum[right] - pre_sum[i] - pos[i] * (right - i) ans = min(ans, s1 + s2) return ans + max_changes * 2