“趣味”or“燒腦”算法 之 王子救公主

| 題引

相信大部分人童年都玩過大富豪這樣一類的棋,棋格上面有加多少分,減多少分等等設置,比賽最終誰的分值最多(類似下面這個棋盤)
在這裏插入圖片描述


| 正題

設置小遊戲爲一個二維矩陣,
王子位於左上角,公主位於右下角,
每個單元格將會出現怪物或者補給

怪物:打鬥減血(負數表示,且爲整數)
補給:加血(正數表示,且爲整數)

王子只能往下或者往右前進,
如果王子血量爲0或負數,即爲拯救失敗,
求王子至少需要多少初始血量,才能抵達公主?
在這裏插入圖片描述
最優解: -2 — -3 — 3 — 1 — -5 需要初始血量爲7


| 分析

先分析題目的重點:

  • 只能往右或着往下前進
  • 任意步只要血量掉0即失敗(也就意味每步結束後血量至少保持1點)
  • 每步可能加血、減血,或者不變(爲0時)

分析完重點,來看一些場景
在這裏插入圖片描述


在這裏插入圖片描述


在這裏插入圖片描述


看完3個小場景後,按常規思路,總結一個小規律
往下往右的時候,判斷下和右的大小前進

那麼這個結論是不是正確的呢,再來看下面一個場景…


在這裏插入圖片描述
按照上面總結的規律,得出這樣的結論,如果細心的去計算的話,發現並不是最優解,雖然第一步的時候-3,但是+5之後,立馬又回血了,正確的最優解如下:
在這裏插入圖片描述


至此,說明上面的小規律總結並不成立,那麼就說明從正面打通思路就不行了,那麼就逆向思維看看是否有解決辦法?

再分析:
從後往前計算,計算當我達到這步的時候,應該需要多少血量(爲了方便計算,我們計算每步的臨界值,即到達這步血量爲0,最終結果+1即可

以場景4爲例:
在這裏插入圖片描述
計算到“王子”位置時,結果爲2,表示王子位置出發,必須至少需要2+1=3血量

總結一下:

  • 逆向思維倒推,從後往前計算每步至少血量
  • 最右和最下邊,可通過下面或後面的值,再與當前棋格的值計算可得出至少血量
  • 中間部分可通過相鄰的右邊和下邊值判斷出,再與當前棋格的值計算可得出至少血量

計算順序邏輯圖如下:
在這裏插入圖片描述


|編碼

分析了這麼多,我們按照分析思路去編碼實現(小夥伴們可以自己先嚐試編碼看看

/**
 * @author yanghao
 * @version LeetCode174Test.java, v 0.1 2019-11-12 16:43
 */
public class LeetCode174Test {

    public static void main(String[] args) {
        LeetCode174Test test = new LeetCode174Test();

        /*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};*/

        /*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};*/

        int[][] dungeon = new int[3][3];
        dungeon[0] = new int[]{1, -3, 5};
        dungeon[1] = new int[]{0, -5, 0};
        dungeon[2] = new int[]{-3, -3, 1};

        /*int[][] dungeon = new int[2][2];
        dungeon[0] = new int[]{2, 1};
        dungeon[1] = new int[]{1, -1};*/

        /*int[][] dungeon = new int[2][2];
        dungeon[0] = new int[]{0, 5};
        dungeon[1] = new int[]{-2, -3};*/

        /*int[][] dungeon = new int[2][1];
        dungeon[0] = new int[]{2};
        dungeon[1] = new int[]{1};*/

        /*int[][] dungeon = new int[2][1];
        dungeon[0] = new int[]{1};
        dungeon[1] = new int[]{-1};*/

        /*int[][] dungeon = new int[2][3];
        dungeon[0] = new int[]{3, -20, 30};
        dungeon[1] = new int[]{-3, 4, 0};*/

        /*int[][] dungeon = new int[3][1];
        dungeon[0] = new int[]{1};
        dungeon[1] = new int[]{-2};
        dungeon[2] = new int[]{1};*/

        /*int[][] dungeon = new int[1][2];
        dungeon[0] = new int[]{0, 0};*/

        System.out.println(test.leetCode174(dungeon));

    }

    public int leetCode174(int[][] dungeon) {
        int x = dungeon[0].length;
        int y = dungeon.length;

        if (dungeon == null || x == 0 || y == 0) {
            return 0;
        }

        //定義一個矩陣存儲每格需要的血量,從後往前推算每格最少需要多少血量
        int[][] blood = new int[y][x];
        //填充最後一格
        blood[y - 1][x - 1] = Math.max(0, -dungeon[y - 1][x - 1]);
        //定義所需最少血量
        int lowBlood = 0;

        //填充最後一行
        for (int i = x - 2; i >= 0; i--) {
            lowBlood = blood[y - 1][i + 1] - dungeon[y - 1][i];
            blood[y - 1][i] = Math.max(0, lowBlood);
        }

        //填充最後一列
        for (int i = y - 2; i >= 0; i--) {
            lowBlood = blood[i + 1][x - 1] - dungeon[i][x - 1];
            blood[i][x - 1] = Math.max(0, lowBlood);
        }

        //填充中間部分
        for (int i = x - 2; i >= 0; i--) {
            for (int j = y - 2; j >= 0; j--) {
                lowBlood = Math.min(blood[j][i + 1], blood[j + 1][i]) - dungeon[j][i];
                blood[j][i] = Math.max(0, lowBlood);
            }
        }

        return blood[0][0] + 1;

    }
    
}


|贈語

學習算法,思想很重要,要學會突破常規思路
(本題嘗試了很多解決思路,這個算是比較優的解決辦法,當然還可以通過其他方式去實現,小夥伴們盡情發揮自己的大腦吧,本題目來自LeetCode 174題,題目爲了易懂,稍作精簡改編)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章