• dp入门(二)


    目录

    45、跳跃计划

    53、最大子数组和

    55、跳跃游戏

    62、不同路径

     63、不同路径2

     64、最小路径和

     70、爬楼梯

     72、编辑距离

    84、柱形图中最大的矩形

    85、最大矩形

     4721、排队


    45、跳跃计划

     

    当前可移动距离尽可能多走,如果还没到终点,步数再加一。整体最优:一步尽可能多走,从而达到最小步数。

    思路虽然是这样,但在写代码的时候还不能真的就能跳多远跳远,那样就不知道下一步最远能跳到哪里了。

    所以真正解题的时候,要从覆盖范围出发,不管怎么跳,覆盖范围内一定是可以跳到的,以最小的步数增加覆盖范围,覆盖范围一旦覆盖了终点,得到的就是最小步数!

    这里需要统计两个覆盖范围,当前这一步的最大覆盖和下一步最大覆盖。

    如果移动下标达到了当前这一步的最大覆盖最远距离了,还没有到终点的话,那么就必须再走一步来增加覆盖范围,直到覆盖范围覆盖了终点。

    1. class Solution {
    2. public int jump(int[] nums) {
    3. int next = 0;
    4. int ans = 0;
    5. int end = 0;
    6. int n = nums.length;
    7. for (int i = 0; i < n - 1; i ++) {
    8. next = Math.max(next,nums[i] + i);
    9. if (i == end) {
    10. ans ++;
    11. end = next;
    12. }
    13. }
    14. return ans;
    15. }
    16. }

    53、最大子数组和

    贪心思路:当我们连续子段和是负数时,我们是没必要留给下一个数的,因为下一个数不要这一段的和肯定更大,因此我们发现如果sum已经小于0了,我们就留给下一段一个0,每次更新最大值就行

    1. class Solution {
    2. public int maxSubArray(int[] nums) {
    3. int n = nums.length;
    4. int res = Integer.MIN_VALUE;
    5. for (int i = 0,sum = 0; i < n; i ++) {
    6. sum += nums[i];
    7. res = Math.max(res,sum);
    8. if (sum < 0) sum = 0;
    9. }
    10. return res;
    11. }
    12. }

    动态规划:f[i] 表示nums中以i结尾的区间中最大和,f[i] 可以拆分为长度为1和长度大于1的区间,那么我们可以得到 f[i] 可以由 nums[i] 或者 f[i - 1] + nums[i]转移,两边取掉 nums[i],那就成了 nums[i - 1] + max(f[i - 1],0),我们每次遇到的都是 f[i - 1] ,因此可以用 last 来替换!

    1. class Solution {
    2. public int maxSubArray(int[] nums) {
    3. int n = nums.length;
    4. int res = Integer.MIN_VALUE;
    5. int last = 0;
    6. for (int i = 0; i < n; i ++) {
    7. last = Math.max(last,0) + nums[i];
    8. res = Math.max(res,last);
    9. }
    10. return res;
    11. }
    12. }

    55、跳跃游戏

     思路:暴力解法,记录我们可以到达的最远的点,如果我们到达了最远的点还不能到终点,就返回false ,否则返回true

    1. class Solution {
    2. public boolean canJump(int[] nums) {
    3. int n = nums.length;
    4. int end = 0;
    5. for (int i = 0; i < n; i ++) {
    6. end = Math.max(end,nums[i] + i);
    7. if (i == end && i != n - 1) return false;
    8. }
    9. return true;
    10. }
    11. }

    62、不同路径

    动态规划模板题,任何学动态规划的童鞋都不能没做过这道题!!!!

    想要求dp[i][j],只能有两个方向来推导出来,即dp[i - 1][j] 和 dp[i][j - 1]。

    此时在回顾一下 dp[i - 1][j] 表示啥,是从(0, 0)的位置到(i - 1, j)有几条路径,dp[i][j - 1]同理。

    那么很自然,dp[i][j] = dp[i - 1][j] + dp[i][j - 1],因为dp[i][j]只有这两个方向过来。

    1. class Solution {
    2. public int uniquePaths(int m, int n) {
    3. int[] f = new int[n];
    4. for (int i = 0; i < m; i ++) {
    5. for (int j = 0; j < n; j ++) {
    6. if (i == 0 && j == 0) f[j] = 1;
    7. else if (j > 0) {
    8. f[j] += f[j - 1];
    9. }
    10. }
    11. }
    12. return f[n - 1];
    13. }
    14. }

    加了维度压缩,因为是有一层是上一层转移,j 没变,但我记得比赛时候这题有可能爆int。。。。。

     63、不同路径2

     思路:一模一样,如果当前格子不是障碍物,它才有所谓的路径数量

    1. class Solution {
    2. public int uniquePathsWithObstacles(int[][] obstacleGrid) {
    3. int n = obstacleGrid.length;
    4. int m = obstacleGrid[0].length;
    5. int[][] f = new int[n][m];
    6. for (int i = 0; i < n; i ++) {
    7. for (int j = 0; j < m; j ++) {
    8. if (obstacleGrid[i][j] == 0) {
    9. if (i == 0 && j == 0) f[i][j] = 1;
    10. else {
    11. if (i > 0) f[i][j] += f[i - 1][j];
    12. if (j > 0) f[i][j] += f[i][j - 1];
    13. }
    14. }
    15. }
    16. }
    17. return f[n - 1][m - 1];
    18. }
    19. }

     64、最小路径和

     思路:这三道题转移方程其实都是一样的,这道题唯一不同的点在于我们求得是最小值,不能让默认初始化为0干扰我们最终答案,因此我们初始化为无穷即可

    1. class Solution {
    2. public int minPathSum(int[][] grid) {
    3. int n = grid.length;
    4. int m= grid[0].length;
    5. int[][] f = new int[n][m];
    6. for (int i = 0; i < n; i ++) Arrays.fill(f[i],0x3f3f3f3f);
    7. f[0][0] = grid[0][0];
    8. for (int i = 0; i < n; i ++) {
    9. for (int j = 0; j < m; j ++) {
    10. if (i > 0) f[i][j] = Math.min(f[i][j],f[i - 1][j] + grid[i][j]);
    11. if (j > 0) f[i][j] = Math.min(f[i][j],f[i][j - 1] + grid[i][j]);
    12. }
    13. }
    14. return f[n - 1][m - 1];
    15. }
    16. }

     70、爬楼梯

     思路:同样的,我们f【i】可以由它的前一个(i - 1)跳一步或者 i - 2 (跳两步)得来,累加即可

    1. class Solution {
    2. public int climbStairs(int n) {
    3. if (n == 0 || n == 1) return 1;
    4. int[] f = new int[n + 1];
    5. f[0] = 1;
    6. f[1] = 1;
    7. for (int i = 2 ; i <= n; i ++) {
    8. f[i] += f[i - 1] + f[i - 2];
    9. }
    10. return f[n];
    11. }
    12. }

    我们为了省空间可以用a,b来代替

    1. class Solution {
    2. public int climbStairs(int n) {
    3. if (n == 0 || n == 1) return 1;
    4. int[] f = new int[n + 1];
    5. int a = 1;
    6. int b = 1;
    7. for (int i = 2 ; i <= n; i ++) {
    8. int c = a + b;
    9. a = b;
    10. b = c;
    11. }
    12. return b;
    13. }
    14. }

     72、编辑距离

    转移方程如上图所示,初始化记得我们每一个有意义字符串对应另一个字符串为0时的值应该时 有意义字符串的长度(删)

    1. class Solution {
    2. public int minDistance(String word1, String word2) {
    3. int n = word1.length();
    4. int m = word2.length();
    5. word1 = " " + word1;
    6. word2 = " " + word2;
    7. int[][] f = new int[n + 1][m + 1];
    8. // for (int i = 0; i <= n; i ++) Arrays.fill(f[i],0x3f3f3f3f);
    9. for (int i = 1; i <= n; i ++) f[i][0] = i;
    10. for (int j = 1; j <= m; j ++) f[0][j] = j;
    11. for (int i = 1; i <= n; i ++) {
    12. for (int j = 1; j <= m; j ++) {
    13. f[i][j] = Math.min(f[i - 1][j],f[i][j - 1]) + 1;
    14. f[i][j] = Math.min(f[i][j],f[i - 1][j - 1] + (word1.charAt(i) == word2.charAt(j) ? 0 : 1));
    15. }
    16. }
    17. return f[n][m];
    18. }
    19. }

    为什么这里可以去掉初始化,因为我们更新最大值时没有用到它未更新时的状态,因此动态规划依然按照拓扑序

    84、柱形图中最大的矩形

    1、 此题的本质是找到每个柱形条左边和右边最近的比自己低的矩形条,然后用宽度乘上当前柱形条的高度作为备选答案。
    2、解决此类问题的经典做法是单调栈,维护一个单调递增的栈,如果当前柱形条 i 的高度比栈顶要低,则栈顶元素 cur 出栈。出栈后,cur 右边第一个比它低的柱形条就是 i,左边第一个比它低的柱形条是当前栈中的 top。不断出栈直到栈为空或者柱形条 i 不再比 top 低。
    3、满足 2 之后,当前矩形条 i 进栈。

    1. class Solution {
    2. public int largestRectangleArea(int[] heights) {
    3. int n = heights.length;
    4. Stack stk = new Stack<>();
    5. int[] l = new int[n];
    6. int[] r = new int[n];
    7. for (int i = 0; i < n; i ++) {
    8. while (!stk.isEmpty() && heights[stk.peek()] >= heights[i]) stk.pop();
    9. if (stk.isEmpty()) l[i] = -1;
    10. else l[i] = stk.peek();
    11. stk.push(i);
    12. }
    13. stk.clear();
    14. for (int i = n - 1; i >= 0; i --) {
    15. while (!stk.isEmpty() && heights[stk.peek()] >= heights[i]) stk.pop();
    16. if (stk.isEmpty()) r[i] = n;
    17. else r[i] = stk.peek();
    18. stk.push(i);
    19. }
    20. int res = 0;
    21. for (int i = 0; i < n; i ++) {
    22. res = Math.max(res,(r[i] - l[i] - 1) * heights[i]);
    23. }
    24. return res;
    25. }
    26. }

    85、最大矩形

     

    (单调栈) O(nm)O(nm)
    1、将 Largest Rectangle in Histogram 问题扩展到二维。
    2、一行一行考虑,类比 Largest Rectangle in Histogram,一行内所有柱形条的高度 heights 就是当前 (i, j) 位置能往上延伸的最大高度。
    3、直接套用 Largest Rectangle in Histogram 的单调栈算法即可。

    1. class Solution {
    2. public int largestRectangleArea(int[] heights) {
    3. int n = heights.length;
    4. Stack stk = new Stack<>();
    5. int[] l = new int[n];
    6. int[] r = new int[n];
    7. for (int i = 0; i < n; i ++) {
    8. while (!stk.isEmpty() && heights[stk.peek()] >= heights[i]) stk.pop();
    9. if (stk.isEmpty()) l[i] = -1;
    10. else l[i] = stk.peek();
    11. stk.push(i);
    12. }
    13. stk.clear();
    14. for (int i = n - 1; i >= 0; i --) {
    15. while (!stk.isEmpty() && heights[stk.peek()] >= heights[i]) stk.pop();
    16. if (stk.isEmpty()) r[i] = n;
    17. else r[i] = stk.peek();
    18. stk.push(i);
    19. }
    20. int res = 0;
    21. for (int i = 0; i < n; i ++) {
    22. res = Math.max(res,(r[i] - l[i] - 1) * heights[i]);
    23. }
    24. return res;
    25. }
    26. public int maximalRectangle(char[][] matrix) {
    27. int n = matrix.length;
    28. int m = matrix[0].length;
    29. int[][] f = new int[n][m];
    30. for (int i = 0; i < n; i ++) {
    31. for (int j = 0; j < m; j ++) {
    32. if (matrix[i][j] == '1') {
    33. if (i == 0) f[i][j] = 1;
    34. else f[i][j] = 1 + f[i - 1][j];
    35. }
    36. }
    37. }
    38. int res = 0;
    39. for (int i = 0; i < n; i ++) res = Math.max(res,largestRectangleArea(f[i]));
    40. return res;
    41. }
    42. }

     4721、排队

     

    这道题是单调栈加二分的经典题目,以往的单调栈最常用是求我们最靠近且最小(大)的值,而本题中求的是;最远最小的值,因此我们要改变单调栈的添加顺序,从后往前,试想:如果存在一个靠左的数比靠右的数还大,那它是没有必要存在栈中的,因此栈中一定是从大到小的顺序,因此我们可以对每一个元素二分求小于它的数里边最大的一个

    1. import java.util.*;
    2. public class Main{
    3. public static void main(String[] args) {
    4. Scanner sc = new Scanner(System.in);
    5. int n = sc.nextInt();
    6. int[] h = new int[n];
    7. for (int i = 0; i < n; i ++) h[i] = sc.nextInt();
    8. int top = 0;
    9. int[] stk = new int[n + 10];
    10. int[] res = new int[n];
    11. for (int i = n - 1; i >= 0; i --) {
    12. if (top == 0 || h[stk[top - 1]] >= h[i]) res[i] = -1;
    13. else {
    14. int l = 0;
    15. int r = top - 1;
    16. while (l < r) {
    17. int mid = l + r >> 1;
    18. if (h[stk[mid]] < h[i]) r = mid;
    19. else l = mid + 1;
    20. }
    21. res[i] = stk[r] - i - 1;
    22. }
    23. if (top == 0 || h[i] < h[stk[top - 1]]) stk[top ++] = i;
    24. }
    25. for (int i = 0; i < n; i ++) {
    26. System.out.print(res[i] + " ");
    27. }
    28. }
    29. }

     

  • 相关阅读:
    基础课18——智能客服系统架构
    SpringMVC枚举类型字段处理
    MyBatis 事务源码分析
    Elasticsearch搜索辅助功能解析(十)
    springboot个性化课程推荐系统毕业设计源码131805
    《少有人走的路:心智成熟的旅程》笔记
    oracle 还原被覆盖的视图
    WPF 常用布局方式
    开源任务调度框架
    微服务架构中,二次浅封装实践
  • 原文地址:https://blog.csdn.net/qq_59539549/article/details/128007829