Maximum Balanced Subsequence Sum
HardPrompt
You are given a 0-indexed integer array nums.
A subsequence of nums having length k and consisting of indices i0 < i1 < ... < ik-1 is balanced if the following holds:
nums[ij] - nums[ij-1] >= ij - ij-1, for everyjin the range[1, k - 1].
A subsequence of nums having length 1 is considered balanced.
Return an integer denoting the maximum possible sum of elements in a balanced subsequence of nums.
A subsequence of an array is a new non-empty array that is formed from the original array by deleting some (possibly none) of the elements without disturbing the relative positions of the remaining elements.
Example 1:
Input: nums = [3,3,5,6]
Output: 14
Explanation: In this example, the subsequence [3,5,6] consisting of indices 0, 2, and 3 can be selected.
nums[2] - nums[0] >= 2 - 0.
nums[3] - nums[2] >= 3 - 2.
Hence, it is a balanced subsequence, and its sum is the maximum among the balanced subsequences of nums.
The subsequence consisting of indices 1, 2, and 3 is also valid.
It can be shown that it is not possible to get a balanced subsequence with a sum greater than 14.Example 2:
Input: nums = [5,-1,-3,8]
Output: 13
Explanation: In this example, the subsequence [5,8] consisting of indices 0 and 3 can be selected.
nums[3] - nums[0] >= 3 - 0.
Hence, it is a balanced subsequence, and its sum is the maximum among the balanced subsequences of nums.
It can be shown that it is not possible to get a balanced subsequence with a sum greater than 13.Example 3:
Input: nums = [-2,-1]
Output: -1
Explanation: In this example, the subsequence [-1] can be selected.
It is a balanced subsequence, and its sum is the maximum among the balanced subsequences of nums.
Constraints:
1 <= nums.length <= 105-109 <= nums[i] <= 109
Approaches
2 approaches with complexity analysis and trade-offs.
This approach uses dynamic programming to solve the problem. First, we transform the problem's condition. The condition nums[i_j] - nums[i_{j-1}] >= i_j - i_{j-1} can be rewritten as nums[i_j] - i_j >= nums[i_{j-1}] - i_{j-1}. Let's define a new array b where b[i] = nums[i] - i. The problem now is to find a subsequence with non-decreasing b values that has the maximum sum of corresponding nums values.
We define dp[i] as the maximum sum of a balanced subsequence ending at index i. To compute dp[i], we look at all previous indices j < i. If b[j] <= b[i], it means we can extend a balanced subsequence ending at j with the element at i. We choose the j that gives the maximum dp[j] to maximize the new sum.
Algorithm
- Create a
dparray of sizen, wherenis the length ofnums. - Initialize a variable
max_sumto the smallest possible long value. - Loop
ifrom0ton-1:- Initialize
maxPrevSum = 0. - Loop
jfrom0toi-1:- If
(long)nums[j] - j <= (long)nums[i] - i, updatemaxPrevSum = Math.max(maxPrevSum, dp[j]).
- If
- Set
dp[i] = nums[i] + maxPrevSum. - Update
max_sum = Math.max(max_sum, dp[i]).
- Initialize
- Return
max_sum.
Walkthrough
The recurrence relation is dp[i] = nums[i] + max({0} U {dp[j] | 0 <= j < i and b[j] <= b[i]}), where b[k] = nums[k] - k.
We iterate through each element of the array from left to right. For each element nums[i], we calculate the maximum possible sum of a balanced subsequence ending at this index. This is done by finding the maximum sum of a valid preceding subsequence and adding nums[i] to it. A preceding subsequence ending at index j is valid if j < i and nums[j] - j <= nums[i] - i. If no such valid preceding subsequence exists or if all of them have a sum less than or equal to zero, we can start a new subsequence with just nums[i], which means we add 0 to nums[i].
The algorithm is as follows:
- Create an array
dpof the same size asnums, wheredp[i]will store the maximum sum of a balanced subsequence ending at indexi. - Iterate through the
numsarray with indexifrom 0 ton-1. - For each
i, initialize a variablemaxPrevSumto 0. - Start an inner loop with index
jfrom 0 toi-1. - Inside the inner loop, check if the balanced condition is met:
(long)nums[j] - j <= (long)nums[i] - i. - If the condition is met, update
maxPrevSum = Math.max(maxPrevSum, dp[j]). - After the inner loop, calculate
dp[i] = nums[i] + maxPrevSum. - The final answer is the maximum value found in the
dparray.
import java.util.Arrays; class Solution { public long maxBalancedSubsequenceSum(int[] nums) { int n = nums.length; long[] dp = new long[n]; long maxSum = Long.MIN_VALUE; for (int i = 0; i < n; i++) { long maxPrevSum = 0; for (int j = 0; j < i; j++) { if ((long)nums[j] - j <= (long)nums[i] - i) { maxPrevSum = Math.max(maxPrevSum, dp[j]); } } dp[i] = (long)nums[i] + maxPrevSum; maxSum = Math.max(maxSum, dp[i]); } return maxSum; }}Complexity
Time
O(n^2), where `n` is the length of the `nums` array. The nested loops dominate the runtime.
Space
O(n) to store the `dp` array.
Trade-offs
Pros
Simple to understand and implement.
Correctly solves the problem based on the DP formulation.
Cons
Inefficient due to the nested loops.
Time complexity of O(n^2) is too slow for the given constraints (
n <= 10^5), leading to a "Time Limit Exceeded" error on large inputs.
The O(n^2) DP approach can be optimized. The bottleneck is the inner loop which finds the maximum dp[j] among all j < i satisfying b[j] <= b[i] (where b[i] = nums[i] - i). This subproblem is a range maximum query. We can use a data structure to perform this query efficiently. A Fenwick Tree (also known as a Binary Indexed Tree or BIT) is suitable for this.
Since the values of b[i] can be large and negative, we first need to perform coordinate compression on them. We map each unique b[i] value to a smaller, non-negative integer rank. Then, we use a Fenwick Tree that supports range maximum queries. As we iterate through i from 0 to n-1, we query the Fenwick Tree for the maximum dp value for all b values less than or equal to the current b[i], calculate the new dp[i], and then update the Fenwick Tree with this new value at the rank corresponding to b[i].
Algorithm
- Create an array
bwhereb[i] = nums[i] - i. - Perform coordinate compression on
b:- Get the unique values of
b. - Sort these unique values.
- Create a map from each unique value to its sorted index (rank).
- Get the unique values of
- Initialize a Fenwick Tree
ftof size equal to the number of uniquebvalues. The tree should support range maximum queries and its neutral element should be 0. - Initialize
maxSumtoLong.MIN_VALUE. - Loop
ifrom0ton-1:- Get the rank of
b[i]from the map. - Query
ftup to this rank to getprevMaxSum. This value representsmax(0, max_{j<i, b[j]<=b[i]} dp[j]). - Calculate
currentSum = nums[i] + prevMaxSum. - Update
ftat the rank ofb[i]withcurrentSum. - Update
maxSum = max(maxSum, currentSum).
- Get the rank of
- Return
maxSum.
Walkthrough
The core idea is to maintain a data structure that, for each possible value of v = nums[j] - j, stores the maximum balanced subsequence sum ending with that v. When we process nums[i], we need to find the maximum sum among all subsequences that can precede it. These are the ones ending at j < i where nums[j] - j <= nums[i] - i.
The algorithm is as follows:
- Create a new array
bwhereb[i] = nums[i] - i. - The values in
bcan be large, so we perform coordinate compression. We collect all unique values fromb, sort them, and create a map from each value to its rank. - Initialize a Fenwick Tree (BIT) designed for range maximum queries. The size of the BIT will be the number of unique values in
b. Its neutral element for queries should be 0, representing the choice to start a new subsequence. - Iterate through the
numsarray fromi = 0ton-1. - For each
i, find the rank ofb[i]. - Query the BIT to find the maximum value in the range up to
b[i]'s rank. This givesmax(0, max_{j<i, b[j]<=b[i]} dp[j]). Let's call thisprevMaxSum. - Calculate the maximum sum for a balanced subsequence ending at
i:currentSum = nums[i] + prevMaxSum. - Update the BIT at
b[i]'s rank withcurrentSum. - Keep track of the overall maximum sum found so far.
- The maximum of all
currentSumvalues calculated is the result.
import java.util.*; class Solution { static class FenwickTree { private long[] tree; private int size; private static final long NEUTRAL_ELEMENT = 0L; public FenwickTree(int size) { this.size = size; this.tree = new long[size + 1]; } public void update(int idx, long val) { idx++; // 1-based index while (idx <= size) { tree[idx] = Math.max(tree[idx], val); idx += idx & -idx; } } public long query(int idx) { idx++; // 1-based index long maxVal = NEUTRAL_ELEMENT; while (idx > 0) { maxVal = Math.max(maxVal, tree[idx]); idx -= idx & -idx; } return maxVal; } } public long maxBalancedSubsequenceSum(int[] nums) { int n = nums.length; int[] b = new int[n]; for (int i = 0; i < n; i++) { b[i] = nums[i] - i; } Set<Integer> uniqueBSet = new HashSet<>(); for (int val : b) { uniqueBSet.add(val); } List<Integer> sortedUniqueB = new ArrayList<>(uniqueBSet); Collections.sort(sortedUniqueB); Map<Integer, Integer> rankMap = new HashMap<>(); for (int i = 0; i < sortedUniqueB.size(); i++) { rankMap.put(sortedUniqueB.get(i), i); } FenwickTree ft = new FenwickTree(sortedUniqueB.size()); long maxSum = Long.MIN_VALUE; for (int i = 0; i < n; i++) { int currentRank = rankMap.get(b[i]); long prevMaxSum = ft.query(currentRank); long currentSum = (long)nums[i] + prevMaxSum; ft.update(currentRank, currentSum); maxSum = Math.max(maxSum, currentSum); } return maxSum; }}Complexity
Time
O(n log n). Coordinate compression takes O(n log n). The main loop runs `n` times, with each Fenwick Tree operation taking O(log n) time.
Space
O(n). This includes space for the `b` array, coordinate compression map, and the Fenwick Tree, each of which can be up to O(n) in size.
Trade-offs
Pros
Highly efficient with O(n log n) time complexity, which passes the constraints.
Solves a more general class of problems (Maximum Sum Non-decreasing Subsequence on a transformed array).
Cons
More complex to implement than the brute-force DP.
Requires understanding of advanced data structures like Fenwick Trees and coordinate compression.
Solutions
Solution
class BinaryIndexedTree { private int n ; private long [] c ; private final long inf = 1L << 60 ; public BinaryIndexedTree ( int n ) { this . n = n ; c = new long [ n + 1 ]; Arrays . fill ( c , - inf ); } public void update ( int x , long v ) { while ( x <= n ) { c [ x ] = Math . max ( c [ x ], v ); x += x & - x ; } } public long query ( int x ) { long mx = - inf ; while ( x > 0 ) { mx = Math . max ( mx , c [ x ]); x -= x & - x ; } return mx ; } } class Solution { public long maxBalancedSubsequenceSum ( int [] nums ) { int n = nums . length ; int [] arr = new int [ n ]; for ( int i = 0 ; i < n ; ++ i ) { arr [ i ] = nums [ i ] - i ; } Arrays . sort ( arr ); int m = 0 ; for ( int i = 0 ; i < n ; ++ i ) { if ( i == 0 || arr [ i ] != arr [ i - 1 ]) { arr [ m ++] = arr [ i ]; } } BinaryIndexedTree tree = new BinaryIndexedTree ( m ); for ( int i = 0 ; i < n ; ++ i ) { int j = search ( arr , nums [ i ] - i , m ) + 1 ; long v = Math . max ( tree . query ( j ), 0 ) + nums [ i ]; tree . update ( j , v ); } return tree . query ( m ); } private int search ( int [] nums , int x , int r ) { int l = 0 ; while ( l < r ) { int mid = ( l + r ) >> 1 ; if ( nums [ mid ] >= x ) { r = mid ; } else { l = mid + 1 ; } } return l ; } }class BinaryIndexedTree { private: int n ; vector < long long > c ; const long long inf = 1e18 ; public: BinaryIndexedTree ( int n ) { this -> n = n ; c . resize ( n + 1 , - inf ); } void update ( int x , long long v ) { while ( x <= n ) { c [ x ] = max ( c [ x ], v ); x += x & - x ; } } long long query ( int x ) { long long mx = - inf ; while ( x > 0 ) { mx = max ( mx , c [ x ]); x -= x & - x ; } return mx ; } }; class Solution { public: long long maxBalancedSubsequenceSum ( vector < int >& nums ) { int n = nums . size (); vector < int > arr ( n ); for ( int i = 0 ; i < n ; ++ i ) { arr [ i ] = nums [ i ] - i ; } sort ( arr . begin (), arr . end ()); arr . erase ( unique ( arr . begin (), arr . end ()), arr . end ()); int m = arr . size (); BinaryIndexedTree tree ( m ); for ( int i = 0 ; i < n ; ++ i ) { int j = lower_bound ( arr . begin (), arr . end (), nums [ i ] - i ) - arr . begin () + 1 ; long long v = max ( tree . query ( j ), 0LL ) + nums [ i ]; tree . update ( j , v ); } return tree . query ( m ); } };class BinaryIndexedTree : def __init__ ( self , n : int ): self . n = n self . c = [ - inf ] * ( n + 1 ) def update ( self , x : int , v : int ): while x <= self . n : self . c [ x ] = max ( self . c [ x ], v ) x += x & - x def query ( self , x : int ) -> int : mx = - inf while x : mx = max ( mx , self . c [ x ]) x -= x & - x return mx class Solution : def maxBalancedSubsequenceSum ( self , nums : List [ int ]) -> int : arr = [ x - i for i , x in enumerate ( nums )] s = sorted ( set ( arr )) tree = BinaryIndexedTree ( len ( s )) for i , x in enumerate ( nums ): j = bisect_left ( s , x - i ) + 1 v = max ( tree . query ( j ), 0 ) + x tree . update ( j , v ) return tree . query ( len ( s ))Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.