Hard

题目描述

你有一个如上图所示的键盘布局,其中每个英文大写字母都位于某个坐标上。

例如,字母 ‘A’ 位于坐标 (0, 0),字母 ‘B’ 位于坐标 (0, 1),字母 ‘P’ 位于坐标 (2, 3),字母 ‘Z’ 位于坐标 (4, 1)。

给定字符串 word,返回仅使用两根手指输入该字符串的最小总距离。

坐标 (x1, y1) 和 (x2, y2) 之间的距离是 |x1 - x2| + |y1 - y2|。

注意,两根手指的初始位置被认为是免费的,所以不计入你的总距离,同时你的两根手指不必从第一个字母或前两个字母开始。

示例 1:

输入:word = "CAKE"
输出:3
解释:使用两根手指,输入 "CAKE" 的一种最优方式是:
手指 1 在字母 'C' 上 -> 成本 = 0
手指 1 在字母 'A' 上 -> 成本 = 从字母 'C' 到字母 'A' 的距离 = 2
手指 2 在字母 'K' 上 -> 成本 = 0
手指 2 在字母 'E' 上 -> 成本 = 从字母 'K' 到字母 'E' 的距离 = 1
总距离 = 3

示例 2:

输入:word = "HAPPY"
输出:6
解释:使用两根手指,输入 "HAPPY" 的一种最优方式是:
手指 1 在字母 'H' 上 -> 成本 = 0
手指 1 在字母 'A' 上 -> 成本 = 从字母 'H' 到字母 'A' 的距离 = 2
手指 2 在字母 'P' 上 -> 成本 = 0
手指 2 在字母 'P' 上 -> 成本 = 从字母 'P' 到字母 'P' 的距离 = 0
手指 1 在字母 'Y' 上 -> 成本 = 从字母 'A' 到字母 'Y' 的距离 = 4
总距离 = 6

约束条件:

  • 2 <= word.length <= 300
  • word 由大写英文字母组成

解题思路

这是一道经典的动态规划问题。核心思路是跟踪两根手指的位置状态。

基本思路: 首先需要建立字母到坐标的映射,键盘布局是6行5列的网格,可以通过字母的ASCII值计算出坐标位置。

状态定义: 使用三维动态规划 dp[i][j][k] 表示当一根手指在第 i 个字符位置,另一根手指在第 j 个字符位置时,已经输入前 k 个字符的最小移动距离。

状态转移: 对于每个新字符,我们有两个选择:

  1. 使用手指1移动到新位置,手指2保持不变
  2. 使用手指2移动到新位置,手指1保持不变

为了优化空间复杂度,可以使用二维数组滚动更新,因为当前状态只依赖于前一个状态。

边界处理: 初始时两根手指可以免费放置在任意位置,所以前两个字符的放置成本为0。

推荐解法: 使用二维DP优化的版本,既节省空间又保持代码简洁性。时间复杂度O(n²),空间复杂度O(n)。

代码实现

class Solution {
public:
    int minimumDistance(string word) {
        int n = word.length();
        if (n == 0) return 0;
        
        auto getPos = [](char c) -> pair<int, int> {
            int idx = c - 'A';
            return {idx / 6, idx % 6};
        };
        
        auto getDist = [&](char a, char b) -> int {
            auto [x1, y1] = getPos(a);
            auto [x2, y2] = getPos(b);
            return abs(x1 - x2) + abs(y1 - y2);
        };
        
        vector<vector<int>> dp(n, vector<int>(n, INT_MAX));
        
        // 初始化:前两个字符可以免费放置
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                if (i == 0 || j == 1) {
                    dp[i][j] = 0;
                }
            }
        }
        
        for (int k = 2; k < n; k++) {
            vector<vector<int>> newDp(n, vector<int>(n, INT_MAX));
            
            for (int i = 0; i < k; i++) {
                for (int j = 0; j < k; j++) {
                    if (dp[i][j] == INT_MAX) continue;
                    
                    // 手指1移动到新位置k,手指2保持在j
                    newDp[k][j] = min(newDp[k][j], dp[i][j] + getDist(word[i], word[k]));
                    
                    // 手指2移动到新位置k,手指1保持在i
                    newDp[i][k] = min(newDp[i][k], dp[i][j] + getDist(word[j], word[k]));
                }
            }
            dp = newDp;
        }
        
        int result = INT_MAX;
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                result = min(result, dp[i][j]);
            }
        }
        
        return result;
    }
};
class Solution:
    def minimumDistance(self, word: str) -> int:
        n = len(word)
        if n == 0:
            return 0
        
        def get_pos(c):
            idx = ord(c) - ord('A')
            return (idx // 6, idx % 6)
        
        def get_dist(a, b):
            x1, y1 = get_pos(a)
            x2, y2 = get_pos(b)
            return abs(x1 - x2) + abs(y1 - y2)
        
        dp = [[float('inf')] * n for _ in range(n)]
        
        # 初始化:前两个字符可以免费放置
        for i in range(n):
            for j in range(n):
                if i == 0 or j == 1:
                    dp[i][j] = 0
        
        for k in range(2, n):
            new_dp = [[float('inf')] * n for _ in range(n)]
            
            for i in range(k):
                for j in range(k):
                    if dp[i][j] == float('inf'):
                        continue
                    
                    # 手指1移动到新位置k,手指2保持在j
                    new_dp[k][j] = min(new_dp[k][j], dp[i][j] + get_dist(word[i], word[k]))
                    
                    # 手指2移动到新位置k,手指1保持在i
                    new_dp[i][k] = min(new_dp[i][k], dp[i][j] + get_dist(word[j], word[k]))
            
            dp = new_dp
        
        result = float('inf')
        for i in range(n):
            for j in range(n):
                result = min(result, dp[i][j])
        
        return result
public class Solution {
    public int MinimumDistance(string word) {
        int n = word.Length;
        if (n == 0) return 0;
        
        (int, int) GetPos(char c) {
            int idx = c - 'A';
            return (idx / 6, idx % 6);
        }
        
        int GetDist(char a, char b) {
            var (x1, y1) = GetPos(a);
            var (x2, y2) = GetPos(b);
            return Math.Abs(x1 - x2) + Math.Abs(y1 - y2);
        }
        
        int[,] dp = new int[n, n];
        
        // 初始化
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                dp[i, j] = int.MaxValue;
                if (i == 0 || j == 1) {
                    dp[i, j] = 0;
                }
            }
        }
        
        for (int k = 2; k < n; k++) {
            int[,] newDp = new int[n, n];
            for (int i = 0; i < n; i++) {
                for (int j = 0; j < n; j++) {
                    newDp[i, j] = int.MaxValue;
                }
            }
            
            for (int i = 0; i < k; i++) {
                for (int j = 0; j < k; j++) {
                    if (dp[i, j] == int.MaxValue) continue;
                    
                    // 手指1移动到新位置k,手指2保持在j
                    newDp[k, j] = Math.Min(newDp[k, j], dp[i, j] + GetDist(word[i], word[k]));
                    
                    // 手指2移动到新位置k,手指1保持在i
                    newDp[i, k] = Math.Min(newDp[i, k], dp[i, j] + GetDist(word[j], word[k]));
                }
            }
            dp = newDp;
        }
        
        int result = int.MaxValue;
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                result = Math.Min(result, dp[i, j]);
            }
        }
        
        return result;
    }
}
var minimumDistance = function(word) {
    const getPos = (char) => {
        const code = char.charCodeAt(0) - 65;
        return [Math.floor(code / 6), code % 6];
    };
    
    const getDist = (char1, char2) => {
        const [x1, y1] = getPos(char1);
        const [x2, y2] = getPos(char2);
        return Math.abs(x1 - x2) + Math.abs(y1 - y2);
    };
    
    const n = word.length;
    const memo = new Map();
    
    const dp = (i, left, right) => {
        if (i === n) return 0;
        
        const key = `${i},${left},${right}`;
        if (memo.has(key)) return memo.get(key);
        
        const char = word[i];
        let result = Infinity;
        
        if (left === -1) {
            result = Math.min(result, dp(i + 1, i, right));
        } else {
            result = Math.min(result, getDist(word[left], char) + dp(i + 1, i, right));
        }
        
        if (right === -1) {
            result = Math.min(result, dp(i + 1, left, i));
        } else {
            result = Math.min(result, getDist(word[right], char) + dp(i + 1, left, i));
        }
        
        memo.set(key, result);
        return result;
    };
    
    return dp(0, -1, -1);
};

复杂度分析

复杂度类型复杂度说明
时间复杂度O(n³)外层循环n次,内层双重循环最多n²次
空间复杂度O(n²)需要n×n的二维数组存储状态

相关题目