動態規劃

前言

最近晚上沒事的時候看了一下leetcode上面的算法題,這幾天看了一下動態規劃的題目,發現這些題目都頗有趣,好比爬樓梯最小花費爬樓梯打家劫舍等,用的思想都很巧妙,因此記錄一下。因爲好長時間沒有用kotlin了,因此我這裏給出java和kotlin兩種寫法,複習複習kotlin的用法(這裏再加一種dart寫法)。java

定義

動態規劃:經過把原問題分解爲相對簡單的子問題的方式求解複雜問題的方法。
動態規劃的基本思想:若要解一個給定問題,咱們須要解其不一樣部分(即子問題),在根據子問題的解得出原問題的解。算法

例子

  • 爬樓梯
  • 最小花費爬樓梯
  • 打家劫舍

爬樓梯

問題:有n階的樓梯,每次你能夠爬1或2個臺階。你有多少種方法能夠爬到樓頂?數組

分析:這個應該屬於最簡單的動態規劃問題了,基本上有必定數學基礎都能寫出來。首先咱們來分析一下:
第i階樓梯能夠由如下兩種方法獲得:bash

  1. 在第(i - 1)階後向上爬一階。
  2. 在第(i - 2)階後向上爬2階。

因此第i階總數就是第(i -1)階和第(i - 2)階的方法數之和。
數學表達式爲:f(i) = f(i - 1) + f(i - 2);
由此咱們能夠得出代碼(java)。ui

public class Solution {
    public int climbStairs(int n){
        if(n==0 || n == 1){
            return n;
        }
        //這裏設置數組長度爲n+1是爲了讓數組從1開始計數。
        //若是從0開始計數則設置數組長度爲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];
    }
}
複製代碼

下面咱們介紹一下kotlin的寫法(kotlin)spa

fun clibStairs(n:Int) : Int{
    if(n == 0 || n == 1){
        return n
    }
    val dp = IntArray(n +1)
    dp[1] = 1
    dp[2] = 2
    for(i in 3..n){
        dp[i] = dp[i -1]+ dp[i -2]
    }
    return dp[n]
}
複製代碼

下面介紹一下dart的寫法(dart)code

int climbStairs(int n){
    if(n == 0 || n == 1){
        return n;
    }
    var dp = List<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];
}
複製代碼

最小花費爬樓梯

問題:數組的每一個索引做爲一個階梯,第i個階梯對應着一個非負數的體力花費值cost[i](索引從0開始)。每當你爬上一個階梯你都要花費對應的體力花費值,而後你能夠選擇繼續爬一個階梯或者爬兩個階梯。求到達樓頂的最低花費。(開始時,你能夠選擇從索引爲0或1的元素做爲初始階梯)索引

分析:這個問題在爬樓梯的基礎上面加了一個體力值的消耗,因此它再也不是簡單的找到全部路徑,而是找出這些路徑中花費體力最小的路徑,若是咱們從總體來講可能無從下手,總不能把全部路徑的花費值都算出來比較大小吧。這個時候咱們須要將問題劃分爲子問題,從子問題中概括出總體的解。分析步驟以下:
咱們假設有i個階梯,數組用nums表示(數組下標從0開始,因此這裏咱們讓i也從0開始);leetcode

  1. 當i=0時,花費最小體力值nums[0];
  2. 當i=1時,花費最小體力值nums[1];
  3. 當i=2時,有兩種狀況:
    1. 當nums[0] > nums[1]時,最小花費體力爲nums[0] + nums[2];
    2. 當nums[0] < nums[1]時,最小花費體力爲nums[1] + nums[2];

根據上面的推論,咱們能夠得出一個數學表達式:
f(i) = min(f(i-1), f(i -2))+nums[i]get

有了數學表達式,咱們能夠寫出代碼以下(java):

public class Solution{
    public int minCostClimbingStairs(int[] cost){
        if(cost.length == 0){
            return 0;
        }else if(cost.length == 1){
            return cost[0];
        }
        int[] dp = new int[cost.length];
        dp[0] = cost[0];
        dp[1] = cost[1];
        for(int i = 2; i < cost.length; i++){
            dp[i] = Math.min(dp[i -1], dp[i - 2]) + cost[i];
        }
        return Math.min(dp[cost.length - 1], dp[cost.length - 2]);
    }
}
複製代碼

一樣給出kotlin下的寫法(kotlin)

fun minCostClimbingStairs(cost:Array<Int>) : Int{
    if(cost.isEmpty()){
        return 0
    }else if(cost.size == 1){
        return cost[0]
    }
    val dp = IntArray(cost.size)
    dp[0] = cost[0]
    dp[1] = cost[1]
    for(i in 2..(cost.size - 1)){
        dp[i] = Math.max(dp[i - 1], dp[i - 2]) + cost[i]
    }
    return Math.min(dp[cost.size - 1], dp[cost.size - 2])
}
複製代碼

dart語言下的寫法(dart)

int minCostClimbingStairs(List<Int> cost){
    if(cost.isEmpty){
        return 0;
    }else if(cost.length == 1){
        return cost[0];
    }
    var dp = List<int>(cost.length);
    dp[0] = cost[0];
    dp[1] = cost[1];
    for(int i = 2; i < cost.length; i++){
        //這裏要導入 'dart:math'類
        dp[i] = max(dp[i - 1],dp[i - 2]) + cost[i];
    }
    return min(dp[cost.length - 1], dp[cost.length - 2]);
}
複製代碼

打家劫舍

問題:你是專業的小偷,計劃偷竊沿街的房屋。每間房內都藏有必定的現金,影響你偷竊的惟一制約因素就是相鄰的房屋裝有相互連通的防盜系統,若是兩間相鄰的房屋在同一夜被小偷闖入,系統會自動報警。 給定一個表明每一個房屋存放金額的非負整數數組,計算你在不觸動報警裝置的狀況下,可以偷竊到的最高金額。

分析:這個問題在爬樓梯問題的基礎上面加深了一下,可是原理仍是差很少的,咱們假設有i個階梯,數組爲nums;

  1. 當i=1時,最大金額爲nums[0];
  2. 當i=2時,最大金額爲max(nums[0],nums[1]);
  3. 當i=3時,有兩種狀況:
    1. 搶第三個房子,將數額與第一個房子相加
    2. 不搶第三個房子,保持現有最大金額。

根據上面的推論,咱們能夠得出一個數學表達式:
f(i) = max(f(i -1),f(i - 2) + nums[i])
動態規劃最重要的一點就是你可以根據簡單的子問題概括出整個問題的解,用數學表達式表示出來。有了數學表達式咱們就很好寫出代碼。
具體代碼以下(java)

public class Solution{
    publc int rob(int[] nums){
        if(nums.length == 0){
            return 0;
        }
        //這裏咱們讓dp數組從下標1開始計數,因此數組長度加了1,固然也能夠直接從0開始計數。
        int[] dp = new int[nums.length + 1];
        dp[0] = 0;
        dp[1] = nums[0];
        for(int i = 2; i<= nums.length; i++){
            dp[i] = Math.max(dp[i -1],dp[i -2] + nums[i - 1]);
        }
        return dp[nums.length];
    }
}
複製代碼

一樣給出kotlin下的寫法(kotlin)

fun rob(nums: Array<Int>) : Int{
    if(nums.isEmpty()){
        return 0
    }
    val dp = IntArray(nums.size + 1)
    dp[0] = 0
    dp[1] = nums[0]
    for (i in 2..nums.size){
        dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i - 1])
    }
    return dp[nums.size]
}
複製代碼

dart語言的寫法(dart)

int rob(List<int> nums){
    if(nums.isEmpty){
        return 0;
    }
    var dp = List<int>(nums.length + 1);
    dp[0] = 0;
    dp[1] = nums[0];
    for(int i = 2; i <= nums.length; i++){
        //這裏要導入 'dart:math'類
        dp[i] = max(dp[i - 1],dp[i - 2] + nums[i - 1]);
    }
    return dp[nums.length];
}
複製代碼

總結

這三個問題算是動態規劃中很是簡單而又經典的題目了,並且將動態規劃中拆分紅相對簡單的子問題來解決複雜問題用到了極致。能夠做爲咱們理解動態規劃入門的算法題。

參考文獻

相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息