1. 程式人生 > >使用最小花費爬樓梯(Min Cost Climbing Stairs) Java動態規劃入門分析一

使用最小花費爬樓梯(Min Cost Climbing Stairs) Java動態規劃入門分析一

前言

題幹

陣列的每個索引做為一個階梯,第 i個階梯對應著一個非負數的體力花費值 cost[i](索引從0開始)。

每當你爬上一個階梯你都要花費對應的體力花費值,然後你可以選擇繼續爬一個階梯或者爬兩個階梯。

您需要找到達到樓層頂部的最低花費。在開始時,你可以選擇從索引為 0 或 1 的元素作為初始階梯。

示例 1:

輸入: cost = [10, 15, 20]
輸出: 15
解釋: 最低花費是從cost[1]開始,然後走兩步即可到階梯頂,一共花費15。
 示例 2:

輸入: cost = [1, 100, 1, 1, 1, 100, 1, 1, 100, 1]
輸出: 6
解釋: 最低花費方式是從cost[0]開始,逐個經過那些1,跳過cost[3],一共花費6。
注意:

cost 的長度將會在 [2, 1000]。
每一個 cost[i] 將會是一個Integer型別,範圍為 [0, 999]。

動態規劃-入門解析一(正常思維)

首先分析題幹,題目的注意事項,假設陣列b是儲存值陣列,a陣列是原陣列,首先 你可以選擇從索引為 0 或 1 的元素作為初始階梯 這句話分析得 b[0] = a[0],b[1] = a[1] ,接著分析陣列a有最少的元素的時候為兩個元素的時候:
f(2) = Math.min(b[0],b[1]);
然後分析陣列a有三個元素的時候:
f(3) = Math.min(b[0]+a[2],b[1]+a[2]) = f(2)+a[2];
比如陣列a為 [10,15,20],通過我們的大腦肯定一眼看出,答案為15,但是計算機需要有嚴謹的計算步驟:(Math.min(10 + 20, 15 + 20))= 30,此時陣列b,為[10,15,30],我們還缺少一步,這個僅僅是b[0],b[1]做了對比就加a[2],我們實際上對比應該是b[i] 和 b[i+1] = Math.min(b[i - 2],b[i - 1]) +a[i]做對比。

程式碼

public static int minCostClimbingStairs(int[] cost) {
        int[] dp = new int[cost.length];
        dp[0] = cost[0];//兩個起點
        dp[1] = cost[1];
        for(int i = 2; i < cost.length; i++) {
            int costCurrent = cost[i];
            dp[i] = Math.min(dp[i - 1], dp[i - 2]) + costCurrent;
        }
        return
Math.min(dp[cost.length - 1],dp[cost.length-2]); }

動態規劃-入門分析二(正常思維下的巧妙思想)

看了分析一後的程式碼,有沒有發現有一些相同的地方,或者我們沒考慮到的地方,或者更加巧妙的地方呢?觀察:
return Math.min(dp[cost.length - 1],dp[cost.length-2])
dp[i] = Math.min(dp[i - 1], dp[i - 2]) + costCurrent
它們區別除了 + costCurrent 也沒什麼區別,我們何不來統一一下,使得結構巧妙一些

程式碼二

 public int minCostClimbingStairs(int[] cost) {
        int[] dp = new int[cost.length + 1];
        dp[0] = cost[0];
        dp[1] = cost[1];
        for(int i = 2; i <= cost.length; i++) {
            int costCurrent = i == cost.length ? 0 : cost[i];
            dp[i] = Math.min(dp[i - 1], dp[i - 2]) + costCurrent;
        }
        return dp[cost.length];
    }

評價一個演算法的優劣應該從:正確性,可讀性,健壯性,高效性(時間複雜度,空間複雜度),
所以我們應該在保證自己寫的正確的情況下減小時間複雜度,空間複雜度。
首先分析原始碼的時間複雜度和空間複雜度:
時間複雜度:找出所有語句中頻度最大的那條語句作為基本語句,計算基本語句的頻度得到的問題規模n的某個函式f(n) = a^m n^m+a^m-1 n^m-1+…+a n+a是一個m次多項式,則T(n) = O(n^m)
所以通過以上分析得,該演算法時間複雜度為O(n);
空間複雜度:對一個演算法在執行過程中臨時佔用儲存空間大小的量度(輔助儲存空間),記做S(n)=O(f(n)),所以通過以上分析空間複雜度為O(n);
然後我們在分析題目,此時我們已經不能再減小時間複雜度(必須有迴圈),我們可以考慮一下優化空間複雜度,通過分析 dp[i] = Math.min(dp[i - 1], dp[i - 2]) + costCurrent; 我們發現每一次迭代的過程中我們只要使用前兩個的狀態就可以得到新的狀態,所以我們完全沒有必要把值全部保留下來,每次保留前兩個就可以,所以空間複雜度就降低到O(1)。

程式碼三

    public static int minCostClimbingStairs(int[] cost) {
        int sum = 0,sum1 = cost[0],sum2 = cost[1];
        for(int i = 2; i <= cost.length; i++) {
            int costCurrent = i == cost.length ? 0 : cost[i];
            sum = Math.min(sum1, sum2) + costCurrent;
            sum1 = sum2;
            sum2 = sum;
        }
        return sum;
    }