问数轴上有n个小球,每个有它的运动速度(向左或向右)和位置,小球碰撞(无论是相向撞还是追尾都算)这两个小球就会消失,怎么快速的计算出最后一对小球碰撞的时间?

总述

前几天面试的时候被问到了这个问题,当时没有get到面试官的考察思路,导致无从下手,面试结束后我仔细思考了一下这道题,发现其中的过程和细节还是挺多的,所以记录下这道题的解题思路和代码。

分析

这道题因为涉及了n个球和随机方向、位置,比较抽象不好整理逻辑,所以最好是自己写一个测试用例,在纸上推演一遍再想思路比较好,我当时就没有从简单的情况开始考虑,感觉是紧张了。
小球碰撞有个特点,就是在最开始的情景下,第一次发生碰撞的一定是相邻的小球,这意味着我们在计算时可以将小球按照位置排序,然后两两计算相遇时间,我们的第一次碰撞时间一定在这第一次遍历中能够得到,接下来就是计算相撞的两个小球之外的球的碰撞时间了,根据前面的思路,第一组小球假设为i,j,两个小球相撞时间最短,然后i和j消失,那么此时下一次碰撞的两个小球一定还是去除了i和j之后相邻的小球,这里显然并不需要重新计算所有的相邻小球的碰撞时间,因为只有i - 1 和j + 1从不相邻变得相邻了,我们只需要更新这一对小球的碰撞时间进去,那么我们怎么持续的得到所有时间里最短的碰撞时间呢?
这里考虑使用一个小顶堆,根据碰撞时间来升序即可,那我们i和j碰撞之后,是不是还要把队列里面i和i-1,j和j+1的碰撞时间去掉?其实也不用,用一个额外的数组标记碰撞过的小球就可以了。
这样一来,我们大概可以先写出两个结构体:

struct Ball
{
    int velocity; // 速度
    int pos; // 位置
};

struct CollisionEvent
{
    double time;
    int ball1;
    int ball2;
    bool operator>(const CollisionEvent& other) const
    {
        return time > other.time;
    }
};

然后是计算两球相撞消耗的时间:

// 确定会碰撞的两球的碰撞时间
double CalculateCollisionTime(const Ball& ball1, const Ball& ball2)
{
    // 确保相向或追尾才能发生碰撞
    int distance = ball2.pos - ball1.pos;
    int relativeVelocity = ball1.velocity - ball2.velocity;

    // 相向碰撞的情况
    if ((ball1.velocity > 0 && ball2.velocity < 0 && ball1.pos < ball2.pos) ||
        (ball1.velocity < 0 && ball2.velocity > 0 && ball1.pos > ball2.pos)) {
        relativeVelocity = abs(ball1.velocity) + abs(ball2.velocity);
        return static_cast<double>(abs(distance)) / relativeVelocity;
    }

    // 同向追尾的情况
    if ((ball1.velocity > ball2.velocity && ball1.pos < ball2.pos && ball1.velocity > 0) ||
        (ball1.velocity < ball2.velocity && ball1.pos > ball2.pos && ball1.velocity < 0)) {
        relativeVelocity = abs(ball1.velocity - ball2.velocity);
        return static_cast<double>(abs(distance)) / relativeVelocity;
    }

    // 如果不会发生碰撞,返回 infinity
    return numeric_limits<double>::infinity();
}

接下来可以开始写主要的逻辑,那么对小球位置排序时,如果位置相同的两个小球该如何排序?根据推理,应该是速度更慢的小球在左边,速度更快的小球在右边。

// 将小球按位置排序,如果位置相同,则速度小的排在前面
sort(balls.begin(), balls.end(), [](const Ball& a, const Ball& b) {
    return a.pos == b.pos ? abs(a.velocity) < abs(b.velocity) : a.pos < b.pos;
    });

初始化一个最小堆

// 初始化一个最小堆来存储碰撞事件
priority_queue<CollisionEvent, vector<CollisionEvent>, greater<CollisionEvent>> minHeap;

针对初始状态,两两计算相邻小球的可能碰撞时间,目的是为了找出最早碰撞的时间,这个最早碰撞的时间很关键,并且一定是实际发生了碰撞,初次之外在这里计算的其他时间都有可能实际上没碰着

for (int i = 0; i < balls.size() - 1; ++i) {
    double time = CalculateCollisionTime(balls[i], balls[i + 1]);
    if (std::isfinite(time)) {
        minHeap.push({ time, i, i + 1 }); // 列表初始化
        cout << time << ' ' << i << ' ' << balls[i].pos<< endl;
    }
}

接下来根据最小堆的内容开始pop最短时间的碰撞,根据前面的思路,我们要记录已经碰撞的小球,因为这两个小球已经消失了,他们参与的其他可能的碰撞事件应该无效了,并且我们应该只需要更新这组已消失小球两侧的小球的碰撞

vector<bool> active(balls.size(), true);  // 标记小球是否仍在场
double lastCollisionTime = 0;

// 处理碰撞事件
while (!minHeap.empty())
{
    CollisionEvent ce = minHeap.top();
    cout << ce.ball1 << ' ' << ce.ball2 << endl;
    minHeap.pop();

    // 如果小球已消失,跳过该事件
    if (!active[ce.ball1] || !active[ce.ball2]) continue;

    lastCollisionTime = ce.time;
    // 将小球 i 和 j 标记为消失
    active[ce.ball1] = false;
    active[ce.ball2] = false;

    // 更新相邻的有效小球的碰撞事件
    int left = ce.ball1 - 1;
    int right = ce.ball2 + 1;

    cout << left << ' ' << right << endl;

    // 仅检查活跃小球对 `left` 和 `right` 之间的碰撞
    if (left >= 0 && active[left] && right < balls.size() && active[right]) {
        double time = CalculateCollisionTime(balls[left], balls[right]);
        if (isfinite(time)) {
            minHeap.push({ time, left, right });
        }
    }
}

return lastCollisionTime;

这样就基本完成了这道题目,完整代码如下,附带一个小的测试用例,可能不全面,因为这道题我没在网上见到过:

#include<iostream>
#include<queue>
#include<vector>

using namespace std;

struct Ball
{
    int velocity; // 速度
    int pos; // 位置
};

struct CollisionEvent
{
    double time;
    int ball1;
    int ball2;
    bool operator>(const CollisionEvent& other) const
    {
        return time > other.time;
    }
};

// 确定会碰撞的两球的碰撞时间
double CalculateCollisionTime(const Ball& ball1, const Ball& ball2)
{
    // 确保相向或追尾才能发生碰撞
    int distance = ball2.pos - ball1.pos;
    int relativeVelocity = ball1.velocity - ball2.velocity;

    // 相向碰撞的情况
    if ((ball1.velocity > 0 && ball2.velocity < 0 && ball1.pos < ball2.pos) ||
        (ball1.velocity < 0 && ball2.velocity > 0 && ball1.pos > ball2.pos)) {
        relativeVelocity = abs(ball1.velocity) + abs(ball2.velocity);
        return static_cast<double>(abs(distance)) / relativeVelocity;
    }

    // 同向追尾的情况
    if ((ball1.velocity > ball2.velocity && ball1.pos < ball2.pos && ball1.velocity > 0) ||
        (ball1.velocity < ball2.velocity && ball1.pos > ball2.pos && ball1.velocity < 0)) {
        relativeVelocity = abs(ball1.velocity - ball2.velocity);
        return static_cast<double>(abs(distance)) / relativeVelocity;
    }

    // 如果不会发生碰撞,返回 infinity
    return numeric_limits<double>::infinity();
}

double FindLastCollisionTime(vector<Ball> balls)
{
    if (balls.size() < 2) {
        return 0;  // 没有碰撞
    }

    // 将小球按位置排序,如果位置相同,则速度小的排在前面
    sort(balls.begin(), balls.end(), [](const Ball& a, const Ball& b) {
        return a.pos == b.pos ? abs(a.velocity) < abs(b.velocity) : a.pos < b.pos;
        });

    // 初始化一个最小堆来存储碰撞事件
    priority_queue<CollisionEvent, vector<CollisionEvent>, greater<CollisionEvent>> minHeap;
    vector<bool> active(balls.size(), true);  // 标记小球是否仍在场

    for (int i = 0; i < balls.size() - 1; ++i) {
        double time = CalculateCollisionTime(balls[i], balls[i + 1]);
        if (std::isfinite(time)) {
            minHeap.push({ time, i, i + 1 }); // 列表初始化
            cout << time << ' ' << i << ' ' << balls[i].pos<< endl;
        }
    }

    double lastCollisionTime = 0;

    // 处理碰撞事件
    while (!minHeap.empty())
    {
        CollisionEvent ce = minHeap.top();
        cout << ce.ball1 << ' ' << ce.ball2 << endl;
        minHeap.pop();

        // 如果小球已消失,跳过该事件
        if (!active[ce.ball1] || !active[ce.ball2]) continue;

        lastCollisionTime = ce.time;
        // 将小球 i 和 j 标记为消失
        active[ce.ball1] = false;
        active[ce.ball2] = false;

        // 更新相邻的有效小球的碰撞事件
        int left = ce.ball1 - 1;
        int right = ce.ball2 + 1;

        cout << left << ' ' << right << endl;

        // 仅检查活跃小球对 `left` 和 `right` 之间的碰撞
        if (left >= 0 && active[left] && right < balls.size() && active[right]) {
            double time = CalculateCollisionTime(balls[left], balls[right]);
            if (isfinite(time)) {
                minHeap.push({ time, left, right });
            }
        }
    }

    return lastCollisionTime;
}

int main()
{
    // 示例:5个小球,位置和速度如下
    vector<Ball> balls = { {0, 2}, {2, 1}, {5, -3}, {3, 2}, {6, -1} };

    // 计算最后一对小球碰撞的时间
    cout << "Last collision time: " << FindLastCollisionTime(balls) << endl;
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FangYwang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值