Medium

题目描述

给你一个由字符 ‘U’、‘D’、‘L’ 和 ‘R’ 组成的字符串 s,表示在无限 2D 笛卡尔网格上的移动。

  • ‘U’:从 (x, y) 移动到 (x, y + 1)
  • ‘D’:从 (x, y) 移动到 (x, y - 1)
  • ‘L’:从 (x, y) 移动到 (x - 1, y)
  • ‘R’:从 (x, y) 移动到 (x + 1, y)

同时给你一个正整数 k。

你必须从 s 中选择并删除恰好一个长度为 k 的连续子串。然后,从坐标 (0, 0) 开始,按顺序执行剩余的移动。

返回一个整数,表示可达到的不同最终坐标的数量。

示例 1:

输入:s = "LUL", k = 1
输出:2
解释:
删除长度为 1 的子串后,s 可以是 "UL"、"LL" 或 "LU"。
执行这些移动后,最终坐标将分别是 (-1, 1)、(-2, 0) 和 (-1, 1)。
有两个不同的点 (-1, 1) 和 (-2, 0),所以答案是 2。

示例 2:

输入:s = "UDLR", k = 4
输出:1
解释:
删除长度为 4 的子串后,s 只能是空字符串。最终坐标将是 (0, 0)。
只有一个不同的点 (0, 0),所以答案是 1。

示例 3:

输入:s = "UU", k = 1
输出:1
解释:
删除长度为 1 的子串后,s 变成 "U",总是在 (0, 1) 结束,
所以只有一个不同的最终坐标。

约束条件:

  • 1 <= s.length <= 10^5
  • s 仅由 ‘U’、‘D’、‘L’ 和 ‘R’ 组成
  • 1 <= k <= s.length

解题思路

这道题的关键思路是使用前缀和来高效计算删除任意长度为k的连续子串后的最终坐标。

核心分析:

  1. 将每个移动字符转化为坐标变化:‘U’→(0,1),‘D’→(0,-1),‘L’→(-1,0),‘R’→(1,0)
  2. 如果我们删除区间[i, i+k-1]的子串,最终坐标就是前i个字符的移动 + 后面所有字符的移动
  3. 这等价于:总移动 - 被删除区间的移动

算法步骤:

  1. 计算每个位置的前缀和,得到prefix_x[i]和prefix_y[i]表示前i个字符在x轴和y轴上的累积移动
  2. 对于每个可能的删除起始位置i(0 ≤ i ≤ n-k),计算删除[i, i+k-1]后的最终坐标
  3. 最终坐标 = (总x移动 - 区间x移动, 总y移动 - 区间y移动)
  4. 使用哈希集合记录所有不同的最终坐标

时间复杂度优化: 通过前缀和,我们可以在O(1)时间内计算任意区间的移动量,整体时间复杂度为O(n),比暴力模拟的O(n²)要好得多。

代码实现

class Solution {
public:
    int distinctPoints(string s, int k) {
        int n = s.length();
        vector<int> prefix_x(n + 1, 0), prefix_y(n + 1, 0);
        
        // 计算前缀和
        for (int i = 0; i < n; i++) {
            prefix_x[i + 1] = prefix_x[i];
            prefix_y[i + 1] = prefix_y[i];
            
            if (s[i] == 'U') prefix_y[i + 1]++;
            else if (s[i] == 'D') prefix_y[i + 1]--;
            else if (s[i] == 'L') prefix_x[i + 1]--;
            else if (s[i] == 'R') prefix_x[i + 1]++;
        }
        
        set<pair<int, int>> distinct_points;
        
        // 尝试删除每个长度为k的子串
        for (int i = 0; i <= n - k; i++) {
            int total_x = prefix_x[n];
            int total_y = prefix_y[n];
            int removed_x = prefix_x[i + k] - prefix_x[i];
            int removed_y = prefix_y[i + k] - prefix_y[i];
            
            int final_x = total_x - removed_x;
            int final_y = total_y - removed_y;
            
            distinct_points.insert({final_x, final_y});
        }
        
        return distinct_points.size();
    }
};
class Solution:
    def distinctPoints(self, s: str, k: int) -> int:
        n = len(s)
        prefix_x = [0] * (n + 1)
        prefix_y = [0] * (n + 1)
        
        # 计算前缀和
        for i in range(n):
            prefix_x[i + 1] = prefix_x[i]
            prefix_y[i + 1] = prefix_y[i]
            
            if s[i] == 'U':
                prefix_y[i + 1] += 1
            elif s[i] == 'D':
                prefix_y[i + 1] -= 1
            elif s[i] == 'L':
                prefix_x[i + 1] -= 1
            elif s[i] == 'R':
                prefix_x[i + 1] += 1
        
        distinct_points = set()
        
        # 尝试删除每个长度为k的子串
        for i in range(n - k + 1):
            total_x = prefix_x[n]
            total_y = prefix_y[n]
            removed_x = prefix_x[i + k] - prefix_x[i]
            removed_y = prefix_y[i + k] - prefix_y[i]
            
            final_x = total_x - removed_x
            final_y = total_y - removed_y
            
            distinct_points.add((final_x, final_y))
        
        return len(distinct_points)
public class Solution {
    public int DistinctPoints(string s, int k) {
        int n = s.Length;
        int[] prefixX = new int[n + 1];
        int[] prefixY = new int[n + 1];
        
        // 计算前缀和
        for (int i = 0; i < n; i++) {
            prefixX[i + 1] = prefixX[i];
            prefixY[i + 1] = prefixY[i];
            
            if (s[i] == 'U') prefixY[i + 1]++;
            else if (s[i] == 'D') prefixY[i + 1]--;
            else if (s[i] == 'L') prefixX[i + 1]--;
            else if (s[i] == 'R') prefixX[i + 1]++;
        }
        
        HashSet<(int, int)> distinctPoints = new HashSet<(int, int)>();
        
        // 尝试删除每个长度为k的子串
        for (int i = 0; i <= n - k; i++) {
            int totalX = prefixX[n];
            int totalY = prefixY[n];
            int removedX = prefixX[i + k] - prefixX[i];
            int removedY = prefixY[i + k] - prefixY[i];
            
            int finalX = totalX - removedX;
            int finalY = totalY - removedY;
            
            distinctPoints.Add((finalX, finalY));
        }
        
        return distinctPoints.Count;
    }
}
var distinctPoints = function(s, k) {
    const n = s.length;
    const prefixX = new Array(n + 1).fill(0);
    const prefixY = new Array(n + 1).fill(0);
    
    // 计算前缀和
    for (let i = 0; i < n; i++) {
        prefixX[i + 1] = prefixX[i];
        prefixY[i + 1] = prefixY[i];
        
        if (s[i]

复杂度分析

复杂度类型复杂度说明
时间复杂度O(n)计算前缀和需要O(n),遍历所有可能删除位置需要O(n-k+1),总体为O(n)
空间复杂度O(n)前缀和数组占用O(n)空间,哈希集合最多存储O(n)个不同坐标