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 变量),但避免了多次遍历或创建子字符串。对于此题,"修剪"法在逻辑上更直观、代码更简单。
相关推荐
listhi5201 小时前
激光雷达点云拟合中的ICP(迭代最近点)算法
算法
三块可乐两块冰1 小时前
【第二十二周】机器学习笔记二十一
人工智能·笔记·机器学习
IMPYLH2 小时前
Lua 的 type 函数
开发语言·笔记·后端·junit·lua
持续学习的程序员+12 小时前
强化学习阶段性总结
人工智能·算法
YANshangqian2 小时前
QOwnNotes(事务笔记管理)
笔记
爱装代码的小瓶子2 小时前
【cpp知识铺子】map与set的底层AVL树
开发语言·数据结构·c++·b树·算法·链表
IT·小灰灰2 小时前
腾讯HY2.0 Think推理模型深度解析:技术突破、应用场景与实践指南
开发语言·人工智能·python·深度学习·神经网络·算法·数据分析
思成不止于此2 小时前
MySQL 查询基础(一):列选择、算数运算与别名使用
数据库·笔记·sql·学习·oracle
修炼地2 小时前
代码随想录算法训练营第二十八天 | 动态规划理论基础、509. 斐波那契数、70. 爬楼梯、746. 使用最小花费爬楼梯
c++·算法·动态规划