朝花夕拾——动态规划
Overview
最長連續子序列和
-2 6 -1 5 4 -7 2 3求連續的子數列最大和。
根據遞歸的思路想:f(n) = max{ f(n-1) + num[n], num[n] }
LeetCode 300. 最長遞增子序列 LIS
題目鏈接:https://leetcode-cn.com/problems/longest-increasing-subsequence/
給你一個整數數組 nums ,找到其中最長嚴格遞增子序列的長度。(子序列可以不連續)
輸入:nums = [10,9,2,5,3,7,101,18] 輸出:4 解釋:最長遞增子序列是 [2,3,7,101],因此長度為 4 。輸入:nums = [7,7,7,7,7,7,7] 輸出:1終于碰到這個題了,之前一直分不清LCS和LIS。
方法1:樸素動態規劃
用一個二重循環來實現,時間復雜度n方。
定義dp[i]的含義,以nums[i]結尾的子序列的最大長度。
for (int i = 0; i < n; ++i) {for (int j = 0; j < i; ++j) {if (nums[j] < nums[i]) {dp[i] = max(dp[i], dp[j] + 1);}}}方法2:二分+動態規劃
考慮方法一中的內層循環,是否可以優化。
重新定義dp[i],長度為i的子序列中最后一位數字的最小值。
做法:維護一個單調數組dp[],在遍歷nums[]的時候,如果當前元素a比dp的棧頂元素大,則直接推入棧,如果比棧頂元素小,則二分這個數組,找到最小的比a大的數dp[k],替換它,以保證在長度k下,dp[k]中的值始終最小。
dp的長度就是最終結果。
int lengthOfLIS(vector<int>& nums) {if (nums.size() == 0) return 0;vector<int> dp(1, nums[0]);for (int i=1; i<nums.size(); ++i) {if (nums[i] > dp[dp.size() - 1]) {dp.push_back(nums[i]);continue;}int lo = 0;int hi = dp.size() - 1;while (lo < hi) {int mid = (lo + hi) / 2;if (nums[i] > dp[mid]) {lo = mid + 1;} else {hi = mid;}}dp[lo] = nums[i];}return dp.size();}非連續最大子集
給你一個序列arr = {}
請你求出它的一個子集,要求,子集和最大,且子集的元素在原序列終不能相鄰
入門DP
對于每一個數,dp[i] = max{ dp[i-1], dp[i] + arr[i-1] }
那么就和斐波那契有點像了
LeetCode 1143. 最長公共子序列 LCS
給定兩個字符串 text1 和 text2,返回這兩個字符串的最長 公共子序列 的長度。如果不存在 公共子序列 ,返回 0 。
輸入:text1 = "abcde", text2 = "ace" 輸出:3 解釋:最長公共子序列是 "ace" ,它的長度為 3 。創建二維數組dp,其中dp[i][j] 表示text[0:i]和text2[0:j]的最長公共子序列的長度。
hdu1506 Largest Rectangle in a Histogram
對于每一個高度h[i],搜索它能到達的最左,和最右,最大面積Smax = Max{ i | 面積Si = (最右 - 最左 + 1) *h[i] }
這樣的時間復雜度為O(n^2),必超時
舉例 2,3,4,5
要判斷2能到達最右的位置,不需要循環比較,只需要知道3能到達的最右是哪里,因為若3能到達的位置,2必然也能到達
1 #include<cstdio>2 #include<iostream>3 using namespace std;4 5 int main()6 {7 int n;8 while(scanf("%d", &n) && n)9 { 10 long long h[100007]; 11 long long l[100007]; 12 long long r[100007]; 13 for(int i=1;i<=n;i++) 14 { 15 scanf("%lld", &h[i]); 16 l[i] = r[i] = i; 17 } 18 h[0] = h[n+1] = -1; 19 for(int i=1;i<=n;i++) 20 { 21 while(h[i] <= h[l[i]-1]) { 22 l[i] = l[l[i]-1]; 23 } 24 } 25 for(int i=n;i>=1;i--) 26 { 27 while(h[i] <= h[r[i]+1]) { 28 r[i] = r[r[i]+1]; 29 } 30 } 31 long long ans = 0; 32 for(int i=1;i<=n;i++) 33 { 34 ans = ans > (r[i]-l[i]+1)*h[i] ? ans : (r[i]-l[i]+1)*h[i]; 35 } 36 printf("%lld\n",ans); 37 } 38 }LeetCode 338. Counting Bits
第一道自己做出來的DP!!!紀念合影,你沒聽錯我就是這么菜
給你一個非負整數num,求從0到num的num+1個數中,每個數二進制表示的1的個數,返回一個數組
就是dp嘛,暴力的辦法就是for from 0 to num,每個數的每一個二進制位比較,設二進制位最壞長度為31位,那么復雜度就是O(31*n)
其中大量重復計算,所以用動態規劃解決,自底向上
1 class Solution {2 public:3 vector<int> countBits(int num) {4 vector<int>dp(num+1);5 dp.resize(num+1);6 dp[0] = 0;7 for(int i=1;i<=num;i++)8 {9 if((i & 1) == 0) 10 dp[i] = dp[i>>1]; 11 else 12 dp[i] = dp[i>>1] + 1; 13 } 14 return dp; 15 } 16 };能不能組成S?
給你一個序列arr = {},和一個數S,問你序列里的數能不能組成S,又是簡單的選和不選問題
web數據挖掘中的編輯距離
這個有點難想
稍微變了一小下的01背包
uim拉著基友小A到了一家……餐館,很低端的那種。uim指著墻上的價目表說:“隨便點”。
題目描述
不過uim由于買了一些書,口袋里只剩M元(M≤10000)。
餐館雖低端,但是菜品種類不少,有N種(N≤100),第i種賣a?元(ai?≤1000)。由于是很低端的餐館,所以每種菜只有一份。
小A奉行“不把錢吃光不罷休”,所以他點單一定剛好吧uim身上所有錢花完。他想知道有多少種點菜方法。
思路:和裸的價值+容量的01背包不同,原來是求可放入背包且價值最大化,這個是必須裝滿背包,求有多少種方案。
根據題意設數組dp[i][j]為前i種菜品恰好花費j元的點菜方法數。那么,考慮第i種菜點或不點,若不點,則說明前i-1種菜,已經恰好花費j元;若點,則說明前i-1種菜,花費j-ai元,這樣,在點完第i種菜后,恰好花費j元。
因此容易得出公式
dp[i][j] = dp[i-1][j] + dp[i-1][j-ai]然而很多人想到這里以后就不知道接下來怎么做了。(比如說我)
但是在看到"j - ai"后,應該能想到,對j - ai的情況進行枚舉。
- 若j > ai,則和上式一樣。
- 若j < ai,則說明第i道菜,壓根就買不起,因此dp[i][j] = dp[i-1][j]。
- 若j == ai,那么說明可以只點這一道菜作為一種新的方案,dp[i][j] = dp[i-1][j] + 1。
96. 不同的二叉搜索樹
給定一個整數 n,求以 1 … n 為節點組成的二叉搜索樹有多少種?
來源:力扣(LeetCode)
鏈接:https://leetcode-cn.com/problems/unique-binary-search-trees/
著作權歸領扣網絡所有。商業轉載請聯系官方授權,非商業轉載請注明出處。
這個題我一開始沒有想到能拿dp做。
354. 俄羅斯套娃信封問題
題目鏈接:https://leetcode-cn.com/problems/russian-doll-envelopes/
給定一些標記了寬度和高度的信封,寬度和高度以整數對形式(w, h)出現。當另一個信封的寬度和高度都比這個信封大的時候,這個信封就可以放進另一個信封里,如同俄羅斯套娃一樣。
請計算最多能有多少個信封能組成一組“俄羅斯套娃”信封(即可以把一個信封放到另一個信封里面)。
說明:
不允許旋轉信封。
是不是很像LIS的變型?但是這里要注意對原數組進行一個從小到大的排序。對于w從小到大排序,對于h從大到小排序,我這里還沒有想明白,為什么h降序排序就是對的,升序排序就是錯的。
int maxEnvelopes(vector<vector<int>>& envelopes) {sort(envelopes.begin(), envelopes.end(), [](const vector<int> &u, const vector<int> &v){return u[0] < v[0] || (u[0] == v[0] && u[1] > v[1]);});vector<pair<int, int>> dp(1, {envelopes[0][0], envelopes[0][1]});for (const auto &envelope : envelopes) {if (envelope[0] > dp[dp.size() - 1].first && envelope[1] > dp[dp.size() - 1].second) {dp.push_back({envelope[0], envelope[1]});continue;}int lo = 0;int hi = dp.size() - 1;while (lo < hi) {int mid = (lo + hi) / 2;if (envelope[0] > dp[mid].first && envelope[1] > dp[mid].second) {lo = mid + 1;} else {hi = mid;}}dp[lo] = {envelope[0], envelope[1]};}return dp.size(); }我的代碼還是比較復雜了,看了題解以后發現,只需要對h做LIS即可,因為h是降序排列的,當你對h做LIS時,別忘了,LIS是最長遞增子序列,只要被你選入的h一定是遞增的,但是你又是降序排列的,這說明你選取的兩個信封一定有w2 > w1,這里是嚴格大于,仔細想一想。
總結
以上是生活随笔為你收集整理的朝花夕拾——动态规划的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: OpenCV实践(4)- 叠加两幅图片
- 下一篇: 容灾恢复 | 记一次K8S集群中etcd