High level thought
- it is true that people claim DP is just an optimization of recursion. But in terms of writing actual code, how are they different?
- How is recursion different from divide and conquer?
Questions
62. Unique Paths
1 Line Math Solution (Python)
def uniquePaths(self, m, n):
return math.factorial(m+n-2)/math.factorial(m-1)/math.factorial(n-1)
63. Unique Paths II
def uniquePathsWithObstacles(self, obstacleGrid):
m = len(obstacleGrid)
n = len(obstacleGrid[0])
obstacleGrid[0][0] = 1 - obstacleGrid[0][0]
for i in range(1, n):
if not obstacleGrid[0][i]:
obstacleGrid[0][i] = obstacleGrid[0][i-1]
else: obstacleGrid[0][i] = 0
for i in range(1, m):
if not obstacleGrid[i][0]:
obstacleGrid[i][0] = obstacleGrid[i-1][0]
else:
obstacleGrid[i][0] = 0
for i in range(1, m):
for j in range(1, n):
if not obstacleGrid[i][j]:
obstacleGrid[i][j] = obstacleGrid[i][j-1]+obstacleGrid[i-1][j]
else:
obstacleGrid[i][j] = 0
return obstacleGrid[-1][-1]
A shorter version:
def uniquePathsWithObstacles(self, obstacleGrid):
m = len(obstacleGrid)
n = len(obstacleGrid[0])
ResGrid = [[0 for x in range(n+1)] for x in range(m+1)]
ResGrid[0][1] = 1
for i in range(1, m+1):
for j in range(1, n+1):
if not obstacleGrid[i-1][j-1]:
ResGrid[i][j] = ResGrid[i][j-1]+ResGrid[i-1][j]
return ResGrid[m][n]
120. Triangle
# O(n*n/2) space, top-down
def minimumTotal1(self, triangle):
if not triangle: return
res = [[0 for i in xrange(len(row))] for row in triangle]
res[0][0] = triangle[0][0]
for i in xrange(1, len(triangle)):
for j in xrange(len(triangle[i])):
if j == 0:
res[i][j] = res[i-1][j] + triangle[i][j]
elif j == len(triangle[i])-1:
res[i][j] = res[i-1][j-1] + triangle[i][j]
else:
res[i][j] = min(res[i-1][j-1], res[i-1][j]) + triangle[i][j]
return min(res[-1])
# Modify the original triangle, top-down
def minimumTotal2(self, triangle):
if not triangle: return
for i in xrange(1, len(triangle)):
for j in xrange(len(triangle[i])):
if j == 0:
triangle[i][j] += triangle[i-1][j]
elif j == len(triangle[i])-1:
triangle[i][j] += triangle[i-1][j-1]
else:
triangle[i][j] += min(triangle[i-1][j-1], triangle[i-1][j])
return min(triangle[-1])
** don't understand these two bottom-up algm:
# Modify the original triangle, bottom-up
def minimumTotal3(self, triangle):
if not triangle: return
for i in xrange(len(triangle)-2, -1, -1):
for j in xrange(len(triangle[i])):
triangle[i][j] += min(triangle[i+1][j], triangle[i+1][j+1])
return triangle[0][0]
# bottom-up, O(n) space
def minimumTotal(self, triangle):
if not triangle: return
res = triangle[-1]
for i in xrange(len(triangle)-2, -1, -1):
for j in xrange(len(triangle[i])):
res[j] = min(res[j], res[j+1]) + triangle[i][j]
return res[0]
64. Minimum Path Sum
def minPathSum(self, grid):
m = len(grid)
n = len(grid[0])
for i in range(1, n):
grid[0][i] += grid[0][i-1]
for i in range(1, m):
grid[i][0] += grid[i-1][0]
for i in range(1, m):
for j in range(1, n):
grid[i][j] += min(grid[i-1][j], grid[i][j-1])
return grid[-1][-1]
53. Maximum Subarray
Analysis of this problem: Apparently, this is a optimization problem, which can be usually solved by DP. So when it comes to DP, the first thing for us to figure out is the format of the sub problem(or the state of each sub problem). The format of the sub problem can be helpful when we are trying to come up with the recursive relation.
At first, I think the sub problem should look like: maxSubArray(int A[], int i, int j), which means the maxSubArray for A[i: j]. In this way, our goal is to figure out what maxSubArray(A, 0, A.length - 1) is. However, if we define the format of the sub problem in this way, it's hard to find the connection from the sub problem to the original problem(at least for me). In other words, I can't find a way to divided the original problem into the sub problems and use the solutions of the sub problems to somehow create the solution of the original one.
So I change the format of the sub problem into something like: maxSubArray(int A[], int i) , which means the maxSubArray for A[0:i ] which must has A[i] as the end element. Note that now the sub problem's format is less flexible and less powerful than the previous one because there's a limitation that A[i] should be contained in that sequence and we have to keep track of each solution of the sub problem to update the global optimal value. However, now the connect between the sub problem & the original one becomes clearer:
maxSubArray(A, i) = maxSubArray(A, i - 1) > 0 ? maxSubArray(A, i - 1) : 0 + A[i];
public int maxSubArray(int[] A) {
int n = A.length;
int[] dp = new int[n];//dp[i] means the maximum subarray ending with A[i];
dp[0] = A[0];
int max = dp[0];
for(int i = 1; i < n; i++){
dp[i] = A[i] + (dp[i - 1] > 0 ? dp[i - 1] : 0);
max = Math.max(max, dp[i]);
}
return max;
}
class Solution {
public:
int maxSubArray(int A[], int n) {
int ans=A[0],i,j,sum=0;
for(i=0;i<n;i++){
sum+=A[i];
ans=max(sum,ans);
sum=max(sum,0);
}
return ans;
}
};
def maxSubArray(self, A):
if not A: return 0
curSum = maxSum = A[0]
for num in A[1:]:
curSum = max(num, curSum + num)
maxSum = max(maxSum, curSum)
return maxSum
121. Best Time to Buy and Sell Stock
The question is simple. You want to find the difference of the maximum and the minimum. The only trick is that the bigger number should come after the smaller number.
So, here is how I tackled it. Instead of going forward, I scanned through the list of prices backward to store the current maximum number. Update the biggest difference along the way.
# backward iteration
def maxProfit(self, prices):
length = len(prices)
if length==0: return 0
maxPrice = prices[length-1]
res = 0
for i in range(length-1,-1,-1):
maxPrice = max(maxPrice ,prices[i])
if maxPrice - prices[i] > res:
res = maxPrice - prices[i]
return res
# forward iteration
def maxProfit(self, prices):
if len(prices) == 0: return 0
minPrice = prices[0]
maxProfit = 0
for i in range(0, len(prices)):
if prices[i] < minPrice:
minPrice = prices[i]
if prices[i] - minPrice > maxProfit:
maxProfit = prices[i] - minPrice
return maxProfit