2025-12-04-LeetCode刷题笔记-2211-统计道路上的碰撞次数

题目信息


题目描述

在一条无限长的公路上,有 n 辆车,给你一个下标从 0 开始的字符串 directions,长度为 ndirections[i] 可以是 'L''R''S',分别表示第 i 辆车是向左、向右或静止。所有移动的车辆都以相同速度行驶。

碰撞规则如下:

  • 'R''L' 相撞:碰撞次数增加 2,两车都变为 'S'
  • 移动车辆和 'S' 相撞:碰撞次数增加 1,移动车辆变为 'S'

计算所有碰撞发生后,总的碰撞次数。


算法分析

  • 核心思想:识别并排除那些永远不会发生碰撞的车辆。

    1. 最左侧连续向左行驶的车辆(L)永远不会与任何车辆碰撞,因为它们前方没有车。
    2. 最右侧连续向右行驶的车辆(R)也永远不会与任何车辆碰撞,因为它们后方没有车。
  • 解题步骤

    1. "修剪"字符串 :我们可以逻辑上(或实际上)移除 directions 字符串开头所有的 'L' 和末尾所有的 'R'
    2. 统计碰撞 :在"修剪"后的中间部分,任何移动的车辆(即非 'S' 的车辆)最终都将参与至少一次碰撞并变为静止。
      • 一个向右的 'R' 会一直向右行驶,直到遇到一个 'S' 或一个向左的 'L',然后停下来。
      • 一个向左的 'L' 会一直向左行驶,直到遇到一个 'S' 或一个已经停下的 'R',然后停下来。
    3. 计算结果 :因此,中间部分中每个 'L''R' 都会贡献 1 次碰撞。我们只需要统计这个中间区域中非 'S' 车辆的数量即可。
  • 时间复杂度:O(n),其中 n 是字符串的长度。我们需要遍历字符串来找到中间区域并进行计数。

  • 空间复杂度:O(1)(如果使用指针/索引)或 O(n)(如果创建了新的子字符串)。


代码实现

方案一:指针/索引法 (C++)

cpp 复制代码
class Solution {
public:
    int countCollisions(string directions) {
        int n = directions.size();

        // 找到第一个不为 'L' 的车
        int l = 0;
        while (l < n && directions[l] == 'L')
            ++l;
        
        // 找到最后一个不为 'R' 的车
        int r = n - 1;
        while (r >= l && directions[r] == 'R')
            --r;

        int ans = 0;
        // 统计中间区域非 'S' 的车辆数
        for (int i = l; i <= r; ++i) {
            if (directions[i] != 'S')
                ++ans;
        }
        return ans;
    }
};

方案二:字符串处理 (Python & C++20)

这种方法更简洁,直接利用语言的内置或库函数功能。

Python:

python 复制代码
class Solution:
    def countCollisions(self, directions: str) -> int:
        # 移除前导 'L' 和后导 'R'
        trimmed_directions = directions.lstrip("L").rstrip("R")
        # 统计剩余部分中 'L' 和 'R' 的总数
        return len(trimmed_directions) - trimmed_directions.count("S")

C++20 Ranges:

cpp 复制代码
#include <string>
#include <algorithm>
#include <ranges>

class Solution {
public:
    int countCollisions(std::string directions) {
        // C++20 Ranges a bit verbose for this, but demonstrates the concept
        auto view = directions | std::views::drop_while([](char c){ return c == 'L'; });
        
        std::string temp;
        std::ranges::copy(view, std::back_inserter(temp));
        
        auto rview = temp | std::views::reverse | std::views::drop_while([](char c){ return c == 'R'; });
        
        int count = 0;
        for (char c : rview) {
            if (c != 'S') {
                count++;
            }
        }
        return count;
    }
};

方案三:查找边界 (Rust)

rust 复制代码
impl Solution {
    pub fn count_collisions(directions: String) -> i32 {
        let s = directions.as_bytes();
        let n = s.len();
        
        let mut l = 0;
        while l < n && s[l] == b'L' {
            l += 1;
        }

        let mut r = n;
        while r > l && s[r - 1] == b'R' {
            r -= 1;
        }
        
        let mut count = 0;
        for i in l..r {
            if s[i] != b'S' {
                count += 1;
            }
        }
        count
    }
}

方案四:单次遍历模拟 (Go)

这种方法不先"修剪"字符串,而是在一次遍历中通过状态机或计数器来模拟碰撞过程。

go 复制代码
// 模拟: flag 记录右行车的数量
func countCollisions(directions string) int {
	ans := 0
	flag := -1 // -1: 没有遇到R; 0: 遇到S或碰撞后的静止车队; >0: 连续R的数量
	for _, c := range directions {
		if c == 'L' {
			if flag >= 0 { // 如果左边有R车或S车
				ans += flag + 1 // L与R车队碰撞(flag辆R+1辆L)或与S碰撞(1辆L)
				flag = 0      // 碰撞后形成静止车队
			}
		} else if c == 'S' {
			if flag > 0 { // R车队撞上S
				ans += flag
			}
			flag = 0 // 形成或加入静止车队
		} else { // c == 'R'
			if flag >= 0 {
				flag++
			} else {
				flag = 1
			}
		}
	}
	return ans
}

总结与反思

  1. 问题简化:此题的关键在于正确地简化问题模型。通过识别问题的"边界条件"(即永不碰撞的车辆),可以将一个看似复杂的模拟问题转化为一个简单的计数问题(如方案一、二、三)。这是解决此类问题的常用技巧。
  2. 多种实现:同样的核心逻辑可以通过不同的编程范式实现。Python 的字符串处理函数提供了非常简洁的写法。C++ 和 Rust 的指针/索引法则提供了更底层的控制。C++20 的 Ranges 库也提供了声明式的处理方式,尽管在本例中可能比传统循环更冗长。
  3. 模拟法对比 :直接的单次遍历模拟(如方案四的 Go 实现)是另一种有效的思路。它不依赖于先"修剪"掉永不碰撞的车辆,而是在遍历过程中动态计算碰撞。这种方法通常需要维护更复杂的状态(如此处的 flag 变量),但避免了多次遍历或创建子字符串。对于此题,"修剪"法在逻辑上更直观、代码更简单。
相关推荐
参.商.2 分钟前
【Day43】49. 字母异位词分组
leetcode·golang
m0_5698814711 分钟前
C++中的装饰器模式变体
开发语言·c++·算法
笒鬼鬼12 分钟前
【API接口】最新可用红果短剧接口
算法·api·笒鬼鬼·红果短剧·接口源码
weixin_4219226913 分钟前
C++与边缘计算
开发语言·c++·算法
2401_8319207416 分钟前
C++编译期数组操作
开发语言·c++·算法
殷紫川22 分钟前
秒杀系统高并发核心优化与落地全指南
算法·架构
野犬寒鸦34 分钟前
JVM垃圾回收机制面试常问问题及详解
java·服务器·开发语言·jvm·后端·算法·面试
风酥糖40 分钟前
Godot游戏练习01-第16节-游戏中的状态机
算法·游戏·godot
参.商.43 分钟前
【Day45】647. 回文子串 5. 最长回文子串
leetcode·golang
budingxiaomoli44 分钟前
优选算法--优先级队列(堆)
算法