838. Push Dominoes
Problem Description
You have n
dominoes standing upright in a line. Initially, you push some dominoes either to the left or to the right simultaneously.
The dominoes follow these rules:
- Each second, a domino falling left pushes the domino immediately to its left
- Each second, a domino falling right pushes the domino immediately to its right
- When a vertical domino receives forces from both sides at the same time, it stays upright due to balanced forces
- A falling domino doesn't apply extra force to an already falling or fallen domino
You're given a string dominoes
representing the initial state where:
dominoes[i] = 'L'
means the i-th domino was pushed to the leftdominoes[i] = 'R'
means the i-th domino was pushed to the rightdominoes[i] = '.'
means the i-th domino wasn't pushed
Your task is to return a string representing the final state after all dominoes have stopped moving.
For example, if you have ".L.R...LR..L.."
, the dominoes will propagate their falls over time. An R
domino will cause dominoes to its right to fall right, an L
domino will cause dominoes to its left to fall left, and when these forces meet at the same time, the domino stays upright.
The solution uses a multi-source BFS approach where all initially pushed dominoes serve as sources that propagate their forces outward simultaneously. It tracks the time each domino is first affected and the forces acting on it. If a domino receives only one force, it falls in that direction. If it receives opposite forces at the same moment, it remains upright.
Intuition
The key insight is to think of this problem as a wave propagation scenario. When dominoes are pushed, they create "waves" of force that spread outward over time. An R
domino creates a rightward wave, and an L
domino creates a leftward wave.
Consider what happens in real physics: if you push multiple dominoes simultaneously, their effects spread outward at the same speed. Each domino falls one unit of time after the previous one in the chain. This naturally suggests a BFS approach where we process dominoes layer by layer based on time.
Why BFS? Because we need to simulate the simultaneous propagation of forces. All initially pushed dominoes (the sources) start falling at time 0. Their immediate neighbors are affected at time 1, then those neighbors' neighbors at time 2, and so on. This is exactly what BFS does - it processes nodes level by level.
The critical observation is about collision handling: when two waves meet, we need to know if they arrive at the same time. If a rightward wave and a leftward wave reach the same domino at the exact same moment, the forces cancel out and the domino stays upright. If one wave arrives first, that domino falls in that direction and blocks the other wave.
This timing aspect is crucial. We can't just scan left-to-right or right-to-left because forces propagate simultaneously from multiple sources. We need to track:
- When each domino is first affected (
time[i]
) - What forces reach it at that moment (
force[i]
)
By using a queue and processing dominoes in the order they're affected, we naturally simulate the simultaneous propagation of all forces. When we encounter a domino that's already been visited, we check if the new force arrives at the same time (creating a standoff) or later (in which case it's blocked).
This multi-source BFS elegantly handles all the complex interactions between multiple falling sequences, making what could be a complicated simulation into a clean, systematic traversal.
Learn more about Two Pointers and Dynamic Programming patterns.
Solution Approach
The solution implements a multi-source BFS where all initially pushed dominoes (L
or R
) serve as sources that propagate their forces outward simultaneously.
Data Structures:
q
: A deque (double-ended queue) to perform BFS traversaltime[i]
: An array tracking when the i-th domino is first affected by a force (-1 means not affected yet)force[i]
: A dictionary of lists storing all force directions ('L'
or'R'
) acting on domino i at the same momentans
: The result array initialized with'.'
for all positions
Algorithm Steps:
-
Initialization Phase:
- Scan through the input string to find all initially pushed dominoes (
L
orR
) - For each pushed domino at index
i
:- Add index
i
to the queue - Set
time[i] = 0
(affected at time 0) - Add the force direction to
force[i]
- Add index
- Scan through the input string to find all initially pushed dominoes (
-
BFS Propagation Phase: While the queue is not empty:
- Dequeue index
i
- If
len(force[i]) == 1
, the domino falls in directionf = force[i][0]
- Set
ans[i] = f
- Calculate next domino index:
j = i - 1
iff == 'L'
, elsej = i + 1
- If
j
is within bounds[0, n)
:- Case 1: If
time[j] == -1
(not affected yet):- Enqueue
j
- Set
time[j] = time[i] + 1
- Append force
f
toforce[j]
- Enqueue
- Case 2: If
time[j] == time[i] + 1
(affected at the same next moment):- Append force
f
toforce[j]
- This creates a standoff if forces are opposite
- Append force
- Case 1: If
- Set
- If
len(force[i]) == 2
, the domino stays upright (balanced forces), soans[i]
remains'.'
- Dequeue index
-
Result Construction:
- Join the
ans
array to form the final string
- Join the
Key Insights:
- The BFS ensures we process dominoes in chronological order of when they're affected
- By checking
time[j] == time[i] + 1
, we detect when two waves meet at the same moment - When
force[j]
contains two elements, it means opposing forces arrived simultaneously, keeping the domino upright - The algorithm naturally handles complex scenarios like multiple force sources and wave collisions
Time Complexity: O(n)
where n is the length of the string, as each domino is processed at most once
Space Complexity: O(n)
for the auxiliary data structures
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's trace through the example ".L.R...LR..L.."
step by step.
Initial Setup:
- Input:
".L.R...LR..L.."
- Positions:
0 1 2 3 4 5 6 7 8 9 10 11 12 13
- Initially pushed dominoes are at indices: 1 (L), 3 (R), 7 (L), 8 (R), 11 (L)
Initialization Phase:
- Queue:
[1, 3, 7, 8, 11]
time = [-1, 0, -1, 0, -1, -1, -1, 0, 0, -1, -1, 0, -1, -1]
force = {1: ['L'], 3: ['R'], 7: ['L'], 8: ['R'], 11: ['L']}
ans = ['.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.']
BFS Propagation:
Processing index 1 (L):
- Single force 'L', so
ans[1] = 'L'
- Next domino:
j = 0
(to the left) time[0] = -1
, so enqueue 0, settime[0] = 1
,force[0] = ['L']
- Queue:
[3, 7, 8, 11, 0]
Processing index 3 (R):
- Single force 'R', so
ans[3] = 'R'
- Next domino:
j = 4
(to the right) time[4] = -1
, so enqueue 4, settime[4] = 1
,force[4] = ['R']
- Queue:
[7, 8, 11, 0, 4]
Processing index 7 (L):
- Single force 'L', so
ans[7] = 'L'
- Next domino:
j = 6
(to the left) time[6] = -1
, so enqueue 6, settime[6] = 1
,force[6] = ['L']
- Queue:
[8, 11, 0, 4, 6]
Processing index 8 (R):
- Single force 'R', so
ans[8] = 'R'
- Next domino:
j = 9
(to the right) time[9] = -1
, so enqueue 9, settime[9] = 1
,force[9] = ['R']
- Queue:
[11, 0, 4, 6, 9]
Processing index 11 (L):
- Single force 'L', so
ans[11] = 'L'
- Next domino:
j = 10
(to the left) time[10] = -1
, so enqueue 10, settime[10] = 1
,force[10] = ['L']
- Queue:
[0, 4, 6, 9, 10]
Processing index 0 (L from index 1):
- Single force 'L', so
ans[0] = 'L'
- Next domino:
j = -1
(out of bounds), skip - Queue:
[4, 6, 9, 10]
Processing index 4 (R from index 3):
- Single force 'R', so
ans[4] = 'R'
- Next domino:
j = 5
(to the right) time[5] = -1
, so enqueue 5, settime[5] = 2
,force[5] = ['R']
- Queue:
[6, 9, 10, 5]
Processing index 6 (L from index 7):
- Single force 'L', so
ans[6] = 'L'
- Next domino:
j = 5
(to the left) time[5] = 2
andtime[6] + 1 = 2
, so same moment!- Append 'L' to
force[5]
, nowforce[5] = ['R', 'L']
- Queue:
[9, 10, 5]
Processing index 9 (R from index 8):
- Single force 'R', so
ans[9] = 'R'
- Next domino:
j = 10
(to the right) time[10] = 1
andtime[9] + 1 = 2
, different times, skip- Queue:
[10, 5]
Processing index 10 (L from index 11):
- Single force 'L', so
ans[10] = 'L'
- Next domino:
j = 9
(to the left) time[9] = 1
andtime[10] + 1 = 2
, different times, skip- Queue:
[5]
Processing index 5:
force[5] = ['R', 'L']
(two forces), so domino stays uprightans[5]
remains '.'- Queue:
[]
Final Result:
ans = ['L', 'L', '.', 'R', 'R', '.', 'L', 'L', 'R', 'R', 'L', 'L', '.', '.']
- Output:
"LL.RR.LLRRLL.."
The key moment was at index 5, where forces from both directions arrived at time 2, creating a standoff and keeping that domino upright.
Solution Implementation
1from collections import deque, defaultdict
2from typing import List
3
4class Solution:
5 def pushDominoes(self, dominoes: str) -> str:
6 """
7 Simulates dominoes falling after being pushed.
8 Uses BFS to track forces propagating through the dominoes.
9
10 Args:
11 dominoes: String representing initial state ('L', 'R', or '.')
12
13 Returns:
14 String representing final state after all dominoes have fallen
15 """
16 n = len(dominoes)
17
18 # Queue for BFS processing
19 queue = deque()
20
21 # Track when each position receives its first force (-1 means not yet reached)
22 time_reached = [-1] * n
23
24 # Store all forces reaching each position at each time step
25 forces_at_position = defaultdict(list)
26
27 # Initialize queue with all initially pushed dominoes
28 for index, domino_state in enumerate(dominoes):
29 if domino_state != '.':
30 queue.append(index)
31 time_reached[index] = 0
32 forces_at_position[index].append(domino_state)
33
34 # Initialize result array with all dominoes standing
35 result = ['.'] * n
36
37 # Process forces using BFS
38 while queue:
39 current_index = queue.popleft()
40
41 # If only one force reached this position, the domino falls in that direction
42 if len(forces_at_position[current_index]) == 1:
43 force_direction = forces_at_position[current_index][0]
44 result[current_index] = force_direction
45
46 # Calculate next position based on force direction
47 next_index = current_index - 1 if force_direction == 'L' else current_index + 1
48
49 # Check if next position is valid
50 if 0 <= next_index < n:
51 current_time = time_reached[current_index]
52
53 # If this position hasn't been reached yet
54 if time_reached[next_index] == -1:
55 queue.append(next_index)
56 time_reached[next_index] = current_time + 1
57 forces_at_position[next_index].append(force_direction)
58 # If this position is being reached at the same time by another force
59 elif time_reached[next_index] == current_time + 1:
60 forces_at_position[next_index].append(force_direction)
61
62 return ''.join(result)
63
1class Solution {
2 public String pushDominoes(String dominoes) {
3 int length = dominoes.length();
4
5 // Queue to process dominoes in BFS manner
6 Deque<Integer> queue = new ArrayDeque<>();
7
8 // Track when each domino falls (time step)
9 int[] fallTime = new int[length];
10 Arrays.fill(fallTime, -1);
11
12 // Track forces acting on each domino at each time step
13 List<Character>[] forces = new List[length];
14 for (int i = 0; i < length; i++) {
15 forces[i] = new ArrayList<>();
16 }
17
18 // Initialize queue with dominoes that are already falling
19 for (int i = 0; i < length; i++) {
20 char currentDomino = dominoes.charAt(i);
21 if (currentDomino != '.') {
22 queue.offer(i);
23 fallTime[i] = 0;
24 forces[i].add(currentDomino);
25 }
26 }
27
28 // Result array to store final state of dominoes
29 char[] result = new char[length];
30 Arrays.fill(result, '.');
31
32 // BFS to simulate domino falling process
33 while (!queue.isEmpty()) {
34 int currentIndex = queue.poll();
35
36 // Only process if exactly one force is acting (no cancellation)
37 if (forces[currentIndex].size() == 1) {
38 char fallingDirection = forces[currentIndex].get(0);
39 result[currentIndex] = fallingDirection;
40
41 // Calculate next domino index based on falling direction
42 int nextIndex = (fallingDirection == 'L') ? currentIndex - 1 : currentIndex + 1;
43
44 // Check if next domino is within bounds
45 if (nextIndex >= 0 && nextIndex < length) {
46 int currentTime = fallTime[currentIndex];
47
48 // If next domino hasn't been visited yet
49 if (fallTime[nextIndex] == -1) {
50 queue.offer(nextIndex);
51 fallTime[nextIndex] = currentTime + 1;
52 forces[nextIndex].add(fallingDirection);
53 }
54 // If next domino is being reached at the same time from another direction
55 else if (fallTime[nextIndex] == currentTime + 1) {
56 forces[nextIndex].add(fallingDirection);
57 }
58 }
59 }
60 }
61
62 return new String(result);
63 }
64}
65
1class Solution {
2public:
3 string pushDominoes(string dominoes) {
4 int n = dominoes.size();
5 queue<int> processingQueue;
6 vector<int> timeReached(n, -1); // Time when force reaches each position (-1 if not reached)
7 vector<string> forces(n); // Forces acting on each position
8
9 // Initialize queue with all non-vertical dominoes
10 for (int i = 0; i < n; i++) {
11 if (dominoes[i] == '.') continue;
12
13 processingQueue.emplace(i);
14 timeReached[i] = 0;
15 forces[i].push_back(dominoes[i]);
16 }
17
18 string result(n, '.');
19
20 // BFS to simulate domino falling
21 while (!processingQueue.empty()) {
22 int currentPos = processingQueue.front();
23 processingQueue.pop();
24
25 // Only process if single force (no cancellation)
26 if (forces[currentPos].size() == 1) {
27 char forceDirection = forces[currentPos][0];
28 result[currentPos] = forceDirection;
29
30 // Calculate next position based on force direction
31 int nextPos = (forceDirection == 'L') ? (currentPos - 1) : (currentPos + 1);
32
33 // Check if next position is within bounds
34 if (nextPos >= 0 && nextPos < n) {
35 int currentTime = timeReached[currentPos];
36
37 // If position not yet reached, add to queue
38 if (timeReached[nextPos] == -1) {
39 processingQueue.emplace(nextPos);
40 timeReached[nextPos] = currentTime + 1;
41 forces[nextPos].push_back(forceDirection);
42 }
43 // If reached at same time, add competing force
44 else if (timeReached[nextPos] == currentTime + 1) {
45 forces[nextPos].push_back(forceDirection);
46 }
47 }
48 }
49 }
50
51 return result;
52 }
53};
54
1/**
2 * Simulates dominoes falling using BFS approach
3 * @param dominoes - String representing initial state of dominoes ('L', 'R', or '.')
4 * @returns Final state of dominoes after all have fallen
5 */
6function pushDominoes(dominoes: string): string {
7 const dominoCount: number = dominoes.length;
8 const processQueue: number[] = [];
9 const timeReached: number[] = Array(dominoCount).fill(-1);
10 const forcesAtPosition: string[][] = Array.from({ length: dominoCount }, () => []);
11
12 // Initialize queue with all dominoes that are already falling
13 for (let position = 0; position < dominoCount; position++) {
14 const currentDomino = dominoes[position];
15 if (currentDomino !== '.') {
16 processQueue.push(position);
17 timeReached[position] = 0;
18 forcesAtPosition[position].push(currentDomino);
19 }
20 }
21
22 const resultState: string[] = Array(dominoCount).fill('.');
23 let queueIndex = 0;
24
25 // Process dominoes using BFS
26 while (queueIndex < processQueue.length) {
27 const currentPosition = processQueue[queueIndex++];
28
29 // Only process if exactly one force is acting on this position
30 if (forcesAtPosition[currentPosition].length === 1) {
31 const dominoDirection = forcesAtPosition[currentPosition][0];
32 resultState[currentPosition] = dominoDirection;
33
34 // Calculate next position based on falling direction
35 const nextPosition = dominoDirection === 'L' ? currentPosition - 1 : currentPosition + 1;
36
37 // Check if next position is valid
38 if (nextPosition >= 0 && nextPosition < dominoCount) {
39 const currentTime = timeReached[currentPosition];
40
41 // If position hasn't been reached yet, add it to queue
42 if (timeReached[nextPosition] === -1) {
43 processQueue.push(nextPosition);
44 timeReached[nextPosition] = currentTime + 1;
45 forcesAtPosition[nextPosition].push(dominoDirection);
46 }
47 // If position is reached at the same time, add competing force
48 else if (timeReached[nextPosition] === currentTime + 1) {
49 forcesAtPosition[nextPosition].push(dominoDirection);
50 }
51 }
52 }
53 }
54
55 return resultState.join('');
56}
57
Time and Space Complexity
Time Complexity: O(n)
The algorithm uses BFS traversal where:
- Initial setup iterates through all
n
dominoes once to find non-'.' dominoes and add them to the queue:O(n)
- The while loop processes each position at most once. Each domino position can be added to the queue at most once (when
time[j] == -1
), and each position is dequeued at most once - For each dequeued position, we perform constant time operations: checking force length, updating answer, and potentially adding one neighbor to the queue
- Total operations:
O(n)
for initialization +O(n)
for BFS traversal =O(n)
Space Complexity: O(n)
The algorithm uses several data structures:
time
array of sizen
:O(n)
force
defaultdict that in worst case stores forces for alln
positions (though each position stores at most 2 forces):O(n)
ans
array of sizen
:O(n)
q
deque that contains at mostn
elements:O(n)
- Total space:
O(n) + O(n) + O(n) + O(n) = O(n)
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Not Handling Simultaneous Force Arrivals Correctly
The Pitfall: A common mistake is to process dominoes in the wrong order or not properly track when multiple forces reach the same domino simultaneously. If you process dominoes sequentially without considering timing, you might incorrectly apply one force before another when they should cancel out.
Example of Incorrect Approach:
# WRONG: Processing left-to-right then right-to-left separately
def pushDominoes_wrong(dominoes):
result = list(dominoes)
# First pass: process R forces
for i in range(len(dominoes)):
if result[i] == 'R' and i + 1 < len(result) and result[i + 1] == '.':
result[i + 1] = 'R'
# Second pass: process L forces
for i in range(len(dominoes) - 1, -1, -1):
if result[i] == 'L' and i - 1 >= 0 and result[i - 1] == '.':
result[i - 1] = 'L'
return ''.join(result)
This fails for input like "R.L"
because it would produce "RRL"
instead of the correct "R.L"
(middle domino stays upright).
The Solution: Use BFS with proper time tracking to ensure forces that arrive simultaneously are processed together:
- Track the exact time each domino is first affected
- Only allow forces to be added if they arrive at the correct time
- Check
time_reached[next_index] == current_time + 1
to identify simultaneous arrivals
2. Incorrectly Handling Already Falling Dominoes
The Pitfall: Attempting to add forces to dominoes that have already been processed or allowing a domino to change direction after it has started falling.
Example of Incorrect Logic:
# WRONG: Allowing dominoes to be re-queued multiple times if 0 <= next_index < n: queue.append(next_index) # Always adding without checking forces_at_position[next_index].append(force_direction)
The Solution: Only process each domino once by:
- Using the
time_reached
array to ensure a domino is only added to the queue when first affected - Not allowing new forces to affect a domino after its initial time window has passed
3. Off-by-One Errors in Boundary Checking
The Pitfall: Forgetting to check array boundaries when calculating the next domino position, leading to index out of bounds errors.
Example of Incorrect Code:
# WRONG: No boundary check next_index = current_index - 1 if force_direction == 'L' else current_index + 1 queue.append(next_index) # Could be -1 or n!
The Solution: Always validate the next index before using it:
if 0 <= next_index < n: # Safe to process next_index
4. Misunderstanding Force Cancellation
The Pitfall: Assuming that any two forces cancel out, regardless of their directions or thinking that a domino with two forces should fall in some combined direction.
The Correct Understanding:
- Only opposite forces (
'L'
and'R'
) arriving simultaneously cause a domino to remain upright - If the same force arrives twice (e.g., two
'R'
forces), it's still just one directional force - The code correctly handles this by checking
len(forces_at_position[current_index]) == 1
to determine if the domino falls
5. Not Initializing the Result Array Properly
The Pitfall: Starting with the input string as the result and modifying it in place, which can lead to confusion about which dominoes have been processed.
Example of Problematic Approach:
# RISKY: Using input as starting point
result = list(dominoes) # Contains 'L', 'R', and '.'
The Solution: Initialize the result array with all dots (standing dominoes) and only update positions as they're processed:
result = ['.'] * n # Clear initial state
This ensures that any domino not affected by forces remains standing (.
) in the final result.
Is the following code DFS or BFS?
void search(Node root) { if (!root) return; visit(root); root.visited = true; for (Node node in root.adjacent) { if (!node.visited) { search(node); } } }
Recommended Readings
Tech Interview Pattern Two Pointers Introduction If you prefer videos here's a super quick introduction to Two Pointers div class responsive iframe iframe src https www youtube com embed xZ4AfXHQ1VQ title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture allowfullscreen iframe div Two pointers is a common interview
What is Dynamic Programming Prerequisite DFS problems dfs_intro Backtracking problems backtracking Memoization problems memoization_intro Pruning problems backtracking_pruning Dynamic programming is an algorithmic optimization technique that breaks down a complicated problem into smaller overlapping sub problems in a recursive manner and uses solutions to the sub problems to construct a solution
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Don’t Miss This!