动态规划(一)

The mind is everything. What you think you become.

Posted by MuZhou on November 2, 2018

十一月是动态规划的一个月,这个月目标做3道easy,四道Medium,一道Hard。

先学习一下动态规划,维基百科上对动态规划对定义是:

一种在数学、管理科学、计算机科学、经济学和生物信息学中使用的,通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。
常常适用于有重叠子问题和最优子结构性质的问题。

计算机领域里很多算法都是把一个复杂问题分解成子问题去求解,比如分治、比如递归,动态规划的特点在于分解的子问题有重叠,通过存储子问题的解来减少计算量, 所以实际上有些动态规划的题目也可以用递归来求解,只不过计算量会大一些,比如求解斐波那契数列。

递归方式

/**
 * @author nidaren
 */
public class Test {
    //n >= 0
    int fib(int n) {
        if (n < 2) {
            return n;
        }
        return fib(n - 1) + fib(n - 2);
    }
}

动态规划方式

/**
 * @author nidaren
 */
public class Test {
    //n >= 0
    int fib(int n) {
        if (n < 2) {
            return n;
        }
        int[] fibArr = new int[n + 1];
        fibArr[0] = 0;
        fibArr[1] = 1;
        for (int i = 2; i <= n; i ++) {
            fibArr[i] = fibArr[i - 1] + fibArr[i - 2];
        }
        return fibArr[n];
    }
}

除了重叠子问题之外,动态规划还用来解决最优子结构问题。 所谓最优子结构问题就是一个问题的最优解包含其子问题的最优解,局部最优解能决定全局最优解,比如爬楼梯问题、背包问题、最长公共子序列问题(LCS)。

今天是动态规划第一次题目,先是一个简单的爬楼梯,题目链接在这里。 这是动态规划里经典题目之一,题目大意是:

爬楼梯,每一层楼梯有不同的cost,耗费该层的cost后可以向上爬一层或者两层,求爬到楼顶(n层)的最小cost

先来刻画最优解的结构特征,这也是解决动态规划题目最难的一步。 我们将cost视为停留cost,即cost[i]表示在i层停留的成本,用dp[i]来表示爬到第i层并停留的最优cost,那么所求解就是Min(dp[n - 1], dp[n - 2]). 最优解的结构特征:

dp[i] = Min(dp[i - 1], dp[i - 2]) + cost[i]

有了结构特征后我们来确认一下初始值,很显然我们需要先求出前两项dp[0]和dp[1],不难得出:

dp[0] = cost[0]
dp[1] = cost[1]

初始值求出后我们再来考虑一下corner case: 当n小于2时,也就是到楼顶到阶梯小于两层(只有1层或更少),那么显然我们中间可以不用停留,直接跨到楼顶,cost=0.

接下来可以开始写代码了:

class Solution {
    public int minCostClimbingStairs(int[] cost) {
        if (cost.length < 2) {
            return 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]);
    }
}

上述解法时间复杂度O(n),空间复杂度也是O(n),可以更进一步优化,不引入dp而使用cost,可以将空间复杂度将为O(1),不过这会改变输入数据,需要视情况而定。

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