動態規劃/leetcode/直接推導遞推公式
通過編寫動態規劃系列題目,拓寬思路
583. Delete Operation for Two Strings
針對兩個字串刪除一定的字元使這兩個字元相同,求最少的刪除數。 可以轉換為求最長公共子序列問題。求出來之後兩個字串的長度和減去最長子序列的兩倍即可。 對最長公共子序列動態規劃的理解,應該先從自定向下的遞迴方法理解。 參考連結提供所有方法:https://leetcode.com/problems/delete-operation-for-two-strings/solution/ 裡面首先介紹遞迴的方法,然後介紹在遞迴的基礎上加儲存陣列的方法,然後介紹動態規劃的方法,並介紹了一種不轉換為最長子序列的動態規劃方法。通常的壓縮方法也介紹了。 下面是最長子序列的二維動態規劃方法。class Solution { public: int minDistance(string word1, string word2) { vector<vector<int>> dp(word1.size()+1,vector<int>(word2.size()+1,0));//採用這種擴容的方法,可以防止一個數組為空的情況,而且可以把邊界情況放到一個迴圈中討論 for(int i=0;i<=word1.size();i++) for(int j=0;j<=word2.size();j++) { if(i==0||j==0) continue; if(word1[i-1]==word2[j-1])//注意這裡要考慮實際位置和標號位置的對應 dp[i][j]=dp[i-1][j-1]+1; else { dp[i][j]=max(dp[i-1][j],dp[i][j-1]); } } return word1.size()+word2.size()-2*dp[word1.size()][word2.size()]; } };
413 Arithmetic slices 等差數列切片 子陣列(連續子序列)
題意:尋找一個數組的連續子陣列,使得這個子陣列是等差數列。請求出這種子陣列的個數。
我的思路
(1)n方複雜度,首先求累加和陣列,然後根據等差數列求和充要條件,判斷序列是否是等差數列,但是解僱不對
(2)動態規劃方法class Solution { public: int numberOfArithmeticSlices(vector<int>& A) { int n=A.size(); if(n<=2) return 0; vector<int> sum(n+1,0); //求累加和陣列 for(int i=1;i<=n;i++) sum[i]=sum[i-1]+A[i-1]; int count=0; for(int i=3;i<=n;i++) { for(int k=3;i-k>=0;k++) { int cursum=sum[i]-sum[i-k]; if((cursum-k*A[i-k])%(k*(k-1)/2)==0) count++; } } return count; } };
設dp[i]表示到第i1個時,前面構成的數列個數,
如果 A[i-1]-A[i-2]==A[i-2]-A[i-3] dp[i]=dp[i-1]+1+(len-2)+1,這裡的len是在前面至少是3個數的時候的長度3,如果前面不構成等差序列,len=0,公式變為dp[i]=dp[i-1]+1
如果A[i-1]-A[i-2]!=A[i-2]-A[i-3] ,dp[i]=dp[i-1] 此時把len變成0.
class Solution { public: int numberOfArithmeticSlices(vector<int>& A) { int n=A.size(); vector<int> dp(n+1,0); int len=0; for(int i=3;i<=n;i++) { if(A[i-1]-A[i-2]==A[i-2]-A[i-3]) { if(len==0)//說明之前的三個不是這種序列,從現在開始重新構成3個數的等差序列 { dp[i]=dp[i-1]+1; len=3;//在結尾改變len } else//說明當前等差數列的長度在遞增過程 { dp[i]=dp[i-1]+len-1; len++;//len自增後是當前這一片的最大長度 } } else { dp[i]=dp[i-1]; len=0;// } } return dp[n]; } };
可以把dp壓縮成 一個變數:
class Solution {
public:
int numberOfArithmeticSlices(vector<int>& A) {
int n=A.size();
int dp=0;
int len=0;
for(int i=3;i<=n;i++)
{
if(A[i-1]-A[i-2]==A[i-2]-A[i-3])
{
if(len==0)
{
dp=dp+1;
len=3;
}
else
{
dp=dp+len-1;
len++;
}
}
else
{
dp=dp;
len=0;
}
}
return dp;
}
};
參考方法:
(1)dp[i]表示以i結尾的等差數列個數,如果A[i] - A[i -1] == A[i - 1] - A[i - 2],dp[i]= dp[i -1] +1;否則dp[i]=0
class Solution {
public:
int numberOfArithmeticSlices(vector<int>& A) {
int res = 0, n = A.size();
vector<int> dp(n, 0);
for (int i = 2; i < n; ++i) {
if (A[i] - A[i - 1] == A[i - 1] - A[i - 2]) {
dp[i] = dp[i - 1] + 1;
}
res += dp[i];
}
return res;
}
};
上述dp可以用一個變數代替,因為每次只涉及當前值和前面的值。
class Solution {
public:
int numberOfArithmeticSlices(vector<int>& A) {
int res = 0, cur = 0;
for (int i = 2; i < A.size(); ++i) {
if (A[i] - A[i - 1] == A[i - 1] - A[i - 2]) {
cur += 1;
res += cur;
} else {
cur = 0;
}
}
return res;
}
};
(2)使用公式[1,2,3,4]含有3個長度至少為3的算數切片,我們再來看[1,2,3,4,5]有多少個呢: len = 3: [1,2,3], [2,3,4], [3,4,5] len = 4: [1,2,3,4], [2,3,4,5] len = 5: [1,2,3,4,5] 那麼我們可以找出遞推式,長度為n的等差數列中含有長度至少為3的算數切片的個數為(n-1)(n-2)/2,那麼題目就變成了找原陣列中等差數列的長度,然後帶入公式去算個數即可 下面的程式碼count=1時表示長度為3.
public class Solution {
public int numberOfArithmeticSlices(int[] A) {
int count = 0;
int sum = 0;
for (int i = 2; i < A.length; i++) {
if (A[i] - A[i - 1] == A[i - 1] - A[i - 2]) {//如果滿足,長度自增,1的時候說明長度是3,2的時候說明長度是4
count++;
} else {
sum += (count + 1) * (count) / 2;//連續數列結束,把之前的數加上
count = 0;//並把count變成0
}
}//注意這種方法不會有重複,它實際分別找到陣列中成片出現的最長數列,每個這樣的數列不可能相連
return sum += count * (count + 1) / 2;//在長度自增過程中,並沒有把最後算的數列個數加到總和中去,最後需要做補充操作。當最後不構成數列時,count為0
}
}
同樣的一個實現: 只是len和count的取值不同。len為3時,表示長度為3 這使得公式與推導公式完全一樣,但是最後的時候,可能len為2,不構成數列,這個時候需要加一個判斷
class Solution {
public:
int numberOfArithmeticSlices(vector<int>& A) {
int res = 0, len = 2, n = A.size();
for (int i = 2; i < n; ++i) {
if (A[i] - A[i - 1] == A[i - 1] - A[i - 2]) {
++len;
} else {
if (len > 2) res += (len - 1) * (len - 2) * 0.5;
len = 2;
}
}
if (len > 2) res += (len - 1) * (len - 2) * 0.5;
return res;
}
};
446. Arithmetic Slices II - Subsequence 等差數列切片 子序列(可以不連續)
646 maxmum length of pair chain pair的最大鏈
給定n組數,每組的第一個數小於第二個。如果兩個陣列,第一個組的大值小於第二個組的小值,那麼構成一個鏈。題目要求返回最大的鏈的長度。
343 integer break 整數切分乘積最大
一個數可以分成幾個數的和,這些數相乘的最大值是多少?
357 Count Numbers with Unique Digits
給定一個數n,在0 ≤ x < 10n中找所有的數的沒有相同數字的數量
486Predict the Winner 紙牌博弈問題
題目:有一個整型陣列A,代表數值不同的紙牌排成一條線。玩家a和玩家b依次拿走每張紙牌,規定玩家a先拿,玩家b後拿,但是每個玩家每次只能拿走最左或最右的紙牌,玩家a和玩家b都絕頂聰明,他們總會採用最優策略。請返回最後獲勝者的分數。給定紙牌序列A及序列的大小n,請返回最後分數較高者得分數(相同則返回任意一個分數)。(獲得的牌數相加)
思路:
遞迴推導:
//遞迴推導
class Solution {
public:
bool PredictTheWinner(vector<int>& nums) {
int sum=accumulate(nums.begin(),nums.end());
int me=p(nums,0,nums.size()-1);
if(sum-me>=me) return false;
else
return true;
}
//遞迴推導
int p1(vector<int>& nums,int i,int j)//i表示牌的最左邊,j表示牌的最右邊
{
//可以取i,也可以取j
//如果只剩一張牌,取走這個牌
if(i==j) return nums[i];
//如果剩兩張牌,因為奇偶性不同最後剩得牌不一樣
if(i=j-1) return max(nums[i],nums[j]);
int sum=0;
//如果我取走i,給對手的牌是i-1到j,對手的可以取走i-2或者j,他會選擇剩下的牌加和小的那種情況
//所以我取走i的話,對手給我留下下面兩種牌堆
//p(nums,i+2,j),p(nums,i+1,j-1);
//他他媽的一定會留較小的那個:min(p(nums,i+2,j),p(nums,i+1,j-1))
//同樣道理,我也可以取走j
//留給對手的牌堆是:i到j-1.他留給我的牌堆是:min(p(nums,i+1,j-1),p(nums,i,j-2))
//但是現在決定權在我這裡,所以我會選擇:
return max(nums[i]+min(p(nums,i+2,j-1),p(nums,i+1,j-1)),nums[j]+min(p(nums,i+1,j-1),p(nums,i,j-2)));
}
};
改成動態規劃class Solution {
public:
bool PredictTheWinner(vector<int>& nums) {
int sum=std::accumulate(nums.begin(),nums.end(),0);
int n=nums.size();
if(n==1) return true;
vector<vector<int>> dp(n,vector<int>(n,0));
for(int i=n-1;i>=0;i--)
{
for(int j=i;j<n;j++)
{
if(i==j) dp[i][j]=nums[i];
else if(i+1==j) dp[i][j]=max(nums[i],nums[j]);
else
{
dp[i][j]=max(nums[i]+min(dp[i+2][j],dp[i+1][j-1]),nums[j]+min(dp[i+1][j-1],dp[i][j-2]));
}
}
}
if(sum-dp[0][n-1]<=dp[0][n-1]) return true;
else
return false;
}
};
改成一維陣列:由於依賴左下方的反斜對角線,因此需要從左上至右下沿反斜對角線遍歷
標準答案:
464Can I Win
給定數的範圍,每次兩個人輪流拿出一個數,這些數加起來大於目標數的時候的那個人贏,問第一個拿數的人能贏嗎?假設兩個人都灰常聰明375Guess Number Higher or Lower II
猜數,猜多少花多少,問最少帶多少錢能猜對?392Is Subsequence 判斷一個字串是否是另一個字串的子序列
494target Sum
這道題給了我們一個數組,和一個目標值,讓我們給陣列中每個數字加上正號或負號,然後求和要和目標值相等,求有多少中不同的情況。
6502 Keys Keyboard
文本里只有一個字元A,每次的操作只能是複製貼上當前所有數字或者再貼上一遍,求給定字元長度所需要的最小操作次數
通過推導找規律,發現dp[i]:if(k*j==i) dp[i]=min(dp[k]+dp[j]) ,就是說一個數的操作次數等於它相乘因子的運算元之和, 取sqrt找到最相近的兩個因數相加即可。
class Solution {
public:
int minSteps(int n) {
//dp[i]:if(k*j==i) dp[i]=min(dp[k]+dp[j])
if(n<=1) return 0;
vector<int> dp(n+1,0);
dp[1]=0;
for(int i=2;i<=n;i++)
{
dp[i]=i;
int factor=sqrt(i);
for(int j=factor;j>0;j--)
{
if(i%j==0) {
dp[i]=dp[j]+dp[i/j];
break;
}
}
}
return dp[n];
}
};
另外一種解法:
由於2 * 2 = 2 + 2,2 * 3 > 2 + 3,4 * 4 > 4 + 4 ,而之前的那種方法,得到的兩個因子,每個因子可以分別分解。這個題目相當於將一個數分解成多個因子的加和。比如30->5+6,6->2+3,就是2+3+5.所以可以有遞迴實現:
class Solution {
public:
int minSteps(int n) {
if (n == 1) return 0;
for (int i = 2; i < n; i++)
if (n % i == 0) return i + minSteps(n / i);
return n;
}
};
這個遞迴實現可以改成迭代實現:
public int minSteps(int n) {
int s = 0;
for (int d = 2; d <= n; d++) {
while (n % d == 0) {
s += d;
n /= d;
}
}
return s;
}
這樣複雜度達到logn377Combination Sum IV
給定一個集合,每個數都不同,給定一個目標,在集合中選擇數(可重複)的加和是這個目標值,問有多少種(次序不同的算不同種)?
class Solution {
public:
int combinationSum4(vector<int>& nums, int target) {
int n=nums.size();
vector<int> com(target+1,0);
com[0]=1;
for(int i=1;i<=target;i++)
for(int j=0;j<n;j++)
{
if(i-nums[j]>=0)
com[i]+=com[i-nums[j]];//組成i的最後一個元素可以是nums中的任何一個(只要不比總和大),所以com[i]是減去任何一個(並滿足要求)的所有加和
}
return com[target];
}
};
638Shopping Offers
打折促銷組合,給定需要購買的商品型別和數量,求買這些東西花費的最少的錢
309Best Time to Buy and Sell Stock with Cooldown
可以任意的購買拋售股票,但是拋售之後的一天不能購買股票,求賺錢最多是多少?
416Partition Equal Subset Sum把一個數組分成兩組,問能不能兩組的元素加和相等
376Wiggle Subsequence 增減最長子序列
這個題看的題解 思路1:設定up[i]表示到i位置時首差值為正的擺動子序列的最大長度,down[i]表示到i位置時首差值為負的擺動子序列的最大長度
對於每一個up[i]遍歷之前的down[j],當滿足nums[i]>nums[j]時,選擇是否更新up[i]
對於每一個down[i]遍歷之前的up[j],當滿足nums[i]<nums[j]時,選擇是否更新down[i]
程式碼如下:
class Solution {
public:
int wiggleMaxLength(vector<int>& nums) {
int n=nums.size();
if(n<=1) return n;
vector<int> up(n,1);//初始值是1
vector<int> down(n,1);
for(int i=1;i<n;i++)
for(int j=0;j<i;j++)
{
if(nums[i]>nums[j])
{
up[i]=max(up[i],down[j]+1);
}
else if(nums[i]<nums[j])
{
down[i]=max(down[i],up[j]+1);
}
}
return max(up[n-1],down[n-1]);
}
};
思路2:
如果nums[i]>nums[i-1] 那麼up[i]=down[i-1]+1,down[i]=down[i-1]
如果nums[i]>nums[i-1] 那麼down[i]=up[i-1]+1,up[i]=up[i-1]
如果兩者相等,down[i]=down[i-1] up[i]=up[i-1]
只需遍歷一遍即可,注意down[0]和up[0]都是1
class Solution {
public:
int wiggleMaxLength(vector<int>& nums) {
int n=nums.size();
if(n<=1) return n;
vector<int> up(n,0);//初始值是1
vector<int> down(n,0);
down[0]=1;
up[0]=1;
for(int i=1;i<n;i++)
{
if(nums[i]<nums[i-1] )
{
up[i]=down[i-1]+1;
down[i]=down[i-1];
}
if(nums[i]>nums[i-1])
{
down[i]=up[i-1]+1;
up[i]=up[i-1];
}
if(nums[i]==nums[i-1])
{
down[i]=down[i-1];
up[i]=up[i-1];
}
}
return max(up[n-1],down[n-1]);
}
};
貪心演算法:
考慮增減曲線,我們在找點的時候,如果遇到連續升序或者連續降序的時候,一定而且只能選擇這段的兩個節點,當出現增減時,這些部分也包含進來。可以理解為,這個題目是將原來的節點做了一種“濾波處理”:節點的作用只描述趨勢變化的增減特點,在一定時間內連續增加或減少時,我們只關心這一段的開始和結束,而忽略中間的過程。
public class Solution {
public int wiggleMaxLength(int[] nums) {
if (nums.length < 2)
return nums.length;
int prevdiff = nums[1] - nums[0];
int count = prevdiff != 0 ? 2 : 1;
for (int i = 2; i < nums.length; i++) {
int diff = nums[i] - nums[i - 1];
if ((diff > 0 && prevdiff <= 0) || (diff < 0 && prevdiff >= 0)) {
count++;
prevdiff = diff;
}
}
return count;
}
}