文章目录

  • 一、动态规划
    • 1. 定义
    • 2. 基本思想和策略
    • 3. 解题思路
    • 4. 使用情况
  • 二、算法笔记
    • 509. 斐波那契数
    • 1137. 第 N 个泰波那契数
    • 70. 爬楼梯
    • 746. 使用最小花费爬楼梯
    • 198. 打家劫舍
    • 213. 打家劫舍 II
    • 740. 删除并获得点数
    • 55. 跳跃游戏
    • 45. 跳跃游戏 II
    • 53. 最大子序和
    • 918. 环形子数组的最大和
    • 152. 乘积最大子数组
    • 1567. 乘积为正数的最长子数组长度
    • 1014. 最佳观光组合
    • 121. 买卖股票的最佳时机
    • 122. 买卖股票的最佳时机 II
    • 309. 最佳买卖股票时机含冷冻期
    • 714. 买卖股票的最佳时机含手续费
    • 139. 单词拆分
    • 42. 接雨水
    • 413. 等差数列划分
    • 91. 解码方法
    • 264. 丑数 II
    • 96. 不同的二叉搜索树
    • 118. 杨辉三角
    • 119. 杨辉三角 II
    • 931. 下降路径最小和
    • 120. 三角形最小路径和
    • 1314. 矩阵区域和
    • 62. 不同路径
    • 63. 不同路径 II
    • 64. 最小路径和
    • 221. 最大正方形
    • 300. 最长递增子序列
    • 376. 摆动序列

动态规划(DP)问题是算法刷题过程中一大类问题,下面是在刷动态规划题目时的一些总结。

一、动态规划

1. 定义

动态规划算法是通过拆分问题,定义问题状态和状态之间的关系,使得问题能够以递推(或者说分治)的方式去解决。

动态规划算法的基本思想与分治法类似,也是将待求解的问题分解为若干个子问题(阶段),按顺序求解子阶段,前一子问题的解,为后一子问题的求解提供了有用的信息。在求解任一子问题时,列出各种可能的局部解,通过决策保留那些有可能达到最优的局部解,丢弃其他局部解。依次解决各子问题,最后一个子问题就是初始问题的解。

2. 基本思想和策略

由于动态规划解决的问题多数有重叠子问题这个特点,为减少重复计算,对每一个子问题只解一次,将其不同阶段的不同状态保存在一个二维数组中。

主要是三点:

  1. 拆分问题。就是根据问题的可能性把问题划分成一步一步这样就可以通过递推或者递归来实现。
    关键就是这个步骤:动态规划有一类问题就是从后往前推到。有时候我们很容易知道:如果只有一种情况时,最佳的选择应该怎么做。然后根据这个最佳选择往前一步推导,得到前一步的最佳选择
  2. 定义问题状态和状态之间的关系。前面拆分的步骤之间的关系,用一种量化的形式表现出来,类似于高中学的推导公式,因为这种式子很容易用程序写出来,也可以说对程序比较亲和(也就是最后所说的状态转移方程式)。
  3. 找到最优解。我们应该将最优解保存下来,为了往前推导时能够使用前一步的最优解,在这个过程中难免有一些相比于最优解差的解,此时我们应该放弃,只保存最优解,这样我们每一次都把最优解保存了下来,大大降低了时间复杂度。

3. 解题思路

  1. 将原问题分解为子问题。 (注意:1.子问题与原问题形式相同或类似,只是问题规模变小了,从而变简单了;2,子问题一旦求出就要保存下来,保证每个子问题只求解一遍。)
  2. 确定状态。(状态:在动规解题中,我们将和子问题相关的各个变量的一组取值,称之为一个"状态"。一个状态对应一个或多个子问题所谓的在某个状态的值,这个就是状态所对应的子问题的解,所有状态的集合称为"状态空间"。我的理解就是状态就是某个问题某组变量,状态空间就是该问题的所有组变量。)
    另外:整个问题的时间复杂度就是状态数目乘以每个状态所需要的时间
  3. 确定一些初始状态(边界条件)的值。 (这个视情况而定,千万别以为就是最简单的那个子问题解。)
  4. 确定状态转移方程 (这一步和第三步是最关键的 记住"人人为我"递推,由已知推未知。)

4. 使用情况

  1. 问题具有最优子结构
  2. 无后效性。(一般遇到的求最优解问题都可以用动态规划)

二、算法笔记

下面是力扣刷题过程中的

509. 斐波那契数

斐波那契数,通常用 F(n) 表示,形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。也就是:

F(0) = 0,F(1) = 1
F(n) = F(n - 1) + F(n - 2),其中 n > 1

给你 n ,请计算 F(n) 。

示例一:

输入:2
输出:1
解释:F(2) = F(1) + F(0) = 1 + 0 = 1

示例二:

输入:3
输出:2
解释:F(3) = F(2) + F(1) = 1 + 1 = 2

代码实现:

class Solution {public int fib(int n) {if(n <= 1 ) return n;int[] dp = new int[n+1];dp[0] = 0;dp[1] = 1;for(int i = 2; i <= n; i++){dp[i] = dp[i-1] + dp[i-2];}return dp[n];}
}

1137. 第 N 个泰波那契数

泰波那契序列 Tn 定义如下:

T0 = 0, T1 = 1, T2 = 1, 且在 n >= 0 的条件下 Tn+3 = Tn + Tn+1 + Tn+2

给你整数 n,请返回第 n 个泰波那契数 Tn 的值。

示例一:

输入:n = 4
输出:4
解释:
T_3 = 0 + 1 + 1 = 2
T_4 = 1 + 1 + 2 = 4

示例二:

输入:n = 25
输出:1389537

代码实现:

class Solution {public int tribonacci(int n) {// if(n <= 1) return n;// if(n == 2) return 1;// int[] dp = new int[n+1];// dp[0] = 0;// dp[1] = 1;// dp[2] = 1;// for(int i = 3; i <= n; i++){//     dp[i] = dp[i-1] + dp[i-2] + dp[i-3];// }// return dp[n];if (n == 0) return 0;if (n == 1 || n == 2) return 1;int a = 0, b = 1, c = 1;for (int i = 3; i <= n; i++) {int d = a + b + c;a = b;b = c;c = d;}return c;}
}

70. 爬楼梯

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

示例一:

输入: 2
输出: 2
解释: 有两种方法可以爬到楼顶。
1.  1 阶 + 1 阶
2.  2 阶

示例二:

输入: 3
输出: 3
解释: 有三种方法可以爬到楼顶。
1.  1 阶 + 1 阶 + 1 阶
2.  1 阶 + 2 阶
3.  2 阶 + 1 阶

代码实现:

class Solution {public int climbStairs(int n) {if(n == 1) return 1;if(n == 2) return 2;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];}
}

746. 使用最小花费爬楼梯

数组的每个下标作为一个阶梯,第 i 个阶梯对应着一个非负数的体力花费值 cost[i](下标从 0 开始)。

每当你爬上一个阶梯你都要花费对应的体力值,一旦支付了相应的体力值,你就可以选择向上爬一个阶梯或者爬两个阶梯。

请你找出达到楼层顶部的最低花费。在开始时,你可以选择从下标为 0 或 1 的元素作为初始阶梯。

示例一:

输入:cost = [10, 15, 20]
输出:15
解释:最低花费是从 cost[1] 开始,然后走两步即可到阶梯顶,一共花费 15 。

示例二:

输入:cost = [1, 100, 1, 1, 1, 100, 1, 1, 100, 1]
输出:6
解释:最低花费方式是从 cost[0] 开始,逐个经过那些 1 ,跳过 cost[3] ,一共花费 6 。

代码实现:

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

198. 打家劫舍

你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。

给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。

示例一:

输入:[1,2,3,1]
输出:4
解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。偷窃到的最高金额 = 1 + 3 = 4 。

示例二:

输入:[2,7,9,3,1]
输出:12
解释:偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。偷窃到的最高金额 = 2 + 9 + 1 = 12 。

状态转移方程
// 状态转移方程,偷第i个房间,则金额为前i-2的金额加该房间
// 不偷该房间,则最大金额为前i-1个房间的,故最大金额为两者最大的

dp[i] = Math.max(dp[i-2] + nums[i], dp[i-1]);

只有一间房子:dp[0] = nums[0];
只有两间房子:dp[1] = Math.max(nums[0], nums[1]);

代码实现:

class Solution {public int rob(int[] nums) {int n = nums.length;int[] dp = new int[n];if(n == 1) return nums[0];if(n == 2) return Math.max(nums[0], nums[1]);dp[0] = nums[0];dp[1] = Math.max(nums[0], nums[1]);for(int i = 2; i < n; i++){// 状态转移方程,偷第i个房间,则金额为前i-2的金额加该房间// 不偷该房间,则最大金额为前i-1个房间的,故最大金额为两者最大的dp[i] = Math.max(dp[i-2] + nums[i], dp[i-1]);  } return dp[n-1];}
}

213. 打家劫舍 II

你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。

给定一个代表每个房屋存放金额的非负整数数组,计算你 在不触动警报装置的情况下 ,今晚能够偷窃到的最高金额。

示例一:

输入:nums = [2,3,2]
输出:3
解释:你不能先偷窃 1 号房屋(金额 = 2),然后偷窃 3 号房屋(金额 = 2), 因为他们是相邻的。

示例二:

输入:nums = [1,2,3,1]
输出:4
解释:你可以先偷窃 1 号房屋(金额 = 1),然后偷窃 3 号房屋(金额 = 3)。偷窃到的最高金额 = 1 + 3 = 4 。

示例三:

输入:nums = [0]
输出:0

分为两种情况
// 如果偷窃第一间房子,则偷窃范围为0, n - 2;如果偷窃最后一间房子,则偷窃范围为1, n - 1

代码实现:

class Solution {public int rob(int[] nums) {// 因为是一个圆,所以不能偷完第一个偷最后一个int n = nums.length;if(n == 1) return nums[0];if(n == 2) return Math.max(nums[0], nums[1]);// 如果偷窃第一间房子,则偷窃范围为0, n - 2;如果偷窃最后一间房子,则偷窃范围为1, n - 1return Math.max(robRange(nums, 0, n - 2), robRange(nums, 1, n - 1));}public int robRange(int[] nums, int start, int end){int first = nums[start], second = Math.max(nums[start], nums[start + 1]);for (int i = start + 2; i <= end; i++) {int temp = second;second = Math.max(first + nums[i], second);first = temp;}return second;}
}

740. 删除并获得点数

给你一个整数数组 nums ,你可以对它进行一些操作。

每次操作中,选择任意一个 nums[i] ,删除它并获得 nums[i] 的点数。之后,你必须删除 所有 等于 nums[i] - 1 和 nums[i] + 1 的元素。

开始你拥有 0 个点数。返回你能通过这些操作获得的最大点数。

示例一:

输入:nums = [3,4,2]
输出:6
解释:
删除 4 获得 4 个点数,因此 3 也被删除。
之后,删除 2 获得 2 个点数。总共获得 6 个点数。

示例二:

输入:nums = [2,2,3,3,3,4]
输出:9
解释:
删除 3 获得 3 个点数,接着要删除两个 2 和 4 。
之后,再次删除 3 获得 3 个点数,再次删除 3 获得 3 个点数。
总共获得 9 个点数。

思路:
根据题意,在选择了元素 x 后,该元素以及所有等于 x−1 或 x+1 的元素会从数组中删去。若还有多个值为 x 的元素,由于所有等于 x−1 或 x+1 的元素已经被删除,我们可以直接删除 x 并获得其点数。因此若选择了 x,所有等于 x 的元素也应一同被选择,以尽可能多地获得点数。

记元素 x 在数组中出现的次数为 cx,我们可以用一个数组 sum 记录数组 num 中所有相同元素之和,即 sum[x]=x⋅cx。若选择了 x,则可以获取 sum[x]] 的点数,且无法再选择 x−1 和 x+1。

代码实现:

class Solution {public int deleteAndEarn(int[] nums) {int maxVal = 0;for (int val : nums) {maxVal = Math.max(maxVal, val);}int[] sum = new int[maxVal + 1];for (int val : nums) {sum[val] += val;}return rob(sum);}public int rob(int[] nums) {int size = nums.length;int first = nums[0], second = Math.max(nums[0], nums[1]);for (int i = 2; i < size; i++) {int temp = second;second = Math.max(first + nums[i], second);first = temp;}return second;}
}

55. 跳跃游戏

给定一个非负整数数组 nums ,你最初位于数组的 第一个下标 。

数组中的每个元素代表你在该位置可以跳跃的最大长度。

判断你是否能够到达最后一个下标。

示例一:

输入:nums = [2,3,1,1,4]
输出:true
解释:可以先跳 1 步,从下标 0 到达下标 1, 然后再从下标 1 跳 3 步到达最后一个下标。

示例二:

输入:nums = [3,2,1,0,4]
输出:false
解释:无论怎样,总会到达下标为 3 的位置。但该下标的最大跳跃长度是 0 , 所以永远不可能到达最后一个下标。

思路:
贪心算法:

for循环存储每个位置最远所能达到的位置

right = Math.max(right, i + nums[i]);

如果能达到最后一个元素,则表明可以跳到。

代码实现:

class Solution {public boolean canJump(int[] nums) {int right = nums[0]; // 存储最远可达到位置for(int i = 0; i < nums.length; i++){if(i <= right){right = Math.max(right, i + nums[i]);if(right >= nums.length-1){return true;}}}return false;}
}

45. 跳跃游戏 II

给你一个非负整数数组 nums ,你最初位于数组的第一个位置。

数组中的每个元素代表你在该位置可以跳跃的最大长度。

你的目标是使用最少的跳跃次数到达数组的最后一个位置。

假设你总是可以到达数组的最后一个位置。

示例一:

输入: nums = [2,3,1,1,4]
输出: 2
解释: 跳到最后一个位置的最小跳跃数是 2。从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。

示例二:

输入: nums = [2,3,0,1,4]
输出: 2

贪心算法,每次都跳最远距离

求最少的跳跃次数,和Ⅰ相同,首先计算每个位置最远能到达的位置
记录该位置,步数加1.

代码实现:

class Solution {public int jump(int[] nums) {int length = nums.length;int end = 0;int maxPosition = 0; int steps = 0;for (int i = 0; i < length - 1; i++) {maxPosition = Math.max(maxPosition, i + nums[i]);  // 找到所能达到的最远位置if (i == end) { // end为最远位置,到达则步数加1end = maxPosition;steps++;}}return steps;}
}

53. 最大子序和

给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

示例一:

输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。

示例二:

输入:nums = [1]
输出:1

示例三:

输入:nums = [-1]
输出:-1

dp[i] 表示以第 i 个数结尾的连续子数组的最大和,找最大的dp[i]

nums[i] += Math.max(nums[i-1], 0)

以 i 结尾的最大子序和等于以 i 结尾的元素加上上一个元素与0的最大值
最大子序和等于更新的res与nums[i]的最大值

代码实现:

class Solution {public int maxSubArray(int[] nums) {int res = nums[0]; //存储和的最大值for(int i = 1; i < nums.length; i++){nums[i] += Math.max(nums[i-1], 0);res = Math.max(res, nums[i]);}return res;}
}

918. 环形子数组的最大和

给定一个由整数数组 A 表示的环形数组 C,求 C 的非空子数组的最大可能和。

在此处,环形数组意味着数组的末端将会与开头相连呈环状。(形式上,当0 <= i < A.length 时 C[i] = A[i],且当 i >= 0 时 C[i+A.length] = C[i])

此外,子数组最多只能包含固定缓冲区 A 中的每个元素一次。(形式上,对于子数组 C[i], C[i+1], …, C[j],不存在 i <= k1, k2 <= j 其中 k1 % A.length = k2 % A.length)

示例一:

输入:[1,-2,3,-2]
输出:3
解释:从子数组 [3] 得到最大和 3

示例二:

输入:[5,-3,5]
输出:10
解释:从子数组 [5,5] 得到最大和 5 + 5 = 10

示例三:

输入:[3,-1,2,-1]
输出:4
解释:从子数组 [2,-1,3] 得到最大和 2 + (-1) + 3 = 4

分两种情况:
1.最大数组在中间,与最大子序和相同
2.最大数组跨越头尾,两边大中间小,找最小

代码实现:

class Solution {public int maxSubarraySumCircular(int[] nums) {//两种情况,第一种最大数组在中间int dp = nums[0], max = dp, sum = dp, min = 0;for(int i = 1; i < nums.length; i++){sum += nums[i];  // 统计数组元素总和dp = nums[i] + Math.max(dp, 0);max = Math.max(dp, max);}// 第二种情况,最大数组和跨越头尾,两边大中间最小,找最小dp = nums[0];for(int i = 1; i < nums.length-1; i++){dp = nums[i] + Math.min(dp, 0);min = Math.min(dp, min);}return Math.max(sum-min, max);}
}

152. 乘积最大子数组

给你一个整数数组 nums ,请你找出数组中乘积最大的连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。

示例一:

输入: [2,3,-2,4]
输出: 6
解释: 子数组 [2,3] 有最大乘积 6。

示例二:

输入: [-2,0,-1]
输出: 0
解释: 结果不能为 2, 因为 [-2,-1] 不是子数组。

动态规划,但要考虑遇到正负两种情况。

代码实现:

class Solution {public int maxProduct(int[] nums) {// 与最大连续子数组和不同,乘积需要考虑正负int max = Integer.MIN_VALUE, imax = 1, imin = 1;for(int i = 0; i < nums.length; i++){// 如果是负数,会导致最大的变最小,最小的变最大,交换两个值if(nums[i] < 0){int tmp = imax;imax = imin;imin = tmp;}imax = Math.max(imax * nums[i], nums[i]);  // 存储正的最大值imin = Math.min(imin * nums[i], nums[i]);  // 存储负的最小值max = Math.max(max, imax);}return max;}
}

1567. 乘积为正数的最长子数组长度

给你一个整数数组 nums ,请你求出乘积为正数的最长子数组的长度。

一个数组的子数组是由原数组中零个或者更多个连续数字组成的数组。

请你返回乘积为正数的最长子数组长度。

示例一:

输入:nums = [1,-2,-3,4]
输出:4
解释:数组本身乘积就是正数,值为 24 。

示例二:

输入:nums = [0,1,-2,-3,-4]
输出:3
解释:最长乘积为正数的子数组为 [1,-2,-3] ,乘积为 6 。
注意,我们不能把 0 也包括到子数组中,因为这样乘积为 0 ,不是正数。

示例三:

输入:nums = [-1,-2,-3,0,1]
输出:2
解释:乘积为正数的最长子数组是 [-1,-2] 或者 [-2,-3] 。

示例四:

输入:nums = [-1,2]
输出:1

代码实现:

class Solution {public int getMaxLen(int[] nums) {int len= nums.length;int positive =nums[0]>0?1:0;int negative =nums[0]<0?1:0;int max = positive;for(int i = 1 ;i<len;i++){if(nums[i]>0){positive++;//接下的数是 正数,直接加1就完事negative=negative>0?negative+1:0; //负数×正数=负数  // 所以1.只要negetive存在 长度就加一 2.不存在 只有一个正数   负数在i位置长度为0}else if(nums[i]<0){//nums<0  正数×负数 变号->负连续 不管正数是多少 ,往后加1int newNegative = positive+1;//nums<0 negative>0  负数×负数 变号->正连续 因为要求正连续 所以把这个值给positive,如果前面没有负数,那么就为0 int newPositive = negative>0?negative+1:0;                positive=newPositive;negative=newNegative;        }else{//因为nums=0直接砍断了连续,长度直接变成0positive=0;negative=0;}max=Math.max(max,positive);//每个地方 都可能最长 所以都要比较一下}return max;}
}

1014. 最佳观光组合

给你一个正整数数组 values,其中 values[i] 表示第 i 个观光景点的评分,并且两个景点 i 和 j 之间的 距离 为 j - i。

一对景点(i < j)组成的观光组合的得分为 values[i] + values[j] + i - j ,也就是景点的评分之和 减去 它们两者之间的距离。

返回一对观光景点能取得的最高分。

示例一:

输入:values = [8,1,5,2,6]
输出:11
解释:i = 0, j = 2, values[i] + values[j] + i - j = 8 + 5 + 0 - 2 = 11

示例二:

输入:values = [1,2]
输出:2

代码实现:

class Solution {public int maxScoreSightseeingPair(int[] values) {// 变形为 values[i]+i 和 values[j]-j 两部分,分别求最大值int left = values[0], res= Integer.MIN_VALUE;for(int i = 1; i < values.length; i++){res = Math.max(res, left + values[i] - i);  // 更新left = Math.max(left, values[i] + i);  // 更新values[i]+i的最大值}return res;}
}

121. 买卖股票的最佳时机

给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。

你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。

返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。

示例一:

输入:[7,1,5,3,6,4]
输出:5
解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。

示例二:

输入:prices = [7,6,4,3,1]
输出:0
解释:在这种情况下, 没有交易完成, 所以最大利润为 0。

代码实现:

class Solution {public int maxProfit(int[] prices) {// dp[i]表示前i天获得的最大利润,等于前i-1的最大利润,或第i天卖出的最大利润// dp[i] = max(dp[i-1], prices[i]- min(prices[0,i-1]))int profit = 0, cost = Integer.MAX_VALUE;// profit 更新最大利润, cost更新前i-1最低价格for(int price : prices){cost = Math.min(cost, price);profit = Math.max(profit, price - cost);}return profit;}
}

122. 买卖股票的最佳时机 II

给定一个数组 prices ,其中 prices[i] 是一支给定股票第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。

注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

示例一:

输入: prices = [7,1,5,3,6,4]
输出: 7
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3

示例二:

输入: prices = [1,2,3,4,5]
输出: 4
解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。

示例三:

输入: prices = [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。

代码实现:

class Solution {public int maxProfit(int[] prices) {// 1.贪心算法,只要后一天比前一天大,就把差值加一下// int res = 0;// for(int i = 1; i < prices.length; i++){//     if(prices[i] > prices[i-1]){//         res += prices[i] - prices[i-1];//     }// }// return res;// 2.动态规划// dp[i][0] 表示第i天交易后手里没有股票的最大利润,dp[i][1] 表示第i天交易后手里持有股票的最大利润// dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i]) 可能是第i-1天没有股票的利润,也可能是第i-1天持有,加上卖出的利润// dp[i][1] = max(dp[i-1][1], dp[i-1][0] - prices[i]) 可能是第i-1天持有股票的利润,也可能是第i-1天没有,加上买入的利润int[][] dp = new int[prices.length][2];dp[0][0] = 0;dp[0][1] = - prices[0];for(int i = 1; i < prices.length; i++){dp[i][0] = Math.max(dp[i-1][0], dp[i-1][1] + prices[i]);dp[i][1] = Math.max(dp[i-1][1], dp[i-1][0] - prices[i]);}return dp[prices.length-1][0];}
}

309. 最佳买卖股票时机含冷冻期

给定一个整数数组,其中第 i 个元素代表了第 i 天的股票价格 。​

设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):

你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。

示例一:

输入: [1,2,3,0,2]
输出: 3
解释: 对应的交易状态为: [买入, 卖出, 冷冻期, 买入, 卖出]

代码实现:

class Solution {public int maxProfit(int[] prices) {// 动态规划,相比2多了一个冷冻期,冷冻期什么也不能干,冷冻期结束转为不持股状态if(prices == null || prices.length == 0) return 0;int[][] dp = new int[prices.length][3]; // 0 不持股,1 持股,2 冷冻期dp[0][0] = 0;  // 不持股dp[0][1] = - prices[0]; // 持股dp[0][2] = 0; // 冷冻期for(int i = 1; i < prices.length; i++){// 第i天不持股可以从两种状态转移, 1. 第i-1天不持股,第i天仍不买  2.冷冻期结束了,但不买dp[i][0] = Math.max(dp[i-1][0], dp[i-1][2]);// 第i天持股可以从两种状态转移, 1. 第i-1天不持股,分为i-1天是冷冻期,结束后转为不持股状态和第i-1天本身就不持股,然后买// 2.第i-1天持股,第i天不卖出dp[i][1] = Math.max(dp[i-1][0] - prices[i], dp[i-1][1]);// 只有第i-1天卖出,第i天才处于冷冻期dp[i][2] = dp[i-1][1] + prices[i];}// 只有最后一天不持股或者前一天已经卖掉(今天为冷冻期),最大值在二者之间产生return Math.max(dp[prices.length-1][0], dp[prices.length-1][2]);}
}

714. 买卖股票的最佳时机含手续费

给定一个整数数组 prices,其中第 i 个元素代表了第 i 天的股票价格 ;整数 fee 代表了交易股票的手续费用。

你可以无限次地完成交易,但是你每笔交易都需要付手续费。如果你已经购买了一个股票,在卖出它之前你就不能再继续购买股票了。

返回获得利润的最大值。

注意:这里的一笔交易指买入持有并卖出股票的整个过程,每笔交易你只需要为支付一次手续费。

示例一:

输入:prices = [1, 3, 2, 8, 4, 9], fee = 2
输出:8
解释:能够达到的最大利润:
在此处买入 prices[0] = 1
在此处卖出 prices[3] = 8
在此处买入 prices[4] = 4
在此处卖出 prices[5] = 9
总利润: ((8 - 1) - 2) + ((9 - 4) - 2) = 8

示例二:

输入:prices = [1,3,7,5,10,3], fee = 3
输出:6

代码实现:

class Solution {public int maxProfit(int[] prices, int fee) {// 动态规划,含手续费的股票交易// int[][] dp = new int[prices.length][2];// dp[0][0] = 0;  // 不持股// dp[0][1] = - prices[0] - fee; // 持股// for(int i = 1; i < prices.length; i++){//     dp[i][0] = Math.max(dp[i-1][0], dp[i-1][1] + prices[i]);//     dp[i][1] = Math.max(dp[i-1][1], dp[i-1][0] - prices[i] - fee);// }// return dp[prices.length-1][0];// // 动态规划,减少状态变量// int a = 0;  // 表示今日手里   没有股票的最大收益。// int b = Integer.MIN_VALUE;  // 表示今日手里   有股票的的最大收益。// for (int price : prices) {//     // 没有股票,可能昨天就没有,或者昨天有,今天出手了。//     a = Math.max(a, b + price);//     // 有股票,可能昨天就有,或者今天刚买的。//     b = Math.max(b, a - price - fee);// }// return a;// 贪心算法,int n = prices.length;int buy = prices[0] + fee;int profit = 0;for (int i = 1; i < n; ++i) {if (prices[i] + fee < buy) {buy = prices[i] + fee;} else if (prices[i] > buy) {profit += prices[i] - buy;buy = prices[i];}}return profit;}
}

139. 单词拆分

给你一个字符串 s 和一个字符串列表 wordDict 作为字典,判定 s 是否可以由空格拆分为一个或多个在字典中出现的单词。

说明:拆分时可以重复使用字典中的单词。

示例一:

输入: s = "leetcode", wordDict = ["leet", "code"]
输出: true
解释: 返回 true 因为 "leetcode" 可以被拆分成 "leet code"。

示例二:

输入: s = "applepenapple", wordDict = ["apple", "pen"]
输出: true
解释: 返回 true 因为 "applepenapple" 可以被拆分成 "apple pen apple"。注意你可以重复使用字典中的单词。

示例三:

输入: s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"]
输出: false

代码实现:

class Solution {public boolean wordBreak(String s, List<String> wordDict) {// 动态规划// dp[i] 表示以i-1结尾的字符串能否拆分成字典中得单词boolean[] dp = new boolean[s.length()+1];dp[0] = true;for(int i = 1; i <= s.length(); i++){for(int j = 0; j < i; j++){if(dp[j] && wordDict.contains(s.substring(j, i))){dp[i] = true;break;}}}return dp[s.length()];}
}

42. 接雨水

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

示例一:

输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
输出:6
解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。

示例二:

输入:height = [4,2,0,3,2,5]
输出:9

这是一道困难题,可以采用动态规划和单调栈两种做法。

代码实现:

class Solution {public int trap(int[] height) {// 动态规划int n = height.length;if(n <= 2) return 0;// 状态定义 dp[i][0..1]表示i位置的左右最大高度// 状态转移 正向遍历得到左最大高度 dp[i][0] = max(h[i], dp[i-1][0])//          反向遍历得到右最大高度 dp[i][1] = max(h[i], dp[i+1][1])int[][] dp = new int[n][2];dp[0][0] = height[0];dp[n-1][1] = height[n-1];for(int i = 1; i < n-1; i++){dp[i][0] = Math.max(height[i], dp[i-1][0]);  // 左最大高度dp[n-1-i][1] = Math.max(height[n-1-i], dp[n-i][1]);   // 右最大高度}int sum = 0;for(int i = 1; i < n-1; i++){sum += Math.min(dp[i][0], dp[i][1]) - height[i];  // 每处可接雨水等于左右高度最小值减去自身的高度}return sum;}
}

单调栈实现:

使用单调栈存储,使用单调递减处找低洼处1.单调栈分为单调递增栈和单调递减栈
11. 单调递增栈即栈内元素保持单调递增的栈
12. 同理单调递减栈即栈内元素保持单调递减的栈2.操作规则(下面都以单调递增栈为例)
21. 如果新的元素比栈顶元素大,就入栈
22. 如果新的元素较小,那就一直把栈内元素弹出来,直到栈顶比新元素小3.加入这样一个规则之后,会有什么效果
31. 栈内的元素是递增的
32. 当元素出栈时,说明这个新元素是出栈元素向后找第一个比其小的元素
33. 当元素出栈后,说明新栈顶元素是出栈元素向前找第一个比其小的元素

代码模板:

stack<int> st;
for(int i = 0; i < nums.size(); i++)
{while(!st.empty() && st.top() > nums[i]){st.pop();}st.push(nums[i]);
}

代码实现:

> class Solution {public int trap(int[] height) { // 使用单调递减栈来计算int sum = 0;Deque<Integer> stack = new LinkedList<>(); // 用Deque实现栈for(int i = 0; i < height.length; i++){ // 遍历高度数组//如果栈不空,且右边柱子(当前元素)大于左边柱子(栈顶元素),说明形成低洼处了while(!stack.isEmpty() && height[i] > height[stack.peek()]){// 栈顶的小元素(低洼处索引)出栈,低洼处弹出,尝试此低洼处能积攒雨水int top = stack.pop();// 看栈里是否有东西即左墙是否存在,有右墙+低洼,没有左墙没用if(stack.isEmpty()){break;}// 栈顶索引值就是左墙位置int left = stack.peek();// 能积攒的水=(右墙位置-左墙位置-1)*(min(右墙高度,左墙高度)-低洼处高度)sum += (i - left - 1) * (Math.min(height[i], height[left]) - height[top]);}// 当前索引的高度小于栈顶的会直接入栈,大于栈顶就一直出栈,直到小于栈顶或栈空stack.push(i);}return sum;}
}

413. 等差数列划分

如果一个数列 至少有三个元素 ,并且任意两个相邻元素之差相同,则称该数列为等差数列。

例如,[1,3,5,7,9]、[7,7,7,7] 和 [3,-1,-5,-9] 都是等差数列。

给你一个整数数组 nums ,返回数组 nums 中所有为等差数组的 子数组 个数。

子数组 是数组中的一个连续序列。

示例一:

输入:nums = [1,2,3,4]
输出:3
解释:nums 中有三个子等差数组:[1, 2, 3]、[2, 3, 4] 和 [1,2,3,4] 自身。

示例二:

输入:nums = [1]
输出:0

代码实现:

class Solution {public int numberOfArithmeticSlices(int[] nums) {// 找规律//定义状态:dp[i]表示从nums[0]到nums[i]且以nums[i]为结尾的等差数列子数组的数量。//状态转移方程: if nums[i]-nums[i-1]==nums[i-1]-nums[i-2] dp[i] = dp[i-1]+1 else 0//如果nums[i]能和nums[i-1]nums[i-2]组成等差数列,则以nums[i-1]结尾的等差数列均可以nums[i]结尾,且多了一个新等差数列[nums[i],       //nums[i-1],nums[i-2]]if(nums == null || nums.length < 3) return 0;int res = 0;int count = 0;for(int i = 2; i < nums.length; i++){if(nums[i] - nums[i-1] == nums[i-1] - nums[i-2]){  // count++;res += count;}else{count = 0;}}return res;}
}

91. 解码方法

一条包含字母 A-Z 的消息通过以下映射进行了 编码 :

‘A’ -> 1
‘B’ -> 2

‘Z’ -> 26

要 解码 已编码的消息,所有数字必须基于上述映射的方法,反向映射回字母(可能有多种方法)。例如,“11106” 可以映射为:

"AAJF" ,将消息分组为 (1 1 10 6)
"KJF" ,将消息分组为 (11 10 6)

注意,消息不能分组为 (1 11 06) ,因为 “06” 不能映射为 “F” ,这是由于 “6” 和 “06” 在映射中并不等价。

给你一个只含数字的 非空 字符串 s ,请计算并返回 解码 方法的 总数 。

题目数据保证答案肯定是一个 32 位 的整数。

示例一:

输入:s = "12"
输出:2
解释:它可以解码为 "AB"(1 2)或者 "L"(12)。

示例二:

输入:s = "226"
输出:3
解释:它可以解码为 "BZ" (2 26), "VF" (22 6), 或者 "BBF" (2 2 6) 。

示例三:

输入:s = "0"
输出:0
解释:没有字符映射到以 0 开头的数字。
含有 0 的有效映射是 'J' -> "10" 和 'T'-> "20" 。
由于没有字符,因此没有有效的方法对此进行解码,因为所有数字都需要映射。

示例四:

输入:s = "06"
输出:0
解释:"06" 不能映射到 "F" ,因为字符串含有前导 0("6" 和 "06" 在映射中并不等价)。

代码实现:

class Solution {public int numDecodings(String s) {// 动态规划, s[i]表示第i个字符// dp[i]表示前i个字符的解码方法数// 1.使用一个字符解码,s[i]!= 0, dp[i]=dp[i-1]// 2.使用两个字符解码,s[i-1]!=0 且s[i-1]和s[i]组成的数字小于26,dp[i]=dp[i-2]int[] dp = new int[s.length()+1];dp[0] = 1; //空字符串for(int i = 1; i <= s.length(); i++){if(s.charAt(i-1) != '0'){dp[i] += dp[i-1];}if(i > 1 && s.charAt(i-2) != '0' && ((s.charAt(i-2) - '0') * 10 + (s.charAt(i-1) - '0')) <= 26){dp[i] += dp[i-2];}}return dp[s.length()];}
}

优化后的动态规划:

class Solution {public int numDecodings(String s) {// 状态转移方程只与三个变量有关int[] dp = new int[s.length()+1];int a=0, b=1, c=0;for(int i = 1; i <= s.length(); i++){c = 0;if(s.charAt(i-1) != '0'){c += b;}if(i > 1 && s.charAt(i-2) != '0' && ((s.charAt(i-2) - '0') * 10 + (s.charAt(i-1) - '0')) <= 26){c += a;}a = b;b = c;}return c;}
}

264. 丑数 II

给你一个整数 n ,请你找出并返回第 n 个 丑数 。

丑数 就是只包含质因数 2、3 和/或 5 的正整数。

示例一:

输入:n = 10
输出:12
解释:[1, 2, 3, 4, 5, 6, 8, 9, 10, 12] 是由前 10 个丑数组成的序列。

示例二:

输入:n = 1
输出:1
解释:1 通常被视为丑数。

代码实现:

class Solution {public int nthUglyNumber(int n) {// 后面的丑数一定是有前面的丑数乘以2,乘以3或乘以5得到的// dp[i] 表示第i个丑数,定义三个指针,下一个丑数是当前指针指向的丑数乘以对应的质因数// 每次判断,指针所指向的丑数乘以对应的因数中最小的int n2 = 0, n3 = 0, n5 = 0;int[] dp = new int[n];dp[0] = 1;for(int i = 1; i < n; i++){dp[i] = Math.min(2 * dp[n2], Math.min(3 * dp[n3], 5 * dp[n5]));if(dp[i] == 2 * dp[n2]) n2++;if(dp[i] == 3 * dp[n3]) n3++;if(dp[i] == 5 * dp[n5]) n5++;}return dp[n-1];}
}

96. 不同的二叉搜索树

给你一个整数 n ,求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。

示例一:

输入:n = 3
输出:5

示例二:

输入:n = 1
输出:1

代码实现:

class Solution {public int numTrees(int n) {// n个节点存在二叉排序树的个数是G(n),当1为根节点,左子树节点个数为0,右子树节点个数为n-1// G(n) = G(0)*G(n-1)+G(1)*(n-2)+...+G(n-1)*G(0)// dp[i] 表示i个节点的二叉排序树个数,则以不同的数为根节点时,计算左右两边的个数if(n <= 2) return n;int[] dp = new int[n+1];dp[0] = 1;dp[1] = 1;dp[2] = 2;// 外层循环是节点个数,填充数组for(int i = 3; i <= n; i++){// 内层循环遍历各个元素用作根节点的情况for(int j = 1; j <= i; j++){dp[i] += dp[j-1] * dp[i-j];}}return dp[n];}
}

118. 杨辉三角

给定一个非负整数 numRows,生成「杨辉三角」的前 numRows 行。

在「杨辉三角」中,每个数是它左上方和右上方的数的和。

示例一:

输入: numRows = 5
输出: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]]

示例二:

输入: numRows = 1
输出: [[1]]

代码实现:

class Solution {public List<List<Integer>> generate(int numRows) {List<List<Integer>> res = new ArrayList<>();int[][] arr = new int[numRows][numRows];for(int i = 0; i < numRows; i++){List<Integer> list = new ArrayList<>();for(int j = 0; j <= i; j++){if(j == 0 || j==i){arr[i][j] = 1;}else{arr[i][j] = arr[i-1][j-1] + arr[i-1][j];}list.add(arr[i][j]);}res.add(list);}return res;}
}

119. 杨辉三角 II

给定一个非负索引 rowIndex,返回「杨辉三角」的第 rowIndex 行。

在「杨辉三角」中,每个数是它左上方和右上方的数的和。

示例一:

输入: rowIndex = 3
输出: [1,3,3,1]

示例二:

输入: rowIndex = 0
输出: [1]

示例三:

输入: rowIndex = 1
输出: [1,1]

代码实现:

杨辉三角Ⅰ的基础上,直接计算所求索引的一行值。需要注意的是起始行不同。

class Solution {public List<Integer> getRow(int rowIndex) {List<List<Integer>> res = new ArrayList<>();int[][] arr = new int[rowIndex+1][rowIndex+1];for(int i = 0; i <= rowIndex; i++){List<Integer> list = new ArrayList<>();for(int j = 0; j <= i; j++){if(j == 0 || j==i){arr[i][j] = 1;}else{arr[i][j] = arr[i-1][j-1] + arr[i-1][j];}list.add(arr[i][j]);}res.add(list);}return res.get(rowIndex);}
}

方法二:倒着计算当前行

class Solution {public List<Integer> getRow(int rowIndex) {// 倒着计算当前行,当前行第 i 项的计算只与上一行第 i−1 项及第 i 项有关List<Integer> row = new ArrayList<Integer>();row.add(1);for (int i = 1; i <= rowIndex; ++i) {row.add(0);for (int j = i; j > 0; --j) {row.set(j, row.get(j) + row.get(j - 1));}}return row;}
}

931. 下降路径最小和

给你一个 n x n 的 方形 整数数组 matrix ,请你找出并返回通过 matrix 的下降路径 的 最小和 。

下降路径 可以从第一行中的任何元素开始,并从每一行中选择一个元素。在下一行选择的元素和当前行所选元素最多相隔一列(即位于正下方或者沿对角线向左或者向右的第一个元素)。具体来说,位置 (row, col) 的下一个元素应当是 (row + 1, col - 1)、(row + 1, col) 或者 (row + 1, col + 1) 。

示例一:

输入:matrix = [[2,1,3],[6,5,4],[7,8,9]]
输出:13
解释:下面是两条和最小的下降路径,用* *标注:
[[2,*1*,3],      [[2,*1*,3],[6,*5*,4],       [6,5,*4*],[*7*,8,9]]       [7,*8*,9]]

示例二:

输入:matrix = [[-19,57],[-40,-5]]
输出:-59
解释:下面是一条和最小的下降路径,用* *标注:
[[*-19*,57],[*-40*,-5]]

示例三:

输入:matrix = [[-48]]
输出:-48

代码实现:

class Solution {public int minFallingPathSum(int[][] matrix) {// 先把每一行的最小值累加到最后一行,最后比较最后一行的最小值// 根据后一行找前一行的最小值,左边一行,正上面或右边一行的最小值// 先判断是否是边界int n = matrix.length, res = Integer.MAX_VALUE;for(int i = 1; i < n; i++){for(int j = 0; j < n; j++){if(j == 0){matrix[i][j] += Math.min(matrix[i-1][j], matrix[i-1][j+1]);}else if(j == n-1){matrix[i][j] += Math.min(matrix[i-1][j], matrix[i-1][j-1]);}else{matrix[i][j] += Math.min(Math.min(matrix[i-1][j-1], matrix[i-1][j]), matrix[i-1][j+1]);}}}for(int j = 0; j < n; j++){ // 比较最后一行的最小值res = Math.min(res, matrix[n-1][j]);}return res;}
}

120. 三角形最小路径和

给定一个三角形 triangle ,找出自顶向下的最小路径和。

每一步只能移动到下一行中相邻的结点上。相邻的结点 在这里指的是 下标 与 上一层结点下标 相同或者等于 上一层结点下标 + 1 的两个结点。也就是说,如果正位于当前行的下标 i ,那么下一步可以移动到下一行的下标 i 或 i + 1 。

示例一:

输入:triangle = [[2],[3,4],[6,5,7],[4,1,8,3]]
输出:11
解释:如下面简图所示:23 46 5 7
4 1 8 3
自顶向下的最小路径和为 11(即,2 + 3 + 5 + 1 = 11)。

示例二:

输入:triangle = [[-10]]
输出:-10

代码实现:

class Solution {public int minimumTotal(List<List<Integer>> triangle) {int n = triangle.size(), res = Integer.MAX_VALUE;int[][] dp = new int[n][n];  // 存储最小和dp[0][0] = triangle.get(0).get(0);for(int i = 1; i < n; i++){// 第一列,只能有对应的上一列得到dp[i][0] = dp[i-1][0] + triangle.get(i).get(0);for(int j = 1; j < i; j++){dp[i][j] = Math.min(dp[i-1][j-1], dp[i-1][j]) + triangle.get(i).get(j);}// 第i列,只能有第i-1列的i-1行得到dp[i][i] = dp[i-1][i-1] + triangle.get(i).get(i);}// 遍历最后一行找最小值for(int i = 0; i < n; i++){res = Math.min(res, dp[n-1][i]);}return res;}
}

1314. 矩阵区域和

给你一个 m x n 的矩阵 mat 和一个整数 k ,请你返回一个矩阵 answer ,其中每个 answer[i][j] 是所有满足下述条件的元素 mat[r][c] 的和:

i - k <= r <= i + k,
j - k <= c <= j + k 且
(r, c) 在矩阵内。

示例一:

输入:mat = [[1,2,3],[4,5,6],[7,8,9]], k = 1
输出:[[12,21,16],[27,45,33],[24,39,28]]

示例二:

输入:mat = [[1,2,3],[4,5,6],[7,8,9]], k = 2
输出:[[45,45,45],[45,45,45],[45,45,45]]

思路:矩阵问题,使用到二维前缀和

存储二维前缀和,P[i][j] 表示数组 mat 中以 (0, 0) 为左上角,(i - 1, j - 1) 为右下角的矩形子数组的元素之和

代码实现:

class Solution {public int[][] matrixBlockSum(int[][] mat, int k) {int m = mat.length, n = mat[0].length;int[][] answer = new int[m][n];int[][] dp = new int[m+1][n+1];  // 存储二维前缀和,P[i][j] 表示数组 mat 中以 (0, 0) 为左上角,(i - 1, j - 1) 为右下角的矩形子数组的元素之和for(int i = 0; i < m; i++){for(int j = 0; j < n; j++){dp[i+1][j+1] = dp[i][j+1] + dp[i+1][j] - dp[i][j] + mat[i][j];}}// 分析边界特殊情况后,计算answerfor(int i = 0; i < m; i++){for(int j = 0; j < n; j++){// 左上角坐标int r1 = Math.max(i-k, 0);int c1 = Math.max(j-k, 0);// 右下角坐标int r2 = Math.min(i+k, m-1);int c2 = Math.min(j+k, n-1);// 计算(r1, c1)到(r2, c2)的元素和answer[i][j] = dp[r2+1][c2+1] - dp[r1][c2+1] - dp[r2+1][c1] + dp[r1][c1];}}return answer;}
}

62. 不同路径

一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。

机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。

问总共有多少条不同的路径?

示例一:

输入:m = 3, n = 7
输出:28

示例二:

输入:m = 3, n = 2
输出:3
解释:
从左上角开始,总共有 3 条路径可以到达右下角。
1. 向右 -> 向下 -> 向下
2. 向下 -> 向下 -> 向右
3. 向下 -> 向右 -> 向下

代码实现:

class Solution {public int uniquePaths(int m, int n) {// 动态规划// dp[i][j]表示从左上角到i,j的不同路径数// dp[i][j] = dp[i - 1][j] + dp[i][j - 1]int[][] dp = new int[m][n];        for (int i = 0; i < m; i++) {for (int j = 0; j < n; j++) {if (i == 0 || j == 0)dp[i][j] = 1;else {dp[i][j] = dp[i - 1][j] + dp[i][j - 1];}}}return dp[m - 1][n - 1];  }
}

63. 不同路径 II

一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。

机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。

现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?


示例一:

输入:obstacleGrid = [[0,0,0],[0,1,0],[0,0,0]]
输出:2
解释:
3x3 网格的正中间有一个障碍物。
从左上角到右下角一共有 2 条不同的路径:
1. 向右 -> 向右 -> 向下 -> 向下
2. 向下 -> 向下 -> 向右 -> 向右

示例二:

输入:obstacleGrid = [[0,1],[0,0]]
输出:1

代码实现:

class Solution {public int uniquePathsWithObstacles(int[][] obstacleGrid) {// 如果网格 (i,j) 上有障碍物,则 dp[i][j] 值为 0,表示走到该格子的方法数为 0;// 否则网格 (i,j) 可以从网格 (i−1,j) 或者 网格 (i,j−1) 走过来,因此走到该格子的方法数为走到网格 (i−1,j)和网格 (i,j−1)的方法数之和,// 即 dp[i,j]=dp[i−1,j]+dp[i,j−1]。int m = obstacleGrid.length, n = obstacleGrid[0].length;int[][] dp = new int[m][n];// 初始化 第一行和第一列for (int i = 0; i < m && obstacleGrid[i][0] == 0; i++) {dp[i][0] = 1;}for (int j = 0; j < n && obstacleGrid[0][j] == 0; j++) {dp[0][j] = 1;}// 根据状态转移方程 dp[i][j] = dp[i - 1][j] + dp[i][j - 1] 进行递推。for (int i = 1; i < m; i++) {for (int j = 1; j < n; j++) {if (obstacleGrid[i][j] == 0) {dp[i][j] = dp[i - 1][j] + dp[i][j - 1];}}}return dp[m-1][n-1];}
}

64. 最小路径和

给定一个包含非负整数的 m x n 网格 grid ,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。

说明:每次只能向下或者向右移动一步。

示例一:

输入:grid = [[1,3,1],[1,5,1],[4,2,1]]
输出:7
解释:因为路径 1→3→1→1→1 的总和最小。

示例二:

输入:grid = [[1,2,3],[4,5,6]]
输出:12

代码实现:

class Solution {public int minPathSum(int[][] grid) {// 动态规划int m = grid.length, n = grid[0].length;int[][] dp = new int[m][n];dp[0][0] = grid[0][0];for(int i = 1; i < m; i++){dp[i][0] = dp[i-1][0] + grid[i][0];}for(int j = 1; j < n; j++){dp[0][j] = dp[0][j-1] + grid[0][j];}for(int i = 1; i < m; i++){for(int j = 1; j < n; j++){dp[i][j] += Math.min(dp[i-1][j], dp[i][j-1]) + grid[i][j];}}return dp[m-1][n-1];}
}

221. 最大正方形

在一个由 ‘0’ 和 ‘1’ 组成的二维矩阵内,找到只包含 ‘1’ 的最大正方形,并返回其面积。

示例一:

输入:matrix = [["1","0","1","0","0"],["1","0","1","1","1"],["1","1","1","1","1"],["1","0","0","1","0"]]
输出:4

示例二:

输入:matrix = [["0","1"],["1","0"]]
输出:1

示例三:

输入:matrix = [["0"]]
输出:0

代码实现:

class Solution {public int maximalSquare(char[][] matrix) {// 动态规划, dp[i][j]表示以i,j为右下角所能构成的最大正方形的边长// 状态转移方程:dp[i][j] = Math.min(Math.min(dp[i - 1][j], dp[i][j - 1]), dp[i - 1][j - 1]) + 1;// 当前位置的边长值等于三个相邻位置的元素的最小值加1// 该位置的值为1int maxSide = 0;int rows = matrix.length, columns = matrix[0].length;int[][] dp = new int[rows][columns];for (int i = 0; i < rows; i++) {for (int j = 0; j < columns; j++) {if (matrix[i][j] == '1') {if (i == 0 || j == 0) {dp[i][j] = 1;} else {dp[i][j] = Math.min(Math.min(dp[i - 1][j], dp[i][j - 1]), dp[i - 1][j - 1]) + 1;}maxSide = Math.max(maxSide, dp[i][j]);}}}int res = 0;res = maxSide * maxSide;return res;}
}

300. 最长递增子序列

给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。

子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。

示例一:

输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。

示例二:

输入:nums = [0,1,0,3,2,3]
输出:4

示例三:

输入:nums = [7,7,7,7,7,7,7]
输出:1

代码实现:

动态规划:

class Solution {public int lengthOfLIS(int[] nums) {// 动态规划int[] dp = new int[nums.length];Arrays.fill(dp, 1);for(int i = 0; i < dp.length; i++){for(int j = 0; j < i; j++){if(nums[i] - nums[j] > 0){dp[i] = Math.max(dp[i], dp[j]+1);}}}int res = 0;for(int i = 0; i < dp.length; i++){res = Math.max(res, dp[i]);}return res;}
}

动态规划 + 二分查找:

class Solution {public int lengthOfLIS(int[] nums) {int[] dp = new int[nums.length];// 动态规划 + 二分查找Arrays.fill(dp, Integer.MAX_VALUE);int res = 0;for(int num : nums){int i = 0, j = nums.length-1;while(i <= j){int mid = (j - i)/2 + i;if(dp[mid] < num) i = mid + 1;else j = mid - 1;}dp[i] = num;if(dp[res] != Integer.MAX_VALUE) res++;}return res;}
}

376. 摆动序列

如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为 摆动序列 。第一个差(如果存在的话)可能是正数或负数。仅有一个元素或者含两个不等元素的序列也视作摆动序列。

例如, [1, 7, 4, 9, 2, 5] 是一个 摆动序列 ,因为差值 (6, -3, 5, -7, 3) 是正负交替出现的。
相反,[1, 4, 7, 2, 5] 和 [1, 7, 4, 5, 5] 不是摆动序列,第一个序列是因为它的前两个差值都是正数,第二个序列是因为它的最后一个差值为零。

子序列 可以通过从原始序列中删除一些(也可以不删除)元素来获得,剩下的元素保持其原始顺序。

给你一个整数数组 nums ,返回 nums 中作为 摆动序列 的 最长子序列的长度 。

示例一:

输入:nums = [1,7,4,9,2,5]
输出:6
解释:整个序列均为摆动序列,各元素之间的差值为 (6, -3, 5, -7, 3) 。

示例二:

输入:nums = [1,17,5,10,13,15,10,5,16,8]
输出:7
解释:这个序列包含几个长度为 7 摆动序列。
其中一个是 [1, 17, 10, 13, 10, 16, 8] ,各元素之间的差值为 (16, -7, 3, -3, 6, -8) 。

示例三:

输入:nums = [1,2,3,4,5,6,7,8,9]
输出:2

代码实现:

动态规划:

class Solution {public int wiggleMaxLength(int[] nums) {// 动态规划,分别统计递增的元素个数和递减的元素个数,比较大小// up[i] 表示以前 i 个元素中的某一个为结尾的最长的「上升摆动序列」的长度。// down[i]表示以前 i 个元素中的某一个为结尾的最长的「下降摆动序列」的长度。int n = nums.length;if(n < 2) return n;// 原始动态规划int[] up = new int[n];int[] down = new int[n];up[0] = down[0] = 1;for (int i = 1; i < n; i++) {if (nums[i] > nums[i - 1]) {up[i] = Math.max(up[i - 1], down[i - 1] + 1);down[i] = down[i - 1];} else if (nums[i] < nums[i - 1]) {up[i] = up[i - 1];down[i] = Math.max(up[i - 1] + 1, down[i - 1]);} else {up[i] = up[i - 1];down[i] = down[i - 1];}}return Math.max(up[n - 1], down[n - 1]);}
}

优化后的动态规划:

class Solution {public int wiggleMaxLength(int[] nums) {// 动态规划,分别统计递增的元素个数和递减的元素个数,比较大小// up[i] 表示以前 i 个元素中的某一个为结尾的最长的「上升摆动序列」的长度。// down[i]表示以前 i 个元素中的某一个为结尾的最长的「下降摆动序列」的长度。int n = nums.length;if(n < 2) return n;// 优化动态规划,直接用up和down减少变量存储int up = 1, down = 1;for(int i = 1; i < n; i++){if(nums[i] > nums[i-1]) {up = down + 1;}if(nums[i] < nums[i-1]) {down = up + 1;}}return Math.max(up, down);}
}

贪心算法:

class Solution {public int wiggleMaxLength(int[] nums) {// 动态规划,分别统计递增的元素个数和递减的元素个数,比较大小// up[i] 表示以前 i 个元素中的某一个为结尾的最长的「上升摆动序列」的长度。// down[i]表示以前 i 个元素中的某一个为结尾的最长的「下降摆动序列」的长度。int n = nums.length;if(n < 2) return n;// 贪心算法,保持区间波动,只需要把单调区间上的元素移除就可以了// 当前差值int curDiff = 0;// 上一个插值int preDiff = 0;int count = 1;for(int i = 1; i < n; i++){// 得到当前差值curDiff = nums[i] - nums[i-1];//如果当前差值和上一个差值为一正一负//等于0的情况表示初始时的preDiff,将当前差值赋值给前差值,继续判断if ((curDiff > 0 && preDiff <= 0) || (curDiff < 0 && preDiff >= 0)) {count++;preDiff = curDiff;}}return count;}
}

Java动态规划算法从入门的到熟练相关推荐

  1. 辛星Java动态规划算法教程汇总【刷题用】

    线性动规 第一篇: 菲波那切数列 https://blog.csdn.net/xinguimeng/article/details/89364875 第二篇:最长公共子序列(LCS),时间复杂度为O( ...

  2. Java实现算法竞赛入门经典例题-蚂蚁

    问题描述 一根长度为L厘米的木棍上有n只蚂蚁,每只蚂蚁要么朝左爬,要么朝右爬,速度为1厘米/秒. 当两只蚂蚁相撞时,二者同时掉头(掉头时间忽略不计). 给出每只蚂蚁的初始位置和朝向,计算T秒之后每只蚂 ...

  3. 动态规划算法入门---java版

    转载:http://blog.csdn.net/p10010/article/details/50196211 动态规划算法(后附常见动态规划为题及Java代码实现) 一.基本概念 动态规划过程是:每 ...

  4. java 动态规划视频_157-动态规划算法解决背包问题1

    2.网上数据结构和算法的课程不少,但存在两个问题: 1)授课方式单一,大多是照着代码念一遍,数据结构和算法本身就比较难理解,对基础好的学员来说,还好一点,对基础不好的学生来说,基本上就是听天书了 2) ...

  5. java数据结构-动态规划算法-一次性学会

    java数据结构-动态规划算法 算法介绍 最佳实践-背包问题 代码实践 寻找最长回文子串的求解 总结 算法介绍 动态规划算法的核心思想是:将大问题划分为小问题,一步步获取最优解 与分治算法类似,但也有 ...

  6. Rosalind Java|Longest Increasing Subsequence动态规划算法

    Rosalind编程问题之计算集合中最长的递增元素子集. Longest Increasing Subsequence Problem: A subsequence of a permutation ...

  7. java实现动态规划算法解决存钱罐问题(piggy bank)

    一.实验目的 练习使用动态规划算法解决实际问题(使用Java语言实现) 二.实验内容 [问题描述] 给定一个空存钱罐的重量和这个存钱罐最多能装进去的重量,现在需要在不打破这个存钱罐的情况下猜测里面最少 ...

  8. 详解:动态规划算法【Java实现】——背包问题

    动态规划 动态规划算法介绍 动态规划算法最佳实践-背包问题 思路分析: 图解分析: ​Java代码实现: 动态规划算法介绍 1)动态规划(Dynamic Programming)算法的核心思想是:将大 ...

  9. Java使用动态规划算法思想解决01背包问题

    Java使用动态规划算法思想解决背包问题 背包问题是一种组合优化的NP完全问题.问题可以描述为:给定一组物品,每种物品都有自己的重量和价格,在限定的总重量内,我们如何选择,才能使得物品的总价格最高 动 ...

最新文章

  1. hdu 3416(最短路+最大流)
  2. ubuntu下安装jre的步骤
  3. maven 加入第三方库_关于maven,你还要翻阅多少资料才能整理出这一份完整文档...
  4. mysql存储引擎 索引优化_MySQL存储引擎,索引及基本优化策略
  5. pythonassert关键字_Python assert 关键字
  6. 并不对劲的AC自动机
  7. 微型计算机外观分为,2015计算机应用基础单选练习题1.1
  8. 微搭低代码入门教程02
  9. 艺龙深耕酒店VS携程 布局旅游全产业
  10. 解决oracle 报 ORA-20000(ORU-10027)错误的方法
  11. 域名邮箱什么,如何开通自定义邮箱后缀的邮箱?
  12. 腾讯位置服务仿微信发送位置功能
  13. 线性代数系列(十一)--正交矩阵和正交化
  14. 人工智能背后的“人工”: 数据标注时薪缩水一半,欠薪高发
  15. 如何用PS切图和输出网页?
  16. 怎么判断两个对象相等
  17. XP系统测试显示器软件在哪,WinXP系统下如何检测显示器白点
  18. 倾角传感器在倾斜稳定性测量中的应用
  19. c++模板声明与定义
  20. 【Typora】This beta version of Typora is expired, please download and install a newer version.

热门文章

  1. html语言的网页带超链接的,HTML语言与网页设计 ——超链接.ppt
  2. apk加固之360加固
  3. [知识小节]Windbg常用指令(笔记本)
  4. antd Image 组件在线上环境 抽屉组件里面 预览时不展示顶部工具栏和其他icon 解决方案 附带官方解决地址
  5. 消费类电子产品相关接口技术的发展
  6. 基于A*算法的迷宫小游戏
  7. Kafka启动成功且运行程序无报错,无法消费数据,即外网无法连接Kafka的消费者或生产者
  8. elementUI时间轴
  9. 财务软件提示服务器无响应,但是财务软件再也进不了,怎么办?
  10. 国产分布式事务数据库在金融领域的实战经验