动态规划(dynamic programming)是运筹学的一个分支,是求解决策过程(decision process)最优化的数学方法。20世纪50年代初美国数学家R.E.Bellman等人在研究多阶段决策过程(multistep decision process)的优化问题时,提出了著名的最优化原理(principle of optimality),把多阶段过程转化为一系列单阶段问题,利用各阶段之间的关系,逐个求解,创立了解决这类过程优化问题的新方法——动态规划。
利用动态规划原理解决问题的大致步骤为:
- 确认原问题和子问题
- 确认状态
- 确认边界状态的值
- 确定状态转移方程
以下是我在leetcode上刷的关于动态规划的几道习题的代码和思路
- 爬楼梯----leetcode70
/* 爬楼梯----leetcode70
* 动态规划原理
* 1. 确认原问题和子问题
* 原问题是求 n 阶台阶所有走法的数量,子问题是求 1、2、3....n-1
* 阶台阶的走法
* 2. 确认状态
* 本题的动态规划状态单一,第i个状态即为i阶台阶的所有走法数量
* 3. 确认边界状态的值
* 边界状态为1阶台阶和2阶台阶的走法,1阶台阶有1种走法,2阶台阶有2种走法,即dp[0]=1,dp[1]=2
* 4. 确定状态转移方程
* 将求第i 个状态的值转化为求第i-1 个状态的值和第i-2 个状态的值,
* 动态规划转换方程为 :dp[i]=dp[i-1]+dp[i-2]
**/
public static int climbStairs(int n) {
int[] dp = new int[n];
if(n <= 2) {
return n;
}
dp[0] = 1;
dp[1] = 2;
for(int i = 2; i < n; i++) {
dp[i] = dp[i-1] + dp[i-2];
}
return dp[n-1];
}
- 打家劫舍—leetcode198
/*
* 打家劫舍---leetcode198
* 动态规划原理
* 1. 确认原问题和子问题
* 原问题为n 个房间的最优解,子问题为求前1个房间、前两个房间、前n-1个房间的最优解
* 2. 确认状态
* 第i 个状态即为前i 能够获得的最大财宝(最优解)个房间
* 3. 确认边界状态的值
* 前1个房间的最优解为第1个房间的财宝
* 前2个房间的最优解为第1、2个房间中财宝的较大值
* 4. 确定状态转移方程
* a. 选择第i 个房间:第i 个房间 + 前i-2 个房间的最优解
* b. 选择第i-1 个房间:前i-1 个房间的最优解
* 动态规划转移方程:
* dp[i] = max(dp[i-1], dp[i-2] + nums[i])
* */
public static int rob(int[] nums) {
if(nums.length == 0) {
return 0;
}
if(nums.length == 1) {
return nums[0];
}
if(nums.length == 2) {
return Math.max(nums[0], nums[1]);
}
int[] dp = new int[nums.length];
dp[0] = nums[0];
dp[1] = Math.max(nums[0], nums[1]);
for(int i = 2; i < nums.length; i++) {
dp[i] = Math.max(dp[i-1], dp[i-2]+nums[i]);
}
return dp[nums.length-1];
}
- 最大子序和—leetcode53
/*
* 最大子序和---leetcode53
* 动态规划原理
* 1. 确认原问题和子问题
* 原问题为在数组中找到一个具有最大和的连续子数组,
* 子问题为找到以原数组中每个元素结尾的子数组的最优解
* 2. 确认状态
* 第i 个状态即为以第i 个数字结尾的最优解
* 3. 确认边界状态的值
* 以第一个数字结尾的最大字段和dp[0]=nums[0]
* 4. 确定状态转移方程
* a. 当dp[i-1]>0:dp[i] = dp[i-1]+nums[i]
* b. 当dp[i-1]<0:dp[i] = nums[i];
* dp[i] = max(dp[i-1]+nums[i], nums[i])
* */
public static int maxSubArray(int[] nums) {
if(nums.length == 0) {
return 0;
}
if(nums.length == 1) {
return nums[0];
}
int[] dp = new int[nums.length];
dp[0] = nums[0];
dp[1] = Math.max(dp[0]+nums[1], nums[1]);
int max = dp[0];
for(int i = 1; i < nums.length; i++) {
dp[i] = Math.max(dp[i-1]+nums[i], nums[i]);
if(dp[i] > max) {
max = dp[i];
}
}
return max;
}
- 零钱兑换—leetcode322
/*
* 零钱兑换---leetcode322
* 动态规划
* dp[i]数组用来表示0到amount数额之间的每个值所需的最小硬币数量
* 因为数组下标是从0开始的,因此dp数组的长度应该为amount+1
* 设 coins为[1, 2, 5],amount=11,则dp[0]=0, dp[1]=1,dp[2]=1,dp[3]=2,dp[4]=2,
* dp[5]=1, dp[6]=2, dp[7]=2, dp[8]=3, dp[9]=3, dp[10]=2, dp[11]=3,
* 因此当amount为11时的结果为3
* */
public static int coinChange(int[] coins, int amount) {
if(coins == null || coins.length == 0 || amount <= 0) {
return 0;
}
int[] dp = new int[amount+1];
for(int i = 0; i < dp.length; i++) {
dp[i] = amount+1;
}
dp[0] = 0;
for(int i = 0; i <= amount; i++) {
for(int j = 0; j < coins.length; j++) {
if(coins[j] <= i) {
dp[i] = Math.min(dp[i], dp[i-coins[j]]+1);
}
}
}
return dp[amount] > amount ? -1 : dp[amount];
}
- 三角形最小路径和—leetcode120(经典动态规划练习题)
/*
* 三角形最小路径和---leetcode120(经典动态规划练习题)
* 算法思路:
* 1.设置一个二维数组,最优值三角形dp[][],并初始化数组元素为0,
* dp[i][j]代表了数组三角形第i行, 第j列的最优解(从底往上推)
* 2.从三角形的地面向三角形上方进行动态规划
* a. 动态规划边界条件:地面上的最优解的即为数字三角形的最后一层
* b. 利用i循环,从倒数第二层递推至正数第一层,对于每层的各列,进行动态规划递推:
* 第i行, 第j列的最优解为dp[i][j],可到达(i, j)的两个位置的最优解为
* dp[i+1][j]、dp[i+1]dp[j+1]
* 状态转换方程为:dp[i][j]=min(dp[i+1][j], dp[i+1][j+1])+triangle[i][j]
* */
public static int minimumTotal(List<List<Integer>> triangle) {
if(triangle == null || triangle.size() == 0) {
return 0;
}
int[][] dp = new int[triangle.size()][triangle.size()];
for(int i = triangle.size()-1; i >= 0; i--) {
List<Integer> list = triangle.get(i);
for(int j = 0; j < list.size(); j++) {
dp[i][j] = Math.min(dp[i+1][j], dp[i+1][j+1])+list.get(j);
}
}
return dp[0][0];
}
- 最长上升子序列—leetcode300
/*
* 最长上升子序列---leetcode300
* 动态规划
* 若第i个状态dp[i]代表以第i个元素结尾的最大上升子序列的长度:
* dp[i-1]代表以第i-1个元素结尾的最大上升子序列的长度
* */
public static int lengthOfLIS(int[] nums) {
if(nums == null || nums.length == 0) {
return 0;
}
int maxlen = 1;
int[] dp = new int[nums.length];
dp[0] = 1;
for(int i = 1; i < nums.length; i++) {
int max = 0;
for(int j = 0; j < i; j++) {
if(nums[i] > nums[j]) {
max = Math.max(max, dp[j]);
}
}
dp[i] = max + 1;
maxlen = Math.max(maxlen, dp[i]);
}
return maxlen;
}
- 最小路径和—leetCode64
/*
* 最小路径和---leetCode64
* 和三角形的最小路径和相似(但是要注意边界条件的判断)
* */
public static int minPathSum(int[][] grid) {
if(grid == null || grid.length == 0) {
return 0;
}
int[][] dp = new int[grid.length+1][grid[0].length+1];
for(int i = grid.length-1; i >= 0; i--) {
for(int j = grid[0].length - 1; j >= 0; j--) {
if(i == grid.length - 1 && j != grid[0].length - 1)
dp[i][j] = grid[i][j] + dp[i][j + 1];
else if(j == grid[0].length - 1 && i != grid.length - 1)
dp[i][j] = grid[i][j] + dp[i + 1][j];
else if(j != grid[0].length - 1 && i != grid.length - 1)
dp[i][j] = grid[i][j] + Math.min(dp[i + 1][j], dp[i][j + 1]);
else
dp[i][j] = grid[i][j];
}
}
return dp[0][0];
}