Target problems
Dynamic Programming is targeting to solve most of the Optimization Problems.
- Simple Subproblems: there should be a simple way of defining subproblems with just a few indices like i, j, k…
- Subproblem Optimization
- Subproblem Overlap: optimal solutions to unrelated subproblems can contain subproblems in common
It uses Memoization Algorithm, which means store and return the value rather than compute them again.
Template solution:
- Store temp data in a temp[] array
- Introduction
- The process of solving easier-to-solve sub-problems and building up the answer from that
Count Path
1 | int countPaths(boolean[][] grid, int row, int col, int[][]paths) { |
Fibbonaci
Thoughts to be added.
1 | # Fibbonaci using memoization |
53. Maximum Subarray
1 | public int maxSubArray(int[] A) { |
64. Minimum Path Sum
Thoughts to be added.
1 | class Solution { |
63. Unique Paths II
Thoughts to be added.
1 | class Solution { |
62. Unique Paths
1 | TBA |
95. Unique Binary Search Trees II
Leetcode: https://leetcode.com/problems/unique-binary-search-trees-ii/description/
Introduction: https://www.youtube.com/watch?v=GZ0qvkTAjmw
[Solution] See: https://leetcode.com/problems/unique-binary-search-trees-ii/solution/
1 | class Solution { |
96. Unique Binary Search Trees
Leetcode: https://leetcode.com/problems/unique-binary-search-trees/description/
[Solution] See https://leetcode.com/articles/unique-binary-search-trees/1
2
3
4
5
6
7
8let F(i, n): nums of unique BST of root = i, in range n
let G(n): nums of unique BST in range n
F(i, n) = G(i-1) * G(n-i)
Also, since G(n) = Sum(F(i,n), i->n)
G(n) = Sum(G(i-1) * G(n-i))
let i = n; j = i, we can get:
G(i) = Sum(G(j-1) * G(i - j))
1 | class Solution { |
Subsequence problem
115. Distinct Subsequence
Leetcode: https://leetcode.com/problems/distinct-subsequences/description/
[Solution] https://www.youtube.com/watch?v=mPqqXh8XvWY&t=187s
1 | Use a (m + 1) * (n + 1) 2-D array to store results |
1 | class Solution { |
1143. Longest Common Subsequence
Leetcode: https://leetcode.com/problems/longest-common-subsequence/
1 | def longestCommonSubsequence(self, text1: str, text2: str) -> int: |
With space optimization
1 |
279. Perfect Squares
Leetcode: https://leetcode.com/problems/perfect-squares/description/
Video: https://www.youtube.com/watch?v=jVnZxPxzyDA
1-D DP problem using intermediate state transfer
[Solution]
- dp[i]: The least number of perfect square numbers of a given integer i
- state transfer equation
dp[i] = min(dp[i + j * j]) (j = 1 -> sqrt(i))
Using intermemiate state
1 | class Solution { |
139. Word Break
Leetcode: https://leetcode.com/problems/word-break/description/
Video: https://www.youtube.com/watch?v=WepWFGxiwRs
[Solution]
https://leetcode.com/problems/word-break/solution/
- Use two for-loops to check if there exist a point where can split current substring into two parts and those two parts are also in the dictionary
1 | class Solution(object): |
1 | var wordBreak = function(s, wordDict) { |
140. Word Break II
Leetcode: https://leetcode.com/problems/word-break-ii/
String = prefix + tail
472. Concatenated Words
Leetcode: https://leetcode.com/problems/concatenated-words/
- A string can only be formed by string shorter than itself
- DP array: dp[i]: a boolean value indicate if s[i] can be formed by what’s in the dict
1 | def findAllConcatenatedWordsInADict(self, words: List[str]) -> List[str]: |
String Match Problem
10. Regular Expression Matching
String match usually use DP to solve.
Leetcode: https://leetcode.com/problems/regular-expression-matching/description/
Video: https://www.youtube.com/watch?v=DqhPJ8MzDKM
- State
- Init
- Function
- Result
Note:
- Need to deal with a lot edge cases (index overflow)
1 | def isMatch(self, s: str, p: str) -> bool: |
72.Edit Distance
Leetcode: https://leetcode.com/problems/edit-distance/description/
Key:1
2
3
4if (str1[i] == str2[j]):
T[i][j] = T[i-1][j-1]
else:
T[i][j] = min(T[i-1][j], T[i][j-1], T[i-1][j-1]) {Since this is calculating min distance, we should select a minimum way to calculate next value}
1 | def minDistance(self, word1: str, word2: str) -> int: |
44. Wildcard Matching
Leetcode: https://leetcode.com/problems/wildcard-matching/submissions/
Analyze:1
2
3
4
5
6
7if "*" ->
- if "": dp[i - 1][j]
- if not "": "*" len = 1: dp[i - 1][j - 1], "*" len > 1: dp[i][j - 1]
if "?" ->
- dp[i - 1][j - 1]
if ch ->
- if match: dp[i - 1][j - 1]
1 | def isMatch(self, s: str, p: str) -> bool: |
647. Palindromic Substrings
Leetcode: https://leetcode.com/problems/palindromic-substrings/
- base case: len is 1, dp[i][j] = 1, len is 2, if s[i] == s[j] then dp[i][j] = 1
1 | def countSubstrings(self, s: str) -> int: |
5. Longest Palindromic Substring
Leetcode: https://leetcode.com/problems/longest-palindromic-substring/
- Start from
l = 1, l = 2
as base cases - If
dp[i + 1][j - 1] and s[i] == s[j]
thens[i: j + 1]
is also palindrome
1 | def longestPalindrome(self, s: str) -> str: |
516. Longest Palindromic Subsequence
- Palindrom string problem always checks
dp[i + 1][j - 1] and s[i] == s[j]
, if this exists, the longest length froms[i]
tos[j]
isdp[i + 1][j - 1] + 2
1 | def longestPalindromeSubseq(self, s: str) -> int: |
LIS (Longest Increasing Sequence Problem)
300. Longest Increasing Subsequence
Leetcode: https://leetcode.com/problems/longest-increasing-subsequence/
Video: https://www.youtube.com/watch?v=l2rCz7skAlk
Solution 1: Normal Dynamic Programming O(N^2)
1 | def lengthOfLIS(self, nums: List[int]) -> int: |
Solution 2: Optimized Dynamic Programming with greedy (O(nlogn) (Patience Sorting)
- dp[i] = the smallest ending number of a subsequence that has length i + 1
- Init dp = []
- For each num, we can use it to:
- Extend the longest subsequence
- Replace a number to generate a better subsequence
- Find the smallest last number for the sequence
- DP array is in acsending order, we can use binary search to find the insertion position
Binary search in dp array:
- Use a spare array to store the increasing subsequence (keep it increasing)
- For current element, append if it’s greater than the last element of the increasing subsequence, replace it if it’s smaller (keep the subsequence compact so that it can fit more numbers in the future)
1 | def lengthOfLIS(self, nums: List[int]) -> int: |
1235. Maximum Profit in Job Scheduling
Leetcode: https://leetcode.com/problems/maximum-profit-in-job-scheduling/
Original DP solution
1 | def jobScheduling(self, startTime: List[int], endTime: List[int], profit: List[int]) -> int: |
Optimzed DP solutioon: DP + knapsack?
- Sort by ending time: for each interval: take it (
dp[prevEnd] + curProfit
) or notdp[arr[i - 1][1]]
1 | def jobScheduling(self, startTime: List[int], endTime: List[int], profit: List[int]) -> int: |
My DFS solution (TLE)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48def jobScheduling(self, startTime: List[int], endTime: List[int], profit: List[int]) -> int:
# make tuples and sort by startTime
n = len(profit)
time = [] # []
for i in range(n):
time.append((startTime[i], endTime[i], profit[i]))
time.sort(key = lambda x: x[0])
# construct graph: {(timeIndex, weight): [index startTime >= endTime[timeIndex]]}
def bsearch(target):
# search for the smallest startTime that's larger than target
l, r = 0, n - 1
while l < r:
mid = (l + r) // 2
if time[mid][0] < target:
l = mid + 1
else:
r = mid
return l
graph = collections.defaultdict(int)
indegree = [0 for _ in range(n)]
for i in range(n):
index = bsearch(time[i][1])
if time[index][0] >= time[i][1]:
graph[i] = index
for i in range(index, n):
indegree[i] += 1
else:
graph[i] = n
queue = []
for index in range(n):
if indegree[index] == 0:
queue.append((index, time[index][2]))
res = 0
def dfs(index, curWeight):
nonlocal res
res = max(res, curWeight)
for nb in range(graph[index], n):
dfs(nb, curWeight + time[nb][2])
return
for start, weight in queue:
dfs(start, weight)
return res
Similar problem: 1713, 673
1713. Minimum Operations to Make a Subsequence
Leetcode: https://leetcode.com/problems/minimum-operations-to-make-a-subsequence/
Video: https://www.youtube.com/watch?v=5vBPESNPEu4
1 |
673. Number of Longest Increasing Subsequence
leetcode: https://leetcode.com/problems/number-of-longest-increasing-subsequence/
1 | def findNumberOfLIS(self, nums: List[int]) -> int: |
Similar problem: 1143. Longest Common Subsequence
1143. Longest Common Subsequence
Leetcode: https://leetcode.com/problems/longest-common-subsequence/
1 | def longestCommonSubsequence(self, text1: str, text2: str) -> int: |
354. Russian Doll Envelopes
Leetcode: https://leetcode.com/problems/russian-doll-envelopes/
- sort increasing in the first dimension
- also sort decreasing on the second dimension, so two envelopes that are equal in the first dimension can never be in the same increasing subsequence
1 | def maxEnvelopes(self, envelopes: List[List[int]]) -> int: |
Jump Problem
403. Frog Jump
Leetcode: https://leetcode.com/problems/frog-jump/description/
Video: https://www.youtube.com/watch?v=oTCPG1ezlKc
[Solution]
- Create a dictionary with key representing each stone, value is a
set
containing the possible next steps - Iterate through stones, keep adding possible next steps into sets when iterating.
1 | class Solution: |
55. Jump Game
Leetcode: https://leetcode.com/problems/jump-game/
Note: Start from the end of the nums, see if I start from i
, can I reach to the nearest “good point”. A “good point” is the location where I can reach to the end.
1 | def canJump(self, nums: List[int]) -> bool: |
91. Decode Ways
Leetcode: https://leetcode.com/problems/decode-ways/
[Solution]
- use an array
dp[]
to do memoization - Go though
s
by length 1 and 2 - Check validation of each elem
1 | class Solution: |
583. Delete Operation for Two Strings
Leetcode: https://leetcode.com/problems/delete-operation-for-two-strings/
[Solution]
- Find the longest common sub sequence (not need to be continued)
- Construct dp array
- The last element in the array reprents the final result after deleting characters
1 | # longest common sequence |
338. Counting Bits
Leetcode: https://leetcode.com/problems/counting-bits/
1 | # Solution 1: P(x)=P(x/2)+(xmod2) |
312. Burst Balloons
Leetcode: https://leetcode.com/problems/burst-balloons/
Video: https://www.youtube.com/watch?v=z3hu2Be92UA
[Solution]
- [0,]
Largest area problem
221. Maximal Square
Leetcode: https://leetcode.com/problems/maximal-square/
[Solution]
- Use a m*n dp matrix
- Each cell in the dp represents the maximum length of the square when (i, j) is the right bottom corner of the square
- Transition function:
min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1]) + 1
1 | def maximalSquare(self, matrix: List[List[str]]) -> int: |
84. Largest Rectangle in Histogram
Leetcode: https://leetcode.com/problems/largest-rectangle-in-histogram/
85. Maximal Rectangle
Leetcode: https://leetcode.com/problems/maximal-rectangle/
Solution I
- Store maximum width when current cell is the right bottom corner fo rectangle
- Go up
(row - curRow) * min(maxWidth in the column)
is the maximum area when the current cell is the right bottom corner
Time Complexity
O(N^2M). Computing the maximum area for one point takes O(N). There are N * M points.
1 | def maximalRectangle(self, matrix: List[List[str]]) -> int: |
Solution II
TBA
1277. Count Square Submatrices with All Ones
1 | def countSquares(self, matrix: List[List[int]]) -> int: |
1048. Longest String Chain
Leetcode: https://leetcode.com/problems/longest-string-chain/
My DFS super long solution1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43class GraphNode:
def __init__(self, value):
self.val = value
self.children = []
self.indegree = 0
self.outdegree = 0
class Solution:
def longestStrChain(self, words: List[str]) -> int:
words.sort(key = lambda x: len(x))
def pred(a, b):
if len(b) - len(a) != 1:
return False
for ch in a:
if ch not in b:
return False
return True
graph = {}
for word in words:
graph[word] = GraphNode(word)
for i in range(len(words)):
for j in range(i + 1, len(words)):
if (pred(words[i], words[j])):
graph[words[i]].children.append(graph[words[j]])
graph[words[j]].indegree += 1
graph[words[i]].outdegree += 1
# print(graph)
res = 0
stack = []
for word in graph:
node = graph[word]
if node.indegree == 0:
stack.append([node, 1])
while stack:
cur = stack.pop()
# print(cur)
res = max(res, cur[1])
children = cur[0].children
for child in children:
stack.append([child, cur[1] + 1])
return res
DFS + Memoization
1 | import collections |
DP Solution:
TBA
91. Decode Ways
Leetcode: https://leetcode.com/problems/decode-ways/
Need to consider a lot of edge cases with “0”.
1 | def numDecodings(self, s: str) -> int: |
239. Sliding Window Maximum
Leetcode: https://leetcode.com/problems/sliding-window-maximum/
Solution 1: Use a heap. Time complexity: O(Nlogk)
1 | def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]: |
Solution 2:
- Use a
deque
,popleft()
when getting new numbers into window,pop()
while incoming number is larger thandeque[-1]
, and then append incoming number at the end of thedeque
. In this way,deque[0]
would always be the largest one.
1 | def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]: |
Solution 3: Dynamic Programming
- Split input array into blocks with
length = k
- Use two arrays
left
andright
to record the maximum number within the sub-windows max(right[rightBorder], left[leftBorder])
1 | def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]: |
312. Burst Balloons
Leetcode: https://leetcode.com/problems/burst-balloons/
Good Video: https://www.youtube.com/watch?v=Ci39lcoLbyw
Each cell in the dp
matrix represents the maximum value we can get using left
and right
as borders. Within the border, we calculate each cell i in range(left + 1, right)
assuming i
is the last balloon to burst. And that makes the transition formula as: nums[i] * nums[left] * nums[right] + dp[left][i] + dp[i + 1][right]
1 | def maxCoins(self, nums: List[int]) -> int: |
152. Maximum Product Subarray
Leetcode: https://leetcode.com/problems/maximum-product-subarray/
The key part is to deal with negative values. We use two value max
and min
to keep track of maximum and minimum product so far, when we meet negative values, we swap them.
1 | def maxProduct(self, nums: List[int]) -> int: |
198. House Robber
Leetcode: https://leetcode.com/problems/house-robber/
Use mx
as a memoization to store the position of the largest value so far.
1 | def rob(self, nums: List[int]) -> int: |
0/1 knapsack problem
0: Don’t pick the item.
1: Pick the item.
Video: https://www.youtube.com/watch?v=8LusJS5-AGo
- Initiate DP array of
n * capacity
: n the number of coins dp[i][j] = max(not_select(i), select(i)) = max(dp[i - 1][j], dp[][capacity - elements[i]])
322. Coin Change Problem
dp[i][j]
represents maxim value with coins incoins[:i]
1 | def coinChange(self, coins: List[int], amount: int) -> int: |
416. Partition Equal Subset Sum
Leetcode: https://leetcode.com/problems/partition-equal-subset-sum/
Solution: If there exists some combinations of the set that sums to total // 2
, which means the left elements in the array can also sum up to total // 2
, then return true.d[i][j]
meas whether the specific sum j
can be gotten from the first i
numbers.
DP Solution:1
2
3
4
5
6
7
8
9
10
11
12
13
14def canPartition(self, nums: List[int]) -> bool:
total = sum(nums)
if total % 2 == 1:
return False
dp = [[0 for j in range(total // 2 + 1)] for i in range(len(nums) + 1)]
for i in range(len(nums) + 1):
for j in range(total // 2 + 1):
if j == 0:
dp[i][j] = 1
elif i > 0 and j - nums[i - 1] >= 0:
dp[i][j] = dp[i - 1][j] or dp[i - 1][j - nums[i - 1]]
if dp[-1][-1] == 1:
return True
return False
Hash table + memo solution:
1 | class Solution: |
474. Ones and Zeroes
Leetcode: https://leetcode.com/problems/ones-and-zeroes/
Video: https://www.youtube.com/watch?v=R-U_pY0OBKI&t=655s&ab_channel=HuifengGuan
dp[i][j]
用i个1和j个0最多能装多少个元素- Go through the
strs
array, process strings one by one - Update DP array from the end to avoid unintended overriding
1 | def findMaxForm(self, strs: List[str], m: int, n: int) -> int: |
518. Coin Change 2
Leetcode: https://leetcode.com/problems/coin-change-2/
dp[i][j]
: j
is amount, i
is current coins. dp[i][j]
is the sum of select current coin + not select current coin
1 | def change(self, amount: int, coins: List[int]) -> int: |
Optimize to space O(n):
1 | def change(self, amount: int, coins: List[int]) -> int: |
1090. Largest Values From Labels
983. Minimum Cost For Tickets
Leetcode: https://leetcode.com/problems/minimum-cost-for-tickets/
- Each ticket’s minimum price is only dependent on the previous potential start of ticket period
1 | def mincostTickets(self, days: List[int], costs: List[int]) -> int: |
32. Longest Valid Parentheses
Leetcode: https://leetcode.com/problems/longest-valid-parentheses/
See stack solution at leetcode-stack chapter
dp[i]
: the longest valid substring ending ats[i]
: thus all(
is zero- For each
)
, we look at the previousi-1
element, if it’s(
thendp[i] = dp[i - 2] + 2
, else if it’s)
, then we find the length of the valid substring ending ati - 1
:dp[i - 1]
, and find what’s before this previous valid string, if it’s(
, thendp[i] = dp[i - dp[i - 1] - 2] + dp[i - 1] + 2
, else it’s0
1 | def longestValidParentheses(self, s: str) -> int: |
1423. Maximum Points You Can Obtain from Cards
Leetcode: https://leetcode.com/problems/maximum-points-you-can-obtain-from-cards/
Brute force: try every possibility (TLE)
1 | def maxScore(self, cardPoints: List[int], k: int) -> int: |
Save sums from 2 directions in 2 arrays and use 2 pointers.
1 | def maxScore(self, cardPoints: List[int], k: int) -> int: |
Non-traditional DP
264. Ugly Number II
Leetcode: https://leetcode.com/problems/ugly-number-ii/
Solution: We have an array k of first n ugly number. We only know, at the beginning, the first one, which is 1. Then1
2k[1] = min( k[0]x2, k[0]x3, k[0]x5). The answer is k[0]x2. So we move 2's pointer to 1. Then we test:
k[2] = min( k[1]x2, k[0]x3, k[0]x5). And so on. Be careful about the cases such as 6, in which we need to forward both pointers of 2 and 3.
1 | def nthUglyNumber(self, n: int) -> int: |
Stock Problems
309. Best Time to Buy and Sell Stock with Cooldown
Leetcode: https://leetcode.com/problems/best-time-to-buy-and-sell-stock-with-cooldown/
Video: https://www.youtube.com/watch?v=oL6mRyTn56M
1 | var maxProfit = function(prices) { |
410. Split Array Largest Sum
Leetcode: https://leetcode.com/problems/split-array-largest-sum/
Video: https://www.youtube.com/watch?v=_k-Jb4b7b_0
- non-aftereffect property: Once the state of a certain stage is determined, it is not affected by the state in the future. In this problem, if we get the largest subarray sum for splitting
nums[0...i]
intoj
parts, this value will not affected by how we split the remaining part of numbers dp[i][j]
i
: num of groups,j
: up to nums[:j]- Need to visit every
0 <= k < j
to get the minimal possible value:dp[i][j] = min(dp[i][j], max(dp[i - 1][k], sum(nums[k + 1: j + 1])))
DP solution (TLE) O(mn^2)
1 | def splitArray(self, nums, m): |
Recursive solution with memoization (TLE)
1 | def splitArray(self, nums: List[int], m: int) -> int: |
Binary search solution: see binary search page
1139. Largest 1-Bordered Square
Leetcode: https://leetcode.com/problems/largest-1-bordered-square/
Note:
dp
array is a 2-dimension array, each cell is also an array[left, up]
, storing the count of continuous1
s to the left of the current cell and to the up of the current cell- Then we start traversing the grid, if
grid[i][j] == 1
- from edge
l = 1
, we get the four corners of the current square - !! Remember to check if the left-up corner is also 1
- We calculate if the increasing of
1
s between two corner matches our currentl
- from edge
1 | def largest1BorderedSquare(self, grid): |