Quick Overview

An object on a grid changes direction whenever a timestamped action arrives and moves one cell per time unit until a blocked cell or the grid edge stops it. The task asks for its cell at a given end time, testing precise reading of the rules, including that dot cells are obstacles, and efficient handling of very large timestamps.

Final Grid Position Under Timestamped Direction Changes With Dot Obstacles

Company: Hudson River Trading

Role: Software Engineer

Category: Coding & Algorithms

Difficulty: hard

Interview Round: Technical Screen

An object sits on a rectangular grid. Over time, a list of timestamped actions sets the direction in which it travels: up, down, left or right. While a direction is set, the object moves one cell per time unit in that direction for as long as the next cell is open. When the next cell is blocked or outside the grid, the object waits in place until a later action changes its direction. Given the grid, the starting cell, the actions and an end time, return the cell the object occupies at the end time. Read the grid legend carefully: a dot `'.'` marks a **blocked** cell that the object can never enter, and `'o'` marks an open cell. ### Function Signature ```python def final_position(grid: list[str], start: list[int], actions: list[tuple[int, str]], end_time: int) -> list[int]: ``` `start` is `[row, col]`. Each action is `(time, direction)`. ### Rules - `grid[r][c]` is `'o'` for an open cell and `'.'` for a blocked cell. - Row `0` is the top row and column `0` is the leftmost column. From `(row, col)`, `"up"` leads to `(row - 1, col)`, `"down"` to `(row + 1, col)`, `"left"` to `(row, col - 1)` and `"right"` to `(row, col + 1)`. - The object is at rest from time `0` until the first action. An action `(t, d)` sets the direction to `d` at time `t`, and that direction stays in effect until the next action. - Time advances in whole units. For each unit `s = 0, 1, ..., end_time - 1`, the direction during that unit is the one set by the last action with `time <= s`; if there is no such action, the object does not move during that unit. If a direction is set and the neighboring cell in that direction is inside the grid and open, the object moves into it at the end of the unit. Otherwise it stays where it is. - Cells outside the grid behave like blocked cells, so the object never leaves the grid. - Actions with `time >= end_time` have no effect. - Return the object's cell `[row, col]` at time `end_time`. If `end_time` is `0`, this is `start`. ### Constraints - `1 <= len(grid) <= 500` and `1 <= len(grid[0]) <= 500`; all rows have the same length, and every character is `'o'` or `'.'`. - `start` is an open cell inside the grid. - `0 <= len(actions) <= 10^5` - `0 <= time <= 10^9` for every action, and action times are strictly increasing. - Every direction is one of `"up"`, `"down"`, `"left"` and `"right"`. - `0 <= end_time <= 10^9` ### Examples **Example 1** ```text Input: grid = ["ooooo", "o.o.o", "ooooo"], start = [0, 0], actions = [(0, "right"), (3, "down"), (5, "left")], end_time = 7 Output: [0, 1] ``` During units 0 to 2 the object moves right to `(0, 3)`. From unit 3 the direction is down, but `(1, 3)` is a dot, so it is blocked and the object waits during units 3 and 4. During units 5 and 6 it moves left to `(0, 2)` and then `(0, 1)`. **Example 2** ```text Input: grid = ["ooooo", "o.o.o", "ooooo"], start = [2, 4], actions = [(2, "left"), (4, "up"), (50, "right")], end_time = 60 Output: [0, 4] ``` Units 0 and 1 have no direction, so the object stays at `(2, 4)`. During units 2 and 3 it moves left to `(2, 2)`. From unit 4 it moves up through the open cell `(1, 2)` to `(0, 2)`, and from unit 6 the top edge stops it. From unit 50 it moves right to `(0, 3)` and `(0, 4)`, and the right edge stops it from unit 52 until the end. **Example 3** ```text Input: grid = ["ooo"], start = [0, 2], actions = [(0, "left"), (1, "right"), (3, "left")], end_time = 3 Output: [0, 2] ``` During unit 0 the object moves left to `(0, 1)`. During unit 1 it moves right back to `(0, 2)`. During unit 2 the edge of the grid blocks it. The action at time 3 is not before `end_time`, so it has no effect.

Overview: An object on a grid changes direction whenever a timestamped action arrives and moves one cell per time unit until a blocked cell or the grid edge stops it. The task asks for its cell at a given end time, testing precise reading of the rules, including that dot cells are obstacles, and efficient handling of very large timestamps.

Read the full Hudson River Trading Software Engineer interview experience this question came from

An object sits on a rectangular grid. Over time, a list of timestamped actions sets the direction in which it travels: up, down, left or right. While a direction is set, the object moves one cell per time unit in that direction for as long as the next cell is open. When the next cell is blocked or outside the grid, the object waits in place until a later action changes its direction. Given the grid, the starting cell, the actions and an end time, return the cell the object occupies at the end time. Read the grid legend carefully: a dot `'.'` marks a **blocked** cell that the object can never enter, and `'o'` marks an open cell. Implement `final_position(grid, start, actions, end_time)`. `grid` is a list of equal-length strings, `start` is `[row, col]`, each element of `actions` is a pair `(time, direction)`, and `end_time` is an integer. Return the final cell as a two-element list `[row, col]`. ### Rules - `grid[r][c]` is `'o'` for an open cell and `'.'` for a blocked cell. - Row `0` is the top row and column `0` is the leftmost column. From `(row, col)`, `"up"` leads to `(row - 1, col)`, `"down"` to `(row + 1, col)`, `"left"` to `(row, col - 1)` and `"right"` to `(row, col + 1)`. - The object is at rest from time `0` until the first action. An action `(t, d)` sets the direction to `d` at time `t`, and that direction stays in effect until the next action. - Time advances in whole units. For each unit `s = 0, 1, ..., end_time - 1`, the direction during that unit is the one set by the last action with `time <= s`; if there is no such action, the object does not move during that unit. If a direction is set and the neighboring cell in that direction is inside the grid and open, the object moves into it at the end of the unit. Otherwise it stays where it is. - Cells outside the grid behave like blocked cells, so the object never leaves the grid. - Actions with `time >= end_time` have no effect. - Return the object's cell `[row, col]` at time `end_time`. If `end_time` is `0`, this is `start`. ### Constraints - `1 <= len(grid) <= 500` and `1 <= len(grid[0]) <= 500`; all rows have the same length, and every character is `'o'` or `'.'`. - `start` is an open cell inside the grid. - `0 <= len(actions) <= 10^5` - `0 <= time <= 10^9` for every action, and action times are strictly increasing. - Every direction is one of `"up"`, `"down"`, `"left"` and `"right"`. - `0 <= end_time <= 10^9` - No value exceeds `2^31 - 1`: times, `end_time`, rows and columns all fit in a signed 32-bit integer. ### Example 1 ```text Input: grid = ["ooooo", "o.o.o", "ooooo"], start = [0, 0], actions = [(0, "right"), (3, "down"), (5, "left")], end_time = 7 Output: [0, 1] ``` During units 0 to 2 the object moves right to `(0, 3)`. From unit 3 the direction is down, but `(1, 3)` is a dot, so it is blocked and the object waits during units 3 and 4. During units 5 and 6 it moves left to `(0, 2)` and then `(0, 1)`. ### Example 2 ```text Input: grid = ["ooooo", "o.o.o", "ooooo"], start = [2, 4], actions = [(2, "left"), (4, "up"), (50, "right")], end_time = 60 Output: [0, 4] ``` Units 0 and 1 have no direction, so the object stays at `(2, 4)`. During units 2 and 3 it moves left to `(2, 2)`. From unit 4 it moves up through the open cell `(1, 2)` to `(0, 2)`, and from unit 6 the top edge stops it. From unit 50 it moves right to `(0, 3)` and `(0, 4)`, and the right edge stops it from unit 52 until the end.

Constraints

  • 1 <= len(grid) <= 500 and 1 <= len(grid[0]) <= 500; all rows have the same length, and every character is 'o' or '.'.
  • start is an open cell inside the grid.
  • 0 <= len(actions) <= 10^5
  • 0 <= time <= 10^9 for every action, and action times are strictly increasing.
  • Every direction is one of "up", "down", "left" and "right".
  • 0 <= end_time <= 10^9
  • No value exceeds 2^31 - 1: times, end_time, rows and columns all fit in a signed 32-bit integer.

Examples

Input: (['ooooo', 'o.o.o', 'ooooo'], [0, 0], [(0, 'right'), (3, 'down'), (5, 'left')], 7)

Expected Output: [0, 1]

Explanation: Source Example 1: right three cells to (0, 3), down is blocked by the dot at (1, 3) for units 3-4, then left two cells to (0, 1).

Input: (['ooooo', 'o.o.o', 'ooooo'], [2, 4], [(2, 'left'), (4, 'up'), (50, 'right')], 60)

Expected Output: [0, 4]

Explanation: Source Example 2: no movement in units 0-1, left to (2, 2), up through (1, 2) to (0, 2), then right to (0, 4) where the edge stops it.

Hints

  1. Only actions with time strictly less than end_time can matter, and each one stays in effect until the next action's time or end_time, whichever comes first.
  2. Times go up to 10^9, so simulating every single unit is too slow. While one direction stays in effect, how far can the object actually travel?
  3. Mind the legend: '.' is blocked, 'o' is open, and the grid boundary behaves like a blocked cell.

Loading coding console...

Show the approach

Approach

Process the actions in time order instead of simulating every unit (times reach 10^9). Units before the first action never move the object. An action (t, d) with t < end_time keeps direction d in effect for the window [t, w), where w is the next action's time or end_time, whichever is smaller, so it lasts L = w - t units (L > 0 because times strictly increase). Within that window the grid and the direction are fixed, so the object steps in direction d while the next cell is open, and once the next cell is '.' or outside the grid it stays blocked for the rest of the window. Hence it moves exactly min(L, F) cells, where F is the number of consecutive open cells from the current cell in direction d before the first '.' or the grid boundary. F is found by searching the current row string (left/right) or a column string built once by transposing the grid (up/down) for the nearest '.' on that side; with no dot the boundary is used. Invariant: after handling action i, the position equals the unit-by-unit simulation at time w. Because times strictly increase, the loop stops at the first action with time >= end_time, so those actions have no effect. Edge cases: no actions or end_time 0 return start; a 1x1 grid never moves; an action at end_time - 1 moves at most one cell; all values fit in 32-bit integers.

Time complexity:
O(R*C + A*(R + C)), where R and C are the grid dimensions and A is the number of actions
Space complexity:
O(R*C) for the transposed column strings