Facebook Pixel

3266. Final Array State After K Multiplication Operations II


Problem Description

You are given an integer array nums, an integer k, and an integer multiplier. Your task is to perform k operations on nums. In each operation:

  • Identify the minimum value x in nums. If there are multiple occurrences of the minimum value, choose the one that appears first.
  • Replace the selected minimum value x with x * multiplier.

After completing all k operations, apply modulo (10^9 + 7) to each value in nums. Your goal is to return an integer array that represents the final state of nums after the k operations and the modulo application.

Intuition

To solve the problem efficiently, we can utilize a priority queue (min-heap) to track the minimum values in nums quickly. The core idea is to repeatedly find and multiply the minimum element until we perform k operations or reach a case where multiplying further is unnecessary (i.e., when all elements are greater than or equal to the maximum element m in nums).

  1. Initial Priority Queue: Use the min-heap to access the smallest element efficiently. This helps in quickly identifying and replacing the minimum value in each operation.

  2. Replacing Minimum Values: For each operation, replace the smallest element by multiplying it with multiplier. Push the updated value back into the heap to maintain the order.

  3. Handling Remaining Operations: Once the operations based on the heap are exhausted (either all elements exceed the initial max value or all k operations are performed), the distribution of remaining operations should be considered. Since each remaining operation involves turning the smallest element into the largest, the modulo of k divided by the size of nums gives the count of smaller elements which will receive one extra multiplication.

  4. Modular Arithmetic: After performing the necessary multiplications, apply the modulo operation to ensure values fit within the required range, using fast exponentiation for efficient calculation.

This approach balances the problem's requirements with computational efficiency, using a combination of heap operations, arithmetic manipulations, and modular constraints to achieve the desired outcome.

Learn more about Heap (Priority Queue) patterns.

Solution Approach

The solution uses a combination of a priority queue (min-heap) and modular arithmetic to achieve the desired transformations on the nums array.

Steps:

  1. Priority Queue Initialization:

    • Begin by converting nums into a min-heap using Python's heapq. This allows efficient retrieval and replacement of the minimum element.
    • Compile a list of tuples (x, i) where x is each element in nums and i is its index. This helps keep track of the original indices after modifying elements.
    pq = [(x, i) for i, x in enumerate(nums)]
    heapq.heapify(pq)
  2. Perform k Operations:

    • Execute up to k operations or until no change can occur (when the minimum element in the heap is not less than m, the maximum number in nums).
    • For each operation, pop the smallest element from the heap, multiply it by multiplier, and push it back.
    while k and pq[0][0] < m:
        x, i = heapq.heappop(pq)
        heapq.heappush(pq, (x * multiplier, i))
        k -= 1
  3. Final Adjustments:

    • If any operations remain, distribute them evenly across the elements using integer division and modulo operations.
    • Sort the heap to prepare for final transformations, precisely adjusting each element based on its number of necessary multiplications remaining.
    pq.sort()
    n = len(nums)
    mod = 10**9 + 7
    for i, (x, j) in enumerate(pq):
        nums[j] = x * pow(multiplier, k // n + int(i < k % n), mod) % mod
  4. Returning Results:

    • The modified nums array is returned, containing the final results of the operations after modulo.

The solution effectively leverages the properties of priority queues to handle frequent minimum element access and updates efficiently. By balancing initial and remaining operations, it efficiently produces the correct final state of nums.

Ready to land your dream job?

Unlock your dream job with a 2-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's consider a simple example where nums = [3, 5, 1, 4], k = 2, and multiplier = 3. We will walk through the solution approach step-by-step.

  1. Priority Queue Initialization:

    • Convert nums into a min-heap using Python's heapq. This allows us to efficiently retrieve and replace the minimum element.
    • Create a list of tuples (x, i) where x is the element and i is its index to track original positions. After initialization, the priority queue will be [(1, 2), (4, 3), (3, 0), (5, 1)].
  2. Perform k Operations:

    • First Operation: Pop (1, 2) from the heap (minimum element 1 at index 2), multiply it by 3 to get 3, and push (3, 2) back into the heap.
      • The heap is now [(3, 0), (3, 2), (5, 1), (4, 3)].
    • Second Operation: Pop (3, 0) from the heap (minimum element 3 at index 0), multiply it by 3 to get 9, and push (9, 0) back into the heap.
      • The heap is now [(3, 2), (4, 3), (5, 1), (9, 0)].
    • We have completed 2 operations (k = 0), so we stop here.
  3. Final Adjustments:

    • No operations remain (k = 0), so there's no need for further distribution adjustments.
  4. Returning Results:

    • Sort the heap to retrieve the final order of original indices. Then modify nums based on these.
    • Applying the sorted priority queue and considering initial indices, the final state of nums is [9, 5, 3, 4] after applying modulo 10^9 + 7.

Thus, after performing all k operations and applying the modulo transformation, the final array is [9, 5, 3, 4].

Solution Implementation

1from typing import List
2import heapq
3
4class Solution:
5    def getFinalState(self, nums: List[int], k: int, multiplier: int) -> List[int]:
6        # If the multiplier is 1, the list remains unchanged
7        if multiplier == 1:
8            return nums
9      
10        # Create a priority queue from the list of numbers with their indices
11        pq = [(x, i) for i, x in enumerate(nums)]
12        heapq.heapify(pq)  # Transform the list into a heap
13      
14        # Find the maximum value in the original list
15        m = max(nums)
16      
17        # Apply the multiplier to the smallest element 'k' times or until all elements are >= maximum
18        while k and pq[0][0] < m:
19            x, i = heapq.heappop(pq)  # Extract the smallest element
20            heapq.heappush(pq, (x * multiplier, i))  # Push back the updated element multiplied by the multiplier
21            k -= 1
22      
23        # Length of the list
24        n = len(nums)
25      
26        # Use modulo operation for large numbers
27        mod = 10**9 + 7
28      
29        # Sort the priority queue based on the updated values
30        pq.sort()
31      
32        # Update nums based on remaining 'k' and sorted priority queue
33        for i, (x, j) in enumerate(pq):
34            # Calculate new value by raising the multiplier to the power determined by remaining operations and position
35            power = k // n + (1 if i < k % n else 0)
36            nums[j] = x * pow(multiplier, power, mod) % mod
37      
38        return nums
39
1import java.util.PriorityQueue;
2import java.util.Arrays;
3
4class Solution {
5    public int[] getFinalState(int[] nums, int k, int multiplier) {
6        // If multiplier is 1, return the original array as no change will occur.
7        if (multiplier == 1) {
8            return nums;
9        }
10
11        // Initialize a priority queue that sorts elements based on their values,
12        // breaking ties with indices.
13        PriorityQueue<long[]> pq = new PriorityQueue<>(
14            (a, b) -> a[0] == b[0] ? Long.compare(a[1], b[1]) : Long.compare(a[0], b[0]));
15
16        int length = nums.length;
17        // Find the maximum value in the array.
18        int maxNum = Arrays.stream(nums).max().getAsInt();
19      
20        // Add all elements with their indices to the priority queue.
21        for (int i = 0; i < length; ++i) {
22            pq.offer(new long[] {nums[i], i});
23        }
24
25        // Multiply the smallest element by the multiplier for at most k times
26        // as long as the smallest element is less than the maximum value.
27        while (k > 0 && pq.peek()[0] < maxNum) {
28            long[] smallest = pq.poll();
29            smallest[0] *= multiplier;
30            pq.offer(smallest);
31            --k;
32        }
33
34        final int mod = (int) 1e9 + 7; // Modulus value to keep results within bounds.
35      
36        // Compute the final state considering remaining operations and modulus.
37        for (int i = 0; i < length; ++i) {
38            long[] current = pq.poll();
39            long value = current[0];
40            int index = (int) current[1];
41          
42            // Calculate the power multiplier effect based on number of full rounds
43            // and remaining operations, updating the nums in mod space.
44            nums[index] = (int) ((value % mod) * qpow(multiplier, k / length + (i < k % length ? 1 : 0), mod) % mod);
45        }
46        return nums;
47    }
48
49    // Helper method to compute power modulus: (a^n) % mod
50    private int qpow(long base, long exp, long mod) {
51        long result = 1 % mod; // To handle the case when mod is very small.
52        while (exp > 0) {
53            // If the current exponent's LSD is 1, multiply the base to the result.
54            if ((exp & 1) == 1) {
55                result = result * base % mod;
56            }
57            // Square the base and reduce the exponent by half.
58            base = base * base % mod;
59            exp >>= 1;
60        }
61        return (int) result;
62    }
63}
64
1class Solution {
2public:
3    vector<int> getFinalState(vector<int>& nums, int k, int multiplier) {
4        // If the multiplier is 1, the numbers remain unchanged.
5        if (multiplier == 1) {
6            return nums;
7        }
8
9        // Define short form for long long and pair<long long, int>.
10        using ll = long long;
11        using pli = pair<ll, int>;
12
13        // Custom comparator for the priority queue, sorting by the first value. If equal, sort by second in reverse.
14        auto cmp = [](const pli& a, const pli& b) {
15            if (a.first == b.first) {
16                return a.second > b.second;
17            }
18            return a.first > b.first;
19        };
20
21        // Priority queue (min-heap by default) to store pairs of number and their original index.
22        priority_queue<pli, vector<pli>, decltype(cmp)> pq(cmp);
23
24        int n = nums.size();
25        int m = *max_element(nums.begin(), nums.end()); // Find the maximum element in nums.
26
27        // Initialize the priority queue with the numbers and their indices.
28        for (int i = 0; i < n; ++i) {
29            pq.emplace(nums[i], i);
30        }
31
32        // Apply the multiplier to the smallest elements until k iterations or until all numbers are >= max value.
33        while (k > 0 && pq.top().first < m) {
34            auto p = pq.top(); // Get the smallest element.
35            pq.pop();          // Remove it from the queue.
36            p.first *= multiplier; // Multiply it by the multiplier.
37            pq.emplace(p);     // Push it back with the updated value.
38            --k;               // Decrease the number of allowed operations.
39        }
40
41        // Exponentiation by squaring function to calculate (a^n) % mod.
42        auto qpow = [&](ll a, ll n, ll mod) {
43            ll ans = 1 % mod;
44            a = a % mod;
45            while (n > 0) {
46                // If n is odd, multiply the answer with the current base.
47                if (n & 1) {
48                    ans = ans * a % mod;
49                }
50                a = a * a % mod; // Square the base.
51                n >>= 1;         // Shift right for division by 2.
52            }
53            return ans;
54        };
55
56        const int mod = 1e9 + 7; // Define modulus to handle large numbers.
57
58        // Adjust each number to account for any remaining multiplier effects using the power function.
59        for (int i = 0; i < n; ++i) {
60            auto p = pq.top(); // Get the top element from the queue.
61            pq.pop();          // Remove it from the queue.
62            long long x = p.first;
63            int j = p.second;
64            // Calculate the number of remaining multiplications and adjust the value.
65            nums[j] = static_cast<int>((x % mod) * qpow(multiplier, k / n + (i < k % n ? 1 : 0), mod) % mod);
66        }
67
68        return nums; // Return the modified array.
69    }
70};
71
1// Define a type for a pair of a number and its original index.
2type Pair = [number, number];
3
4// Custom comparator function for sorting pairs in the priority queue.
5const comparator = (a: Pair, b: Pair): boolean => {
6    if (a[0] === b[0]) {
7        return a[1] > b[1];
8    }
9    return a[0] > b[0];
10};
11
12// Function to perform exponentiation by squaring. Calculates (base^exp) % mod.
13const qpow = (base: number, exp: number, mod: number): number => {
14    let result = 1 % mod;
15    base = base % mod;
16    while (exp > 0) {
17        // If the exponent is odd, multiply result with the current base.
18        if (exp & 1) {
19            result = (result * base) % mod;
20        }
21        base = (base * base) % mod; // Square the base.
22        exp >>= 1; // Divide the exponent by 2.
23    }
24    return result;
25};
26
27// Main function to calculate the final state of the array after applying the multiplier.
28const getFinalState = (nums: number[], k: number, multiplier: number): number[] => {
29    // If multiplier is 1, the numbers remain unchanged.
30    if (multiplier === 1) {
31        return nums;
32    }
33
34    const n = nums.length;
35    const maxNum = Math.max(...nums); // Find the maximum element in nums.
36
37    // Priority queue (min-heap) to store pairs of number and their original index.
38    const pq = new MinHeap<Pair>(comparator);
39
40    // Initialize the priority queue with the numbers and their indices.
41    for (let i = 0; i < n; ++i) {
42        pq.insert([nums[i], i]);
43    }
44
45    // Apply the multiplier to the smallest elements until k operations are done or until all numbers are >= max value.
46    while (k > 0 && pq.peak()[0] < maxNum) {
47        const p = pq.extract(); // Get the smallest element.
48        p[0] *= multiplier; // Multiply it by the multiplier.
49        pq.insert(p); // Push it back with the updated value.
50        --k; // Decrease the number of allowed operations.
51    }
52
53    const mod = 1e9 + 7; // Define modulus to handle large numbers.
54
55    // Adjust each number to account for any remaining multiplier effects using the power function.
56    for (let i = 0; i < n; ++i) {
57        const p = pq.extract(); // Get the top element from the queue.
58        const value = p[0];
59        const index = p[1];
60        // Calculate remaining multiplications and adjust the value.
61        nums[index] = (value % mod) * qpow(multiplier, Math.floor(k / n) + (i < k % n ? 1