动态规划

前言

最近晚上没事的时候看了一下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];
}
复制代码

总结

这三个问题算是动态规划中很是简单而又经典的题目了,并且将动态规划中拆分红相对简单的子问题来解决复杂问题用到了极致。能够做为咱们理解动态规划入门的算法题。

参考文献

相关文章
相关标签/搜索
本站公众号
   欢迎关注本站公众号,获取更多信息