|
1 | 1 | # [Problem 2751: Robot Collisions](https://leetcode.com/problems/robot-collisions/description/?envType=daily-question)
|
2 | 2 |
|
3 | 3 | ## Initial thoughts (stream-of-consciousness)
|
| 4 | +- Oooh, our first "hard" rated problem so far! And with robots! 🤖➡️ ⬅️🤖 |
| 5 | +- Ok, so I'm seeing two potential ways to solve this off the top of my head: |
| 6 | + - Running out the full simulation and keeping track of everything. This would work for small-scale versions of the problem, but it'd be terribly inefficient. In each timestep we need to keep track of potential collisions between every pair of robots. So the runtime would be something like $O(dn^2)$, where $d$ is the max position ($10^9$) and $n$ is the number of robots, which could be up to $10^5$. Clearly this isn't going to scale. |
| 7 | + - Some sort of heap-based solution. What makes me think of heaps is that we know the positions come in unsorted, so we need some way of "sorting" the robots. Also, robots moving left will only colide with robots moving right (and vice versa). For robots moving left, anything to the left of the leftmost right-moving robot will remain unchanged (never collide). Similarly, any right-moving robot to the right of the rightmost left-moving robot will remain unchanged. The collisions happen between...what? Let's see... |
| 8 | + - We know that right-moving robots will never collide with each other, and left-moving robots will never collide with each other |
| 9 | + - So we only have to compare positions *between* right- and left-moving robots |
| 10 | + - Well...hmm |
| 11 | +- Suppose that the robot positions *were* sorted. (We could sort them in $O(n \log n)$ time if needed...) |
| 12 | + - We could do something using a stack, looping through each robot in turn: |
| 13 | + - If the stack is empty, push the next robot and continue |
| 14 | + - Otherwise, we want to do something like the following, looping until either the stack is empty or the robot crashes: |
| 15 | + - Check the top of the stack |
| 16 | + - If the robot there is moving in the same direction, or if the robots are moving in opposite directions but have already passed each other, pop the top of the stack (and...probably put that robot in...another stack?) and then break |
| 17 | + - If the robots are going to crash (opposite drections and haven't passed each other yet): |
| 18 | + - If the top-of-stack robot has more health, remove the current robot and break. Probably update the position of the top-of-stack robot (to the current robot's position? to some mid point between the two robots' positions? hmm...) |
| 19 | + - If the current robot has more health, pop the top of the stack (and delete that robot), decrement the current robot's health by 1, possibly update the current robot's position (in some way we still need to figure out), and then continue |
| 20 | + - Then at the end, re-sort the remaining robots and return their health values. We could represent each robot as a vector, `x`, where `x[0]` is the robot's position, `x[1]` is the robot's direction, and `x[2]` is the robot's health. I'm not sure yet if we'll need to track it, but it might be useful to also have `x[3]` be the time step for that robot (e.g., the number of positions moved from its starting position). That time step property could (maybe?) come in handy if we had to account for updates that occurred out of order...although at the moment I'm not sure that would actually be possible. |
| 21 | +- Since the inputs *aren't* sorted, I was thinking we might want to have one heap (a max heap?) for robots moving to the right, indexed by position. The other heap (a min heap?) would be for left-moving robots, again indexed by position. Then we could do something like: |
| 22 | + - If the left-most left moving robot...eh...I'm having trouble thinking this through. Could we compare its position with the top of the right-moving robot heap? |
| 23 | +- Hmm... Maybe let's take a step back. Ideas so far that seem promising: |
| 24 | + - I still think the full simulation will be too inefficient, so let's nix that idea officially |
| 25 | + - I can't think of how the heap idea would actually work, so let's nix that too |
| 26 | + - I think the stack idea is promising. Now how do we actually implement it... 🤔 |
| 27 | + - I also think we're going to need to sort the positions. |
| 28 | + - I like the "robots as a vector" idea. Robots could also be represented as dicts. Probably lists/vectors require less memory? |
| 29 | +- Ok, so let's go back to assuming the robot positions are now sorted, and each robot is a vector. How might this stack idea work... |
| 30 | + - Let's go through each robot in turn. We know we're starting with the left-most robot and moving right along the track: |
| 31 | + - If the robot is moving _right_, it's not going to collide with anything pushed on the stack so far (because everything will be to its left and moving at the same speed), so let's push it to the stack and continue. |
| 32 | + - If the robot is moving _left_, then it might crash into anything moving right that's in the stack. Let's check the top of the stack: |
| 33 | + - If that top robot is *also* moving left, then we can push our new robot to the top of the stack (they're not going to collide) |
| 34 | + - While whatever robot is on top is moving _right_, then they're going to crash: the top-of-stack robot (`a`) is to the left of the new robot (`b`) and moving right, and `b` is to the right of `a` and moving left. So: |
| 35 | + - if `a.health > b.health` then `b` disappears and we should decrement `a.health` by 1. Note: can health go negative? If it does, what happens? |
| 36 | + - If `a.health < b.health` then we should pop `a`, decrement `b.health` by 1 (again, potential negative health issue!), and continue checking for the next top-of-stack robot |
| 37 | + - If `a.health == b.health` then pop `a` and break (i.e., `b` gets deleted) |
| 38 | + - If the stack is every empty, or if the top robot is moving left, then push `b` to the top of the stack |
| 39 | +- I think this could work; let's go with it... |
4 | 40 |
|
5 | 41 | ## Refining the problem, round 2 thoughts
|
| 42 | +- A few issues to sort out still: |
| 43 | + - Can health ever go negative? And if so, what happens? |
| 44 | + - Suppose two robots, `a` and `b`, are about to collide. If they have never collided with another robot, then their health values are at least 1. |
| 45 | + - If both have health of 1, then they both disappear. |
| 46 | + - If only 1 has a health of 1, it dies and the other robot survives (since its health must be bigger than 1) |
| 47 | + - In the *next* collision, whatever robot it collides with *also* must have a health of at least 1 (since it has *also* either never collided, or survived with at least 1 health before that point). |
| 48 | + - So actually, health can never be negative |
| 49 | + - With the stack idea, I think we can figure out which robots survive and what their final health values are. However, they may not end up in the same orders they started in, since the positions could be out of sorted order. So I think we need to track each robots position in the _sequence_ as well as its position along the track. In the last step (before we return the answer) we'll need to sort by sequence position. That will take $O(n \log n)$ time, but that's fine since we've already paid that cost with our initial sort. |
| 50 | + - I'm still somewhat concerned about runtime. Suppose we have something like R R R R ... R R R R L L L ... L L L L. All of those R-moving robots get pushed on the stack. But now, for each L robot, of which there are $O(n)$ we're going to need to test *every* R robot, of which there are _also_ $O(n)$. So this might be an $O(n^2)$ algorithm, which could be too slow. In practice we won't actually quite hit $n^2$ steps, since robots will be eliminated (and therefore either it's and R robot and won't need to be re-checked, or it's an L robot and won't need to be checked against the remaining R robots) as we progress. |
| 51 | + - At the moment I'm not sure how to improve on this...so again, let's maybe just go with it and see what happens... |
6 | 52 |
|
7 | 53 | ## Attempted solution(s)
|
8 | 54 | ```python
|
9 |
| -class Solution: # paste your code here! |
10 |
| - ... |
| 55 | +class Solution: |
| 56 | + def survivedRobotsHealths(self, positions: List[int], healths: List[int], directions: str) -> List[int]: |
| 57 | + bots = sorted(zip(positions, directions, healths, range(len(positions))), key=lambda x: x[0]) |
| 58 | + |
| 59 | + stack = [] |
| 60 | + for pos, dir, health, i in bots: |
| 61 | + if len(stack) == 0 or dir == 'R': |
| 62 | + stack.append([pos, dir, health, i]) |
| 63 | + else: # dir == 'L' |
| 64 | + while health > 0 and len(stack) > 0 and stack[-1][1] == 'R': # collision... |
| 65 | + if health > stack[-1][2]: # new robot survives, old dies |
| 66 | + health -= 1 |
| 67 | + stack.pop() |
| 68 | + elif health < stack[-1][2]: # new robot dies, old survives |
| 69 | + stack[-1][2] -= 1 |
| 70 | + health = 0 |
| 71 | + else: # both die |
| 72 | + health = 0 |
| 73 | + stack.pop() |
| 74 | + |
| 75 | + if health > 0: |
| 76 | + stack.append([pos, dir, health, i]) |
| 77 | + |
| 78 | + return [x[2] for x in sorted(stack, key=lambda x: x[3])] |
11 | 79 | ```
|
| 80 | +- Given test cases: pass |
| 81 | +- I'm almost out of time for the night, so I'm just going to make up a some totally random test cases in the hopes that if I'm missing an edge case it'll show up there: |
| 82 | + - `positions, healths, directions = [67, 234, 51, 7, 45678, 2, 65, 46, 90, 101, 21, 44, 45, 6, 1], [1, 788, 54, 6, 3, 3, 456, 765, 234, 1, 700, 876, 34, 54, 94], "RLRRLRRLLRLLLRL"`: pass(!!) |
| 83 | + - `positions, healths, directions = [67, 234, 51, 7, 45678, 2, 65, 46, 90, 101, 21, 44, 45, 6, 1], [1, 788, 54, 6, 3, 3, 456, 765, 234, 1, 700, 876, 34, 54, 94], "RLLLRRLLRLLLRRL"`: pass |
| 84 | +- Ok...I'll just submit this; I can't think of any obvious remaining edge cases. My main concern is that the runtime is still bad, but I also can't think of an obvious faster solution 🙃. |
| 85 | + |
| 86 | + |
| 87 | + |
| 88 | +Well alrighty then! I'll call that done! |
| 89 | + |
0 commit comments