九章算法笔记 9.动态规划 Dynamic Programming

递归和动态规划 算法视频QQ_1603159172

从Triangle这个问题说起:

题目:

Given a triangle, find the minimum path sum from top to bottom. Each step you may move to adjacent numbers on the row below.

Example:

Given the following triangle:

[

[2],

[3,4],

[6,5,7],

[4,1,8,3]

]

The minimum path sum from top to bottom is 11 (i.e., 2 + 3 + 5 + 1 = 11).

四种解法

遇到这道题,老师说开始先给出暴力解法之后进行优化是可以的,不一定一开始就给出最优解,想出来一个暴力解法觉得傻就不说话。。。

  • DFS:Traverse

DFS:Traverse

其中时间复杂度为O(2^{n}), 其中n为Triangle中的层数(122*2…)进去n次,每次调用2次自己,所以O(2^{n-1}),近似为O(2^{n}), 注意使用Traverse的方法的时候,结果是当作函数参数传递的。

  • DFS:Divide and Conquer

这里写图片描述

时间复杂度依然为O(2^{n}), 其中n为Triangle中的点数,同使用Traverse的方法不同的是,Divide and Conquer 的结果是作为函数返回值而不是函数参数,要不然没法conquer。

–DFS:Divide and Conquer 加 memorization

这里写图片描述

时间复杂度为O(n^{2}),其中n为Triangle的层数。

其中注意,如果求最小值的时候,初始化往往是int或其类型的最大值;反之如果求最大值,初始化往往为最小值

记忆法的分治时间复杂度计算:

a=一共多少个点

b=每个点处理的时间复杂度

c=每个点访问几次

分治法的总时间复杂度=a*b*c

–Traditional Dynamic Programming

// version 0: top-down
public class Solution {
    /**
     * @param triangle: a list of lists of integers.
     * @return: An integer, minimum path sum.
     */
    public int minimumTotal(int[][] triangle) {
        if (triangle == null || triangle.length == 0) {
            return -1;
        }
        if (triangle[0] == null || triangle[0].length == 0) {
            return -1;
        }

        // state: f[x][y] = minimum path value from 0,0 to x,y
        int n = triangle.length;
        int[][] f = new int[n][n];

        // initialize 
        f[0][0] = triangle[0][0];
        for (int i = 1; i < n; i++) {
            f[i][0] = f[i - 1][0] + triangle[i][0];
            f[i][i] = f[i - 1][i - 1] + triangle[i][i];
        }

        // top down
        for (int i = 1; i < n; i++) {
            for (int j = 1; j < i; j++) {
                f[i][j] = Math.min(f[i - 1][j], f[i - 1][j - 1]) + triangle[i][j];
            }
        }

        // answer
        int best = f[n - 1][0];
        for (int i = 1; i < n; i++) {
            best = Math.min(best, f[n - 1][i]);
        }
        return best;
    }
}



//Version 1: Bottom-Up
public class Solution {
    /**
     * @param triangle: a list of lists of integers.
     * @return: An integer, minimum path sum.
     */
    public int minimumTotal(int[][] triangle) {
        if (triangle == null || triangle.length == 0) {
            return -1;
        }
        if (triangle[0] == null || triangle[0].length == 0) {
            return -1;
        }

        // state: f[x][y] = minimum path value from x,y to bottom
        int n = triangle.length;
        int[][] f = new int[n][n];

        // initialize 
        for (int i = 0; i < n; i++) {
            f[n - 1][i] = triangle[n - 1][i];
        }

        // bottom up
        for (int i = n - 2; i >= 0; i--) {
            for (int j = 0; j <= i; j++) {
                f[i][j] = Math.min(f[i + 1][j], f[i + 1][j + 1]) + triangle[i][j];
            }
        }

        // answer
        return f[0][0];
    }
}

此题中的时间复杂度的a为O(n^{2}), b和c均为O(1), 最后的结果为O(n^{2})。

讨论: 

算法视频QQ_1603159172

动态规划和分治

1.动态规划是一种算法思想, 是高于算法的. 而分治的记忆化搜索是实现动态规划的一种手段.
2.那么什么是动态规划呢?
-就感觉上来说, 动态规划的是"一层一层来", 基于前一个状态推出现在的状态.
3.动态规划为什么会快呢?
-因为减少了很多不必要的重复计算.
4.动态规划和分治的区别?
-动态规划约等于分治+记忆化, 因为有了记忆化, 所以算过的直接用就行, 就不用再算一遍了.
5.动态规划有方向性,不回头,不绕圈儿,无循环依赖。

什么时候使用动态规划:

动态规划适合把暴力时间复杂度为指数型的问题转化为多项式的复杂度,即O(2^{n})或O(n!) 转化为O(n^{2})

1.求最大最小的问题

2.判断可不可行,存不存在

3.统计方案个数

什么时候不用动态规划:

  1. 本来时间复杂度就在O(n^{2})或者O(n^{3})的问题继续优化,因为不怎么能优化…(100%不用DP)• 动态规划擅长与优化指数级别复杂度(2^n,n!)到多项式级别复杂度(n^2,n^3)

    • 不擅长优化n^3到n^2

  2. 求!!所有的,具体的!!方案而不是方案个数,要用DFS而不是DP(99%不用DP), 除了N皇后
  3. 输入数据是一个集合而非序列(70~80%不用DP),除了背包问题。

动态规划的实现方式

算法视频QQ_1603159172

这里写图片描述

多重循环的两种方式:

  • 自底向上
  • 自顶向下两种方法没有太大优劣区别

    思维模式一个正向,一个逆向

    自底向上代码如下:

    这里写图片描述

    时间复杂度: O(n^{2})

    空间复杂度:O(n^{2}),看开了一个多大的数组就是结果,莫想的太复杂…

    自顶向上代码如下:

    这里写图片描述

    时间复杂度依旧: O(n^{2})

    空间复杂度依旧:O(n^{2}),依旧看开了一个多大的数组就是结果,依旧莫想的太复杂…

动态规划的四点要素

 1.状态:即定义,中间的状态
 2.方程:即从前一种状态推出现在的状态.
 3.初始化:极限小的状态,即为起点.
 4.答案:终点

递归三要素:

 1. 定义(状态)
    • 接受什么参数
    • 做了什么事
    • 返回什么值
 2. 拆解(方程)
    • 如何将参数变小
 3. 出口(初始化)
    • 什么时候可以直接 return

状态的六大问题:

 1.坐标型15%
 2.序列型30%
 3.双序列型30%
 4.划分型10%
 5.背包型10%
 5.区间型5%

坐标型动态规划

算法视频QQ_1603159172

这里写图片描述

初始化的小技巧

 没有左上和右上的提出来先算,也可以说成二维DP问题, [i,0]和[0,i]先初始化,即初始化第0行和第0列.

Unique Paths

A robot is located at the top-left corner of a m x n grid.

The robot can only move either down or right at any point in time. The robot is trying to reach the bottom-right corner of the grid.


public class Solution {
    public int uniquePaths(int m, int n) {
        if (m == 0 || n == 0) {
            return 1;
        }

        int[][] sum = new int[m][n];
        for (int i = 0; i < m; i++) {
            sum[i][0] = 1;
        }
        for (int i = 0; i < n; i++) {
            sum[0][i] = 1;
        }
        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                sum[i][j] = sum[i - 1][j] + sum[i][j - 1];
            }
        }
        return sum[m - 1][n - 1];
    }
}

Minimum Path Sum

算法视频QQ_1603159172

Given a m x n grid filled with non-negative numbers, find a path from top left to bottom right which minimizes the sum of all numbers along its path.

public class Solution {
    public int minPathSum(int[][] grid) {
        if (grid == null || grid.length == 0 || grid[0].length == 0) {
            return 0;
        }

        int M = grid.length;
        int N = grid[0].length;
        int[][] sum = new int[M][N];

        sum[0][0] = grid[0][0];

        for (int i = 1; i < M; i++) {
            sum[i][0] = sum[i - 1][0] + grid[i][0];
        }

        for (int i = 1; i < N; i++) {
            sum[0][i] = sum[0][i - 1] + grid[0][i];
        }

        for (int i = 1; i < M; i++) {
            for (int j = 1; j < N; j++) {
                sum[i][j] = Math.min(sum[i - 1][j], sum[i][j - 1]) + grid[i][j];
            }
        }

        return sum[M - 1][N - 1];
    }
}

接龙型DP

算法视频QQ_1603159172

LISLongest Increasing Subsequence

Given a sequence of integers, find the longest increasing subsequence (LIS).

You code should return the length of the LIS.

不要用贪心法, 你能想到的贪心法都是错的

面试不会考贪心法

贪心法没有通用性

起点不确定,终点也不确定,一个小人, 只能往高跳,最多踩多少个木桩:

enter image description here

public class Solution {
    /**
     * @param nums: The integer array
     * @return: The length of LIS (longest increasing subsequence)
     */


    public int longestIncreasingSubsequence(int[] nums) {
        int []f = new int[nums.length];
        int max = 0;
        for (int i = 0; i < nums.length; i++) {
            f[i] = 1;
            for (int j = 0; j < i; j++) {
                if (nums[j]  f[j] + 1 ? f[i] : f[j] + 1;
                }
            }
            if (f[i] > max) {
                max = f[i];
            }
        }
        return max;
    }
}
posted @ 2018-11-02 12:56  pkuhjs  阅读(1543)  评论(0编辑  收藏  举报