“趣味”or“烧脑”算法 之 王子救公主

水深无声 2023-06-13 06:28 128阅读 0赞

| 题引

相信大部分人童年都玩过大富豪这样一类的棋,棋格上面有加多少分,减多少分等等设置,比赛最终谁的分值最多(类似下面这个棋盘)
在这里插入图片描述


| 正题

设置小游戏为一个二维矩阵,
王子位于左上角,公主位于右下角,
每个单元格将会出现怪物或者补给

怪物:打斗减血(负数表示,且为整数)
补给:加血(正数表示,且为整数)

王子只能往下或者往右前进,
如果王子血量为0或负数,即为拯救失败,
求王子至少需要多少初始血量,才能抵达公主?
在这里插入图片描述
最优解: -2 — -3 — 3 — 1 — -5 需要初始血量为7


| 分析

先分析题目的重点:

  • 只能往右或着往下前进
  • 任意步只要血量掉0即失败(也就意味每步结束后血量至少保持1点)
  • 每步可能加血、减血,或者不变(为0时)

分析完重点,来看一些场景
在这里插入图片描述


在这里插入图片描述


在这里插入图片描述


看完3个小场景后,按常规思路,总结一个小规律
往下往右的时候,判断下和右的大小前进

那么这个结论是不是正确的呢,再来看下面一个场景…


在这里插入图片描述
按照上面总结的规律,得出这样的结论,如果细心的去计算的话,发现并不是最优解,虽然第一步的时候-3,但是+5之后,立马又回血了,正确的最优解如下:
在这里插入图片描述


至此,说明上面的小规律总结并不成立,那么就说明从正面打通思路就不行了,那么就逆向思维看看是否有解决办法?

再分析:
从后往前计算,计算当我达到这步的时候,应该需要多少血量(为了方便计算,我们计算每步的临界值,即到达这步血量为0,最终结果+1即可

以场景4为例:
在这里插入图片描述
计算到“王子”位置时,结果为2,表示王子位置出发,必须至少需要2+1=3血量

总结一下:

  • 逆向思维倒推,从后往前计算每步至少血量
  • 最右和最下边,可通过下面或后面的值,再与当前棋格的值计算可得出至少血量
  • 中间部分可通过相邻的右边和下边值判断出,再与当前棋格的值计算可得出至少血量

计算顺序逻辑图如下:
在这里插入图片描述


|编码

分析了这么多,我们按照分析思路去编码实现(小伙伴们可以自己先尝试编码看看

  1. /** * @author yanghao * @version LeetCode174Test.java, v 0.1 2019-11-12 16:43 */
  2. public class LeetCode174Test {
  3. public static void main(String[] args) {
  4. LeetCode174Test test = new LeetCode174Test();
  5. /*int[][] dungeon = new int[3][3]; dungeon[0] = new int[]{-2, -3, 3}; dungeon[1] = new int[]{-5, -10, 1}; dungeon[2] = new int[]{10, 30, -5};*/
  6. /*int[][] dungeon = new int[3][3]; dungeon[0] = new int[]{1,-3,3}; dungeon[1] = new int[]{0,-2,0}; dungeon[2] = new int[]{-3,-3,-3};*/
  7. int[][] dungeon = new int[3][3];
  8. dungeon[0] = new int[]{ 1, -3, 5};
  9. dungeon[1] = new int[]{ 0, -5, 0};
  10. dungeon[2] = new int[]{ -3, -3, 1};
  11. /*int[][] dungeon = new int[2][2]; dungeon[0] = new int[]{2, 1}; dungeon[1] = new int[]{1, -1};*/
  12. /*int[][] dungeon = new int[2][2]; dungeon[0] = new int[]{0, 5}; dungeon[1] = new int[]{-2, -3};*/
  13. /*int[][] dungeon = new int[2][1]; dungeon[0] = new int[]{2}; dungeon[1] = new int[]{1};*/
  14. /*int[][] dungeon = new int[2][1]; dungeon[0] = new int[]{1}; dungeon[1] = new int[]{-1};*/
  15. /*int[][] dungeon = new int[2][3]; dungeon[0] = new int[]{3, -20, 30}; dungeon[1] = new int[]{-3, 4, 0};*/
  16. /*int[][] dungeon = new int[3][1]; dungeon[0] = new int[]{1}; dungeon[1] = new int[]{-2}; dungeon[2] = new int[]{1};*/
  17. /*int[][] dungeon = new int[1][2]; dungeon[0] = new int[]{0, 0};*/
  18. System.out.println(test.leetCode174(dungeon));
  19. }
  20. public int leetCode174(int[][] dungeon) {
  21. int x = dungeon[0].length;
  22. int y = dungeon.length;
  23. if (dungeon == null || x == 0 || y == 0) {
  24. return 0;
  25. }
  26. //定义一个矩阵存储每格需要的血量,从后往前推算每格最少需要多少血量
  27. int[][] blood = new int[y][x];
  28. //填充最后一格
  29. blood[y - 1][x - 1] = Math.max(0, -dungeon[y - 1][x - 1]);
  30. //定义所需最少血量
  31. int lowBlood = 0;
  32. //填充最后一行
  33. for (int i = x - 2; i >= 0; i--) {
  34. lowBlood = blood[y - 1][i + 1] - dungeon[y - 1][i];
  35. blood[y - 1][i] = Math.max(0, lowBlood);
  36. }
  37. //填充最后一列
  38. for (int i = y - 2; i >= 0; i--) {
  39. lowBlood = blood[i + 1][x - 1] - dungeon[i][x - 1];
  40. blood[i][x - 1] = Math.max(0, lowBlood);
  41. }
  42. //填充中间部分
  43. for (int i = x - 2; i >= 0; i--) {
  44. for (int j = y - 2; j >= 0; j--) {
  45. lowBlood = Math.min(blood[j][i + 1], blood[j + 1][i]) - dungeon[j][i];
  46. blood[j][i] = Math.max(0, lowBlood);
  47. }
  48. }
  49. return blood[0][0] + 1;
  50. }
  51. }

|赠语

学习算法,思想很重要,要学会突破常规思路
(本题尝试了很多解决思路,这个算是比较优的解决办法,当然还可以通过其他方式去实现,小伙伴们尽情发挥自己的大脑吧,本题目来自LeetCode 174题,题目为了易懂,稍作精简改编)

发表评论

表情:
评论列表 (有 0 条评论,128人围观)

还没有评论,来说两句吧...

相关阅读

    相关 的珠峰高程测算过程

    最近几天,珠峰高程测量队一直在为登顶测量努力着,其实在专业测绘科技工作者眼中,珠穆朗玛峰的高程并不是只有一个,而是有“雪面海拔高”、“雪面正常高”、“岩石面海拔高”、“雪面大地

    相关 面试时碰到的的题

    1、有8个球,其中1个比另外的要略重。在不用砝码的前提下,你最少要称几次,才能找出这个球? 2、你有不限量的水,还有两个罐子,容量分别是5升和3升。请精确的称量出4升水。