Dynamic Programming

Posted xero10

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Dynamic Programming相关的知识,希望对你有一定的参考价值。

Easy:

Paint Fence: https://leetcode.com/problems/paint-fence/

设两个数组diff和same,分别表示第 i 个位置最后两个post颜色相同和颜色不同的组合数,则diff[i] = (k - 1) * diff[i - 1] + (k - 1) * same[i - 1],same[i] = diff[i - 1],最后返回diff[n - 1] + same[n - 1]即可。可以用四个变量将空间复杂度优化为O(1)

 

 

Medium:

Coin Change: https://leetcode.com/problems/coin-change/

声明数组num[amount + 1],全部初始化为0。i开始遍历num[0, amount],依次加硬币的所有面值,检查以得到数值大小为下标的num是否为0,如果是0,则将它更新为num[i] + 1,否则将其置为min(原值,num[i] + 1)

 

Unique Binary Search Trees II: https://leetcode.com/problems/unique-binary-search-trees-ii/

问题可分解为子问题:从[1, n]中选一个作为root,求以root为根,[1, root - 1]为左儿子,[root + 1, n]为右儿子。通过递归求出左右儿子所有的可能性(此时得到的左/右儿子的vector已经是完整的左/右子树了),再将所有左右儿子的组合push进result即可。需要注意的是,为了在某个儿子为NULL时方便地组合左右儿子,当helper函数的左边界大于右边界时返回的是vector<TreeNode*> (1, NULL)。然而当输入的n == 0时,最顶层函数结果应该是一个空的vector而不是含有一个NULL元素的vector,所以要对n == 0进行特殊处理

 

Decode Ways: https://leetcode.com/problems/decode-ways/

不要初始化dp数组的前两个元素,将dp声明为size + 1,添加一个dp[0] = 1。每次只判断当前字符和它前面的一个字符

 

House Robber II: https://leetcode.com/problems/house-robber-ii/

可以看成是求[0, n - 2]和[1, n - 1]的最大值。空间复杂度可以优化为O(1)

 

Best Time to Buy and Sell Stock: https://leetcode.com/problems/best-time-to-buy-and-sell-stock/

设置一个变量min_price记录访问过的最小价格min_price = min(min_price, price[i]),设定max_profit = max(max_profit, price[i] - min_price)

 

Best Time to Buy and Sell Stock with Cooldown: https://leetcode.com/problems/best-time-to-buy-and-sell-stock-with-cooldown/

设置两个大小为size + 1的数组buy和sell,分别表示在第 i 天,当操作序列的最后一次操作为buy/sell时的最大利润。对于buy,可能今天买,由于i - 1天是cooldown,则此时的值buy[i] = sell[i - 2] - price[i];也可能最后一次买发生在昨天或更早,今天什么也不做,则此时buy[i] = buy[i - 1],所以,buy[i] = max(buy[i - 1], sell[i - 2] - price[i])。对于sell类似,sell[i] = max(sell[i - 1], buy[i - 1] + price[i])。最后return sell[i]即可。需要注意的是,声明的数组大小为size + 1,在最前面添加了一个[0],对于buy,buy[0]要置为INT_MIN,因为buy[1]肯定是负的;对于sell[0]置零即可

这种算法的空间复杂度可以优化为O(1),通过不声明数组而使用4个变量pre_buy,pre_sell,buy和sell即可。但这种方法不太好理解并且边界容易出错

 

Maximum Product Subarray: https://leetcode.com/problems/maximum-product-subarray/

设定max_value和min_value分别记录以[i -1]为结尾的连乘subarray的最大和最小值。在更新max_value时,选择:连乘到当前位置/以当前位置作为新的subarray起始的值,二者中较大的那个:max_value = max(max_value * nums[i], nums[i])。min_value同理。每次loop更新result = max(result, max_value)

 

Maximum Subarray: https://leetcode.com/problems/maximum-subarray/

dp的方法很简单,就是判断是使用到[i - 1]的序列还是从当前位置重新开始。使用divide and conquer方法的思路是,将数组从中间分成两个子数列,分别求每个子数列的:从最左侧开始的最大和数列lmax,从最右侧向左开始的最大和数列rmax,数列所有元素的和sum和数列的最大子数列mx。则当前数列的最大子数列mx可能是mx1,mx2或lmax1 + rmax2;lmax是max(lmax1, sum1 + lmax2),rmax是max(rmax2, rmax1 + sum2)

 

Longest Increasing Sequence: https://leetcode.com/problems/longest-increasing-subsequence/

1)O(n^2):建立一个大小为size,元素均初始化为1的vector,名为max_len,存储以第 [i] 个元素为结尾的最长递增序列的长度,从i = 1开始,遍历它前面的每个max_len的值,将max_len[i]更新为max(max_len[i], max_len[j] + 1),则max_len中的最大值即为结果

2)O(n log n):创建一个vector名为tail,将其初始化为nums[0],从i = 1开始遍历nums:如果nums[i]小于tail[0],则tail[0] = nums[i];如果nums[i]大于tail.back()(tail中的元素是以升序排列的),则将其push_back到tail中;如果nums[i] > tail[0] && nums[i] < tail.back(),则将tail中第一个大于nums[i]的元素替换成nums[i](用binary search)。这样做的具体原理参考文章:

http://www.geeksforgeeks.org/longest-monotonically-increasing-subsequence-size-n-log-n/

 

Maximal Square: https://leetcode.com/problems/maximal-square/

可以创建一个新的同样大小的二维数组dp,其中的每个元素表示以当前点为右下角时所能表示的最大正方形的边长。即如果matrix[i][j] == '0',则dp[i][j] = 0;若matrix[i][j] == '1',则dp[i][j] = min(dp[i][j - 1], dp[i - 1][j - 1], dp[i - 1][j]) + 1。但注意到其实对于每个dp,只需要检查它左侧,上侧和左上侧的三个元素,所以其实不需要二维数组dp,只要用一个vector记录前一行的结果即可

 

 

Paint House: https://leetcode.com/problems/paint-house/

第 i 个位置是red/blue/green的最小值

 

 

Hard

Palindrome Partitioning II: https://leetcode.com/problems/palindrome-partitioning-ii/

对于每一个位置 i,令j = i ~ 0,检查s[j] ~ s[i]是不是palindrome,如果是则cut[i + 1] = min(cut[i + 1], cut[j] + 1)。这里要将cut初始化为size + 1,也就是说位置 i 的cut数是存在cut[i + 1]中的,因为对j == 0,如果0 ~ i 是palindrome,则不需要切,也就是说对应的值应该为0,因此为了这种情况瞎不进行特别处理,所以在最前面添加一个cut[0] = -1。为了不必重复判断一个substring是不是palindrome,用一个的二维vector isPalindrome[j][i]表示j ~ i 是不是palindrome。这个二维vector初始化为false,当确定某段为palindrome时将其置为true。在判断 j ~ i 是不是palindrome时,只要满足s[i] == s[j] && (i - j < 2 || isPalindrome[j + 1][i - 1])即可

 

 

Edit Distance: https://leetcode.com/problems/edit-distance/

设置一个名为count的二维vector,[i][j]表示将word1的前 i 个字符变成word2的前 j 个字符所需要的最小操作数。

若word1[i] == word2[j]:count[i + 1][j + 1] = min(count[i][j], min(count[i][j + 1] + 1, count[i + 1][j] + 1))

若word1[i] != word2[j]:count[i + 1][j + 1] = min(count[i][j] + 1, min(count[i][j + 1] + 1, count[i + 1][j] + 1))

特别需要注意的是,要初始化count[i][0] = i 和count[0][j] = j

 

 

Distinct Subsequences: https://leetcode.com/problems/distinct-subsequences/

这道题的意思是,S的subsequence中有几个是和T相同的。设一个名为count的二维vector,count[i + 1][j + 1]表示S的前 i 个字符的subsequence中包含与T的前 j 个字符相同的个数。则对于S[i] != T[j],count[i][j] = count[i - 1][j];对于S[i] == T[j],count[i][j] = count[i - 1][j] + count[i - 1][j - 1]。注意如果T为空,则只有S的空subsequece才等于T,所以初始化时count[i][0] = 1

 

Interleaving String: https://leetcode.com/problems/interleaving-string/

 

注意这里要求s1和s2恰好能组成s3,不能有剩余的字符。设一个名为can的二维数组,can[i + 1][j + 1]表示s1的前 i 个字符和s2的前 j 个字符能组成s3的前 i + j 个字符,则can[i + 1][j + 1] = (s1[i] == s3[i + j + 1] && can[i][j + 1]) || (s2[j] == s3[i + j + 1] && can[i + 1][j])。注意两点:1)判断时是s1[i]/s2[j] == s3[i + j + 1];2)初始化时,先将can[0][0]初始化为true,然后对i/j == 0的情况进行特殊处理:can[i + 1][0] = s1[i] == s3[i] && can[i][0];can[0][j + 1] = s2[j] == s3[j] && can[0][j]

以上是关于Dynamic Programming的主要内容,如果未能解决你的问题,请参考以下文章

无人车动态规划(Dynamic Programming)入门

noip Dynamic Programming

动态规划(Dynamic Programming)

动态规划(Dynamic Programming)

动态规划-Dynamic Programming(DP)

Python算法-动态规划(Dynamic Programming)