目录
121 买卖股票的最佳时机
打家劫舍
62 不同路径
64 最小路径和
53 最大子数组和 (动归 普通数组部分)
152 乘积最大子数组
300 最长递增子序列
1143 最长公共子序列
72 编辑距离
121 买卖股票的最佳时机
给定一个数组 prices
,它的第 i
个元素 prices[i]
表示一支给定股票第 i
天的价格。
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。
class Solution {public int maxProfit(int[] prices) {int len = prices.length;// dp[i][0]代表第i天持有股票的最大收益// dp[i][1]代表第i天不持有股票的最大收益int[][] dp = new int[len][2];//第一天持有股票,只能是第一天购入,收益为-pricesdp[0][0] = -prices[0];//第一天不持有股票,既没有购买,收益为0dp[0][1] = 0;for (int i = 1; i < len; i++) {//第i天持有,可能是前一天购入,或当天购入dp[i][0] = Math.max(dp[i - 1][0], -prices[i]);//第i天不持有,可能是前一天持有,今天售出或前一天也不持有(保持现状)dp[i][1] = Math.max(dp[i - 1][0] + prices[i], dp[i - 1][1]);}//本题中不持有股票状态所得金钱一定比持有股票状态得到的多!return dp[len - 1][1];}
}
打家劫舍
如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
class Solution {public int rob(int[] nums) {// 考虑下标 i(包括 i)以内的房屋,最多可以偷窃的金额为 dp[i]。// 如果偷第i房间,那么dp[i] = dp[i - 2] + nums[i] 因为求的是能偷的最高金额,因此尽量不走空// 如果不偷第i房间,那么dp[i] = dp[i - 1] (只是考虑偷i-1不一定真的偷)// 然后dp[i]取最大值,即dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);// dp[0] 一定是 nums[0],dp[1]就是nums[0]和nums[1]的最大值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]);for (int i = 2; i < nums.length; i++) {dp[i] = Math.max(dp[i - 2] + nums[i], dp[i - 1]);}return dp[nums.length - 1]; //数组下标从0开始}}
62 不同路径
一个机器人位于一个 m x n网格的左上角 (起始点在下图中标记为 “Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。
问总共有多少条不同的路径?
class Solution {public int uniquePaths(int m, int n) {// dp[i][j] :表示从(0 ,0)出发,到(i, j) 有dp[i][j]条不同的路径。// dp[i][j] = dp[i - 1][j] + dp[i][j - 1]// dp[i][0],dp[0,j]一定都是1int[][] dp = new int[m][n];// 初始化for (int i = 0; i < m; i++) {dp[i][0] = 1;}for (int i = 0; i < n; i++) {dp[0][i] = 1;}for (int i = 1; i < m; i++) {for (int j = 1; j < n; j++) {dp[i][j] = dp[i - 1][j] + dp[i][j - 1];}}return dp[m - 1][n - 1];}
}
64 最小路径和
给定一个包含非负整数的 m x n 网格 grid ,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。
说明:每次只能向下或者向右移动一步。
不使用备忘录计数,更好理解
class Solution {public int minPathSum(int[][] grid) {int m = grid.length;int n = grid[0].length;int[][] dp = new int[m][n];dp[0][0] = grid[0][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++) {dp[i][0] = dp[i - 1][0] + grid[i][0];}// 填充剩余格子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];}
}
53 最大子数组和 (动归 普通数组部分)
子数组是数组中的一个连续部分。
动规解法
class Solution {public int maxSubArray(int[] nums) {int n = nums.length;if (n == 0) return 0;int[] dp = new int[n];// base case// 第一个元素前面没有子数组dp[0] = nums[0];// 状态转移方程for (int i = 1; i < n; i++) {dp[i] = Math.max(nums[i], nums[i] + dp[i - 1]);}// 得到 nums 的最大子数组int res = Integer.MIN_VALUE;for (int i = 0; i < n; i++) {res = Math.max(res, dp[i]);}return res;}
}
152 乘积最大子数组
dp 数组是:dp[i] 记录以 nums[i] 为结尾的「最大子数组和」,从而写出状态转移方程。
这道题和 上一题的最大区别在于,要同时维护「以 nums[i] 结尾的最大子数组」和「以 nums[i] 结尾的最小子数组」,以便适配 nums[i] 可能为负的情况。
class Solution {public int maxProduct(int[] nums) {int n = nums.length;// 定义:以 nums[i] 结尾的子数组,乘积最小为 dp1[i]int[] dp1 = new int[n];// 定义:以 nums[i] 结尾的子数组,乘积最大为 dp2[i]int[] dp2 = new int[n];// base casedp1[0] = nums[0];dp2[0] = nums[0];// 状态转移方程 需要和 nums[i]比较 以nums[i]为结尾的数组//单独的 nums[i] 可能作为一个新的子数组。for (int i = 1; i < n; i++) {dp1[i] = min(dp1[i - 1] * nums[i], dp2[i - 1] * nums[i], nums[i]);dp2[i] = max(dp1[i - 1] * nums[i], dp2[i - 1] * nums[i], nums[i]); }// 遍历所有子数组的最大乘积,求最大值int res = Integer.MIN_VALUE;for (int i = 0; i < n; i++) {res = Math.max(res, dp2[i]);}return res;}int min(int a, int b, int c) {return Math.min(Math.min(a, b), c);}int max(int a, int b, int c) {return Math.max(Math.max(a, b), c);}
}
300 最长递增子序列
子数组一定是连续的,而子序列不一定是连续的。
子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。
dp 数组的定义:dp[i] 表示以 nums[i] 这个数结尾的最长递增子序列的长度。
那么 dp 数组中最大的那个值就是最长的递增子序列长度。
子数组一定是连续的,而子序列不一定是连续的。
class Solution {public int lengthOfLIS(int[] nums) {// dp[i] 表示以 nums[i] 这个数结尾的最长递增子序列的长度int[] dp = new int[nums.length];// base case:dp 数组全都初始化为 1Arrays.fill(dp, 1);//快慢指针for (int i = 0; i < nums.length; i++) {for (int j = 0; j < i; j++) {if (nums[i] > nums[j])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;}
}
1143 最长公共子序列
给定两个字符串 text1 和 text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0 。
- dp[i][j]:长度为[0, i - 1]的字符串text1与长度为[0, j - 1]的字符串text2的最长公共子序列为dp[i][j]
- 两种情况:text1[i - 1] 与 text2[j - 1]相同,text1[i - 1] 与 text2[j - 1]不相同
-
- 如果text1[i - 1] 与 text2[j - 1]相同,那么找到了一个公共元素,所以 dp[i][j] = dp[i - 1][j - 1] + 1;
- 如果text1[i - 1] 与 text2[j - 1]不相同,那就看看text1[0, i - 2]与text2[0, j - 1]的最长公共子序列 和 text1[0, i - 1]与text2[0, j - 2]的最长公共子序列,取最大的dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
- dp 数组的第0行和第0列用于表示空字符串的情况
class Solution {public int longestCommonSubsequence(String text1, String text2) { //dp 数组的第0行和第0列用于表示空字符串的情况int[][] dp = new int[text1.length() + 1][text2.length() + 1];for (int i = 1; i <= text1.length(); i++) {for (int j = 1; j <= text2.length(); j++) {char char1 = text1.charAt(i - 1); //当 dp 数组的索引为 i 和 j 时,我们实际上是在处理 text1 的第 i-1 个字符和 text2 的第 j-1 个字符char char2 = text2.charAt(j - 1);if (char1 == char2) {dp[i][j] = dp[i - 1][j - 1] + 1;} else {dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);}}}return dp[text1.length()][text2.length()];}
}
72 编辑距离
给你两个单词 word1 和 word2, 请返回将 word1 转换成 word2 所使用的最少操作数 。
你可以对一个单词进行如下三种操作:
- 插入一个字符
- 删除一个字符
- 替换一个字符
dp[i][j] 表示以下标i-1为结尾的字符串word1,和以下标j-1为结尾的字符串word2,最近编辑距离为dp[i][j]。
- if (word1[i - 1] == word2[j - 1]) 那么说明不用任何编辑,dp[i][j] 就应该是 dp[i - 1][j - 1],即dp[i][j] = dp[i - 1][j - 1];
- if (word1[i - 1] != word2[j - 1]),此时就需要编辑了,如何编辑呢?
- 操作一:word1删除一个元素,那么就是以下标i - 2为结尾的word1 与 j-1为结尾的word2的最近编辑距离 再加上一个操作。
即 dp[i][j] = dp[i - 1][j] + 1;
- 操作二:word2删除一个元素,那么就是以下标i - 1为结尾的word1 与 j-2为结尾的word2的最近编辑距离 再加上一个操作。
即 dp[i][j] = dp[i][j - 1] + 1;
- 操作三:替换元素,word1替换word1[i - 1],使其与word2[j - 1]相同,此时不用增删加元素。
可以回顾一下,if (word1[i - 1] == word2[j - 1])的时候我们的操作 是 dp[i][j] = dp[i - 1][j - 1] 对吧。
那么只需要一次替换的操作,就可以让 word1[i - 1] 和 word2[j - 1] 相同。
所以 dp[i][j] = dp[i - 1][j - 1] + 1;
class Solution {public int minDistance(String s1, String s2) {int m = s1.length(), n = s2.length();int[][] dp = new int[m + 1][n + 1];// base case 有一个里面没元素for (int i = 1; i <= m; i++) //对word1里的元素全部做删除操作dp[i][0] = i;for (int j = 1; j <= n; j++)dp[0][j] = j;// 自底向上求解for (int i = 1; i <= m; i++)for (int j = 1; j <= n; j++)// 因为dp数组有效位从1开始// 所以当前遍历到的字符串的位置为i-1 | j-1if (s1.charAt(i - 1) == s2.charAt(j - 1))dp[i][j] = dp[i - 1][j - 1];elsedp[i][j] = min(dp[i - 1][j] + 1,//word1删除dp[i][j - 1] + 1,//word2删除dp[i - 1][j - 1] + 1//替换);// 储存着整个 s1 和 s2 的最小编辑距离return dp[m][n];}int min(int a, int b, int c) {return Math.min(a, Math.min(b, c));}
}