从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).
遇到这道题,老师说开始先给出暴力解法之后进行优化是可以的,不一定一开始就给出最优解,想出来一个暴力解法觉得傻就不说话。。。
其中时间复杂度为O(2^{n}), 其中n为Triangle中的层数(122*2…)进去n次,每次调用2次自己,所以O(2^{n-1}),近似为O(2^{n}), 注意使用Traverse的方法的时候,结果是当作函数参数传递的。
时间复杂度依然为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})。
1.动态规划是一种算法思想, 是高于算法的. 而分治的记忆化搜索是实现动态规划的一种手段.
2.那么什么是动态规划呢?
-就感觉上来说, 动态规划的是"一层一层来", 基于前一个状态推出现在的状态.
3.动态规划为什么会快呢?
-因为减少了很多不必要的重复计算.
4.动态规划和分治的区别?
-动态规划约等于分治+记忆化, 因为有了记忆化, 所以算过的直接用就行, 就不用再算一遍了.
5.动态规划有方向性,不回头,不绕圈儿,无循环依赖。
动态规划适合把暴力时间复杂度为指数型的问题转化为多项式的复杂度,即O(2^{n})或O(n!) 转化为O(n^{2})
1.求最大最小的问题
2.判断可不可行,存不存在
3.统计方案个数
? 不擅长优化n^3到n^2
思维模式一个正向,一个逆向
自底向上代码如下:
时间复杂度: 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%
没有左上和右上的提出来先算,也可以说成二维DP问题, [i,0]和[0,i]先初始化,即初始化第0行和第0列.
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];
}
}
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];
}
}
Given a sequence of integers, find the longest increasing subsequence (LIS).
You code should return the length of the LIS.
不要用贪心法, 你能想到的贪心法都是错的
面试不会考贪心法
贪心法没有通用性
起点不确定,终点也不确定,一个小人, 只能往高跳,最多踩多少个木桩:
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;
}
}
九章算法笔记 9.动态规划 Dynamic Programming
原文:https://www.cnblogs.com/jiuzhangsuanfa/p/9895671.html