Leetcode 1320 二指输入的的最小距离

OJ

题意非常的明确,我们直接来进行分析,要求出最小的移动距离,其实非常容易的我们就能想到两个手指最开始的位置,一定都是在键盘中的某个输入串中包含的字符上面的,但是我们并不能确定在哪个位置。不过,我们能确定其中一个手指最开始的位置,那就是输入串第一个字符所在位置,这一点其实非常容易想通,因为我们不管把手指放在什么位置,都要移动去输入第一个字符,那还不如直接放在第一个字符上,但是第二个手指并不能这样确定。还有一点就是,每当我们输入一个字符之后,一个有一个手指是在当前输入字符的位置上的。
再进行分析我们能发现,当我们输入字符串结束之后,其实是一种状态转换,我们假设现在两个手指一个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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值