動態規劃:將一個問題拆成幾個子問題,分別求解這些子問題,即可推斷出大問題的解,
判斷一個問題能否使用DP解決:能將大問題拆成幾個小問題,且滿足無后效性、最優子結構性質
DP的核心思想:盡量縮小可能解空間,
暴力做法是列舉所有的可能解,這是最大的可能解空間,
DP是列舉有希望成為答案的解,這個空間比暴力的小得多,
參考阮行止的回答
例1、青蛙跳臺階問題
題目鏈接

思想
對該問題進行抽象,n個階梯,每個階梯都代表一個位置,然后將這個n個位置相連(只能用長度為1和2的線)

這個問題就轉化成了從 位置0 到 位置10 有幾種不同的路可以走?
遞推關系:dp[n] = dp[n-1] + dp[n-2];
比如7到10有幾種走法=8到10的走法+9到10的走法
遞推結果可以由下面這個陣列說明

參考牛岱的回答
代碼
class Solution {
public:
int numWays(int n) {
vector<int> dp(n+1,1);
for(int i=2;i<=n;i++){
dp[i]=(dp[i-1]+dp[i-2])%1000000007;
}
return dp[n];
}
};
例2、最長上升子序列
題目鏈接

思想
方法1, O(n2)做法

參考
代碼
對應方法1
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
vector<int> dp(nums.size());
for(int i=0;i<nums.size();i++){
dp[i]=1;
for(int j=0;j<i;j++){
if(nums[j]<nums[i]&&dp[j]+1>dp[i])
dp[i]=dp[j]+1;
}
}
//這里我呼叫逆序函式,回傳首部不曉得為啥不行
// sort(dp.rbegin(),dp.rend());
// return dp[0];
int res=0;
for(int i=0;i<dp.size();i++){
if(res<dp[i])
res=dp[i];
}
return res;
}
};
二分查找解法
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
//二分查找解法
vector<int> top=nums;
// 牌堆數初始化為 0
int piles = 0;
for (int i = 0; i < nums.size(); i++) {
// 要處理的撲克牌
int poker = nums[i];
/***** 搜索左側邊界的?分查找 *****/
int left = 0, right = piles;
while (left < right) {
int mid = (left + right) / 2;
if (top[mid] > poker) {
right = mid;
}
else if (top[mid] < poker) {
left = mid + 1;
}
else {
right = mid;
}
}
// 沒找到合適的牌堆, 新建?堆
if (left == piles) piles++;
// 把這張牌放到牌堆頂
top[left] = poker;
}
// 牌堆數就是 LIS ?度
return piles;
}
};
對上訴二分查找代碼進行分析
序列為2 5 3 4 1 7 6
下圖為i=0時剛進入回圈

進while回圈前

i=1


例3、剪繩子
題目鏈接

思想
參考出處
動態規劃5步走
1. 判題題意是否為找出一個問題的最優解
2. 從上往下分析問題,大問題可以分解為子問題,子問題中還有更小的子問題
3. 從下往上分析問題 ,找出這些問題之間的關聯(狀態轉移方程)
4. 討論底層的邊界問題
5. 解決問題(通常使用陣列進行迭代求出最優解)
由5步走可知
-
判題題意是否為找出一個問題的最優解 看到字眼是“可能的最大乘積是多少”,由此判斷是求最優解問題,可以用動態規劃解決;
-
從上往下分析問題,大問題可以分解為子問題,子問題中還有更小的子問題, 題目中舉了個例子:當繩子的長度為8時,我們把它剪成長度分別為2,3,3的三段,此時得到的最大乘積是18;我們可以從這里開始突破,把長度為8繩子的最大乘積分解為數個子問題,長度為8我們可以把它看成長度為1和7的繩子的和,或者長度為2和6的繩子的和,或者長度為3和5的繩子的和and so on! 到這里,相信大家已經看到一絲真理了吧?
-
從下往上分析問題 ,找出這些問題之間的關聯(狀態轉移方程) 在第二點時,我們已經從上到下分析問題了,現在我們要從下往上分析問題了,分析可知, f(8)的值就是f(1)*f(7),f(2)*f(6),f(3)*f(5),f(4)*f(4)它們之中的最大值,即f(8) =
Max{f(1)*f(7),f(2)*f(6),f(3)*f(5),f(4)*f(4)}
只要知道f(1)到f(7)的值就能求出f(8);對于f(7),只要知道f(1)到f(6)的值就能求出f(7);對于f(6),只要知道f(1)到f(5)的值就能求出f(6);以些類推,我們只要知道前幾個邊界的值,就能一步步迭代出后續的結果!
狀態轉移方程: f(n)=Max{f(n-i)*f(i)} i={1,2,3,…,n/2} -
討論底層的邊界問題 底層的邊界問題說的就是最小的前幾個數值的f(n)的值,本題中就是f(0)、f(1)、f(2)、f(3)的值 ,
對于f(0),長度為0的繩子,沒辦法剪,沒有意義
對于f(1),長度為1的繩子,沒辦法剪,設為1
對于f(2),長度為2的繩子,只有一種剪法,剪成兩段長度為1的繩子,但剪后的乘積為1,比自身更小;如果不是求自身的值,要求乘積最大值的話就沒必要剪,
對于f(3),長度為3的繩子,只有一種剪法,剪成兩段長度為1和2的繩子,但剪后的乘積為2,比自身更小;如果不是求自身的值,要求乘積最大值的話也沒必要剪,
代碼
Java版本
public static int cutting(int n) {
//長度小于等等于1沒辦法剪
if(n <= 1)
return 0;
//對于f(2),長度為2的繩子,只有一種剪法,剪成兩段長度為1的繩子,剪后的乘積為1
if(n == 2)
return 1;
//對于f(3),長度為3的繩子,只有一種剪法,剪成兩段長度為1和2的繩子,但剪后的乘積為2
if(n == 3)
return 2;
int max = 0;
//陣列用于存盤繩子乘積最大值
int value[] = new int[n + 1];
value[0] = 0;
value[1] = 1;
//剪后的乘積為1,比自身更小;如果不是求自身的值,要求乘積最大值的話就沒必要剪
value[2] = 2;
//剪后的乘積為2,比自身更小;如果不是求自身的值,要求乘積最大值的話也沒必要剪
value[3] = 3;
//從f(4)開始迭代
for(int i = 4;i <= n; i++) {
max = 0;
for(int j = 1;j <= i/2; j++) {
int val = value[j] * value[i - j];
max = val > max ? val : max;
}
value[i] = max;
}
max = value[n];
return max;
}
C++版本
class Solution {
public:
int cuttingRope(int n) {
vector<int> dp(n+1,0);
dp[0]=1;
for(int i=1;i<=(n+1)/2;i++){
for(int j=i;j<=n;j++){
dp[j]=max(dp[j],dp[j-i]*i);
}
}
return dp[n];
}
};
下面是上訴的c++代碼的執行程序

北大OJ
PAT
codeup
C++的常用函式
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/163724.html
標籤:python
