[LeetCode]1320. Minimum Distance to Type a Word Using Two Fingers 动态规划解法

题目描述

LeetCode原题链接:1320. Minimum Distance to Type a Word Using Two Fingers

You have a keyboard layout as shown above in the X-Y plane, where each English uppercase letter is located at some coordinate.

  • For example, the letter 'A' is located at coordinate (0, 0), the letter 'B' is located at coordinate (0, 1), the letter 'P' is located at coordinate (2, 3) and the letter 'Z' is located at coordinate (4, 1).

Given the string word, return the minimum total distance to type such string using only two fingers.

The distance between coordinates (x1, y1) and (x2, y2) is |x1 - x2| + |y1 - y2|.

Note that the initial positions of your two fingers are considered free so do not count towards your total distance, also your two fingers do not have to start at the first letter or the first two letters.

Example 1:

Input: word = "CAKE"
Output: 3
Explanation: 
Using two fingers, one optimal way to type "CAKE" is: 
Finger 1 on letter 'C' -> cost = 0 
Finger 1 on letter 'A' -> cost = Distance from letter 'C' to letter 'A' = 2 
Finger 2 on letter 'K' -> cost = 0 
Finger 2 on letter 'E' -> cost = Distance from letter 'K' to letter 'E' = 1 
Total distance = 3

Example 2:

Input: word = "HAPPY"
Output: 6
Explanation: 
Using two fingers, one optimal way to type "HAPPY" is:
Finger 1 on letter 'H' -> cost = 0
Finger 1 on letter 'A' -> cost = Distance from letter 'H' to letter 'A' = 2
Finger 2 on letter 'P' -> cost = 0
Finger 2 on letter 'P' -> cost = Distance from letter 'P' to letter 'P' = 0
Finger 1 on letter 'Y' -> cost = Distance from letter 'A' to letter 'Y' = 4
Total distance = 6

Example 3:

Input: word = "NEW"
Output: 3

Example 4:

Input: word = "YEAR"
Output: 7

 

Constraints:

  • 2 <= word.length <= 300
  • word consists of uppercase English letters.

题目分析

刚看到这道题的时候还真是有点懵,想了半天两根手指怎么交叉同时输入两个相隔很远的字母呢...(第一反应两根手指是一只手上相邻的两根手指🤌)好吧,把问题想复杂了。题目的意思这两根手指完全可以是独立开来的,之间并没有什么约束关系。可以想象是你左手的某根手指+右手的某根手指,甚至是你自己的一根手指+别人的一根手指。那么思路就很清晰了,最终的结果肯定是小于用一根手指挨个字母敲出来的步长。我们可以用一个变量sum记录只用一根手指时走过的总距离,save表示加入第二根手指之后少走的步长,那么最终结果就是 sum - save。

那么问题来了,什么时候开始加入第二根手指呢?显然,这个时机是有讲究的,因为有的时候用两根手指并不比用一根手指节省距离:比如输入“CAK”到“K”时再用第二根手指的移动总距离就比在“A”时用第二根手指要少。这就存在一个局部最优解的问题。另外,题目中说了手指的initial position可以是任意的,不过一旦加入了第二根手指,之后如果还想用第二根手指来输入的话,那就要注意它的移动位置是上一次敲字的位置了。嗯,上一次的位置,和上一状态有关、局部最优解,很好,动态规划没跑了。

savaDp[i]用来表示第二根手指移动到 i 位置时的总最大节省距离。数组中所有元素初始化为0(没有使用第二根手指时)。我们让finger1的起始位置就处于第word的第一个字母位置,sum累加计算只使用finger1时走过的总距离。finger1每到一个新的位置,我们就去计算一次此时加入finger2的节省。令finger1的移动方向是now --> to,finger2可以从字母表上的26个位置中任意位置前往to,假设这个任意位置是i,那么finger2这次移动节省的距离就是saveDis = calDistance(now, to) - calDistance(i, to)。saveDp[now]表示的是finger2移动到now位置的总最大节省距离,因此还要加上从起始位置移动到 i 位置的总节省saveDp[i]:如果这次移动是第一次使用finger2,saveDp[i]就是我们初始化时的默认值0;如果这次移动不是第一次使用finger2,那么saveDp[i]就是我们前几轮遍历时计算出来保存在dp数组中的值。最后用Math.max取最优解,那么我们关于saveDp的状态转移方程就是:

saveDp[now] = Math.max(saveDp[now],saveDp[i]+(calDistance(now,to)-calDistance(i,to)))

nice!局部最优解找到了(如果使用finger2的最大节省),再用一个变量在每次关于finger1的for循环时记录全局最优解(是否从此移动使用finger2)。遍历完word后仅使用finger1的总移动距离渐去最大节省距离就能得到最终结果了!

代码示例(JavaScript) 

 1 /**
 2  * @param {string} word
 3  * @return {number}
 4  */
 5 var minimumDistance = function(word) {
 6     let sum = 0, save = 0;
 7     let saveDp = new Array(26);
 8     let A = 'A'.charCodeAt(0);
 9     saveDp.fill(0, 0);
10     for(let cur = 0; cur < word.length - 1; cur++) {
11         let to = word[cur + 1].charCodeAt(0) - A;
12         let now = word[cur].charCodeAt(0) - A;
13         // calculate total distance with finger1
14         sum += calDistance(now, to);
15         // find local maximum save with finger2
16         for(let i = 0; i < saveDp.length; i++) {
17             saveDp[now] = Math.max(saveDp[now], saveDp[i] + (calDistance(now, to) - calDistance(i, to)));
18         }
19         // update total save
20         save = Math.max(saveDp[now], save);
21     }
22     return sum - save;
23 };
24 
25 var calDistance = function(letter1, letter2) {
26     let x1 = Math.floor(letter1 / 6), y1 = Math.floor(letter1 % 6);
27     let x2 = Math.floor(letter2 / 6), y2 = Math.floor(letter2 % 6);
28     return Math.abs(x1 - x2) + Math.abs(y1 - y2);
29 }

 

参考:

https://blog.csdn.net/zhao5502169/article/details/104120452

posted @ 2021-10-11 01:11  夭夭夭夭夭桃子  阅读(79)  评论(0编辑  收藏  举报