Search code examples
algorithmrecursionmemoization

Maximum sum increasing subsequence, changing algorithm to use memoization


I have the following code which implements a recursive solution for this problem, instead of using the reference variable 'x' to store overall max, How can I or can I return the result from recursion so I don't have to use the 'x' which would help memoization?

// Test Cases: 
// Input: {1, 101, 2, 3, 100, 4, 5} Output: 106
// Input:  {3, 4, 5, 10} Output: 22

int sum(vector<int> seq)
{
    int x = INT32_MIN;
    helper(seq, seq.size(), x);
    return x;
}

int helper(vector<int>& seq, int n, int& x)
{
    if (n == 1) return seq[0];

    int maxTillNow = seq[0];
    int res = INT32_MIN;

    for (int i = 1; i < n; ++i)
    {
        res = helper(seq, i, x);
        if (seq[i - 1] < seq[n - 1] &&  res + seq[n - 1] > maxTillNow) maxTillNow = res + seq[n - 1];
    }

    x = max(x, maxTillNow);
    return maxTillNow;
}

Solution

  • First, I don't think this implementation is correct. For this input {5, 1, 2, 3, 4} it gives 14 while the correct result is 10.

    For writing a recursive solution for this problem, you don't need to pass x as a parameter, as x is the result you expect to get from the function itself. Instead, you can construct a state as the following:

    • Current index: this is the index you're processing at the current step.
    • Last taken number: This is the value of the last number you included in your result subsequence so far. This is to make sure that you pick larger numbers in the following steps to keep the result subsequence increasing.

    So your function definition is something like sum(current_index, last_taken_number) = the maximum increasing sum from current_index until the end, given that you have to pick elements greater than last_taken_number to keep it an increasing subsequence, where the answer that you desire is sum(0, a small value) since it calculates the result for the whole sequence. by a small value I mean smaller than any other value in the whole sequence.

    sum(current_index, last_taken_number) could be calculated recursively using smaller substates. First assume the simple cases:

    • N = 0, result is 0 since you don't have a sequence at all.
    • N = 1, the sequence contains only one number, the result is either that number or 0 in case the number is negative (I'm considering an empty subsequence as a valid subsequence, so not taking any number is a valid answer).

    Now to the tricky part, when N >= 2.

    Assume that N = 2. In this case you have two options:

    • Either ignore the first number, then the problem can be reduced to the N=1 version where that number is the last one in the sequence. In this case the result is the same as sum(1,MIN_VAL), where current_index=1 since we already processed index=0 and decided to ignore it, and MIN_VAL is the small value we mentioned above

    • Take the first number. Assume the its value is X. Then the result is X + sum(1, X). That means the solution includes X since you decided to include it in the sequence, plus whatever the result is from sum(1,X). Note that we're calling sum with MIN_VAL=X since we decided to take X, so the following values that we pick have to be greater than X.

    Both decisions are valid. The result is whatever the maximum of these two. So we can deduce the general recurrence as the following:

    sum(current_index, MIN_VAL) = max( sum(current_index + 1, MIN_VAL) // ignore, seq[current_index] + sum(current_index + 1, seq[current_index]) // take ).

    The second decision is not always valid, so you have to make sure that the current element > MIN_VAL in order to be valid to take it.

    This is a pseudo code for the idea:

    sum(current_index, MIN_VAL){
       if(current_index == END_OF_SEQUENCE) return 0
       if( state[current_index,MIN_VAL] was calculated before ) return the perviously calculated result
    
       decision_1 = sum(current_index + 1, MIN_VAL) // ignore case
       if(sequence[current_index] > MIN_VAL) // decision_2 is valid
           decision_2 = sequence[current_index] + sum(current_index + 1, sequence[current_index]) // take case
       else
           decision_2 = INT_MIN
    
       result = max(decision_1, decision_2)
       memorize result for the state[current_index, MIN_VAL]
       return result
    }