1. 程式人生 > >動態規劃/leetcode/直接推導遞推公式

動態規劃/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方複雜度,首先求累加和陣列,然後根據等差數列求和充要條件,判斷序列是否是等差數列,但是解僱不對

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個時,前面構成的數列個數,

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

377Combination 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;
    }
}


 
368Largest Divisible Subset 求最大子集,子集中任意兩個數有一個能被另一個整除264Ugly Number II   求第n個醜數

467Unique Substrings in Wraparound String   

576Out of Boundary Paths   

322Coin Change