2647. Color the Triangle Red 🔒
Problem Description
You have an equilateral triangle with side length n
that is divided into n²
smaller unit equilateral triangles. The large triangle has n
rows (indexed from 1), where row i
contains 2i - 1
unit triangles. Each unit triangle is identified by coordinates (i, j)
where i
is the row number and j
ranges from 1 to 2i - 1
.
Two unit triangles are considered neighbors if they share a common side. For example:
- Triangles
(1,1)
and(2,2)
are neighbors - Triangles
(3,2)
and(3,3)
are neighbors - Triangles
(2,2)
and(3,3)
are NOT neighbors (they don't share a side)
Initially, all unit triangles are white. You need to select k
triangles and color them red, then run this algorithm:
- Find a white triangle that has at least 2 red neighbors
- If no such triangle exists, stop
- Color that triangle red
- Return to step 1
The goal is to find the minimum value of k
and determine which k
triangles to initially color red such that when the algorithm completes, all unit triangles become red.
Return a 2D list containing the coordinates [i, j]
of the triangles to initially color red. If multiple valid solutions exist with the minimum k
, return any one of them.
Intuition
To understand the pattern, let's think about how the coloring propagates. Once we color some triangles red initially, the algorithm will automatically color white triangles that have at least 2 red neighbors. This creates a chain reaction where red triangles spread throughout the entire triangle.
The key insight is that we need to strategically place the initial red triangles so that the propagation can reach every unit triangle. If we observe the structure, we notice that:
-
The top triangle
(1,1)
is isolated - it only has neighbors in row 2, so it must be colored initially since it can never have 2 red neighbors from other triangles. -
For the remaining rows (from row
n
down to row 2), we need to ensure the red color can propagate both horizontally within each row and vertically between rows.
By experimenting with different patterns, we can discover that there's a repeating 4-row pattern that works efficiently:
- In every 4th row (counting from the bottom), we color alternating triangles: positions 1, 3, 5, ...
- In the next row up, we color just one triangle at position 2
- In the next row up, we color alternating triangles starting from position 3: positions 3, 5, 7, ...
- In the next row up, we color just one triangle at position 1
This pattern ensures that:
- Each row gets enough "seed" triangles to trigger propagation
- The colored triangles in adjacent rows are positioned to help propagate the color both within their own row and to neighboring rows
- The pattern repeats every 4 rows, making it a systematic approach
The algorithm works by processing rows from bottom to top (row n
to row 2), applying this 4-row coloring pattern cyclically. The variable k
in the solution tracks which step of the 4-row cycle we're in (0, 1, 2, or 3).
Learn more about Math patterns.
Solution Approach
Based on the pattern discovered, we implement the solution by coloring triangles in a specific order:
-
Always color the top triangle: Start by adding
[1, 1]
to our answer list since this triangle must be colored initially (it can never get 2 red neighbors from other triangles). -
Process rows from bottom to top: We iterate from row
n
down to row 2, applying a 4-row repeating pattern. We use a variablek
to track which step of the pattern we're in (cycles through 0, 1, 2, 3). -
Apply the 4-row coloring pattern:
-
When
k = 0
: Color all odd-positioned triangles in the row. For rowi
, color triangles at positions(i, 1)
,(i, 3)
,(i, 5)
, ..., up to(i, 2i-1)
. This is implemented withfor j in range(1, i << 1, 2)
. -
When
k = 1
: Color only one triangle at position 2. Add[i, 2]
to the answer. -
When
k = 2
: Color odd-positioned triangles starting from position 3. For rowi
, color triangles at positions(i, 3)
,(i, 5)
, ..., up to(i, 2i-1)
. This is implemented withfor j in range(3, i << 1, 2)
. -
When
k = 3
: Color only one triangle at position 1. Add[i, 1]
to the answer.
-
-
Update the pattern counter: After processing each row, increment
k
and take modulo 4 to cycle through the pattern:k = (k + 1) % 4
.
The expression i << 1
is a bit shift operation equivalent to i * 2
, giving us 2i
which is one more than the maximum column index 2i - 1
for row i
.
This systematic approach ensures that:
- Every unit triangle either gets colored initially or can be reached by the propagation algorithm
- We use the minimum number of initially colored triangles
- The red color spreads efficiently through the triangle structure
The final answer is the list of coordinates [row, column]
of all triangles that need to be colored red initially.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small example with n = 5
to illustrate the solution approach.
Step 1: Visualize the triangle structure
Row 1: (1,1) Row 2: (2,1) (2,2) (2,3) Row 3: (3,1) (3,2) (3,3) (3,4) (3,5) Row 4: (4,1) (4,2) (4,3) (4,4) (4,5) (4,6) (4,7) Row 5: (5,1) (5,2) (5,3) (5,4) (5,5) (5,6) (5,7) (5,8) (5,9)
Step 2: Apply the coloring pattern
We start with k = 0
and process from row 5 down to row 2:
-
Always color (1,1): Add
[1, 1]
to our answer -
Row 5 (k = 0): Color odd positions 1, 3, 5, 7, 9
- Add
[5, 1]
,[5, 3]
,[5, 5]
,[5, 7]
,[5, 9]
- Update: k = (0 + 1) % 4 = 1
- Add
-
Row 4 (k = 1): Color only position 2
- Add
[4, 2]
- Update: k = (1 + 1) % 4 = 2
- Add
-
Row 3 (k = 2): Color odd positions starting from 3 → positions 3, 5
- Add
[3, 3]
,[3, 5]
- Update: k = (2 + 1) % 4 = 3
- Add
-
Row 2 (k = 3): Color only position 1
- Add
[2, 1]
- Update: k = (3 + 1) % 4 = 0
- Add
Step 3: Initial red triangles
Row 1: [R] (1,1) is red Row 2: [R] [ ] [ ] (2,1) is red Row 3: [ ] [ ] [R] [ ] [R] (3,3) and (3,5) are red Row 4: [ ] [R] [ ] [ ] [ ] [ ] [ ] (4,2) is red Row 5: [R] [ ] [R] [ ] [R] [ ] [R] [ ] [R] (5,1), (5,3), (5,5), (5,7), (5,9) are red
Step 4: Verify propagation works
Now let's verify a few propagation steps:
- (5,2) has red neighbors (5,1) and (5,3) → becomes red
- (5,4) has red neighbors (5,3) and (5,5) → becomes red
- (4,1) has red neighbors (5,1) and (5,2) → becomes red
- (3,1) has red neighbors (4,1) and (4,2) → becomes red
- (2,2) has red neighbors (1,1) and (3,3) → becomes red
This chain reaction continues until all triangles are colored red.
Final answer: [[1,1], [5,1], [5,3], [5,5], [5,7], [5,9], [4,2], [3,3], [3,5], [2,1]]
Total initially colored triangles: k = 10
Solution Implementation
1class Solution:
2 def colorRed(self, n: int) -> List[List[int]]:
3 # Initialize result with the starting coordinate [1, 1]
4 result = [[1, 1]]
5
6 # Cycle counter for determining which pattern to use (0-3)
7 cycle_index = 0
8
9 # Iterate from n down to 2 (inclusive)
10 for row in range(n, 1, -1):
11 # Case 0: Generate all odd column indices from 1 to 2*row-1
12 if cycle_index == 0:
13 for column in range(1, row << 1, 2): # row << 1 is equivalent to row * 2
14 result.append([row, column])
15
16 # Case 1: Add single coordinate with column 2
17 elif cycle_index == 1:
18 result.append([row, 2])
19
20 # Case 2: Generate odd column indices from 3 to 2*row-1
21 elif cycle_index == 2:
22 for column in range(3, row << 1, 2): # Start from 3 instead of 1
23 result.append([row, column])
24
25 # Case 3: Add single coordinate with column 1
26 else:
27 result.append([row, 1])
28
29 # Move to next case in the cycle (0 -> 1 -> 2 -> 3 -> 0)
30 cycle_index = (cycle_index + 1) % 4
31
32 return result
33
1class Solution {
2 public int[][] colorRed(int n) {
3 // List to store coordinate pairs
4 List<int[]> result = new ArrayList<>();
5
6 // Add the starting position (1, 1)
7 result.add(new int[] {1, 1});
8
9 // Process rows from n down to 2, cycling through 4 different patterns
10 int patternIndex = 0;
11 for (int row = n; row > 1; row--) {
12 // Determine which pattern to apply based on cycle position
13 if (patternIndex == 0) {
14 // Pattern 0: Add all odd column positions up to 2*row - 1
15 for (int column = 1; column < row * 2; column += 2) {
16 result.add(new int[] {row, column});
17 }
18 } else if (patternIndex == 1) {
19 // Pattern 1: Add only column 2
20 result.add(new int[] {row, 2});
21 } else if (patternIndex == 2) {
22 // Pattern 2: Add odd column positions starting from 3 up to 2*row - 1
23 for (int column = 3; column < row * 2; column += 2) {
24 result.add(new int[] {row, column});
25 }
26 } else {
27 // Pattern 3: Add only column 1
28 result.add(new int[] {row, 1});
29 }
30
31 // Move to next pattern in the cycle (0 -> 1 -> 2 -> 3 -> 0)
32 patternIndex = (patternIndex + 1) % 4;
33 }
34
35 // Convert list to 2D array and return
36 return result.toArray(new int[0][]);
37 }
38}
39
1class Solution {
2public:
3 vector<vector<int>> colorRed(int n) {
4 vector<vector<int>> result;
5
6 // Add the starting coordinate (1, 1)
7 result.push_back({1, 1});
8
9 // Process rows from n down to 2, cycling through 4 different patterns
10 for (int row = n, patternIndex = 0; row > 1; --row, patternIndex = (patternIndex + 1) % 4) {
11
12 if (patternIndex == 0) {
13 // Pattern 0: Add all odd column positions from 1 to (2 * row - 1)
14 for (int col = 1; col < (row << 1); col += 2) {
15 result.push_back({row, col});
16 }
17 }
18 else if (patternIndex == 1) {
19 // Pattern 1: Add only column 2
20 result.push_back({row, 2});
21 }
22 else if (patternIndex == 2) {
23 // Pattern 2: Add odd column positions starting from 3 to (2 * row - 1)
24 for (int col = 3; col < (row << 1); col += 2) {
25 result.push_back({row, col});
26 }
27 }
28 else {
29 // Pattern 3: Add only column 1
30 result.push_back({row, 1});
31 }
32 }
33
34 return result;
35 }
36};
37
1/**
2 * Generates coordinates for coloring red cells in a pattern based on the given number.
3 * @param n - The starting row number for the pattern generation
4 * @returns A 2D array containing [row, column] coordinates of red cells
5 */
6function colorRed(n: number): number[][] {
7 // Initialize result array with the starting cell at position [1, 1]
8 const result: number[][] = [[1, 1]];
9
10 // Pattern cycle counter (0-3) to determine which pattern to apply
11 let patternIndex: number = 0;
12
13 // Iterate from row n down to row 2
14 for (let row: number = n; row > 1; row--) {
15 // Apply different patterns based on the current pattern index
16 if (patternIndex === 0) {
17 // Pattern 0: Add all odd column positions in the current row
18 // Column range: 1 to (2 * row - 1), stepping by 2
19 for (let column: number = 1; column < (row << 1); column += 2) {
20 result.push([row, column]);
21 }
22 } else if (patternIndex === 1) {
23 // Pattern 1: Add only column 2 in the current row
24 result.push([row, 2]);
25 } else if (patternIndex === 2) {
26 // Pattern 2: Add odd column positions starting from column 3
27 // Column range: 3 to (2 * row - 1), stepping by 2
28 for (let column: number = 3; column < (row << 1); column += 2) {
29 result.push([row, column]);
30 }
31 } else {
32 // Pattern 3: Add only column 1 in the current row
33 result.push([row, 1]);
34 }
35
36 // Cycle through patterns 0-3
37 patternIndex = (patternIndex + 1) % 4;
38 }
39
40 return result;
41}
42
Time and Space Complexity
The time complexity is O(n²)
, where n
is the parameter given in the problem.
The algorithm iterates through values from n
down to 2 (which is n-1
iterations). In each iteration:
- When
k = 0
: The inner loop runs from 1 to2*i
with step 2, resulting ini
iterations (since2*i/2 = i
) - When
k = 1
: Only one append operation - When
k = 2
: The inner loop runs from 3 to2*i
with step 2, resulting in approximatelyi-1
iterations - When
k = 3
: Only one append operation
Since the pattern repeats every 4 iterations, roughly half of the outer loop iterations will have inner loops that execute O(i)
times. The total number of operations is approximately:
- For
k = 0
cases:n + (n-4) + (n-8) + ... ≈ n²/8
- For
k = 2
cases:(n-2) + (n-6) + (n-10) + ... ≈ n²/8
- For
k = 1
andk = 3
cases:O(n)
The sum gives us O(n²)
time complexity.
The space complexity is O(1)
when ignoring the space consumption of the answer array ans
. The only extra variables used are k
, i
, and j
, which consume constant space regardless of the input size n
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Understanding of Triangle Adjacency
Many developers mistakenly assume that triangles sharing a vertex are neighbors. In this problem, only triangles sharing a complete edge are neighbors. This misunderstanding leads to incorrect propagation logic and may result in some triangles never turning red.
Solution: Carefully verify neighbor relationships by checking if triangles share a full side, not just a vertex. Draw out small examples (n=2, n=3) to visualize the actual adjacency relationships.
2. Off-by-One Errors in Row/Column Indexing
The problem uses 1-based indexing where row i
contains columns from 1 to 2i-1
. Common mistakes include:
- Using 0-based indexing accidentally
- Iterating up to
2i
instead of2i-1
- Confusing the range boundaries when generating odd-positioned triangles
Solution: Be consistent with 1-based indexing throughout. When iterating odd positions, use range(1, 2*i, 2)
which correctly generates 1, 3, 5, ..., up to but not including 2*i
.
3. Incorrect Pattern Cycle Management
The 4-row repeating pattern must be applied correctly from bottom to top. Common errors:
- Starting the cycle at the wrong position (not starting with cycle_index = 0)
- Applying the pattern top-to-bottom instead of bottom-to-top
- Forgetting to update the cycle counter or updating it incorrectly
Solution: Initialize cycle_index = 0
before the loop, process rows from n
down to 2
, and update with cycle_index = (cycle_index + 1) % 4
after each row.
4. Missing the Top Triangle
The triangle at position [1, 1]
must always be colored initially because it has no triangles above it to provide the required 2 red neighbors. Forgetting to include this leads to an incomplete solution.
Solution: Always start the result list with [[1, 1]]
before processing other rows.
5. Using Bit Shift Without Understanding
The code uses row << 1
as an optimization for row * 2
. If modified incorrectly (like row << 2
thinking it means row * 2
), it produces wrong bounds.
Solution: Either stick with the clear multiplication row * 2
for readability, or understand that << 1
means shift left by 1 bit (multiply by 2). Don't modify the shift amount unless you understand binary operations.
Which technique can we use to find the middle of a linked list?
Recommended Readings
Math for Technical Interviews How much math do I need to know for technical interviews The short answer is about high school level math Computer science is often associated with math and some universities even place their computer science department under the math faculty However the reality is that you
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
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!