子序列问题
子序列问题分类:
- 只涉及一个子串。
- 当前状态只和上一个状态有关,即 dp[i] 只和 dp[i-1] 状态有关。(53题)
- 当前状态和前面所有状态有关,即 dp[i] 和 dp[0-(i-1)] 状态有关。(300题)
-
53-最大子序列

题意分析: 判断子数组连续元素和最大。
解题思路:
- 动态规划。dp[i] 状态的结果一定是以 nums[i] 为结束元素的值。
注意点:
拓展:
- 如何输出和最大的连续子数组?
代码:
public int maxSubArray(int[] nums) {int n = nums.length;// 状态数组。nums[1...i] 以 nums[i] 结尾的子数组的最大和int[] dp = new int[n];// base casedp[0] = nums[0];// 状态转换for (int i = 1; i < n; i++) {dp[i] = Math.max(nums[i], dp[i-1] + nums[i]);}// 求解int max_sum = Integer.MIN_VALUE;for (int i = 0; i < n; i++) {if (dp[i] > max_sum) {max_sum = dp[i];}}return max_sum;}
300-最长递增子序列(动规/动规+二分/贪心+二分)

题意分析:
- 从数组中找出最长递增的一串数字。
解题思路:
- 动态规划。当前状态 dp[i] 依赖前面的所有状态。
- 动规+二分 / 贪心 + 二分。 https://leetcode-cn.com/problems/longest-increasing-subsequence/solution/zui-chang-shang-sheng-zi-xu-lie-by-leetcode-soluti/
注意点:
拓展:
- 如何输出最长递增子序列?
代码:
public int lengthOfLIS(int[] nums) {int n = nums.length;// 状态数组int[] dp = new int[n];// base caseArrays.fill(dp, 1);// 状态转换for (int i = 0; i < n; i++) {for (int j = 0; j < i; j++) {if (nums[i] > nums[j]) {// i 会不断更新,dp[i] 会存在覆盖的情况dp[i] = Math.max(dp[j] + 1, dp[i]);}}}// System.out.println(Arrays.toString(dp));// 求数组最大值等价于:ans = Arrays.stream(dp).max().getAsInt();int ans = 0;for (int i = 0; i < n; i++) {ans = Math.max(ans, dp[i]);}return ans;}
股票买卖问题
买卖股票问题(动规)
动态规划问题三部曲:
- 状态数组及状态转换(备忘录)(难点)。
- base case。就是刚开始时的特殊情况。
- 求解。
总体分为三类:
- 只允许交易一次。
- 允许交易无限次,含冷冻期,含手续费。
- 允许交易 k 次(最难)。
股票操作:买入、卖出、无操作。
股票状态:不持有、持有
状态数组定义:
- 只有一次交易或者没有限制交易次数的状态数组:
dp[i][0/1]:第 i 天不持有/持有股票的最大利润。
- 允许进行 k 次交易的状态数组:
dp[i][k][0/1]:第 i 天最多还能进行 k 次交易时不持有/持有股票的最大利润。
拓展:
- 如何对状态数组进行状态压缩?
121-买卖股票的最佳时机

代码:
public int maxProfit(int[] prices) {int len = prices.length;if (len < 2) {return 0;}int[][] dp = new int[len][2];// base casedp[0][0] = 0;dp[0][1] = -prices[0];//状态转换。只允许交易一次,说明只会有某一天持有for(int i = 1; i < len; i++) {dp[i][0] = Math.max(dp[i-1][0], dp[i-1][1] + prices[i]);// 特殊情况,只进行一次交易,如果持有一定是当天买的。dp[i][1] = Math.max(- prices[i], dp[i-1][1]);// System.out.println(i + ": dp[i][0]=" + dp[i][0] + "; dp[i][1]= " + dp[i][1]);}// 求解return dp[len - 1][0];}
122-买卖股票的最佳时机 II

代码:
public int maxProfit(int[] prices) {int len = prices.length;if (len < 2) {return 0;}int[][] dp = new int[len][2];// base casedp[0][0] = 0;dp[0][1] = -prices[0];// 状态转换。允许交易多次,说明持有天数没限制for (int i = 1; i < len; i++) {dp[i][0] = Math.max(dp[i-1][0], dp[i-1][1] + prices[i]);dp[i][1] = Math.max(dp[i-1][0] - prices[i], dp[i-1][1]);// System.out.println(i + ": dp[i][0]=" + dp[i][0] + "; dp[i][1]= " + dp[i][1]);}// 求解return dp[len-1][0];}
123-买卖股票的最佳时机 III

代码:
public int maxProfit(int[] prices) {int len = prices.length;if (len < 2) {return 0;}int max_k = 2;int[][][] dp = new int[len][max_k+1][2];// base case// dp[i][0][0] = 0for (int i = 0; i < len; i++) {dp[i][0][0] = 0;}// dp[i][0][1] = Interger.minvalfor (int i = 0; i < len; i++) {dp[i][0][1] = Integer.MIN_VALUE;}// dp[0][k][0] = 0for (int k = 0; k <= max_k; k++) {dp[0][k][0] = 0;}// dp[0][k][1] = Integer.minval// for (int k = 0; k <= max_k; k++) {// dp[0][k][1] = prices[0];// }dp[0][0][1] = Integer.MIN_VALUE;dp[0][1][1] = - prices[0];dp[0][2][1] = - prices[0];// dp 状态转换for (int i = 1; i < len; i++) {for (int k = 1; k <= max_k; k++) {// 第2种情况:前面最多交易 k 次,这次卖出(一次完整交易)不计入交易次数dp[i][k][0] = Math.max(dp[i-1][k][0], dp[i-1][k][1] + prices[i]);// 第2种情况:前面最多交易 k-1 次,加上这次买入dp[i][k][1] = Math.max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i]);// System.out.println("i = " + i + "; k = " + k + ": dp[i][k][0]=" + dp[i][k][0] + "; dp[i][k][1]= " + dp[i][k][1]);}}// 求解。最后一天,最多交易 k 次后不持有股票的最大利润。return dp[len-1][max_k][0];}
188-买卖股票的最佳时机 IV

代码:
public int maxProfit(int k, int[] prices) {int len = prices.length;if (len < 2) {return 0;}// 这里 k 其实是有上限交易次数的int max_k = Math.min(k, len / 2);int[][][] dp = new int[len][max_k+1][2];// base case// dp[i][0][0] = 0for (int i = 0; i < len; i++) {dp[i][0][0] = 0;}// dp[i][0][1] = Interger.minvalfor (int i = 0; i < len; i++) {dp[i][0][1] = Integer.MIN_VALUE;}// dp[0][k][0] = 0for (int ki = 0; ki <= max_k; ki++) {dp[0][ki][0] = 0;}dp[0][0][1] = Integer.MIN_VALUE;for (int ki = 1; ki <= max_k; ki++)dp[0][ki][1] = - prices[0];// dp 状态转换for (int i = 1; i < len; i++) {for (int ki = 1; ki <= max_k; ki++) {dp[i][ki][0] = Math.max(dp[i-1][ki][0], dp[i-1][ki][1] + prices[i]);dp[i][ki][1] = Math.max(dp[i-1][ki][1], dp[i-1][ki-1][0] - prices[i]);System.out.println("i = " + i + "; ki = " + ki + ": dp[i][ki][0]=" + dp[i][ki][0] + "; dp[i][ki][1]= " + dp[i][ki][1]);}}// 求解return dp[len-1][max_k][0];}
309-最佳买卖股票时机含冷冻期

代码:
public int maxProfit(int[] prices) {int len = prices.length;if (len < 2) {return 0;}int[][] dp = new int[len][2];// base casedp[0][0] = 0;dp[0][1] = -prices[0];// 状态转换for (int i = 1; i < len; i++) {dp[i][0] = Math.max(dp[i-1][0], dp[i-1][1] + prices[i]);if (i == 1 ) {// base case. dp[-1][1] 下标不存在,i 天购买,利润为 -prices[i]dp[i][1] = Math.max(-prices[i], dp[i-1][1]);} else {// 当天进行交易的前一天冷冻dp[i][1] = Math.max(dp[i - 2][0] - prices[i], dp[i - 1][1]);}System.out.println(i + ": dp[i][0]=" + dp[i][0] + "; dp[i][1]= " + dp[i][1]);}// 求解return dp[len-1][0];}
714-买卖股票的最佳时机含手续费

代码:
public int maxProfit(int[] prices, int fee) {int len = prices.length;if (len < 2) {return 0;}int[][] dp = new int[len][2];// base casedp[0][0] = 0;dp[0][1] = -prices[0] - fee;// 状态转换for (int i = 1; i < len; i++) {dp[i][0] = Math.max(dp[i-1][0], dp[i-1][1] + prices[i]);dp[i][1] = Math.max(dp[i-1][0] - prices[i] - fee, dp[i-1][1]);// System.out.println(i + ": dp[i][0]=" + dp[i][0] + "; dp[i][1]= " + dp[i][1]);}// 求解return dp[len-1][0];}
路径问题(动规)
62-不同路径

题意分析:
- m*n 的矩阵中,从左上角走到右下角有多少种走法。
解题思路:
- 动态规划。dp[i][j] 结果和 dp[i-1][j] / dp[i][j-1] 状态的关系。
注意点:
扩展:
代码:
/*** 时间复杂度 O(m*n)* 空间复杂读 O(m*n)* 借助状态数组,增加空间复杂度。** @param m* @param n* @return*/public int uniquePaths(int m, int n) {// 状态数组int[][] dp = new int[m][n];// base casedp[0][0] = 0;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];System.out.println(i + "-" + j + ": dp[i][j] = " + dp[i][j]);}}return dp[m-1][n-1];}
63-不同路径 II


题意分析:
- 加上不可达节点时的走法。
解题思路:
- 动态规划。
注意点:
- 初始化时要考虑障碍的初始值。
扩展:
代码:
public int uniquePathsWithObstacles(int[][] obstacleGrid) {int m = obstacleGrid.length, n = obstacleGrid[0].length;// 状态数组。(数组初始化时值都为0)int[][] dp = new int[m][n];for (int i = 0; i < m; i++) {System.out.println(Arrays.toString(dp[i]));}// base case.// 这里的 && 判断很特殊,如果不满足直接退出 for 循环。// for 循环改成 while 逻辑就好理解,相当于 while 循环直接 break 了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;}// 状态转移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];// System.out.println(i + "-" + j + ": dp[i][j] = " + dp[i][j]);}}// 求解return dp[m-1][n-1];}
64-最小路径和

题意分析:
- 无非就是把不同路径改成计算路径上节点的和,本质一样。
解题思路:
- 动态规划。
注意点:
- 和“不同路径”有点不一样,“不同路径”问题第一行/列的 base case 都是 1,base case 初始化时时要计算节点值的。
扩展:
代码:
public int minPathSum(int[][] grid) {int m = grid.length, n = grid[0].length;// 状态数组int[][] dp = new int[m][n];// base case,初始化时是要计算值的。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++) {// 注意这里 +grid[i][j] 的值是都要加上的dp[i][j] = Math.min(dp[i-1][j], dp[i][j-1]) + grid[i][j];}}// 求解return dp[m-1][n-1];}
贪心问题
55-跳跃游戏

题意分析:
- 从数组下表开始跳跃,判断最终是否能够跳跃到数组最后一个节点。
解题思路:
- 贪心思路。每次找到当前位置可跳跃的最远长度,如果存在某个节点能够跳跃到最后,则直接返回 true。
- 时间复杂度:O(n)。最坏情况下需要遍历数组所有元素。
- 空间复杂度:O(1)。需要常量元素记录跳跃的最长距离。
- 动态规划。动态规划的核心就在dp状态数据的构建和转换过程的转换,动规分为只依赖前一个dp状态和依赖前面所有dp状态,本题属于后者。当前节点能否跳跃到最后节点,需要判断前面所有节点是否存在能够跳跃到最后节点的。
- 时间复杂度:O(nlog(n))。每个位置的dp数组依赖前面所有位置的dp信息。
- 空间复杂度:O(n)。需要额外的dp数组记录是否能够跳跃到最后节点。
注意点:
- 如何将第一种方法理解为贪心算法?
扩展:
代码:
/*** 贪心算法,每次找到当前位置能 jump 的最大距离* @param nums* @return*/public boolean canJump(int[] nums) {int n = nums.length;int fastest = 0;for (int i = 0; i < n - 1; i++) {fastest = Math.max(fastest, i + nums[i]);System.out.println("fastest = " + fastest + "; i = " + i);if (fastest <= i) return false;}return fastest >= n - 1;}/*** 动态规划。* 1、dp 数组。记录每个位置能否到达最后位置。* 2、状态转换。什么情况下会触发状态转换。* 3、base case。* 4、求解。dp[len - 1]* @param nums* @return*/public boolean canJump(int[] nums) {int n = nums.length;// 状态数组。记录每个节点是否能够跳跃到最后节点boolean[] dp = new boolean[n];// base casedp[0] = true;for (int i = 1; i < n; i++) {// 判断从 dp[0~i-1] 中能够跳跃到最后节点的节点开始(即当前状态和前面多个状态有关)for (int j = 0; j < i; j++) {// 判断前面所有位置时,一个条件是前面位置能够到达,另一个条件是能跳过当前节点if (dp[j] && j + nums[j] >= i) {dp[i] = true;break;}}// System.out.println("i = " + i + "; dp[i] = " + dp[i]);}// 求解(动规需要遍历完整个数组)return dp[n - 1];}
*45-跳跃游戏 II

题意分析:
解题思路:
- 贪心算法。每个更新跳跃区间,区间更新即为一次 jump,直到区间中包含最后一个位置(索引),表示该区间是上一跳的最远距离,此时结束即可。
- 优化后的贪心。每次只更远跳跃的最远距离(即只保留右区间),如果抵达了右区间,则更新最区间距离。
注意点:
扩展:
代码:
/*** 贪心算法:每次更新跳跃区间* @param nums* @return*/public int jump(int[] nums) {int n = nums.length;int start = 0, end = 0; // [0,0]int fastest = 0;int step = 0;while (end < n - 1) {// 每次能跳跃的区间for (int i = start; i <= end; i++) {fastest = Math.max(fastest, i + nums[i]);}// 更新区间左右值start = end + 1;end = fastest;step++;System.out.println("start = " + start + "; end = " + end);}return step;}/*** 优化后的贪心算法,每次更新跳跃到最远距离* @param nums* @return*/public int jump2(int[] nums) {int n = nums.length;int fastest = 0;// 跳跃最远位置的 indexint end = 0;int step = 0;for (int i = 0; i < n - 1; i++) {// 每次能跳跃的最远距离fastest = Math.max(fastest, i + nums[i]);// 到达跳跃最远距离的边界if (end == i) {end = fastest;step++;}System.out.println("i = " + i + "; fastest = " + fastest + "; end = " + end + "; step = " + step);}return step;}
134-加油站

题意分析:
- 从某个位置出发,根据加油和耗油判断能否回到远点。
解题思路:
- 暴力解法。直接判断每个节点能否回到远点。
- 时间复杂度:O(n^2)
- 贪心解法。转换为一维数组,判断从某个位置出发,走到的任何节点油箱容量均大于等于0。
注意点:
扩展:
代码:
/*** 暴力破解:* 遍历每个 gas[i] >= cost[i] 的 gas,判断是否能够回到原点。* @param gas* @param cost* @return*/public int canCompleteCircuit2(int[] gas, int[] cost) {int n = gas.length;for (int start = 0; start < n; start++) {if (gas[start] < cost[start]) {continue;}// 每次从新的节点出发油箱容量 tank 均为0int tank = 0;for (int step = 0; step < n; step++) {// 表示每次从 start 节点出发int i = (start + step) % n;tank += gas[i];tank -= cost[i];if (tank < 0) break;}if (tank >= 0) return start;}return -1;}/*** 贪心解法* 1、把 gas[i] - cost[i] 组成一个新的数组,* 目标是找到一个起点,出发一圈后回到起点的过程中油箱容量 tank 一直大于等于0。* gas=[1,2,3,4,5], cost=[3,4,5,1,2] 问题转换为 gap = [-2, -2, -2, 3, 3] 问题。* 2、如果从 i 走到 j 出现 tank < 0,那么从 [i,j] 之间任意一个节点出发 tank 都小于 0。* @param gas* @param cost* @return*/public int canCompleteCircuit(int[] gas, int[] cost) {int n = gas.length;int[] gap = new int[n];int sum = 0;for (int i = 0; i < n; i++) {gap[i] = gas[i] - cost[i];sum += gap[i];}if (sum < 0) {return -1;}System.out.println(Arrays.toString(gap));int tank = 0, start = 0;for (int i = 0; i < n; i++) {tank += gap[i];// i 走到 j 不满足条件,那么 [i,j] 直接都不满足if (tank < 0) {tank = 0;start = i + 1;}}return start;}
区间问题(贪心)
435-无重叠区间(贪心+排序/动态+二分)

题意分析:
- 求出最大无重复区间,然后剩下的就是重复区间。
解题思路:
- 贪心算法。贪心算法求区间问题每次都是先对区间右边界排序,然后比较当前左区间与上一区间的右边界,贪心之处就在于之比较最大值(或最远值,比如跳跃游戏)。
- 动态规划。动态规划求区间问题需要先对左区间排序,然后比较当前区间左边界与上一区间右边界是否重叠,逐一求每个区间的最大无重叠区间。
注意点:
扩展:
代码:
/*** 贪心思想。* 1、先对区间右边界 end 升序排序* 2、比较当前区间右边界与下一区间的左边界,不重叠则不重叠区间+1* @param intervals* @return*/public int eraseOverlapIntervals(int[][] intervals) {Arrays.sort(intervals, new Comparator<int[]>() {@Overridepublic int compare(int[] o1, int[] o2) {return o1[1] - o2[1];}});for (int i = 0; i < intervals.length; i++) {System.out.println(Arrays.toString(intervals[i]));}int ans = 1;int end = intervals[0][1];for (int i = 1; i < intervals.length; i++) {// start >= endif (intervals[i][0] >= end) {ans++;end = intervals[i][1];}}return intervals.length - ans;}/*** 动态规划(超出时间限制)* 1、先按区间 start 排序(为了方便当前区间 start 与上一区间 end 判断)* 2、dp[i]:每个一元数组的最大无重叠子区间* 3、当前状态与前面多个状态均有关* @param intervals* @return*/public int eraseOverlapIntervals2(int[][] intervals) {int n = intervals.length;// 状态数据int[] dp = new int[n];// base case:默认都有一个无重叠区间Arrays.fill(dp, 1);Arrays.sort(intervals, new Comparator<int[]>() {@Overridepublic int compare(int[] o1, int[] o2) {return o1[0] - o2[0];}});for (int i = 0; i < intervals.length; i++) {System.out.println(Arrays.toString(intervals[i]));}// 状态转换for (int i = 0; i < n; i++) {// dp 数组和前面多个状态有关for (int j = 0; j < i; j++) {// 更新重叠子区间条件判断if (intervals[i][0] >= intervals[j][1]) {dp[i] = Math.max(dp[i], dp[j] + 1);}}}// 求解return n - Arrays.stream(dp).max().getAsInt();}
452-用最少数量的箭引爆气球-无重复区间(贪心+排序)

题意分析:
- 重复区间用一支箭射穿即可,说明问题是要求无重复区间的数量。
解题思路:
- 贪心+排序。问题本质就是求最大无重复区间的数量,每个无重复区间用一支箭射穿即可。
注意点:
- 排序时直接返回 o1[1] - o2[1] 时要考虑减法的结果是否超过 INT 限制,会导致排序结果混乱。
扩展:
代码:
/*** 贪心+排序。* 问题本质就是求最大无重复区间的数量,每个无重复区间用一支箭射穿即可。* @param points* @return*/public int findMinArrowShots(int[][] points) {int n = points.length;// 按区间end升序排序Arrays.sort(points, new Comparator<int[]>() {@Overridepublic int compare(int[] o1, int[] o2) {// 排序时直接返回 o1[1]-o2[1] 要考虑返回结果是否超出INT限制if (o1[1] > o2[1])return 1;else if (o1[1] < o2[1])return -1;elsereturn 0;}});int end = points[0][1];int ans = 1;for (int i = 1; i < n; i++) {// 边界如果相等则可以处理两个区间,所以这里不用等于号if (points[i][0] > end) {ans++;end = points[i][1];}}return ans;}
1024-视频拼接(贪心+排序)

题意分析:
- 选取最少的区间数,使得区间完全包含 [0,T] 。
解题思路:
- 贪心+排序。[0,1], [0,5], [0,9] 按照贪心策略,肯定是优先选择 [0,9] 这个大区间,才可能使得最终选择的区间数最少(这便是贪心最有选择策略)。所以这里需要先对数组排序,先按 start 升序排序,再按 end 降序排序。然后判断区间的包含关系更新区间。
注意点:
- 要注意这里区间可能划分小了,比如 [0,4], [2,6], [4,7],两个区间能解决的事就不用三个区间解决,因为还要判断上个区间的结束 pre_end 值,便于更新区间数统计。
扩展:
- 能否打印出对应的区间?
代码:
public int videoStitching(int[][] clips, int time) {int n = clips.length;// 对数组进行排序,先根据 start 升序排序,再按照 cur_end 降序排序Arrays.sort(clips, new Comparator<int[]>() {@Overridepublic int compare(int[] o1, int[] o2) {if (o1[0] == o2[0])return o2[1] - o1[1];return o1[0] - o2[0];}});for (int i = 0; i < n; i++) {System.out.println(Arrays.toString(clips[i]));}int step = 1;int pre_end = 0, cur_end = clips[0][1];// 无法从 0 左区间开始,直接返回无法剪辑if (clips[0][0] > 0 ) return -1;// 如果第一个区间满足要求,则直接返回if (cur_end >= time) return step;LinkedList<int[]> list = new LinkedList<>();list.add(new int[]{clips[0][0], clips[0][1]});for (int i = 1; i < n; i++) {// 下一区间跨越上一区间的end边界if (clips[i][0] <= cur_end && clips[i][1] > cur_end) {// step 更新条件很关键。比如 [0,4],[2,6],[4,7],[6,9],其实只要三个区间就能满足要求 [2,6]是多余的// 因此,这里要记录上一次区间的结束 pre_end 值,只有超过 pre_end 值才更新 stepif (clips[i][0] > pre_end) {step++;pre_end = cur_end;} else {// 说明上一次区间冗余,需要删除掉list.pollLast();}cur_end = clips[i][1];list.add(new int[]{clips[i][0], clips[i][1]});if (cur_end >= time) break;}}if (cur_end >= time) return step;return -1;}
1288-删除被覆盖区间(区间覆盖)

题意分析:
- 对多个区间中包含的区间进行去重,只保留不重复且区间范围大的区间。
解题思路:
- 排序+贪心。先对区间 start 升序和 end 降序,然后画图确认区间覆盖的条件进行过滤。
注意点:
扩展:
代码:
/*** 区间覆盖问题* 1、排序。先对区间 start 升序排序,再对区间 end 降序排序。* 2、画图。判断什么时候覆盖什么是不覆盖。* @param intervals* @return*/public int removeCoveredIntervals(int[][] intervals) {int n = intervals.length;Arrays.sort(intervals, new Comparator<int[]>() {@Overridepublic int compare(int[] o1, int[] o2) {if (o1[0] != o2[0])return o1[0] - o2[0];return o2[1] - o1[1];}});for (int i = 0; i < n; i ++) {System.out.println(Arrays.toString(intervals[i]));}int left = Integer.MAX_VALUE, right = Integer.MIN_VALUE;int res = 0;for (int i = 0; i < n; i++) {if (!(intervals[i][0] >= min && intervals[i][1] <= max)) {res++;left = intervals[i][0];right = intervals[i][1];// System.out.println(left + "; " + right);}}return res;}
56-合并区间(区间合并)

题意分析:
- 对存在重叠或连续的区间进行合并,合并成一个大的区间。
解题思路:
- 排序+贪心。先对区间 start 升序和 end 降序,然后画图确认区间合并的条件进行过滤。
注意点:
- 返回二维数组的数据结构选取,以及如何返回结果。
扩展:
代码:
/*** 区间合并问题* 1、排序。先对区间 start 升序排序,再对区间 end 降序排序。* 2、画图。判断什么时候需要更新合并 list,什么时候更新 list 右区间。* 3、数据结构选取。List<int[]> list,返回 list.toArray(new int[list.size][]);* @param intervals* @return*/public int[][] merge(int[][] intervals) {int n = intervals.length;Arrays.sort(intervals, new Comparator<int[]>() {@Overridepublic int compare(int[] o1, int[] o2) {if (o1[0] != o2[0])return o1[0] - o2[0];return o2[1] - o1[1];}});// for (int i = 0; i < n; i ++) {// System.out.println(Arrays.toString(intervals[i]));// }int left = 0, right = 0;List<int[]> list = new ArrayList<>();for (int i = 0; i < n; i++) {left = intervals[i][0];right = intervals[i][1];// 不需要进行合并操作的情况if (list.size() == 0 || list.get(list.size() - 1)[1] < left) {list.add(new int[]{left, right});}if (left <= list.get(list.size() - 1)[1] && right >= list.get(list.size() - 1)[1]) {// 更新右区间list.get(list.size() - 1)[1] = right;}}return list.toArray(new int[list.size()][]);}
986-区间列表交集(区间交集)

题意分析:
- 求两个区间的交集,单个元素存在交集也要输出。
解题思路:
- 双指针。排序+画图,由于区间已排序,直接画图通过两个指针分别对比两个区间的元素,如果存在交集则添加到 list,如果没交集则左右指针依次前进。
注意点:
- 左右指针的前进条件,什么时候前进 left,什么时候前进 right。
- while 循环的退出条件,如果有一个指针扫描区间结束,则一定不存在交集。
扩展:
代码:
/*** 区间已排序,直接画图判断更新交集区间。* @param firstList* @param secondList* @return*/public int[][] intervalIntersection(int[][] firstList, int[][] secondList) {int m = firstList.length, n = secondList.length;List<int[]> list = new ArrayList<>();int left = 0, right = 0;while (left < m && right < n) {// 取两个区间的左右值int low = Math.max(firstList[left][0], secondList[right][0]);int high = Math.min(firstList[left][1], secondList[right][1]);// 区间存在交集if (low <= high) {list.add(new int[]{low, high});}if (firstList[left][1] < secondList[right][1]) {left++;} else {right++;}}return list.toArray(new int[list.size()][]);}
未分类
70-爬楼梯

题意分析:
解题思路:
注意点:
扩展:
代码:
public int climbStairs(int n) {if (n == 1) return 1;if (n == 2) return 2;// 状态数组int[] dp = new int[n+1];// base case. dp[0] ignoredp[1] = 1;dp[2] = 2;// 状态转换for (int i = 3; i <= n; i++) {dp[i] = dp[i-1] + dp[i-2];}// 求解return dp[n];}
模版
题意分析:
解题思路:
注意点:
扩展:
代码:
