經過編寫動態規劃系列題目,拓寬思路 java
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()]; } };
題意:尋找一個數組的連續子數組,使得這個子數組是等差數列。請求出這種子數組的個數。git
個人思路算法
(1)n方複雜度,首先求累加和數組,而後根據等差數列求和充要條件,判斷序列是不是等差數列,可是解僱不對數組
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; } };(2)動態規劃方法
設dp[i]表示到第i1個時,前面構成的數列個數,spa
若是 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]+1code
若是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]; } };
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; } };
參考方法:leetcode
(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; } };
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)使用公式
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; } };
給定n組數,每組的第一個數小於第二個。若是兩個數組,第一個組的大值小於第二個組的小值,那麼構成一個鏈。題目要求返回最大的鏈的長度。get
一個數能夠分紅幾個數的和,這些數相乘的最大值是多少?
給定一個數n,在0 ≤ x < 10n中找全部的數的沒有相同數字的數量
題目:有一個整型數組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; } };
標準答案:
他的遞歸思路不太同樣https://leetcode.com/problems/predict-the-winner/solution/
這道題給了咱們一個數組,和一個目標值,讓咱們給數組中每一個數字加上正號或負號,而後求和要和目標值相等,求有多少中不一樣的狀況。
文本里只有一個字符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; }這樣複雜度達到logn
給定一個集合,每一個數都不一樣,給定一個目標,在集合中選擇數(可重複)的加和是這個目標值,問有多少種(次序不一樣的算不一樣種)?
https://discuss.leetcode.com/topic/52302/1ms-java-dp-solution-with-detailed-explanation 本文講解了自頂向下的遞歸和自底向上的動態規劃
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]; } };
打折促銷組合,給定須要購買的商品類型和數量,求買這些東西花費的最少的錢
能夠任意的購買拋售股票,可是拋售以後的一天不能購買股票,求賺錢最可能是多少?
設置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]); } };
若是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; } }