Taro Logo

Minimum Moves to Pick K Ones

Hard
Asked by:
Profile picture
17 views
Topics:
ArraysDynamic Programming

You are given a binary array nums of length n, a positive integer k and a non-negative integer maxChanges.

Alice plays a game, where the goal is for Alice to pick up k ones from nums using the minimum number of moves. When the game starts, Alice picks up any index aliceIndex in the range [0, n - 1] and stands there. If nums[aliceIndex] == 1 , Alice picks up the one and nums[aliceIndex] becomes 0(this does not count as a move). After this, Alice can make any number of moves (including zero) where in each move Alice must perform exactly one of the following actions:

  • Select any index j != aliceIndex such that nums[j] == 0 and set nums[j] = 1. This action can be performed at most maxChanges times.
  • Select any two adjacent indices x and y (|x - y| == 1) such that nums[x] == 1, nums[y] == 0, then swap their values (set nums[y] = 1 and nums[x] = 0). If y == aliceIndex, Alice picks up the one after this move and nums[y] becomes 0.

Return the minimum number of moves required by Alice to pick exactly k ones.

Example 1:

Input: nums = [1,1,0,0,0,1,1,0,0,1], k = 3, maxChanges = 1

Output: 3

Explanation: Alice can pick up 3 ones in 3 moves, if Alice performs the following actions in each move when standing at aliceIndex == 1:

  • At the start of the game Alice picks up the one and nums[1] becomes 0. nums becomes [1,0,0,0,0,1,1,0,0,1].
  • Select j == 2 and perform an action of the first type. nums becomes [1,0,1,0,0,1,1,0,0,1]
  • Select x == 2 and y == 1, and perform an action of the second type. nums becomes [1,1,0,0,0,1,1,0,0,1]. As y == aliceIndex, Alice picks up the one and nums becomes [1,0,0,0,0,1,1,0,0,1].
  • Select x == 0 and y == 1, and perform an action of the second type. nums becomes [0,1,0,0,0,1,1,0,0,1]. As y == aliceIndex, Alice picks up the one and nums becomes [0,0,0,0,0,1,1,0,0,1].

Note that it may be possible for Alice to pick up 3 ones using some other sequence of 3 moves.

Example 2:

Input: nums = [0,0,0,0], k = 2, maxChanges = 3

Output: 4

Explanation: Alice can pick up 2 ones in 4 moves, if Alice performs the following actions in each move when standing at aliceIndex == 0:

  • Select j == 1 and perform an action of the first type. nums becomes [0,1,0,0].
  • Select x == 1 and y == 0, and perform an action of the second type. nums becomes [1,0,0,0]. As y == aliceIndex, Alice picks up the one and nums becomes [0,0,0,0].
  • Select j == 1 again and perform an action of the first type. nums becomes [0,1,0,0].
  • Select x == 1 and y == 0 again, and perform an action of the second type. nums becomes [1,0,0,0]. As y == aliceIndex, Alice picks up the one and nums becomes [0,0,0,0].

Constraints:

  • 2 <= n <= 105
  • 0 <= nums[i] <= 1
  • 1 <= k <= 105
  • 0 <= maxChanges <= 105
  • maxChanges + sum(nums) >= k

Solution


Clarifying Questions

When you get asked this question in a real-life environment, it will often be ambiguous (especially at FAANG). Make sure to ask these questions in that case:

  1. What are the constraints on the size of the input array `nums` and the value of `k`? Specifically, what are the maximum possible values for `nums.length` and `k`?
  2. Is `k` guaranteed to be less than or equal to the number of 1s present in the array `nums`? If not, what should I return if there are fewer than `k` ones?
  3. Can `k` be zero? If so, what should I return?
  4. Could the input array `nums` be empty or null? If so, how should I handle this?
  5. If multiple sets of `k` ones can be made adjacent with the same minimum number of moves, is any such set acceptable, or is there a specific criteria for choosing among them (e.g., the set with the smallest starting index)?

Brute Force Solution

Approach

The brute force strategy in this case is like trying every possible combination to pick a specific number of 'ones'. We check each combination to see which one requires the fewest moves to gather the desired count of 'ones'. It's an exhaustive approach, guaranteeing the optimal answer.

Here's how the algorithm would work step-by-step:

  1. Consider every possible set of positions within the sequence.
  2. For each of these sets, calculate the number of moves needed to collect the 'ones' located at those positions.
  3. Compare the number of moves from each possible set.
  4. Select the set that involves the fewest moves to collect the required number of 'ones'.
  5. The number of moves associated with this selected set represents the minimum number of moves needed.

Code Implementation

def min_moves_brute_force(numbers, k_ones):    number_of_elements = len(numbers)
    minimum_moves = float('inf')

    # Iterate through all possible combinations of indices
    for i in range(1 << number_of_elements):
        current_group = []
        for j in range(number_of_elements):
            if (i >> j) & 1:
                current_group.append(j)

        if len(current_group) == k_ones:
            # Only process groups of size k
            moves = calculate_moves(numbers, current_group)
            minimum_moves = min(minimum_moves, moves)

    return minimum_moves

def calculate_moves(numbers, indices):
    ones_positions = []
    for index in indices:
        if numbers[index] == 1:
            ones_positions.append(index)

    if not ones_positions:
        return float('inf')

    total_moves = 0
    for i in range(len(ones_positions)):
        total_moves += abs(ones_positions[i] - ones_positions[len(ones_positions) // 2])

    return total_moves

def main():
    numbers = [1, 0, 0, 1, 0, 1]
    k_ones = 2
    result = min_moves_brute_force(numbers, k_ones)
    print(f"Minimum moves required: {result}")

    numbers = [1, 1, 0, 1]
    k_ones = 2
    result = min_moves_brute_force(numbers, k_ones)
    print(f"Minimum moves required: {result}")

if __name__ == "__main__":
    main()

Big(O) Analysis

Time Complexity
O(n choose k * k) – The brute force solution involves considering every possible combination of k ones chosen from n positions. The number of such combinations is given by the binomial coefficient "n choose k", which can be written as n! / (k! * (n-k)!). For each combination, we need to calculate the moves required, which involves iterating through the k selected positions, giving us a cost of O(k) for each combination. Therefore, the overall time complexity is O(n choose k * k). Since 'n choose k' can grow factorially with n, the O(n choose k * k) accurately represents the complexity.
Space Complexity
O(1) – The provided brute force strategy considers every possible combination to pick K ones. It calculates the number of moves for each combination but doesn't explicitly store all combinations or intermediate results in separate data structures of substantial size. The algorithm primarily uses a few variables to track the current combination and the minimum moves found so far. Therefore, the space used is constant and independent of the input size N, where N is the length of the input sequence.

Optimal Solution

Approach

The problem asks for the smallest range needed to include a certain number of ones. The optimal strategy is to efficiently explore ranges of the input, expanding and contracting them to find the smallest one containing the required number of ones.

Here's how the algorithm would work step-by-step:

  1. Imagine a window sliding across the input. The window's size represents the range we are considering.
  2. Start with a small window size and slide it from left to right.
  3. Count the number of ones inside the current window.
  4. If the window contains the required number of ones, record its size (the difference between its start and end).
  5. If the window does not contain enough ones, expand the window on the right to include more elements.
  6. If the window contains the required number of ones, try shrinking the window from the left to see if you can find a smaller range that still contains the required number of ones.
  7. Keep track of the smallest window size that satisfies the condition. That will be your answer.

Code Implementation

def min_moves_to_pick_k_ones(sequence, k_ones):
    window_start = 0
    number_of_ones_in_window = 0
    min_zeroes = float('inf')
    zeroes_in_window = 0

    for window_end in range(len(sequence)):
        if sequence[window_end] == 1:
            number_of_ones_in_window += 1
        else:
            zeroes_in_window += 1

        # Shrink the window if we have enough ones
        while number_of_ones_in_window == k_ones:

            #Find the minimum number of zeroes
            min_zeroes = min(min_zeroes, zeroes_in_window)

            if sequence[window_start] == 1:
                number_of_ones_in_window -= 1
            else:
                zeroes_in_window -= 1

            window_start += 1

    # If no valid window was found.
    if min_zeroes == float('inf'):
        return -1

    return min_zeroes

Big(O) Analysis

Time Complexity
O(n) – The algorithm uses a sliding window approach. In the worst-case scenario, the right pointer of the window iterates through the entire input array of size n, and the left pointer also iterates through the array, but never goes backward and touches each element at most once. Therefore, each element is visited at most twice (once by the right pointer and once by the left pointer), making the overall time complexity linear with respect to the input size n.
Space Complexity
O(1) – The provided solution uses a sliding window approach, primarily relying on index variables to define the window's boundaries. It doesn't explicitly create or use any auxiliary data structures like arrays, hash maps, or recursion stacks that scale with the input size N (the length of the input). Therefore, the space complexity is constant, requiring only a fixed amount of extra memory regardless of the input size.

Edge Cases

Empty input array nums
How to Handle:
Return 0 if k is also 0, otherwise return -1 to indicate no solution exists.
k is zero
How to Handle:
Return 0, as no moves are needed to select zero ones.
k is greater than the number of ones in nums
How to Handle:
Return -1, indicating that it's impossible to pick k ones.
nums contains only zeros
How to Handle:
Return -1, as it's impossible to pick any ones.
nums contains only ones
How to Handle:
Return k-1, as the ones are already adjacent after optimal placement.
Integer overflow in calculating the cost (number of moves)
How to Handle:
Use long long data type to store the cost to prevent overflow issues.
Large input array with a small k
How to Handle:
The sliding window approach should still be efficient as its complexity depends on the number of ones, not the entire array size.
k equals the number of ones in the array and they are widely spaced
How to Handle:
The sliding window must find the configuration of k ones that minimizes moves, potentially requiring a full iteration.