每日一题 力扣 2751.机器人碰撞 映射 模拟 栈 C++ 题解

文章目录

题目描述

力扣 2751.机器人碰撞

示例 1:
输入:positions = [5,4,3,2,1], healths = [2,17,9,15,10], directions = "RRRRR"

输出:[2,17,9,15,10]

解释:在本例中不存在碰撞,因为所有机器人向同一方向移动。所以,从第一个机器人开始依序返回健康度,[2, 17, 9, 15, 10] 。
示例 2:

输入:positions = [3,5,2,6], healths = [10,10,15,12], directions = "RLRL"

输出:[14]

解释:本例中发生 2 次碰撞。首先,机器人 1 和机器人 2 将会碰撞,因为二者健康度相同,二者都将被从路线中移除。接下来,机器人 3 和机器人 4 将会发生碰撞,由于机器人 4 的健康度更小,则它会被移除,而机器人 3 的健康度变为 15 - 1 = 14 。仅剩机器人 3 ,所以返回 [14] 。
示例 3:

输入:positions = [1,2,5,6], healths = [10,10,11,11], directions = "RLRL"

输出:[]

解释:机器人 1 和机器人 2 将会碰撞,因为二者健康度相同,二者都将被从路线中移除。机器人 3 和机器人 4 将会碰撞,因为二者健康度相同,二者都将被从路线中移除。所以返回空数组 [] 。
提示:

1 <= positions.length == healths.length == directions.length == n <= 105

1 <= positions[i], healths[i] <= 109

directions[i] == 'L' 或 directions[i] == 'R'

positions 中的所有值互不相同

思路简述

解决本题的核心在于如何高效模拟机器人的碰撞过程。首先需要明确:只有当**位置靠左的机器人向右移动('R')且位置靠右的机器人向左移动('L')**时,两者才会发生碰撞。因此,我们可以按照位置对机器人排序,再结合栈结构来处理碰撞。

最开始上手的时候考虑使用哈希表映射位置与机器人编号,但由于位置是动态变化的(虽然本题中机器人速度相同,碰撞后的位置处理可通过逻辑模拟,无需实际更新位置),哈希表的维护成本较高。因此换用更高效的方式:创建一个索引数组 id,其中 id[i] 表示原始数组的下标(对应题目中的机器人编号为 id[i]+1),然后按照 positions[id[i]] 的大小对 id 进行升序排序。这样,id 数组就将机器人按位置从左到右排列好了。

接下来使用栈(实际用 vector 模拟,方便最后排序)来模拟碰撞过程:

  1. 按位置从左到右遍历每个机器人(即遍历排序后的 id 数组)。
  2. 对于当前机器人,检查栈顶是否存在可能与之碰撞的机器人(即栈顶机器人向右,当前机器人向左)。
  3. 若发生碰撞,比较两者健康度:
    • 若当前机器人健康度更高:栈顶机器人被移除,当前机器人健康度减1,继续检查新的栈顶。
    • 若栈顶机器人健康度更高:当前机器人被 "替换" 为栈顶机器人(并将健康度减 1),继续循环检查新的栈顶(看是否会发生连锁碰撞)。
    • 若健康度相等:两者都被移除(当前机器人ID标记为-1),停止检查。
  4. 若当前机器人未被移除(ID不为-1),将其压入栈中。

最后,将栈中剩余的机器人按原始数组下标进行排序,提取健康度即可得到结果。

代码实现

cpp 复制代码
class Solution {
public:
    vector<int> survivedRobotsHealths(vector<int>& positions, vector<int>& healths, string directions) 
    {
        int n = positions.size();
        // 创建索引数组 id,id[i] 表示原始数组中第 i 个机器人的编号
        vector<int> id(n);
        iota(id.begin(), id.end(), 0); // 初始化 id 为 0, 1, 2, ..., n-1
        
        // 按照机器人的位置 positions 对 id 进行升序排序
        // 排序后,id[0] 是位置最靠左的机器人编号,id[1] 次之,以此类推
        sort(id.begin(), id.end(), [&](int a, int b) {
            return positions[a] < positions[b];
        });

        // 使用 vector 模拟栈,存储幸存的机器人
        // 每个元素为 tuple (原始编号, 健康度, 移动方向)
        vector<tuple<int, int, char>> robotStack;
        
        // 按位置从左到右遍历每个机器人(即遍历排序后的 id 数组)
        for (int i : id) {
            int curId = i;           // 当前机器人在原始数组中的下标
            int curHealth = healths[i]; // 当前机器人的健康度
            char curDir = directions[i]; // 当前机器人的移动方向
            
            // 检查栈顶是否存在可能与当前机器人碰撞的机器人
            while (!robotStack.empty()) {
                auto [topId, topHealth, topDir] = robotStack.back();
                
                // 碰撞条件:栈顶机器人向右('R'),当前机器人向左('L')
                if (topDir == 'R' && curDir == 'L') {
                    robotStack.pop_back(); // 先弹出栈顶机器人,准备处理碰撞
                    
                    if (topHealth > curHealth) {
                        // 栈顶机器人健康度更高:当前机器人被移除,栈顶机器人健康度减1
                        curId = topId;
                        curHealth = topHealth - 1;
                        curDir = topDir;
                        // 此时当前机器人变为原来的栈顶机器人,继续检查新的栈顶
                    } else if (topHealth < curHealth) {
                        // 当前机器人健康度更高:栈顶机器人被移除,当前机器人健康度减1
                        curHealth -= 1;
                        // 继续检查新的栈顶,看是否还会发生碰撞
                    } else {
                        // 两者健康度相等:都被移除,标记当前机器人ID为-1
                        curId = -1;
                        break; // 跳出循环,当前机器人无需入栈
                    }
                } else {
                    // 不会发生碰撞,跳出循环
                    break;
                }
            }
            
            // 如果当前机器人未被移除(ID不为-1),将其压入栈中
            if (curId != -1) {
                robotStack.emplace_back(curId, curHealth, curDir);
            }
        }
        
        // 按原始数组下标对栈中机器人排序,以便按输入顺序返回健康度
        sort(robotStack.begin(), robotStack.end());
        
        // 提取幸存机器人的健康度,按原始编号顺序排列
        vector<int> result;
        for (auto& [id, health, dir] : robotStack) {
            result.push_back(health);
        }
        
        return result;
    }
};

复杂度分析

  • 时间复杂度:O(n log n)

    • 对索引数组 id 排序的时间复杂度为 O(n log n)。
    • 遍历机器人的过程中,每个机器人最多入栈和出栈一次,总时间复杂度为 O(n)。
    • 最后对栈中机器人按原始编号排序的时间复杂度为 O(k log k),其中 k 是幸存机器人数量(k ≤ n)。
    • 总体时间复杂度由排序操作主导,为 O(n log n)。
  • 空间复杂度:O(n)

    • 索引数组 id 占用 O(n) 空间。
    • robotStack 最坏情况下(无碰撞)占用 O(n) 空间。
    • 结果数组 result 占用 O(k) 空间(k ≤ n)。
    • 总体空间复杂度为 O(n)。

踩坑记录

  1. 机器人编号与位置的映射 :最初纠结如何高效关联机器人的原始编号和位置,后来发现使用索引数组 id 并按位置排序是最简洁的方式。这里需要特别注意:题目中机器人编号是从 1 到 n 的,但代码中我们使用的是数组下标(0 到 n-1),两者存在一个差值(编号 = 下标 + 1)。虽然这个差值不影响核心逻辑实现(因为最后我们只需要按原始输入顺序返回结果),但在梳理思路时注意到这个对应关系,能有效避免思维混乱。
  2. 碰撞条件的判断:必须明确只有**位置靠左的机器人向右('R')且位置靠右的机器人向左('L')**时才会发生碰撞,方向相反或位置顺序不对都不会碰撞。
  3. 结果的顺序要求 :题目要求按原始机器人编号顺序返回健康度,因此最后必须对栈中幸存机器人按 id 排序,否则结果值正确但顺序错误。
  4. tuple 与 emplace_back 的使用 :使用 tuple 存储机器人信息时,emplace_back 可以直接构造 tuple 元素,比先创建再 push 更高效,要熟悉这两个的用法。

如果这篇博客对你有帮助,别忘了点赞支持一下~也可以收藏起来,方便后续刷题复习时随时翻看。要是能顺手点个关注,爱弥斯还能得到漂泊者批准的游戏时间哦!

相关推荐
果果燕2 小时前
ARM嵌入式学习(四)--- C语言应用:led、beep、key
linux·运维·算法
源码之家2 小时前
计算机毕业设计:基于Python的二手车数据分析可视化系统 Flask框架 可视化 时间序列预测算法 逻辑回归 requests 爬虫 大数据(建议收藏)✅
大数据·hadoop·python·算法·数据分析·flask·课程设计
liuyao_xianhui2 小时前
优选算法_岛屿数量_floodfill算法)_bfs_C++
java·开发语言·数据结构·c++·算法·链表·宽度优先
羊小蜜.2 小时前
Mysql 04: 子查询——5 大核心用法
数据库·mysql·算法·子查询
深邃-2 小时前
字符函数和字符串函数(2)
c语言·数据结构·c++·后端·算法·restful
bekote2 小时前
PTA基础编程题目集-6-11 求自定类型元素序列的中位数(简单解法)
数据结构·c++·算法
米粒112 小时前
力扣算法刷题 Day 27
算法·leetcode·职场和发展
Fuxiao___12 小时前
C 语言核心知识点讲义(循环 + 函数篇)
算法·c#
Mr_Xuhhh13 小时前
LeetCode hot 100(C++版本)(上)
c++·leetcode·哈希算法