Taro Logo

Constrained Subsequence Sum

Medium
2 months ago

Given an integer array nums and an integer k, return the maximum sum of a non-empty subsequence of that array such that for every two consecutive integers in the subsequence, nums[i] and nums[j], where i < j, the condition j - i <= k is satisfied.

A subsequence of an array is obtained by deleting some number of elements (can be zero) from the array, leaving the remaining elements in their original order.

For example:

Input: nums = [10,2,-10,5,20], k = 2 Output: 37 Explanation: The subsequence is [10, 2, 5, 20].

Input: nums = [-1,-2,-3], k = 1 Output: -1 Explanation: The subsequence must be non-empty, so we choose the largest number.

Input: nums = [10,-2,-10,-5,20], k = 2 Output: 23 Explanation: The subsequence is [10, -2, -5, 20].

Sample Answer
## Maximum Sum of Non-empty Subsequence with Distance Constraint

### Problem Description

Given an integer array `nums` and an integer `k`, the goal is to find the maximum sum of a **non-empty** subsequence such that for every two consecutive integers `nums[i]` and `nums[j]` (where `i < j`), the condition `j - i <= k` is satisfied. A subsequence is obtained by deleting some elements from the array, keeping the remaining elements in their original order.

### Naive Approach (Brute Force)

A brute-force approach would involve generating all possible subsequences and checking if each subsequence satisfies the given condition `j - i <= k`. For each valid subsequence, we calculate its sum and keep track of the maximum sum found so far. This approach is highly inefficient due to the exponential number of possible subsequences.

```python
def max_subsequence_sum_brute_force(nums, k):
    n = len(nums)
    max_sum = float('-inf')

    for i in range(1 << n):
        subsequence = []
        indices = []
        for j in range(n):
            if (i >> j) & 1:
                subsequence.append(nums[j])
                indices.append(j)

        if not subsequence:
            continue

        is_valid = True
        for l in range(1, len(indices)):
            if indices[l] - indices[l-1] > k:
                is_valid = False
                break

        if is_valid:
            max_sum = max(max_sum, sum(subsequence))

    return max_sum

Complexity Analysis of Brute Force Approach:

  • Time Complexity: O(2n * n), where n is the number of elements in nums. This is because we generate 2n subsequences and, for each subsequence, we iterate through its elements (up to n elements).
  • Space Complexity: O(n), where n is the number of elements in nums. This is because, in the worst case, we store a subsequence containing all the elements of nums.

Optimal Solution: Dynamic Programming with Heap (Priority Queue)

We can use dynamic programming to solve this problem efficiently. Let dp[i] be the maximum sum of a valid subsequence ending at index i. The recurrence relation is as follows:

dp[i] = nums[i] + max(0, dp[j]) for all j such that i - k <= j < i

To find the maximum dp[j] efficiently, we can use a max heap (priority queue) to store the dp values along with their indices. The heap allows us to quickly retrieve the maximum dp[j] within the k window and remove outdated dp values.

import heapq

def max_subsequence_sum(nums, k):
    n = len(nums)
    dp = [float('-inf')] * n
    heap = []  # (dp[i], i)
    max_sum = float('-inf')

    for i in range(n):
        # Remove outdated dp values from the heap
        while heap and heap[0][1] < i - k:
            heapq.heappop(heap)

        # Calculate dp[i]
        if heap:
            dp[i] = nums[i] + max(0, -heap[0][0])  # Heap stores negative values
        else:
            dp[i] = nums[i]

        max_sum = max(max_sum, dp[i])

        # Add dp[i] to the heap (negated for max heap)
        heapq.heappush(heap, (-dp[i], i))

    # Edge case: all numbers are negative
    if max_sum == float('-inf'):
        return max(nums)

    return max_sum

Explanation:

  1. Initialize a dp array with negative infinity and a max heap heap to store (dp[i], i) pairs.
  2. Iterate through the nums array from i = 0 to n - 1.
  3. Remove elements from the heap whose indices are outside the k window (i.e., i - k > j).
  4. Calculate dp[i] as nums[i] + max(0, dp[j]) where dp[j] is the maximum dp value within the k window. We use 0 to avoid including negative sums.
  5. Update max_sum with the maximum value encountered so far.
  6. Add dp[i] to the heap. Note that we store -dp[i] in the heap to simulate a max heap with heapq.
  7. After the loop, return max_sum.

Edge Cases

  1. Empty array: The problem statement specifies that the array is non-empty, so we don't need to handle this case.
  2. k = 0: The problem statement specifies that k >= 1. The constraint j - i <= k ensures that only consecutive elements or elements that are nearby can be part of subsequence. k = 0 would not make sense in this context.
  3. All negative numbers: If all numbers are negative, the maximum sum would be the largest negative number. The provided code handles this edge case by returning the max element of nums if max_sum is still negative infinity.

Big O Runtime Analysis

  • Time Complexity: O(n log n), where n is the number of elements in nums. The main loop iterates n times. Inside the loop, the heapq.heappop and heapq.heappush operations take O(log n) time. Therefore, the overall time complexity is dominated by the heap operations, resulting in O(n log n).

Big O Space Usage Analysis

  • Space Complexity: O(n), where n is the number of elements in nums. The dp array stores n values, and the heap can store at most n elements in the worst case. Therefore, the space complexity is O(n).