letcode動態規劃專題講解

動態規劃

爬梯子

題目描述

You are climbing a stair case. It takes n steps to reach to the top.
Each time you can either climb 1 or 2 steps. In how many distinct ways can you climb to the top?
Note: Given n will be a positive integer.

Example 1:

Input: 2
Output: 2
Explanation: There are two ways to climb to the top.

  1. 1 step + 1 step
  2. 2 steps
    Example 2:

Input: 3
Output: 3
Explanation: There are three ways to climb to the top.

  1. 1 step + 1 step + 1 step
  2. 1 step + 2 steps
  3. 2 steps + 1 step
思路

爬樓梯每次只能跨一步或者兩邊,需要你來求解爬n層樓梯所有有可能的走法。
如果一共有十層樓梯。現在你只差最後一步到第十層樓梯。那麼有哪幾種情況呢?
當然就是兩種。一個是從第九級走一步到第十級。一個是從第八級跨兩層到第十級。
那麼如果,走到第九層的走法有x種,走到第八層的走法有y種。那麼走到第十層的走法有多少種呢??
答案自然是x+y種。
我們將這種解法進行推廣。求解到n層臺階一共有多少種走法。
因此如果我們設f(n)表示爲到n層樓梯的走法,則有
f(n)=f(n-1)+f(n-2)成立。
找到了這樣一個遞推的關係式。我們如何來求解呢?

遞歸求解

遞歸求解是能夠想到的最自然的一種方式。

class Solution {
public:
    int climbStairs(int n) {
        if(n==0)return 0;
        if(n==1)return 1;
        if(n==2)return 2;
        return climbStairs(n-1)+climbStairs(n-2);
    }
};

時間複雜度達到了O(2^n),在letcode中會顯示超時。

備忘錄算法(記憶化搜索)

我們不難發現如果用簡單的遞歸來完成。有的項會被重複的計算。算f(10)時,需要算f(9)和f(8)。算f(9)時會計算f(8)和f(7)。以此類推,會發現做了很多重複的工作。那麼如何去避免呢?
一種方案就是,我們把已經算過的值保存下來。下次如果再遇到,我們就不用再重複計算了。這叫備忘錄算法,也就是我們熟知的記憶性搜索。代碼如下

class Solution {
public:
    map<int,int>has;
    int climbStairs(int n) {
        if(has[n]) return has[n];
        if(n==0)return 0;
        if(n==1)return 1;
        if(n==2)return 2;
        has[n] = climbStairs(n-1)+climbStairs(n-2);
        return has[n];
    }
};

這樣一來程序的性能得到了很大的提高。時間複雜度和空間複雜度都是O(N)

動態規劃

在之前的求解中,都是自上而下的求解,也就是遞歸的思想。不妨我們換一下思路,自下而上去解決這個題目。

臺階數  1   2    3    4    5    6    7    8    9    10   
走法    1   2    3    5    8    13  21   

有f(1)和f(2)推出f(3)
由f(2)和f(3)推出f(4)
再有f(4)和f(5)去推出f(6)
以此類推,我們就可以自底向上去推出f(n)的結果了

class Solution {
public:
    int climbStairs(int n) {
        int a[n+2];
        a[1] = 1;
        a[2] = 2;
        for(int i=3;i<=n;i++){
            a[i] = a[i-1]+a[i-2];
        }
        return a[n];
    }
};

其實細細看來,依然有需要提高的地方。其實當計算a[i]是我們只需要a[i-1]和a[i-2]。也就是隻需要兩個變量來保存結果即可。因此,可以得到以下的代碼

class Solution {
public:
    map<int,int>has;
    int climbStairs(int n) {
        if(n==0)return 0;
        if(n==1)return 1;
        if(n==2)return 2;
        int a=1;
        int b=2;
        for(int i=3;i<=n;i++){
            int tmp = b;
            b = a + b;
            a = tmp;
        }
        return b;
    }
};

買賣股票的最佳時機

題目描述

Say you have an array for which the ith element is the price of a given stock on day i.

If you were only permitted to complete at most one transaction (i.e., buy one and sell one share of the stock), design an algorithm to find the maximum profit.

Note that you cannot sell a stock before you buy one.

Example 1:

Input: [7,1,5,3,6,4]
Output: 5
Explanation: Buy on day 2 (price = 1) and sell on day 5 (price = 6), profit = 6-1 = 5.
Not 7-1 = 6, as selling price needs to be larger than buying price.
Example 2:

Input: [7,6,4,3,1]
Output: 0
Explanation: In this case, no transaction is done, i.e. max profit = 0.

思路

要求用動態規劃來求解。那麼最關鍵的就是找到狀態轉移方程。
f(n)表示n天買賣股票能夠獲得的最大利潤。很顯然只有兩種情況
第一是今天把股票賣出去了。能夠獲得的最大利潤就是第n天股票的價格減去前n-1天股票的最低價
第二是今天沒有賣股票。買賣股票能夠獲得的最大利潤保持不變,也就是和n-1天的一樣
那麼第n天能夠獲得的最大價值就是這兩種情況中的最大值了。、
這樣我們就可以推出狀態轉移方程
a[i] = max(a[i-1],prices[i]-minprice)
邊界條件自然是n=1時。代碼如下:

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int n = prices.size();
        if(n==0) return 0;
        int a[n+2];
        a[0] = 0;
        int minprice=prices[0];//記錄最低價格
        for(int i=1;i<n;i++){
            a[i] = max(a[i-1],prices[i]-minprice);
            if(prices[i]<minprice){
                minprice = prices[i];
            }
        }
        return a[n-1];
    }
};

最大子序和

Given an integer array nums, find the contiguous subarray (containing at least one number) which has the largest sum and return its sum.

Example:

Input: [-2,1,-3,4,-1,2,1,-5,4],
Output: 6
Explanation: [4,-1,2,1] has the largest sum = 6.
Follow up:

If you have figured out the O(n) solution, try coding another solution using the divide and conquer approach, which is more subtle.

思路

最簡單粗暴而又一定超時的方法就是暴力枚舉。這裏就不說了。
但是值得一提的是,在枚舉的過程中,是把所有位置的元素作爲子序列開頭的情況進行了枚舉。
這裏,我們換一個思路。去找所有以第n個數爲結束點的子序列。
並用f(n)表示以第n個數爲結尾的子序列的最大值。
以n爲結尾的子序列有兩種可能。
一是第n個數,與以第n-1個數爲結束的子序列相連接,形成新的子序列
二是以第n個數,這單獨一個數作爲子序列。
這兩種情況的最大值,也就是f(n)的值。
遍歷求得所有的f(n),再找出f(n)中的最大值,也就是題目的最終答案了。

代碼
class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        //f(n)表示以第n個數爲結束點的子序列的最大和
        // 有遞推關係f(n)=max(f(n-1)+a[n],a[n])
        // 找出這些中最大的那個
        int n = nums.size();
        if(n==0) return 0;
        int ans = nums[0];
        int f[n+1];
        f[0] = nums[0];
        for(int i=1;i<n;i++){
            f[i] = max(f[i-1]+nums[i],nums[i]);
            ans = max(ans,f[i]);//更新最大值
        }
        return ans;
    }
};

打家劫舍

題目描述

You are a professional robber planning to rob houses along a street. Each house has a certain amount of money stashed, the only constraint stopping you from robbing each of them is that adjacent houses have security system connected and it will automatically contact the police if two adjacent houses were broken into on the same night.

Given a list of non-negative integers representing the amount of money of each house, determine the maximum amount of money you can rob tonight without alerting the police.

Example 1:

Input: [1,2,3,1]
Output: 4
Explanation: Rob house 1 (money = 1) and then rob house 3 (money = 3).
Total amount you can rob = 1 + 3 = 4.
Example 2:

Input: [2,7,9,3,1]
Output: 12
Explanation: Rob house 1 (money = 2), rob house 3 (money = 9) and rob house 5 (money = 1).
Total amount you can rob = 2 + 9 + 1 = 12.

思路

考慮所有可能的搶劫方案過於困難。一個自然而然的想法是首先從最簡單的情況開始。記:
f(k) = 從前 k 個房屋中能搶劫到的最大數額,nums[i]= 第 i 個房屋的錢數。
首先看 n = 1 的情況,顯然 f(1) = nums[0]
再看 n = 2,f(2) = max(num[0],num[1])
對於 n = 3,有兩個選項:
搶第三個房子,將數額與第一個房子相加。
不搶第三個房子,保持現有最大數額。
顯然,你想選擇數額更大的選項。於是,可以總結出公式:
ans[i] = max(ans[i-2]+nums[i],ans[i-1])

class Solution {
public:
    int rob(vector<int>& nums) {
        // f(n)表示前n家店打劫的最大金錢。
        //f(n) = max(f(n-2)+a[n],f(n-1)).
        int n = nums.size();
        if(n==0) return 0;
        if(n==1) return nums[0];
        int ans[n];
        ans[0] = nums[0];
        ans[1] = max(nums[0],nums[1]);
        for(int i=2;i<n;i++){
            ans[i] = max(ans[i-2]+nums[i],ans[i-1]);
        }
        return ans[n-1];
    }
};

其實只要記錄前兩個的值就🆗,因此只需要兩個變量就可以完成以上步驟

class Solution {
public:
    int rob(vector<int>& nums) {
        // f(n)表示前n家店打劫的最大金錢。f(n) = max(f(n-2)+a[n],f(n-1)).
        int n = nums.size();  
        int pre=0;
        int cur=0;
        for(int i=0;i<n;i++){
            int tmp = cur;
            cur = max(pre+nums[i],cur);
            pre = tmp;
        }
        return cur;
    }
};
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章