1. 程式人生 > 實用技巧 >leetcode經典動態規劃題解題報告

leetcode經典動態規劃題解題報告

leetcode70 爬樓梯

題目描述

假設你正在爬樓梯。需要 n 階你才能到達樓頂。

每次你可以爬 1 或 2 個臺階。你有多少種不同的方法可以爬到樓頂呢?

解答:
遞推公式:

設f(n)為n階樓梯的爬法
f(n)=f(n-1)+f(n-2) n>2
f(n)=1 n=1
f(n)=2 n=2
到達第n階樓梯,有兩種方法,一種是從第n-1階樓梯,走一步到;另一種是從n-2階樓梯,走兩步到。

根據以上的遞迴公式,我們很容易寫出下面的程式碼:

	public int climbStairs(int n) {
        if (n<3){
            return n;
        }
        int[] dp=new int[n+1];
        dp[1]=1;
        dp[2]=2;
        for(int i=3;i<=n;i++){
            dp[i]=dp[i-1]+dp[i-2];
        }
        return dp[n];
    }

通過觀察,不難發現我們只需要儲存兩個狀態即可.

 public int climbStairs(int n) {
        if (n<3){
            return n;
        }
        int p1=2; //f(n-1)
        int p2=1; //f(n-2)
        for(int i=3;i<=n;i++){
           int cur=p1+p2;
           p2=p1;
           p1=cur;
        }
        return p1;
    }

leetcode198 打家劫舍

題目描述:

你是一個專業的小偷,計劃偷竊沿街的房屋。每間房內都藏有一定的現金,影響你偷竊的唯一制約因素就是相鄰的房屋裝有相互連通的防盜系統,如果兩間相鄰的房屋在同一晚上被小偷闖入,系統會自動報警。

給定一個代表每個房屋存放金額的非負整數陣列,計算你 不觸動警報裝置的情況下 ,一夜之內能夠偷竊到的最高金額。

解答:
設小偷走到第n個房屋旁,他的最大收入為f(n),
那麼有遞推公式:
f(n)=max(f(n-2)+s[n],f(n-1)) 其中s[n]第n個房子可以竊到金額。

這個公式的含義是,當小偷走到第n個房子時,他其實有兩種選擇,偷或上家偷了不偷這家。小偷只需要選一個最優方案即可。

根據遞推公式我們很容易得到以下的程式碼:

public int rob(int[] nums) {
        int n=nums.length;
        if (n==0){
            return 0;
        }else if (n==1){
            return nums[0];
        }else if (n==2){
            return Math.max(nums[0],nums[1]);
        }
        int[] dp=new int[n+1];

        dp[1]=nums[0];
        dp[2]=Math.max(nums[0],nums[1]);

        for (int i=3;i<=n;i++){
            dp[i]= Math.max(dp[i-1],dp[i-2]+nums[i-1]);
        }

        return dp[n];
    }

實際上我們只需要儲存兩個狀態即可,也無需這麼多的特判程式碼。簡化後的程式碼如下:

 public int rob(int[] nums) {
        int p1=0; //f(n-1)
        int p2=0; //f(n-2)

        for (int i=0;i<nums.length;i++){
            int cur=Math.max(p2+nums[i],p1);
            p2=p1;
            p1=cur;
        }

        return p1;
    }

這道題還有一些延申題目:

leetcode213 打家劫舍II

題目描述

你是一個專業的小偷,計劃偷竊沿街的房屋,每間房內都藏有一定的現金。這個地方所有的房屋都圍成一圈,這意味著第一個房屋和最後一個房屋是緊挨著的。同時,相鄰的房屋裝有相互連通的防盜系統,如果兩間相鄰的房屋在同一晚上被小偷闖入,系統會自動報警。

給定一個代表每個房屋存放金額的非負整數陣列,計算你在不觸動警報裝置的情況下,能夠偷竊到的最高金額。

解答:
這道題目和上面題目的不同在於房子是環形排布的。也就是說,如果第一個房子被偷了,那麼最後一個房子就不能被偷。也就是說第一個房子偷不偷可以影響最後一個房子的情況,我們只需要把兩種情況都計算一下即可。

    public int rob(int[] nums) {
        if (nums==null||nums.length==0){
            return 0;
        }
        if (nums.length==1){
            return nums[0];
        }
        return Math.max(helper(nums,0,nums.length-2),helper(nums,1,nums.length-1));
    }

    public int helper(int[] nums,int start,int end){
        int p1=0;// f(n-1)
        int p2=0;// f(n-2)
        for (int i=start;i<=end;i++){
            int cur=Math.max(p2+nums[i],p1);
            p2=p1;
            p1=cur;
        }
        return p1;
    }

leetcode53最大子序和

題目描述

給定一個整數陣列 nums ,找到一個具有最大和的連續子陣列(子陣列最少包含一個元素),返回其最大和。

解答:
設以下標n結尾的子序和為f(n)

因此我們可以得到遞推公式:
f(n)=max(f(n-1),0)+nums[n]

public int maxSubArray(int[] nums) {
        if (nums==null||nums.length==0){
            return 0;
        }

        int max=0;
        for (int i=1;i<nums.length;i++){
            nums[i]=Math.max(nums[i-1],0)+nums[i];
            max=Math.max(max,nums[i]);
        }
        return max;
    }

從遞推公式中可以知道我們需要儲存前一個狀態,我們可以直接利用原陣列即可。

leetcode322 零錢找零

題目描述:

給定不同面額的硬幣 coins 和一個總金額 amount。編寫一個函式來計算可以湊成總金額所需的最少的硬幣個數。如果沒有任何一種硬幣組合能組成總金額,返回 -1。

解答:
設總金額n找零所需的最少硬幣數為f(n)
那麼我們可以得到遞推公式
f(n)=min(f(n-c))+1 c 為硬幣面值

 public int coinChange(int[] coins, int amount) {
        int[] dp=new int[amount+1];
        int max=amount+1;
        Arrays.fill(dp,max);
        dp[0]=0;
        for (int i=1;i<dp.length;i++){
            for (int coin : coins) {
                if (i >= coin) {
                    dp[i]=Math.min(dp[i],dp[i-coin]+1);
                }
            }
        }
        return dp[amount]>amount?-1:dp[amount];
    }

leetcode120 三角形最小路徑和

題目描述:

給定一個三角形,找出自頂向下的最小路徑和。每一步只能移動到下一行中相鄰的結點上。

相鄰的結點 在這裡指的是 下標 與 上一層結點下標 相同或者等於 上一層結點下標 + 1 的兩個結點。

解答:

[
     [2],
    [3,4],
   [6,5,7],
  [4,1,8,3]
]

假如到達第i層的第j個位置,那麼只能從第i-1層的j-1/j位置到達。
我們設到達第i層第j個節點的最小路徑和為dp[i][j]
那麼我們可以得到以下遞推公式:
dp[i][j]=min(dp[i-1][j-1],dp[i-1][j])+d[i][j]

值得注意的是兩邊要特殊處理。

public int minimumTotal(List<List<Integer>> triangle) {
        int n = triangle.size();
        int[][] f = new int[n][n];
        //左邊特殊處理
        f[0][0] = triangle.get(0).get(0);
        for (int i = 1; i < n; ++i) {
            f[i][0] = f[i - 1][0] + triangle.get(i).get(0);
            for (int j = 1; j < i; ++j) {
                f[i][j] = Math.min(f[i - 1][j - 1], f[i - 1][j]) + triangle.get(i).get(j);
            }
            //右邊特殊處理
            f[i][i] = f[i - 1][i - 1] + triangle.get(i).get(i);
        }
        int minTotal = f[n - 1][0];
        for (int i = 1; i < n; ++i) {
            minTotal = Math.min(minTotal, f[n - 1][i]);
        }
        return minTotal;
    }

leetcode300 最長上升子序列

題目描述:

給定一個無序的整數陣列,找到其中最長上升子序列的長度。

示例:
輸入: [10,9,2,5,3,7,101,18]
輸出: 4 
解釋: 最長的上升子序列是 [2,3,7,101],它的長度是 4。

解答:

 public int lengthOfLIS(int[] nums) {
        if (nums.length==0){
            return 0;
        }
        int res=0;
        int[] dp=new int[nums.length];
        Arrays.fill(dp,1);
        for (int end=0;end<nums.length;end++){
            for (int start=0;start<end;start++){
                if (nums[start]<nums[end]){
                    dp[end]=Math.max(dp[end],dp[start]+1);
                }
            }
            res=Math.max(res,dp[end]);
        }
        return res;
    }

leetcode64 最小路徑和

題目描述:

給定一個包含非負整數的 m x n 網格,請找出一條從左上角到右下角的路徑,使得路徑上的數字總和為最小。

說明:每次只能向下或者向右移動一步。

示例:

輸入:
[
  [1,3,1],
  [1,5,1],
  [4,2,1]
]
輸出: 7
解釋: 因為路徑 1→3→1→1→1 的總和最小。

解答:
我們假設到達座標(i,j)的最小路徑和為f(i,j)
因為到達(i,j)要麼從上面過來,要麼從左邊過來。
因此我們可以得到這樣的遞推公式:
f(i,j)=min(f(i-1,j),f(i,j-1))+grid[i][j]
注意第一行和第一列要特殊處理。

public int minPathSum(int[][] grid) {
        int m=grid.length;
        int n=grid[0].length;
        int[][] dp=new int[m][n];
        dp[0][0]=grid[0][0];
        //第一行
        for (int col=1;col<n;col++){
            dp[0][col]=dp[0][col-1]+grid[0][col];
        }
        //第一列
        for (int row=1;row<m;row++){
            dp[row][0]=dp[row-1][0]+grid[row][0];
        }
        for (int row=1;row<m;row++){
            for (int col=1;col<n;col++){
                dp[row][col]=Math.min(dp[row-1][col],dp[row][col-1])+grid[row][col];
            }
        }
        return dp[m-1][n-1];


    }

leetcode174 地下城遊戲

題目描述:

一些惡魔抓住了公主(P)並將她關在了地下城的右下角。地下城是由 M x N 個房間組成的二維網格。我們英勇的騎士(K)最初被安置在左上角的房間裡,他必須穿過地下城並通過對抗惡魔來拯救公主。

騎士的初始健康點數為一個正整數。如果他的健康點數在某一時刻降至 0 或以下,他會立即死亡。

有些房間由惡魔守衛,因此騎士在進入這些房間時會失去健康點數(若房間裡的值為負整數,則表示騎士將損失健康點數);其他房間要麼是空的(房間裡的值為 0),要麼包含增加騎士健康點數的魔法球(若房間裡的值為正整數,則表示騎士將增加健康點數)。

為了儘快到達公主,騎士決定每次只向右或向下移動一步。
編寫一個函式來計算確保騎士能夠拯救到公主所需的最低初始健康點數。

解答:
這道題我們不能從左上角向右下角進行動態規劃;我們需要從右下角到左上角進行動態規劃。
設從(i,j)到終點所需的最小初始值為dp[i][j]
也就是說當我們到達座標(i,j)時,只要路徑和不小於dp[i][j]就能到達終點。
我們可以得到狀態轉移方程:
dp[i][j]=max(min(dp[i+1][j],dp[i][j+1])-dungeon(i,j),1)

   public int calculateMinimumHP(int[][] dungeon) {
        int n = dungeon.length, m = dungeon[0].length;
        int[][] dp = new int[n + 1][m + 1];
        for (int i = 0; i <= n; ++i) {
            Arrays.fill(dp[i], Integer.MAX_VALUE);
        }
        dp[n][m - 1] = dp[n - 1][m] = 1;
        for (int i = n - 1; i >= 0; --i) {
            for (int j = m - 1; j >= 0; --j) {
                int minn = Math.min(dp[i + 1][j], dp[i][j + 1]);
                dp[i][j] = Math.max(minn - dungeon[i][j], 1);
            }
        }
        return dp[0][0];
    }