Line of wines
There are N wines in a row. Each year you sell either the leftmost or the rightmost wine. The i-th wine has initial price p[i] and price k * p[i] in the k-th year. What is the maximum possible total profit?
Solution 1. Bottom up dynamic programming
state:
dp[i][j]: the max possible profit of wines[i, j] at year i - 0 + N - 1 - j + 1 = N + i - j
dp[i][j] = max of {p[i] * (N + i - j) + dp[i + 1][j], p[j] * (N + i -j) + dp[i][j - 1]}
Initialization:
dp[i][i] = N * p[i]
Ans:
dp[0][N - 1]
Loop over i and j. In this case, the loop order matters. In order to compute dp[i][j], we need to have dp[i + 1][j] and dp[i][j - 1] first. This means we need to loop i in decreasing order from N - 1 to 0 and j in increasing order from i + 1 to N - 1.
public int maxProfitBottomUp(int[] p) { int N = p.length; int[][] dp = new int[N][N]; for(int i = 0; i < N; i++) { dp[i][i] = N * p[i]; } for(int i = N - 1; i >= 0; i--) { for(int j = i + 1; j < N; j++) { dp[i][j] = Math.max(p[i] * (N + i - j) + dp[i + 1][j], p[j] * (N + i -j) + dp[i][j - 1]); } } return dp[0][N - 1]; }
Loop over the interval length first, then loop over all possible left start.
public int maxProfitBottomUp(int[] p) { int N = p.length; int[][] dp = new int[N][N]; for(int i = 0; i < N; i++) { dp[i][i] = N * p[i]; } for(int len = 2; len <= N; len++) { for(int l = 0; l <= N - len; l++) { int r = l + len - 1; int y = N + l - r; dp[l][r] = Math.max(p[l] * y + dp[l + 1][r], p[r] * y + dp[l][r - 1]); } } return dp[0][N - 1]; }
Solution II. Top down dynamic programming
State:
dp[l][r]: the max possible profit before we go to interval [l, r].
dp[l][r] = Math.max(dp[l][r + 1] + p[r + 1] * (currY - 1), dp[l - 1][r] + p[l - 1] * (currY - 1)), currY = N + l - r.
Initialization:
dp[0][N - 1] = 0
Answer:
max of {dp[i][i] + p[i] * N}
public int maxProfitTopDown(int[] p) { int N = p.length; int[][] dp = new int[N][N]; dp[0][N - 1] = 0; for(int len = N - 1; len >= 1; len--) { for(int l = 0; l + len <= N; l++) { int r = l + len - 1; int currY = N + l - r; if(r < N - 1) { dp[l][r] = Math.max(dp[l][r], dp[l][r + 1] + p[r + 1] * (currY - 1)); } if(l > 0) { dp[l][r] = Math.max(dp[l][r], dp[l - 1][r] + p[l - 1] * (currY - 1)); } } } int res = 0; for(int i = 0; i < N; i++) { res = Math.max(res, dp[i][i] + p[i] * N); } return res; }
Solution III. Bottom up dynamic programming with prefix sum
Key idea: if we assume every interval starts with year 1 and already have the max profit for interval[l, r - 1] and [l + 1, r], then to compute interval[l, r], we shift all the years for [l, r - 1] and [l + 1, r] by 1, then add either p[r] or p[l]. This is the prefixsum of interval[l, r].
dp[l][r]: the max possible profit of wines[l, r] starting from year 1.
dp[l][r] = Math.max(dp[l][r - 1], dp[l + 1][r]) + prefixSum[l, r]
init: dp[i][i] = 1 * p[i]
Ans: dp[0][N - 1]
public int maxProfitPrefixSum(int[] p) { int N = p.length; int[][] dp = new int[N][N]; for(int i = 0; i < N; i++) { dp[i][i] = p[i]; } int[] prefixSum = new int[N]; prefixSum[0] = p[0]; for(int i = 1; i < N; i++) { prefixSum[i] = prefixSum[i - 1] + p[i]; } for(int i = N - 1; i >= 0; i--) { for(int j = i + 1; j < N; j++) { dp[i][j] = Math.max(dp[i][j - 1], dp[i + 1][j]) + prefixSum[j] - (i > 0 ? prefixSum[i - 1] : 0); } } return dp[0][N - 1]; }