動態規劃/leetcode/直接推導遞推公式

經過編寫動態規劃系列題目,拓寬思路 java

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 等差數列切片 子數組(連續子序列)

題意:尋找一個數組的連續子數組,使得這個子數組是等差數列。請求出這種子數組的個數。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];
    }
};

能夠把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;
    }
};


參考方法: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;
    }
};

上述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組數,每組的第一個數小於第二個。若是兩個數組,第一個組的大值小於第二個組的小值,那麼構成一個鏈。題目要求返回最大的鏈的長度。get



343 integer break  整數切分乘積最大

一個數能夠分紅幾個數的和,這些數相乘的最大值是多少?


357 Count Numbers with Unique Digits 

給定一個數n,在0 ≤ x < 10n中找全部的數的沒有相同數字的數量

486 Predict 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;
    }
};

改爲一維數組:因爲依賴左下方的反斜對角線,所以須要從左上至右下沿反斜對角線遍歷

標準答案:

他的遞歸思路不太同樣https://leetcode.com/problems/predict-the-winner/solution/


464Can I Win  

給定數的範圍,每次兩我的輪流拿出一個數,這些數加起來大於目標數的時候的那我的贏,問第一個拿數的人能贏嗎?假設兩我的都灰常聰明

375Guess Number Higher or Lower II 

猜數,猜多少花多少,問最少帶多少錢能猜對?

392 Is Subsequence   判斷一個字符串是不是另外一個字符串的子序列

494 target Sum 

這道題給了咱們一個數組,和一個目標值,讓咱們給數組中每一個數字加上正號或負號,而後求和要和目標值相等,求有多少中不一樣的狀況。


650 2 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;
    }
這樣複雜度達到logn

377 Combination Sum IV  

給定一個集合,每一個數都不一樣,給定一個目標,在集合中選擇數(可重複)的加和是這個目標值,問有多少種(次序不一樣的算不一樣種)?

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];
    }
};


638 Shopping Offers 

打折促銷組合,給定須要購買的商品類型和數量,求買這些東西花費的最少的錢


309 Best Time to Buy and Sell Stock with Cooldown  

能夠任意的購買拋售股票,可是拋售以後的一天不能購買股票,求賺錢最可能是多少?

 

416 Partition Equal Subset Sum把一個數組分紅兩組,問能不能兩組的元素加和相等

376 Wiggle 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;
    }
}


 
368 Largest Divisible Subset 求最大子集,子集中任意兩個數有一個能被另外一個整除

264 Ugly Number II   求第n個醜數



467 Unique Substrings in Wraparound String   


576 Out of Boundary Paths   

322Coin Change