Given an array of positive integers arr[], find the maximum sum of a subsequence such that the elements of the subsequence form a strictly increasing sequence. In other words, among all strictly increasing subsequences of the array, return the one with the largest possible sum.
Examples:
Input: arr[] = [1, 101, 2, 3, 100]
Output: 106
Explanation: The maximum sum of an increasing sequence is obtained from [1, 2, 3, 100].Input: arr[] = [4, 1, 2, 3]
Output: 6
Explanation: The maximum sum of an increasing sequence is obtained from [1, 2, 3].Input: arr[] = [4, 1, 2, 4]
Output: 7
Explanation: The maximum sum of an increasing sequence is obtained from [1, 2, 4].
Table of Content
- [Naive Approach] Recursion - O(2 ^ n) Time and O(n) Space
- [Better Approach - 1] Top-Down DP or Memoization - O(n ^ 2) Time and O(n ^ 2) Space
- [Better Approach - 2] Bottom-Up DP (Tabulation) - O(n ^ 2) Time and O(n) Space
- [Expected Approach] Optimized DP with Sorted Map - O(n log(n)) Time and O(n) Space
[Naive Approach] Recursion - O(2 ^ n) Time and O(n) Space
This problem is a variation of the standard Longest Increasing Subsequence (LIS) problem. Here we consider the sum of subsequence instead of its length.
We recursively explore all possible subsequences.
For each index i, we decide whether to include it in the subsequence based on the last chosen index j.
- If a[i] > a[j], we have two choices, either include a[i] or skip it and we take the option that gives the maximum sum.
- If a[i] <= a[j], we cannot include a[i], so we simply move to the next index.
#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;
int findMaxSum(vector<int> &arr, int i, int j)
{
if (i == arr.size() + 1)
return 0;
// i - current index(1-based index)
// j - last chosen index(1-based index)
// if j = 0, no element is chosen
if (j == 0 || arr[i - 1] > arr[j - 1])
{
return max(arr[i - 1] + findMaxSum(arr, i + 1, i), findMaxSum(arr, i + 1, j));
}
// if current element <= last chosen element
// we can't chose current element
else
return findMaxSum(arr, i + 1, j);
}
int maxSumIS(vector<int> &arr)
{
int n = arr.size();
return findMaxSum(arr, 1, 0);
}
int main()
{
vector<int> arr = {1, 101, 2, 3, 100};
cout << maxSumIS(arr);
}
class GFG {
static int findMaxSum(int[] arr, int i, int j)
{
if (i == arr.length + 1)
return 0;
// i - current index(1-based index)
// j - last chosen index(1-based index)
// if j = 0, no element is chosen
if (j == 0 || arr[i - 1] > arr[j - 1]) {
return Math.max(arr[i - 1]
+ findMaxSum(arr, i + 1, i),
findMaxSum(arr, i + 1, j));
}
// if current element <= last chosen element
// we can't chose current element
else
return findMaxSum(arr, i + 1, j);
}
static int maxSumIS(int arr[])
{
int n = arr.length;
return findMaxSum(arr, 1, 0);
}
public static void main(String[] args)
{
int[] arr = { 1, 101, 2, 3, 100 };
System.out.println(maxSumIS(arr));
}
}
def findMaxSum(arr, i, j):
if i == len(arr) + 1:
return 0
# i - current index(1-based index)
# j - last chosen index(1-based index)
# if j = 0, no element is chosen
if j == 0 or arr[i - 1] > arr[j - 1]:
return max(arr[i - 1] + findMaxSum(arr, i + 1, i),
findMaxSum(arr, i + 1, j))
# if current element <= last chosen element
# we can't chose current element
else:
return findMaxSum(arr, i + 1, j)
def maxSumIS(arr):
n = len(arr)
return findMaxSum(arr, 1, 0)
if __name__ == "__main__":
arr = [1, 101, 2, 3, 100]
print(maxSumIS(arr))
using System;
class GFG {
static int findMaxSum(int[] arr, int i, int j)
{
if (i == arr.Length + 1)
return 0;
// i - current index(1-based index)
// j - last chosen index(1-based index)
// if j = 0, no element is chosen
if (j == 0 || arr[i - 1] > arr[j - 1]) {
return Math.Max(arr[i - 1]
+ findMaxSum(arr, i + 1, i),
findMaxSum(arr, i + 1, j));
}
// if current element <= last chosen element
// we can't chose current element
else
return findMaxSum(arr, i + 1, j);
}
static int maxSumIS(int[] arr)
{
int n = arr.Length;
return findMaxSum(arr, 1, 0);
}
public static void Main()
{
int[] arr = { 1, 101, 2, 3, 100 };
Console.WriteLine(maxSumIS(arr));
}
}
function findMaxSum(arr, i, j)
{
if (i === arr.length + 1)
return 0;
// i - current index(1-based index)
// j - last chosen index(1-based index)
// if j = 0, no element is chosen
if (j === 0 || arr[i - 1] > arr[j - 1]) {
return Math.max(arr[i - 1]
+ findMaxSum(arr, i + 1, i),
findMaxSum(arr, i + 1, j));
}
// if current element <= last chosen element
// we can't chose current element
else
return findMaxSum(arr, i + 1, j);
}
function maxSumIS(arr)
{
let n = arr.length;
return findMaxSum(arr, 1, 0);
}
// Driver code
let arr = [ 1, 101, 2, 3, 100 ];
console.log(maxSumIS(arr));
Output
106
[Better Approach - 1] Top-Down DP or Memoization - O(n ^ 2) Time and O(n ^ 2) Space
We observe that many subproblems repeat. For example, while finding the maximum sum of an increasing subsequence starting at index i with the last chosen index j, we repeatedly compute results for the same (i, j) for several states (including or excluding i) across different recursive calls.
To avoid recalculating these overlapping subproblems, we store the results of solved states in a DP table and reuse them whenever needed.
#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;
int findMaxSum(vector<int> &arr, int i, int j, vector<vector<int>> &dp)
{
if (i == arr.size() + 1)
return 0;
// if this subproblem is already solved
// return the stored value
if (dp[i][j] != -1)
return dp[i][j];
// i - current index(1-based index)
// j - last chosen index(1-based index)
// if j = 0, no element is chosen
if (j == 0 || arr[i - 1] > arr[j - 1])
{
return dp[i][j] = max(arr[i - 1] + findMaxSum(arr, i + 1, i, dp), findMaxSum(arr, i + 1, j, dp));
}
// if current element <= last chosen element
// we can't chose current element
else
return dp[i][j] = findMaxSum(arr, i + 1, j, dp);
}
int maxSumIS(vector<int> &arr)
{
int n = arr.size();
vector<vector<int>> dp(n + 1, vector<int>(n + 1, -1));
return findMaxSum(arr, 1, 0, dp);
}
int main()
{
vector<int> arr = {1, 101, 2, 3, 100};
cout << maxSumIS(arr);
}
import java.util.Arrays;
class GFG {
static int findMaxSum(int[] arr, int i, int j,
int[][] dp)
{
if (i == arr.length + 1)
return 0;
// if this subproblem is already solved
// return the stored value
if (dp[i][j] != -1)
return dp[i][j];
// i - current index(1-based index)
// j - last chosen index(1-based index)
// if j = 0, no element is chosen
if (j == 0 || arr[i - 1] > arr[j - 1]) {
return dp[i][j] = Math.max(
arr[i - 1]
+ findMaxSum(arr, i + 1, i, dp),
findMaxSum(arr, i + 1, j, dp));
}
// if current element <= last chosen element
// we can't chose current element
else
return dp[i][j] = findMaxSum(arr, i + 1, j, dp);
}
static int maxSumIS(int arr[])
{
int n = arr.length;
int[][] dp = new int[n + 1][n + 1];
// initialize dp array with invalid value
for (int[] row : dp) {
Arrays.fill(row, -1);
}
return findMaxSum(arr, 1, 0, dp);
}
public static void main(String[] args)
{
int[] arr = { 1, 101, 2, 3, 100 };
System.out.println(maxSumIS(arr));
}
}
def findMaxSum(arr, i, j, dp):
if i == len(arr) + 1:
return 0
# if this subproblem is already solved
# return the stored value
if dp[i][j] != -1:
return dp[i][j]
# i - current index(1-based index)
# j - last chosen index(1-based index)
# if j = 0, no element is chosen
if j == 0 or arr[i - 1] > arr[j - 1]:
dp[i][j] = max(
arr[i - 1] + findMaxSum(arr, i + 1, i, dp),
findMaxSum(arr, i + 1, j, dp)
)
return dp[i][j]
# if current element <= last chosen element
# we can't chose current element
else:
dp[i][j] = findMaxSum(arr, i + 1, j, dp)
return dp[i][j]
def maxSumIS(arr):
n = len(arr)
dp = [[-1 for _ in range(n + 1)] for _ in range(n + 1)]
# initialize dp array with invalid value
for row in dp:
for j in range(len(row)):
row[j] = -1
return findMaxSum(arr, 1, 0, dp)
if __name__ == "__main__":
arr = [1, 101, 2, 3, 100]
print(maxSumIS(arr))
using System;
class GFG {
static int findMaxSum(int[] arr, int i, int j,
int[, ] dp)
{
if (i == arr.Length + 1)
return 0;
// if this subproblem is already solved
// return the stored value
if (dp[i, j] != -1)
return dp[i, j];
// i - current index(1-based index)
// j - last chosen index(1-based index)
// if j = 0, no element is chosen
if (j == 0 || arr[i - 1] > arr[j - 1]) {
return dp[i, j] = Math.Max(
arr[i - 1]
+ findMaxSum(arr, i + 1, i, dp),
findMaxSum(arr, i + 1, j, dp));
}
// if current element <= last chosen element
// we can't chose current element
else
return dp[i, j] = findMaxSum(arr, i + 1, j, dp);
}
static int maxSumIS(int[] arr)
{
int n = arr.Length;
int[, ] dp = new int[n + 1, n + 1];
// initialize dp array with invalid value
for (int i = 0; i <= n; i++) {
for (int j = 0; j <= n; j++) {
dp[i, j] = -1;
}
}
return findMaxSum(arr, 1, 0, dp);
}
public static void Main()
{
int[] arr = { 1, 101, 2, 3, 100 };
Console.WriteLine(maxSumIS(arr));
}
}
function findMaxSum(arr, i, j, dp)
{
if (i === arr.length + 1)
return 0;
// if this subproblem is already solved
// return the stored value
if (dp[i][j] !== -1)
return dp[i][j];
// i - current index(1-based index)
// j - last chosen index(1-based index)
// if j = 0, no element is chosen
if (j === 0 || arr[i - 1] > arr[j - 1]) {
return dp[i][j] = Math.max(
arr[i - 1]
+ findMaxSum(arr, i + 1, i, dp),
findMaxSum(arr, i + 1, j, dp));
}
// if current element <= last chosen element
// we can't chose current element
else
return dp[i][j] = findMaxSum(arr, i + 1, j, dp);
}
function maxSumIS(arr)
{
let n = arr.length;
let dp = Array.from({length : n + 1},
() => Array(n + 1).fill(-1));
// initialize dp array with invalid value
for (let i = 0; i <= n; i++) {
dp[i].fill(-1);
}
return findMaxSum(arr, 1, 0, dp);
}
// Driver code
let arr = [ 1, 101, 2, 3, 100 ];
console.log(maxSumIS(arr));
Output
106
[Better Approach - 2] Bottom-Up DP (Tabulation) - O(n ^ 2) Time and O(n) Space
The idea is to find the max sum increasing subsequence ending at each index i. We check all previous indices j such that arr[j] < arr[i].
This condition ensures that adding arr[i] at the end will keep the subsequence increasing. Among all such valid j, we find the one that gives the maximum sum subsequence ending at j, and then add arr[i] to extend it.
After calculating this for every index, we take the maximum value among all these subsequences — that’s our final answer.
#include <bits/stdc++.h>
using namespace std;
int maxSumIS(vector<int> &arr)
{
int n = arr.size();
vector<int> dp(n);
dp[0] = arr[0];
int ans = 0;
for (int i = 0; i < n; i++)
{
// maximum sum of increasing
// subsequence ending at a value < a[i]
int maxSum = 0;
for (int j = 0; j < i; j++)
{
// taking max among all smaller indices
if (arr[j] < arr[i])
maxSum = max(maxSum, dp[j]);
}
// adding current element to
// the subsequence with max sum
dp[i] = maxSum + arr[i];
ans = max(dp[i], ans);
}
return ans;
}
int main()
{
vector<int> arr = {1, 101, 2, 3, 100};
cout << maxSumIS(arr);
}
import java.util.Arrays;
class GFG {
static int maxSumIS(int[] arr)
{
int n = arr.length;
int[] dp = new int[n];
dp[0] = arr[0];
int ans = 0;
for (int i = 0; i < n; i++) {
// maximum sum of increasing
// subsequence ending at a value < a[i]
int maxSum = 0;
for (int j = 0; j < i; j++) {
// taking max among all smaller indices
if (arr[j] < arr[i])
maxSum = Math.max(maxSum, dp[j]);
}
// adding current element to
// the subsequence with max sum
dp[i] = maxSum + arr[i];
ans = Math.max(dp[i], ans);
}
return ans;
}
public static void main(String[] args)
{
int[] arr = { 1, 101, 2, 3, 100 };
System.out.println(maxSumIS(arr));
}
}
def maxSumIS(arr):
n = len(arr)
dp = [0] * n
dp[0] = arr[0]
ans = 0
for i in range(n):
# maximum sum of increasing
# subsequence ending at a value < a[i]
maxSum = 0
for j in range(i):
# taking max among all smaller indices
if arr[j] < arr[i]:
maxSum = max(maxSum, dp[j])
# adding current element to
# the subsequence with max sum
dp[i] = maxSum + arr[i]
ans = max(dp[i], ans)
return ans
if __name__ == "__main__":
arr = [1, 101, 2, 3, 100]
print(maxSumIS(arr))
using System;
class GFG {
static int maxSumIS(int[] arr)
{
int n = arr.Length;
int[] dp = new int[n];
dp[0] = arr[0];
int ans = 0;
for (int i = 0; i < n; i++) {
// maximum sum of increasing
// subsequence ending at a value < a[i]
int maxSum = 0;
for (int j = 0; j < i; j++) {
// taking max among all smaller indices
if (arr[j] < arr[i])
maxSum = Math.Max(maxSum, dp[j]);
}
// adding current element to
// the subsequence with max sum
dp[i] = maxSum + arr[i];
ans = Math.Max(dp[i], ans);
}
return ans;
}
public static void Main()
{
int[] arr = { 1, 101, 2, 3, 100 };
Console.WriteLine(maxSumIS(arr));
}
}
function maxSumIS(arr)
{
const n = arr.length;
const dp = new Array(n).fill(0);
dp[0] = arr[0];
let ans = 0;
for (let i = 0; i < n; i++) {
// maximum sum of increasing
// subsequence ending at a value < a[i]
let maxSum = 0;
for (let j = 0; j < i; j++) {
// taking max among all smaller indices
if (arr[j] < arr[i])
maxSum = Math.max(maxSum, dp[j]);
}
// adding current element to
// the subsequence with max sum
dp[i] = maxSum + arr[i];
ans = Math.max(dp[i], ans);
}
return ans;
}
// Driver code
const arr = [ 1, 101, 2, 3, 100 ];
console.log(maxSumIS(arr));
Output
106
[Expected Approach] Optimized DP with Sorted Map - O(n log(n)) Time and O(n) Space
In the previous approach, for every element, we check all previous elements to find the maximum sum of an increasing subsequence ending at a smaller value.
Here we optimize this by using an ordered map to store useful values and their maximum subsequence sums. This allows us to find the best sum among smaller values in O(log n) time and remove entries that are no longer useful.
Let us understand with an example:
Input: arr[] = [1, 101, 2, 3, 100]
- Initialize dp = {} and ans = 0. For val = 1, bestSmaller = 0, so currSum = 1. Update dp = {1: 1} and ans = 1.
- For val = 101, the best smaller sum is 1. Thus, currSum = 102. Update dp = {1: 1, 101: 102} and ans = 102.
- For val = 2, the best smaller sum is 1. Thus, currSum = 3. Update dp = {1: 1, 2: 3, 101: 102}.
- For val = 3, the best smaller sum is 3. Thus, currSum = 6. Update dp = {1: 1, 2: 3, 3: 6, 101: 102}.
- For val = 100, the best smaller sum is 6. Thus, currSum = 106. Update dp = {1: 1, 2: 3, 3: 6, 100: 106} after removing the dominated entry 101: 102.
The final answer is 106.
#include <algorithm>
#include <iostream>
#include <map>
#include <vector>
using namespace std;
int maxSumIS(vector<int> &arr)
{
// key = value in arr, value = max sum ending
// with that value or less than that value
map<int, int> dp;
int ans = 0;
for (int val : arr)
{
// Find the best sum among
// all elements smaller than val
auto it = dp.lower_bound(val);
int bestSmaller = 0;
if (it != dp.begin())
{
--it;
bestSmaller = it->second;
}
int currSum = bestSmaller + val;
// If this value gives a
// better sum, update TreeMap
if (dp[val] < currSum)
{
dp[val] = currSum;
// Remove entries with greater
// keys with smaller or equal sums
auto higher = dp.upper_bound(val);
while (higher != dp.end() && higher->second <= currSum)
{
higher = dp.erase(higher);
}
}
ans = max(ans, currSum);
}
return ans;
}
int main()
{
vector<int> arr = {1, 101, 2, 3, 100};
cout << maxSumIS(arr);
}
import java.util.Map;
import java.util.TreeMap;
class GFG {
static int maxSumIS(int[] arr)
{
// key = value in arr, value = max sum ending
// with that value or less than that value
TreeMap<Integer, Integer> dp = new TreeMap<>();
int ans = 0;
for (int val : arr) {
// Find the best sum among
// all elements smaller than val
Map.Entry<Integer, Integer> entry
= dp.lowerEntry(val);
int bestSmaller
= (entry == null) ? 0 : entry.getValue();
int currSum = bestSmaller + val;
// If this value gives a
// better sum, update TreeMap
if (dp.getOrDefault(val, 0) < currSum) {
dp.put(val, currSum);
// Remove entries with greater
// keys with smaller or equal sums
Integer higher = dp.higherKey(val);
while (higher != null
&& dp.get(higher) <= currSum) {
dp.remove(higher);
higher = dp.higherKey(val);
}
}
ans = Math.max(ans, currSum);
}
return ans;
}
public static void main(String[] args)
{
int[] arr = { 1, 101, 2, 3, 100 };
System.out.println(maxSumIS(arr));
}
}
from bisect import bisect_left, insort
def maxSumIS(arr):
# key = value in arr, value = max sum ending
# with that value or less than that value
dp = {}
keys = []
ans = 0
for val in arr:
# Find the best sum among
# all elements smaller than val
idx = bisect_left(keys, val)
bestSmaller = 0
if idx > 0:
bestSmaller = dp[keys[idx - 1]]
currSum = bestSmaller + val
# If this value gives a
# better sum, update TreeMap
if val not in dp or dp[val] < currSum:
dp[val] = currSum
if val not in keys:
insort(keys, val)
# Remove entries with greater
# keys with smaller or equal sums
i = bisect_left(keys, val) + 1
while i < len(keys):
if dp[keys[i]] <= currSum:
del dp[keys[i]]
keys.pop(i)
else:
break
ans = max(ans, currSum)
return ans
if __name__ == "__main__":
arr = [1, 101, 2, 3, 100]
print(maxSumIS(arr))
using System;
using System.Collections.Generic;
class GFG {
static int maxSumIS(int[] arr)
{
// key = value in arr, value = max sum ending
// with that value or less than that value
SortedDictionary<int, int> dp
= new SortedDictionary<int, int>();
int ans = 0;
foreach(int val in arr)
{
// Find the best sum among
// all elements smaller than val
int bestSmaller = 0;
foreach(var kv in dp)
{
if (kv.Key < val)
bestSmaller = kv.Value;
else
break;
}
int currSum = bestSmaller + val;
// If this value gives a
// better sum, update TreeMap
if (!dp.ContainsKey(val) || dp[val] < currSum) {
dp[val] = currSum;
// Remove entries with greater
// keys with smaller or equal sums
var keysToRemove = new List<int>();
foreach(var kv in dp)
{
if (kv.Key > val && kv.Value <= currSum)
keysToRemove.Add(kv.Key);
}
foreach(var k in keysToRemove) dp.Remove(k);
}
ans = Math.Max(ans, currSum);
}
return ans;
}
public static void Main()
{
int[] arr = { 1, 101, 2, 3, 100 };
Console.WriteLine(maxSumIS(arr));
}
}
function maxSumIS(arr)
{
// key = value in arr, value = max sum ending
// with that value or less than that value
const dp = new Map();
const sortedKeys = [];
let ans = 0;
for (const val of arr) {
// Find the best sum among
// all elements smaller than val
let bestSmaller = 0;
for (const k of sortedKeys) {
if (k < val)
bestSmaller
= Math.max(bestSmaller, dp.get(k));
else
break;
}
const currSum = bestSmaller + val;
// If this value gives a
// better sum, update Map
if (!dp.has(val) || dp.get(val) < currSum) {
dp.set(val, currSum);
if (!sortedKeys.includes(val)) {
sortedKeys.push(val);
sortedKeys.sort((a, b) => a - b);
}
// Remove entries with greater
// keys with smaller or equal sums
for (let i = sortedKeys.length - 1; i >= 0;
i--) {
const higher = sortedKeys[i];
if (higher > val
&& dp.get(higher) <= currSum) {
dp.delete(higher);
sortedKeys.splice(i, 1);
}
}
}
ans = Math.max(ans, currSum);
}
return ans;
}
// Driver code
const arr = [ 1, 101, 2, 3, 100 ];
console.log(maxSumIS(arr));
Output
106