题意非常的明确,我们直接来进行分析,要求出最小的移动距离,其实非常容易的我们就能想到两个手指最开始的位置,一定都是在键盘中的某个输入串中包含的字符上面的,但是我们并不能确定在哪个位置。不过,我们能确定其中一个手指最开始的位置,那就是输入串第一个字符所在位置,这一点其实非常容易想通,因为我们不管把手指放在什么位置,都要移动去输入第一个字符,那还不如直接放在第一个字符上,但是第二个手指并不能这样确定。还有一点就是,每当我们输入一个字符之后,一个有一个手指是在当前输入字符的位置上的。
再进行分析我们能发现,当我们输入字符串结束之后,其实是一种状态转换,我们假设现在两个手指一个l一个r,当面临一个待输入的字符的时候,我们要么拿l去按,要么拿r去按。那到达拿哪个呢?拿距离最近的那个吗?其实不对,拿最近的一个只是一个局部的最优解(当前步最优),但是对于整个问题来说,可能并不是最优解。比如你可以尝试一下“ZKNBZ”,这个输入串按照这种思路是不能得到一个最小的结果的。
虽然这种方式不行,但是根据我们前面的分析,我们至少能得一个状态转移方程了。其中包含l,r,和当前输入串的下标index。我们就能得到一个状态转移方程了。dp[index][l][r],表示当前在输入串的下标i的位置,l手指在键盘第l个位置,r手指在键盘的第r个位置的最小移动距离,那么转移方程就为
dp[1][l][r] = Math.min(dp[1][l][r], dp[0][L][r] + move(L, l))
或
dp[1][l][r] = Math.min(dp[1][l][r], dp[0][l][R] + move(R, r))
其中的L和R表示上一个状态l手指或者r手指所在位置
注意观察,如果移动的是l手指,那么上一个阶段右指为 r,此时这个阶段右指也必须保持不变,同样为r
根据状态转移方程我们就能写出DP公式了
优化空间
因为当前输入字符的状态只依赖其前一个输入字符的状态,所以可以利用滚动数组来优化,这也是DP的常用优化手段了。
优化时间
我们可以换一个想法,我们只记录其中一个手指所在位置和当前输入串的下标。
我们利用输入串的下标,完全可以推断出另外一个手指的位置,因为你输入了下标为i的位置的字符,那么你一定有一个手指在对应的位置上。所以我们就利用了两个状态来表示上面的三个状态了
时间优化后的代码实现,空间没有优化
public int minimumDistance(String word) {
int[][] dp = new int[word.length()][26];
for (int[] ints : dp) {
Arrays.fill(ints, Integer.MAX_VALUE / 2);
}
for (int i = 0; i < word.length(); i++) {
dp[0][word.charAt(i) - 'A'] = 0;
}
int min = Integer.MAX_VALUE;
for (int i = 1; i < word.length(); i++) {
int cur = word.charAt(i) - 'A';
for (int j = 0; j < 26; j++) {
// 移动下标位置表示的手指到新位置上来
dp[i][j] = Math.min(dp[i][j], dp[i - 1][j] + dis(word.charAt(i) - 'A', word.charAt(i - 1) - 'A'));
// 移动j位置表示的手指到到新位置上来
// 也就是计算j到新下标位置字符的距离
// 前提是之前i-1位置下标表示的字符要与当前j位置所在字符相等
// 相当于表示手指的位置互换了
if (word.charAt(i - 1) - 'A' == j) {
for (int k = 0; k < 26; k++) {
if (dp[i - 1][k] != Integer.MAX_VALUE) {
dp[i][j] = Math.min(dp[i][j], dp[i - 1][k] + dis(k, word.charAt(i) - 'A'));
}
}
}
if (i == word.length() - 1) {
min = Math.min(min, dp[i][j]);
min = Math.min(min, dp[i][word.charAt(i - 1) - 'A']);
}
}
}
return min;
}
// 计算距离
public int dis(int a, int b) {
int x = a / 6, y = a % 6;
int x2 = b / 6, y2 = b % 6;
return (Math.abs(x - x2)) + (Math.abs(y - y2));
}
这一个状态转移方程与上面一样,有一点点不同,可以扩充想法
public int minimumDistance(String word) {
int[][] dp = new int[word.length()][26];
for (int i = 0; i < dp.length; i++) {
Arrays.fill(dp[i], Integer.MAX_VALUE / 2);
}
for (int i = 0; i < word.length(); i++) {
dp[0][word.charAt(i) - 'A'] = 0;
}
int min = Integer.MAX_VALUE;
for (int i = 1; i < word.length(); i++) {
int cur = word.charAt(i) - 'A';
for (int j = 0; j < 26; j++) {
if (dp[i - 1][j] == Integer.MAX_VALUE) {
continue;
}
int disA = dis(word.charAt(i - 1) - 'A', cur);
int disB = dis(j, cur);
dp[i][j] = Math.min(dp[i - 1][j] + disA, dp[i][j]);
dp[i][word.charAt(i-1)-'A'] = Math.min(dp[i - 1][j] + disB, dp[i][word.charAt(i-1)-'A']);
if (i == word.length()-1) {
min = Math.min(min, dp[i][j]);
min = Math.min(min, dp[i][word.charAt(i-1)-'A']);
}
}
}
return min;
}