Taro Logo

Maximum Sum of Subsequence With Non-adjacent Elements

Hard
Google logo
Google
4 views
Topics:
ArraysDynamic Programming

You are given an array nums consisting of integers. You are also given a 2D array queries, where queries[i] = [pos_i, x_i]. For query i, you first set nums[pos_i] equal to x_i, then calculate the answer to query i which is the maximum sum of a subsequence of nums where no two adjacent elements are selected. Return the sum of the answers to all queries modulo 10^9 + 7.

A subsequence is an array that can be derived from another array by deleting some or no elements without changing the order of the remaining elements.

For example: nums = [3,5,9], queries = [[1,-2],[0,-3]] After the 1st query, nums = [3,-2,9] and the maximum sum of a subsequence with non-adjacent elements is 3 + 9 = 12. After the 2nd query, nums = [-3,-2,9] and the maximum sum of a subsequence with non-adjacent elements is 9. The answer is 12 + 9 = 21

As another example: nums = [0,-1], queries = [[0,-5]] After the 1st query, nums = [-5,-1] and the maximum sum of a subsequence with non-adjacent elements is 0 (choosing an empty subsequence). The answer is 0.

How would you approach this problem efficiently?

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. Can the input array contain negative numbers or zero?
  2. What is the expected data type of the array elements (e.g., integers, floats)?
  3. What should I return if the input array is null or empty?
  4. If there are multiple subsequences with the maximum sum, can I return any one of them?
  5. What is the maximum possible size of the input array?

Brute Force Solution

Approach

The brute force approach to finding the maximum sum involves considering every possible combination of numbers from the list where you're not allowed to pick two adjacent numbers. This is done by methodically going through each possible selection of numbers.

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

  1. Start by considering the possibility of picking no numbers at all, which has a sum of zero.
  2. Then, look at picking just the first number, then just the second number, then just the third number, and so on, one at a time.
  3. Next, look at all the ways to pick two numbers that are not next to each other. For example, pick the first and third numbers, or the second and fourth numbers.
  4. Continue doing this for all possible combinations of three numbers, four numbers, and so on, always making sure that you never pick two numbers that are right next to each other in the original list.
  5. For each combination of numbers you pick, calculate their sum.
  6. Keep track of the largest sum you've seen so far.
  7. After you've considered every possible combination, the largest sum you kept track of is the answer.

Code Implementation

def find_maximum_sum_brute_force(number_list):
    maximum_sum = 0
    number_list_length = len(number_list)

    # Iterate through all possible subsets of the number list
    for i in range(1 << number_list_length):
        current_sum = 0
        subset = []
        is_valid_subset = True

        for j in range(number_list_length):
            # Check if j-th element is included in the current subset
            if (i >> j) & 1:
                subset.append(j)

        # Validate that the subset has no adjacent elements
        for k in range(len(subset)):
            if k > 0 and subset[k] == subset[k-1] + 1:

                is_valid_subset = False
                break

        # Only process valid subsets
        if is_valid_subset:

            # Sum elements of the valid subset
            for index in subset:
                current_sum += number_list[index]

            # Update maximum sum if needed
            maximum_sum = max(maximum_sum, current_sum)

    return maximum_sum

Big(O) Analysis

Time Complexity
O(2^n)The brute force approach explores every possible subset of the input array. For each element, we have two choices: either include it in the subset or exclude it. This leads to 2 * 2 * ... * 2 (n times) possible subsets, where n is the size of the input array. Thus, the total number of operations grows exponentially with the input size. Specifically, we're looking at O(2^n) subsets.
Space Complexity
O(1)The brute force approach, as described, does not use any auxiliary data structures that scale with the input size N (the number of elements in the input list). It only keeps track of the largest sum seen so far, which is a single variable. The intermediate sums calculated for each combination are not stored persistently. Therefore, the space complexity remains constant regardless of the input size.

Optimal Solution

Approach

The goal is to find the biggest sum you can get from a list of numbers, but you can't pick numbers that are right next to each other. We'll use a strategy where, as we go through the numbers, we remember the best result we can achieve so far if we include the current number and the best we can achieve if we exclude the current number.

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

  1. Imagine two containers. One container holds the best sum we've found so far that *includes* the current number we're looking at.
  2. The other container holds the best sum we've found so far that *excludes* the current number.
  3. Now, go through the numbers one by one.
  4. For each number, update our containers. The 'include' container will now hold the value of the 'exclude' container from the previous step (since we can't include the previous number if we are now including the current number) plus the current number's value.
  5. The 'exclude' container will hold the larger value between the previous 'include' container and the previous 'exclude' container. This is because if we're excluding the current number, we want the best sum we could have gotten up to that point, whether or not it included the previous number.
  6. After going through all the numbers, the larger of the two containers will hold the maximum possible sum of non-adjacent numbers.

Code Implementation

def max_subset_sum_no_adjacent(array):
    if not array:
        return 0
    if len(array) == 1:
        return array[0]

    include_current = array[0]
    exclude_current = 0

    for i in range(1, len(array)):
        # Best sum *including* current element
        new_include = exclude_current + array[i]

        # Best sum *excluding* current element
        new_exclude = max(include_current, exclude_current)

        include_current = new_include

        exclude_current = new_exclude

    # Return the larger of including or excluding
    return max(include_current, exclude_current)

Big(O) Analysis

Time Complexity
O(n)The algorithm iterates through the input array of n elements exactly once. Inside the loop, it performs a fixed number of operations (addition, comparison, and assignment), which take constant time. Since the number of operations scales linearly with the input size n, the time complexity is O(n).
Space Complexity
O(1)The algorithm uses two variables, 'include' and 'exclude', to store the maximum sums. These variables hold single numerical values and their memory usage remains constant irrespective of the input array's size, N. No additional data structures like arrays or hash maps are used. Therefore, the auxiliary space complexity is constant.

Edge Cases

CaseHow to Handle
Null or empty input arrayReturn 0 if the input is null or empty because there are no elements to sum.
Array with only one elementReturn the single element in the array as the maximum sum of a subsequence with non-adjacent elements.
Array with two elementsReturn the larger of the two elements, as they are non-adjacent.
Array with all negative numbersReturn 0, as an empty subsequence will have a greater sum than any subsequence containing a negative number.
Array with all zerosReturn the sum of all elements, which is 0.
Array with a mix of positive and negative numbersThe dynamic programming approach correctly handles this by considering both including and excluding each element.
Array with very large positive numbers that could lead to integer overflowUse a data type that can accommodate large sums, such as long or BigInteger depending on the language.
Large array size that could lead to memory issues (for recursive solutions)Use an iterative dynamic programming approach instead of recursion to avoid stack overflow errors and improve space complexity.