[LeetCode] Freedom Trail 自由之路


 

In the video game Fallout 4, the quest "Road to Freedom" requires players to reach a metal dial called the "Freedom Trail Ring", and use the dial to spell a specific keyword in order to open the door.

Given a string ring, which represents the code engraved on the outer ring and another string key, which represents the keyword needs to be spelled. You need to find the minimum number of steps in order to spell all the characters in the keyword.

Initially, the first character of the ring is aligned at 12:00 direction. You need to spell all the characters in the string key one by one by rotating the ring clockwise or anticlockwise to make each character of the string key aligned at 12:00 direction and then by pressing the center button. 
At the stage of rotating the ring to spell the key character key[i]:

  1. You can rotate the ring clockwise or anticlockwise one place, which counts as 1 step. The final purpose of the rotation is to align one of the string ring's characters at the 12:00 direction, where this character must equal to the character key[i].
  2. If the character key[i] has been aligned at the 12:00 direction, you need to press the center button to spell, which also counts as 1 step. After the pressing, you could begin to spell the next character in the key (next stage), otherwise, you've finished all the spelling.

 

Example:

 

Input: ring = "godding", key = "gd"
Output: 4
Explanation:
For the first key character 'g', since it is already in place, we just need 1 step to spell this character.
For the second key character 'd', we need to rotate the ring "godding" anticlockwise by two steps to make it become "ddinggo".
Also, we need 1 more step for spelling.
So the final output is 4.

 

Note:

  1. Length of both ring and key will be in range 1 to 100.
  2. There are only lowercase letters in both strings and might be some duplcate characters in both strings.
  3. It's guaranteed that string key could always be spelled by rotating the string ring.

 

這道題是關於輻射4這款游戲出的,博主雖然沒有玩過這款游戲,但是知道確實有些游戲中需要破解一些謎題才能繼續通關,像博主很早以前玩過的恐龍危機啊,生化危機啊啥的,都有一些機關需要破解,博主大部分都要靠看攻略來通關哈哈。這道題講的是一種叫做自由之路的機關,我們需要將密碼字符串都轉出來,讓我們求最短的轉動步數。博主最先嘗試的用貪婪算法來做,就是每一步都選最短的轉法,但是OJ中總有些test case會引誘貪婪算法得出錯誤的結果,因為全局最優解不一定都是局部最優解,而貪婪算法一直都是在累加局部最優解,這也是為啥DP解法這么叼的原因。貪婪算法好想好實現,但是不一定能得到正確的結果。DP解法難想不好寫,但往往才是正確的解法,這也算一個trade off吧。這道題可以用DP來解,難點還是寫遞推公式,博主在充分研究網上大神們的帖子后嘗試着自己理理思路,如果有不正確或者不足的地方,也請各位不吝賜教。此題需要使用一個二維數組dp,其中dp[i][j]表示轉動從i位置開始的key串所需要的最少步數(這里不包括spell的步數,因為spell可以在最后統一加上),此時表盤的12點位置是ring中的第j個字符。不得不佩服這樣的設計的確很巧妙,我們可以從key的末尾往前推,這樣dp[0][0]就是我們所需要的結果,因為此時是從key的開頭開始轉動,而且表盤此時的12點位置也是ring的第一個字符。現在我們來看如何找出遞推公式,對於dp[i][j],我們知道此時要將key[i]轉動到12點的位置,而此時表盤的12點位置是ring[j],我們有兩種旋轉的方式,順時針和逆時針,我們的目標肯定是要求最小的轉動步數,而順時針和逆時針的轉動次數之和剛好為ring的長度n,這樣我們求出來一個方向的次數,就可以迅速得到反方向的轉動次數。為了將此時表盤上12點位置上的ring[j]轉動到key[i],我們要將表盤轉動一整圈,當轉到key[i]的位置時,我們計算出轉動步數diff,然后計算出反向轉動步數,並取二者較小值為整個轉動步數step,此時我們更新dp[i][j],更新對比值為step + dp[i+1][k],這個也不難理解,因為key的前一個字符key[i+1]的轉動情況suppose已經計算好了,那么dp[i+1][k]就是當時表盤12點位置上ring[k]的情況的最短步數,step就是從ring[k]轉到ring[j]的步數,也就是key[i]轉到ring[j]的步數,用語言來描述就是,從key的i位置開始轉動並且此時表盤12點位置為ring[j]的最小步數(dp[i][j])就等價於將ring[k]轉動到12點位置的步數(step)加上從key的i+1位置開始轉動並且ring[k]已經在表盤12點位置上的最小步數(dp[i+1][k])之和。突然發現這不就是之前那道Reverse Pairs中解法一中歸納的順序重現關系的思路嗎,都做了總結,可換個馬甲就又不認識了,淚目中。。。

 

解法一:

class Solution {
public:
    int findRotateSteps(string ring, string key) {
        int n = ring.size(), m = key.size();
        vector<vector<int>> dp(m + 1, vector<int>(n));
        for (int i = m - 1; i >= 0; --i) {
            for (int j = 0; j < n; ++j) {
                dp[i][j] = INT_MAX;
                for (int k = 0; k < n; ++k) {
                    if (ring[k] == key[i]) {
                        int diff = abs(j - k);
                        int step = min(diff, n - diff);
                        dp[i][j] = min(dp[i][j], step + dp[i + 1][k]);
                    }
                }
            }
        }
        return dp[0][0] + m;      
    }
};

 

下面這種解法是用DFS來解的,我們需要做優化,也就是用memo數組來保存已經計算過的結果,否則大量的重復運算是無法通過OJ的。其實這里的memo數組也起到了跟上面解法中的dp數組相類似的作用,還有就是要注意數組v的作用,記錄了每個字母在ring中的出現位置,由於ring中可能有重復字符,而且麻煩的情況是當前位置向兩個方向分別轉動相同的步數會分別到達兩個相同的字符,這也是貪婪算法會失效的一個重要原因,而且也是上面的解法在找到ring[k] == key[i]並處理完之后不break的原因,因為后面還有可能找到。上面的迭代解法中使用到的變量i和j可以直接訪問到,而在遞歸的寫法中必須要把位置變量x和y當作參數導入進去,這樣才能更新正確的地方,參見代碼如下:

 

解法二:

class Solution {
public:
    int findRotateSteps(string ring, string key) {
        int n = ring.size(), m = key.size();
        vector<vector<int>> v(26);
        vector<vector<int>> memo(n, vector<int>(m));
        for (int i = 0; i < n; ++i) v[ring[i] - 'a'].push_back(i);
        return helper(ring, key, 0, 0, v, memo);
    }
    int helper(string ring, string key, int x, int y, vector<vector<int>>&v, vector<vector<int>>& memo) {
        if (y == key.size()) return 0;
        if (memo[x][y]) return memo[x][y];
        int res = INT_MAX, n = ring.size();
        for (int k : v[key[y] - 'a']) {
            int diff = abs(x - k);
            int step = min(diff, n - diff);
            res = min(res, step + helper(ring, key, k, y + 1, v, memo));
        }
        return memo[x][y] = res + 1;
    }
};

 

參考資料:

https://discuss.leetcode.com/topic/81684/concise-java-dp-solution

https://discuss.leetcode.com/topic/82720/evolve-from-brute-force-to-dp

 

LeetCode All in One 題目講解匯總(持續更新中...)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM