[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 题目讲解汇总(持续更新中...)

相关文章
相关标签/搜索