1411. Number of Ways to Paint N × 3 Grid
Problem Description
The problem presents a painting task where one has a grid of size n x 3
, meaning there are n
rows and 3
columns. The objective is to paint each cell with one of three colors: Red, Yellow, or Green. The constraint is that no two adjacent cells (those that are directly beside or above/below each other) can be the same color. You are asked to calculate the number of different ways to paint the entire grid that satisfies this constraint.
Given the value of n
, the number of rows in the grid, your task is to find the total number of valid painting configurations, modulo 10^9 + 7
. This modulo operation is a common requirement in programming challenges to avoid large integer overflow issues.
Calculating the number of possible configurations manually is not feasible as n
can be very large, so we have to come up with an efficient algorithm to compute the result.
Intuition
To arrive at the solution approach, let's understand that we only need to focus on a row-by-row basis since each row's coloring only depends on the row immediately above it. For a given row, two scenarios need to be considered: one where all three cells have different colors from their corresponding cells in the row above, and one where exactly one cell has the same color as the cell above it (forming a 'pattern'). These scenarios are seen as follows:
-
Pattern A: No cell has the same color as the one above it. There are three different ways the first cell could be painted. For each choice of the color in the first cell, there are two choices for the second cell, and finally, two choices for the third cell (since it has to be different from both the first cell and the cell above it in the previous row). Hence, there are
3 * 2 * 2
ways to paint rown
if rown-1
is of Pattern A. -
Pattern B: Exactly one cell has the same color as the one above it. This again allows for two choices for one of the other cells and two more choices for the remaining cells, resulting in a total of
2 * 2 * 1
ways to paint rown
if rown-1
is of Pattern B.
We then calculate the number of ways to paint based on the above observations using dynamic programming. Instead of storing the whole grid, we maintain two variables f0
and f1
, representing the count of configurations following Pattern A and Pattern B for the current row being processed. These are being recalculated for every row based on the previous row's counts. For the base case (first row), there are 6
ways to paint following both patterns. The variables g0
and g1
are the new counts for the next row.
Each iteration updates f0
and f1
, simulating painting the next row until all rows have been 'processed'. The modulo operation ensures the number stays within the integer range specified by the problem.
Finally, the answer is the sum of f0
and f1
after processing all rows, as this represents all possible configurations after painting the last row. This sum is also taken modulo 10^9 + 7
to conform to the problem's requirements.
Learn more about Dynamic Programming patterns.
Solution Approach
The implementation uses a dynamic programming approach to solve the problem efficiently. Here's a step-by-step explanation of how the code operates:
-
Define a modulo constant
mod
that equals10^9 + 7
. This value will be used to perform all calculations under the modulo to prevent integer overflow. -
Initialize two variables,
f0
andf1
, with the value6
.f0
corresponds to the count of ways to paint a row following Pattern A (no cell has the same color as the one above it) andf1
corresponds to the count of ways to paint following Pattern B (exactly one cell has the same color as the one above it). This initialization is for the base case, the first row, which can be freely painted in3 * 2 * 2 = 6
ways for both patterns. -
Iterate over the range
n - 1
which represents the remaining rows that need to be painted after the first one.a. Compute
g0
, which is the new count for Pattern A for the next row. The formula used isg0 = (3 * f0 + 2 * f1) % mod
. It derives from the two ways a Pattern A configuration can be followed by another Pattern A configuration (3 possibilities), and the two ways a Pattern B configuration can lead to a Pattern A configuration in the subsequent row (2 possibilities).b. Compute
g1
, which is the new count for Pattern B. The formula isg1 = (2 * f0 + 2 * f1) % mod
. This considers that a Pattern A configuration can lead to a Pattern B in the next row (2 possibilities), and that a Pattern B can lead to another Pattern B in the next row (2 possibilities).c. Update
f0
andf1
to be the new countsg0
andg1
, respectively. This prepares the algorithm to compute the counts for the next row. -
After the loop, the total number of valid configurations is the sum of both
f0
andf1
, which we also take modulomod
. This represents all the valid ways to paint the grid after considering all rows.
Here's an explanation of the mathematical formulas used in the code:
-
For Pattern A (
g0
), each new row can come from either a Pattern A or Pattern B in the previous row and you have, respectively, 3 or 2 ways to continue the pattern without violating the color constraints. -
For Pattern B (
g1
), each new row can also come from a previous Pattern A or Pattern B, and there are two ways to paint the row for each case.
The use of dynamic programming in this problem allows us to solve it efficiently by breaking it down into subproblems (row-wise computation) and building up the solution from there.
Finally, the code returns f0 + f1
modulo mod
as the final answer.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small example to illustrate the solution approach. Suppose n = 2
, which means we have a grid with 2 rows and 3 columns, and we want to find the number of ways to paint this grid according to the given rules.
For the first row, we don't have any restrictions since there's no row above it. The first cell can be painted with any of the three colors: Red, Yellow, or Green. For simplicity, let's denote these colors by R, Y, and G. After choosing a color for the first cell, we have two options for the second cell, and again two options for the third cell, because adjacent cells cannot be the same color.
Here are the possible ways to paint the first row following Pattern A:
- RYG
- RGY
- YRG
- YGR
- GRG
- GRY
As the problem statement outlined, there are 6 ways to paint the first row. For Pattern A (f0
) and Pattern B (f1
), the initial count is 6
because both patterns will have the same number of ways to paint the first row.
Now let's move on to the second row. We must consider the two patterns for calculating the possibilities for the second row:
For Pattern A (g0
, meaning no cell shares a color with the one above it), each Pattern A configuration in the first row can lead to 3 configurations in the second row, while each Pattern B configuration can lead to 2 configurations in the second row because one color is already fixed for one cell.
For Pattern B (g1
, meaning exactly one cell shares the color with the one above it), each Pattern A or B configuration in the first row can result in 2 configurations in the second row.
Using the formulae from the solution:
For the second row:
- New count for Pattern A (
g0
) = (3 * f0 + 2 * f1) % mod - New count for Pattern B (
g1
) = (2 * f0 + 2 * f1) % mod
Since f0
and f1
are both initialized as 6
:
g0
= (3 * 6 + 2 * 6) % mod = (18 + 12) % mod = 30 % modg1
= (2 * 6 + 2 * 6) % mod = (12 + 12) % mod = 24 % mod
(Note: For this example, we are not applying the modulo operation because the numbers are small, but in the code, we would take modulo 10^9 + 7
to prevent overflow.)
After updating f0
to g0
and f1
to g1
, we proceed to calculate the total number of configurations for n = 2
:
- Total configurations = (f0 + f1) % mod
- Total configurations = (30 + 24) % mod
- Total configurations = 54 % mod (which is simply 54 since it's less than
10^9 + 7
)
Thus, for a grid of size 2 x 3
, there are 54 different ways to paint it according to the rules.
This example clearly demonstrates how the dynamic programming approach leads to an efficient solution for larger values of n
by calculating the number of ways for each row based on the number of ways for the previous row, updating the counts in the process, and ultimately summing up the configurations for the last row.
Solution Implementation
1class Solution:
2 def numOfWays(self, n: int) -> int:
3 # Define the modulo value to ensure the result is within the range.
4 modulus = 10**9 + 7
5
6 # Initialize the number of ways to paint a single row when the first two columns are of the same color (type 0)
7 # and when the first two columns are of different colors (type 1). Both are 6 for n = 1.
8 num_ways_type_0 = num_ways_type_1 = 6
9
10 # Iteratively calculate the number of ways to paint each row based on the number of ways to paint the previous row.
11 for _ in range(n - 1):
12 # Calculate the number of ways to paint the current row given the last row is of type 0.
13 new_ways_type_0 = (3 * num_ways_type_0 + 2 * num_ways_type_1) % modulus
14
15 # Calculate the number of ways to paint the current row given the last row is of type 1.
16 new_ways_type_1 = (2 * num_ways_type_0 + 2 * num_ways_type_1) % modulus
17
18 # Update the number of ways for the next iteration.
19 num_ways_type_0, num_ways_type_1 = new_ways_type_0, new_ways_type_1
20
21 # Return the total number of ways to paint the n rows, sum of both types 0 and 1.
22 return (num_ways_type_0 + num_ways_type_1) % modulus
23
1class Solution {
2
3 // Method to calculate the number of ways to paint a grid
4 public int numOfWays(int n) {
5 final int MOD = (int) 1e9 + 7; // Define the modulo value for avoiding integer overflow
6
7 // Initial counts of color combinations for a single row, f0 for one pattern type, f1 for the other pattern type
8 long countPatternTypeA = 6, countPatternTypeB = 6;
9
10 // Iterate through the grid rows, starting from the second row
11 for (int i = 0; i < n - 1; ++i) {
12 // Calculate the new count for pattern type A using the previous counts
13 long newCountPatternTypeA = (3 * countPatternTypeA + 2 * countPatternTypeB) % MOD;
14 // Calculate the new count for pattern type B using the previous counts
15 long newCountPatternTypeB = (2 * countPatternTypeA + 2 * countPatternTypeB) % MOD;
16
17 // Update the counts for the next iteration
18 countPatternTypeA = newCountPatternTypeA;
19 countPatternTypeB = newCountPatternTypeB;
20 }
21
22 // Return the total count (sum of both pattern types), ensuring the result is modulo-ed
23 return (int) (countPatternTypeA + countPatternTypeB) % MOD;
24 }
25}
26
1using ll = long long; // Define long long as 'll' for convenience.
2
3class Solution {
4public:
5 int numOfWays(int n) {
6 const int MOD = 1e9 + 7; // Define the modulus for calculating the result.
7
8 ll patternsWithThreeColors = 6, // Initialize the number of ways to paint using three colors.
9 patternsWithTwoColors = 6; // Initialize the number of ways to paint using two colors.
10
11 // Loop through the number of tiles left after the first tile.
12 while (--n) {
13 // Calculate new ways to paint using three colors.
14 ll newPatternsWithThreeColors = (patternsWithThreeColors * 3 + patternsWithTwoColors * 2) % MOD;
15
16 // Calculate new ways to paint using two colors.
17 ll newPatternsWithTwoColors = (patternsWithThreeColors * 2 + patternsWithTwoColors * 2) % MOD;
18
19 // Update the variable to the latest calculation.
20 patternsWithThreeColors = newPatternsWithThreeColors;
21 patternsWithTwoColors = newPatternsWithTwoColors;
22 }
23 // Return the total number of ways to paint the tiles, within the modulus.
24 return (int) (patternsWithThreeColors + patternsWithTwoColors) % MOD;
25 }
26};
27
1// Define modulus for calculating the result.
2const MOD: number = 1e9 + 7;
3
4// Define initial count of ways to paint using three colors.
5let patternsWithThreeColors: number = 6;
6
7// Define initial count of ways to paint using two colors.
8let patternsWithTwoColors: number = 6;
9
10function numOfWays(n: number): number {
11 // Iterate through the number of tiles, excluding the first one.
12 while (--n) {
13 // Calculate new count of ways to paint using three colors.
14 let newPatternsWithThreeColors: number = (patternsWithThreeColors * 3 + patternsWithTwoColors * 2) % MOD;
15
16 // Calculate new count of ways to paint using two colors.
17 let newPatternsWithTwoColors: number = (patternsWithThreeColors * 2 + patternsWithTwoColors * 2) % MOD;
18
19 // Update counts to the latest calculations.
20 patternsWithThreeColors = newPatternsWithThreeColors;
21 patternsWithTwoColors = newPatternsWithTwoColors;
22 }
23 // Return the total count of ways to paint the tiles modulo `MOD`.
24 return (patternsWithThreeColors + patternsWithTwoColors) % MOD;
25}
26
Time and Space Complexity
The given Python code represents a dynamic programming solution where n
is the number of rows of a 3xn
grid to be painted in two colors with certain constraints.
Time Complexity:
The time complexity of the code can be determined by analyzing the for loop:
- The loop runs exactly
n-1
times. - Inside the loop, it performs a constant number of operations (simple arithmetic and modulo).
- Since the number of operations inside the loop does not depend on
n
and is constant, the total number of operations is proportional ton
.
Therefore, the time complexity is O(n)
.
Space Complexity:
The space complexity of the code can be analyzed as follows:
- The variables
f0
,f1
,g0
,g1
, andmod
use a fixed amount of space regardless of the input size. - No additional data structures (such as arrays or matrices) that grow with input size are being used.
Thus, the space complexity is O(1)
.
Learn more about how to find time and space complexity quickly using problem constraints.
In a binary min heap, the minimum element can be found in:
Recommended Readings
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
LeetCode 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 we
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 algomonster s3 us east 2 amazonaws com recursion jpg You first
Want a Structured Path to Master System Design Too? Don’t Miss This!