Search code examples
algorithmrecursiondynamic-programming

How to convert this top down dp to bottom up dp


Given two arrays a and b of positive integers of size n and m where n >= m, the task is to maximize the dot product by inserting zeros in the second array but you cannot disturb the order of elements.

Dot product of array a and b of size n is a[0]*b[0] + a[1]*b[1] + ... + a[n-1]*b[n-1].

Here is the solution I wrote and it works fine how ever I am not able to come up with a iterative approach .

public int recurse(int a[], int b[],int aInd, int bInd, int zeroCount,int dp[][]){
        if(bInd>=b.length || aInd>=a.length)
            return 0;
            
       if(dp[aInd][bInd]!=-1)
         return dp[aInd][bInd];
           
       if(zeroCount==0){
            return dp[aInd][bInd]=a[aInd]*b[bInd]+recurse(a,b,aInd+1,bInd+1,0,dp);
        }
        
        
         int makeZero=recurse(a,b,aInd+1,bInd,zeroCount-1,dp);
         int noZero=a[aInd]*b[bInd]+recurse(a,b,aInd+1,bInd+1,zeroCount,dp);
           
         return dp[aInd][bInd]=Math.max(makeZero,noZero);
    }

Solution

  • The steps I like to follow when converting a recursive problem to an iterative one is the following:

    1. Identify the recurrence/transition function
    2. Determine how to model all states in the recurrence with the appropriate data structure
    3. Figure out how to iterate

    Step 1

    Following the checklist, let's figure out the recurrence and transition function. This can be derived directly from an existing recursive solution. For yours, it looks like your recurrence on the current indices of a, b, and the number of zeros that need to be inserted. So, we can determine the recurrence by replacing recurse with a function F that takes in three parameters: i, j, k with the respective parameters. The transitions can be derived directly as well:

    • F(i, j, 0) = a[i] * b[j] + F(i + 1, j + 1, 0), if k == 0
    • F(i, j, k) = max(F(i + 1, j, k - 1), a[i] * b[j] + F(i + 1, j + 1, k)) otherwise.

    Step 2

    Because F takes in three integer parameters it makes sense to use a 3d array for storage, so that dp[i][j][k] maps directly to F(i, j, k).

    Step 3

    The recurrence calculates F(i, j, k) based on larger values of i and j but smaller values of k. This suggests we ought to iterate i and j in decreasing order but k in increasing order.

    Putting It Together

    One iterative solution would appear as follows

    public class SomeTest{
        public static void main(String[] args) {
            int a[] = {1,2,3,4};
            int b[] = {1,1,1};
            System.out.println(solve(a, b));
        }
        
        public static int solve(int a[], int b[]) {
            int N = a.length, M = b.length, total_zeros = N - M;
            // extra allocation for N, M to make it easier to handle boundary elements
            int [][][] dp = new int[N + 1][M + 1][total_zeros + 1];
            int res = 0;
            for(int i = N - 1; i >= 0; i--) {
                for(int j = M - 1; j >= 0; j--) {
                    // zero case
                    dp[i][j][0] = a[i] * b[j] + dp[i + 1][j + 1][0];
                    for(int k = 1; k <= total_zeros; k++) {
                        dp[i][j][k] = Math.max(dp[i + 1][j][k - 1], dp[i + 1][j + 1][k] + a[i] * b[j]);
                    }
                    res = Math.max(res, dp[i][j][total_zeros]);
                }
            }
            
            return res;
        }
    }
    

    Other

    I've gone ahead and mapped your recurse solution directly to an iterative one, but there are certainly other optimizations that can be done. Some that come to mind include not having to track the number of zeros used, which can be done by reformulating the recurrence, and reducing the total amount of spaced used.

    More Efficient Recurrence (edit)

    A recurrence based on just the positions within the arrays is possible. Let F(i, j) be the largest dot product attainable using the remaining elements from i and onwards in a and j, onwards in b. Then the following holds:

    • F(i, j) = max(a[i] * b[j] + F(i + 1, j + 1), F(i + 1, j)), if i < a.length and j < b.length. The lefthand side pairs up a[i] and b[j] and considers the remaining dot product, while the righthand side pairs a[i] with a zero inserted into b instead.

    Hopefully with the new recurrence, going through Steps 2/3 and implementing a solution follows.