Dynamic Programming
A Framework to Solve
Typically, dynamic programming problems can be solved with 3 main components:
- First, we need a function or Array that represents the answer to the problem from a given state. This is often named
dp
.
For instance, we may have an Array dp
where dp[i]
represents the length of the longest increasing subsequence that ends with the element.
- We need a way to transition between states - e.g. from
dp[5]
todp[7]
. This is called the recurrence relation and can be quite difficult to figure out.
Given that we know dp[0]
, dp[1]
and dp[2]
, how can we calculate dp[3]
?
In the case of the longest increasing subsequence, if nums[3] > nums[2]
, we can simply take dp[2]
and increase the length by 1.
Of course we’re trying to maximise dp[3]
, so we need to check dp[0..2]
.
- We need a base case. Typically for this, we initialize every element of
dp
to some value - perhaps1
or0
, sometimestrue
orfalse
.
Example - Longest Increasing Subsequence
- Initialize an array
dp
with lengthnums.length
and all elements equal to 1.dp[i]
represents the length of the longest increasing subsequence that ends with the element at indexi
. - Iterate from
i = 1
toi = nums.length - 1
. At each iteration, use a second for loop to iterate fromj = 0
toj = i - 1
(all the elements before i). For each element beforei
, check if that element is smaller thannums[i]
. If so, setdp[i] = max(dp[i], dp[j] + 1)
. - Return the max value from
dp
.
fun lengthOfLIS(nums: IntArray): Int {
val dp = IntArray(nums.size) { 1 }
for (i in 1 until nums.size) {
// All elements before i
for (j in 0 until i) {
if (nums[j] < nums[i]) {
dp[i] = Math.max(dp[i], dp[j] + 1)
}
}
}
return dp.max()!!
}