[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.html

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.java

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:dom

  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中解法一中概括的順序重現關係的思路嗎,都作了總結,可換個馬甲就又不認識了,淚目中。。。ide

 

解法一:post

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看成參數導入進去,這樣才能更新正確的地方,參見代碼以下:優化

 

解法二:ui

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

 

參考資料:this

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 題目講解彙總(持續更新中...)

相關文章
相關標籤/搜索