Taro Logo

Build Array Where You Can Find The Maximum Exactly K Comparisons

Hard
Amazon logo
Amazon
2 views
Topics:
Dynamic Programming

You are given three integers n, m and k. Consider the following algorithm to find the maximum element of an array of positive integers:

Algorithm:
current_max = arr[0]
search_cost = 0
for i = 1 to arr.length - 1:
    if arr[i] > current_max:
        current_max = arr[i]
        search_cost = search_cost + 1
return search_cost

You should build the array arr which has the following properties:

  1. arr has exactly n integers.
  2. 1 <= arr[i] <= m where (0 <= i < n). In other words, each element of the array must be an integer between 1 and m (inclusive).
  3. After applying the mentioned algorithm to arr, the value search_cost is equal to k.

Return the number of ways to build the array arr under the mentioned conditions. As the answer may grow large, the answer must be computed modulo 10^9 + 7.

Example 1:

n = 2, m = 3, k = 1
Output: 6
Explanation: The possible arrays are [1, 2], [1, 3], [2, 1], [2, 2], [2, 3], [3, 1], [3, 2], [3, 3]. Note how [1,1] only has a search_cost of 0

Example 2:

n = 5, m = 2, k = 3
Output: 0
Explanation: There are no possible arrays that satisfy the mentioned conditions.

Example 3:

n = 9, m = 1, k = 1
Output: 1
Explanation: The only possible array is [1, 1, 1, 1, 1, 1, 1, 1, 1]. In this case the search_cost will always be 0.

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 maximum values for `n`, `m`, and `k`? Are there any constraints on their ranges?
  2. Can `n`, `m`, or `k` be zero? What should the function return in such cases?
  3. If it's impossible to build such an array with the given `n`, `m`, and `k`, what should the function return?
  4. Can the array contain duplicate numbers? If so, how does that affect the 'maximum element' comparisons?
  5. Is it guaranteed that `k` will always be less than or equal to `n`? If `k > n`, what is the expected behavior?

Brute Force Solution

Approach

The brute force approach solves this by checking every single possible array we could make. It's like trying every combination of building blocks until we find one that fits our requirements exactly.

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

  1. Consider every possible array by trying all possible values for each position in the array.
  2. For each of these arrays, run the comparison algorithm that finds the maximum element and counts the number of comparisons.
  3. If the number of comparisons matches the required number and the array length matches the required length, then we've found a valid array, so we count it.
  4. Repeat this process for all possible arrays.
  5. The total number of valid arrays we found after checking all possibilities is our final answer.

Code Implementation

def build_array_brute_force(array_length, max_value, target_comparisons):
    valid_array_count = 0

    def comparison_count(array):
        comparison_amount = 0
        if not array:
            return 0
        maximum_value = array[0]
        for i in range(1, len(array)):
            if array[i] > maximum_value:
                maximum_value = array[i]
                comparison_amount += 1
        return comparison_amount

    def generate_arrays(current_array):
        nonlocal valid_array_count

        if len(current_array) == array_length:
            # Check if the array matches the conditions
            if comparison_count(current_array) == target_comparisons:
                valid_array_count += 1
            return

        # Explore possibilities by adding values to the array
        for value in range(1, max_value + 1):
            generate_arrays(current_array + [value])

    # Start the recursive process
    generate_arrays([])
    return valid_array_count

Big(O) Analysis

Time Complexity
O(m^n)The brute force approach iterates through all possible arrays of length n where each element can have a value from 1 to m. Thus, there are m choices for each of the n positions in the array. This results in m * m * ... * m (n times) which is m^n possible arrays. For each of these arrays, we perform a linear comparison algorithm to find the max, which takes O(n) time. Therefore, the overall time complexity is O(n * m^n). Since m is part of the input, we can approximate this as O(m^n), reflecting the dominant exponential factor of trying every possible array combination.
Space Complexity
O(1)The brute force approach, as described, iterates through all possible arrays and checks each one. While it implicitly generates arrays, it does not persistently store them. The algorithm only needs to hold a few integer variables: to track the array length, the number of comparisons made, and potentially the maximum element found in each considered array. Therefore, the space used remains constant and independent of the input parameters n, m, and k.

Optimal Solution

Approach

This problem seems tricky, but the best way to tackle it is using a clever method called dynamic programming. We break down the big problem into smaller, overlapping subproblems, and store the answers to these subproblems to avoid recalculating them later. This avoids trying every single possibility, making the solution much faster.

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

  1. Imagine we're building our answer piece by piece. The key idea is to figure out, for a specific length of the array and a specific maximum value, how many ways are there to build the array using exactly the given number of comparisons.
  2. We can keep track of how many comparisons we've used and the highest number we've seen so far.
  3. Think about the last number we add to the array. It could be smaller than the current maximum, or it could be the new maximum. If it's the new maximum, that's when a comparison happens.
  4. For each possibility, we look at how we could have built the array *before* adding that last number. We should have already figured out how to do that because we are solving the subproblems in order of size.
  5. We add up all the ways to build the array up to the last number. Storing intermediate results avoids recalculating the same arrangements again.
  6. By carefully building up our solution from the smallest cases to the largest, we ultimately arrive at the total number of arrays that meet the requirements.

Code Implementation

def build_array(array_length, max_value, target_cost):
    modulo = 10**9 + 7
    # Initialize a DP table to store results of subproblems
    dp_table = [[[0] * (target_cost + 1) for _ in range(max_value + 1)] for _ in range(array_length + 1)]

    dp_table[0][0][0] = 1

    for current_length in range(1, array_length + 1):
        for current_max in range(1, max_value + 1):
            for current_cost in range(target_cost + 1):
                # If we don't introduce a new max value
                dp_table[current_length][current_max][current_cost] += (dp_table[current_length - 1][current_max][current_cost] * current_max) % modulo
                dp_table[current_length][current_max][current_cost] %= modulo

                # If we introduce a new max value
                if current_cost > 0:
                    # Adding a new max increases the search cost
                    for previous_max in range(1, current_max):
                        dp_table[current_length][current_max][current_cost] += dp_table[current_length - 1][previous_max][current_cost - 1]
                        dp_table[current_length][current_max][current_cost] %= modulo

    result = 0
    # Sum up all possibilities where the max value is achieved
    for current_max in range(1, max_value + 1):
        result += dp_table[array_length][current_max][target_cost]
        result %= modulo

    return result

Big(O) Analysis

Time Complexity
O(n*m*k*max_val)The dynamic programming solution involves a 4-dimensional state: the length of the array being built (up to n), the number of comparisons used (up to k), the maximum value seen so far (up to m), and the highest possible number that could be inserted into the array (up to max_val). We iterate through all possible states in our dynamic programming table. Therefore, the time complexity depends on the number of states (n*m*k*max_val), and we perform constant-time operations to fill in each state based on previously computed states. This constant time operation includes checking all possible ways we could have built the array *before* adding that last number, using previously computed subproblems.
Space Complexity
O(n*m*k)The dynamic programming approach described stores intermediate results in a multi-dimensional array (or table) to avoid recalculating subproblems. This table tracks the length of the array being built (up to n), the maximum value seen so far (up to m), and the number of comparisons used (up to k). Therefore, the auxiliary space required to store this table is proportional to the product of these three dimensions: n * m * k, where n is the desired length of the array, m is the bound on values in the array, and k is the exact number of comparisons required. This directly corresponds to the 'storing intermediate results' described in the explanation. Thus, the space complexity is O(n*m*k).

Edge Cases

CaseHow to Handle
n = 0, m > 0, k > 0 (invalid input)Return 0 since an empty array cannot satisfy the conditions for k > 0.
n > 0, m = 0, k > 0 (invalid input)Return 0 since there are no possible values that would result in k comparisons.
n > 0, m > 0, k = 0 (invalid input)Return 0, because it's impossible to find the maximum with zero comparisons.
n > 0, m > 0, k > n (invalid input - k exceeds the possible comparisons)Return 0 since at most n-1 comparisons can be made to determine the maximum.
n > 0, m = 1, k = 1 (Only one value allowed and we must find the max with 1 comparison)Return 1, since the only possible array contains all 1's, and the first element is the max after one comparison
Large n and m values causing potential integer overflow during calculation.Use modular arithmetic during calculation and ensure intermediate values don't exceed the integer limit.
m < 1 and k >= 1 (invalid input range for m)Return 0, as m must be at least 1 to create valid elements in the array.
n=1, k=1, m>0 (array of size 1, k=1 comparison)Return m since only one comparison is needed and the first element is the maximum