Off-by-one on range boundaries
Wrong move: Loop endpoints miss first/last candidate.
Usually fails on: Fails on minimal arrays and exact-boundary answers.
Fix: Re-derive loops from inclusive/exclusive ranges before coding.
Break down a hard problem into reliable checkpoints, edge-case handling, and complexity trade-offs.
There is a strange printer with the following two special requirements:
You are given a m x n matrix targetGrid, where targetGrid[row][col] is the color in the position (row, col) of the grid.
Return true if it is possible to print the matrix targetGrid, otherwise, return false.
Example 1:
Input: targetGrid = [[1,1,1,1],[1,2,2,1],[1,2,2,1],[1,1,1,1]] Output: true
Example 2:
Input: targetGrid = [[1,1,1,1],[1,1,3,3],[1,1,3,4],[5,5,1,4]] Output: true
Example 3:
Input: targetGrid = [[1,2,1],[2,1,2],[1,2,1]] Output: false Explanation: It is impossible to form targetGrid because it is not allowed to print the same color in different turns.
Constraints:
m == targetGrid.lengthn == targetGrid[i].length1 <= m, n <= 601 <= targetGrid[row][col] <= 60Problem summary: There is a strange printer with the following two special requirements: On each turn, the printer will print a solid rectangular pattern of a single color on the grid. This will cover up the existing colors in the rectangle. Once the printer has used a color for the above operation, the same color cannot be used again. You are given a m x n matrix targetGrid, where targetGrid[row][col] is the color in the position (row, col) of the grid. Return true if it is possible to print the matrix targetGrid, otherwise, return false.
Start with the most direct exhaustive search. That gives a correctness anchor before optimizing.
Pattern signal: Array · Topological Sort
[[1,1,1,1],[1,2,2,1],[1,2,2,1],[1,1,1,1]]
[[1,1,1,1],[1,1,3,3],[1,1,3,4],[5,5,1,4]]
[[1,2,1],[2,1,2],[1,2,1]]
strange-printer)longest-cycle-in-a-graph)sort-array-by-moving-items-to-empty-space)Source-backed implementations are provided below for direct study and interview prep.
// Accepted solution for LeetCode #1591: Strange Printer II
enum State { INIT, VISITING, VISITED }
class Solution {
public boolean isPrintable(int[][] targetGrid) {
final int MAX_COLOR = 60;
final int m = targetGrid.length;
final int n = targetGrid[0].length;
// graph[u] := {v1, v2} means v1 and v2 cover u
Set<Integer>[] graph = new HashSet[MAX_COLOR + 1];
for (int color = 1; color <= MAX_COLOR; ++color) {
// Get the rectangle of the current color.
int minI = m;
int minJ = n;
int maxI = -1;
int maxJ = -1;
for (int i = 0; i < m; ++i)
for (int j = 0; j < n; ++j)
if (targetGrid[i][j] == color) {
minI = Math.min(minI, i);
minJ = Math.min(minJ, j);
maxI = Math.max(maxI, i);
maxJ = Math.max(maxJ, j);
}
// Add any color covering the current as the children.
graph[color] = new HashSet<>();
for (int i = minI; i <= maxI; ++i)
for (int j = minJ; j <= maxJ; ++j)
if (targetGrid[i][j] != color) {
graph[color].add(targetGrid[i][j]);
}
}
State[] states = new State[MAX_COLOR + 1];
for (int color = 1; color <= MAX_COLOR; ++color)
if (hasCycle(graph, color, states))
return false;
return true;
}
private boolean hasCycle(Set<Integer>[] graph, int u, State[] states) {
if (states[u] == State.VISITING)
return true;
if (states[u] == State.VISITED)
return false;
states[u] = State.VISITING;
for (int v : graph[u])
if (hasCycle(graph, v, states))
return true;
states[u] = State.VISITED;
return false;
}
}
// Accepted solution for LeetCode #1591: Strange Printer II
// Auto-generated Go example from java.
func exampleSolution() {
}
// Reference (java):
// // Accepted solution for LeetCode #1591: Strange Printer II
// enum State { INIT, VISITING, VISITED }
//
// class Solution {
// public boolean isPrintable(int[][] targetGrid) {
// final int MAX_COLOR = 60;
// final int m = targetGrid.length;
// final int n = targetGrid[0].length;
// // graph[u] := {v1, v2} means v1 and v2 cover u
// Set<Integer>[] graph = new HashSet[MAX_COLOR + 1];
//
// for (int color = 1; color <= MAX_COLOR; ++color) {
// // Get the rectangle of the current color.
// int minI = m;
// int minJ = n;
// int maxI = -1;
// int maxJ = -1;
// for (int i = 0; i < m; ++i)
// for (int j = 0; j < n; ++j)
// if (targetGrid[i][j] == color) {
// minI = Math.min(minI, i);
// minJ = Math.min(minJ, j);
// maxI = Math.max(maxI, i);
// maxJ = Math.max(maxJ, j);
// }
// // Add any color covering the current as the children.
// graph[color] = new HashSet<>();
// for (int i = minI; i <= maxI; ++i)
// for (int j = minJ; j <= maxJ; ++j)
// if (targetGrid[i][j] != color) {
// graph[color].add(targetGrid[i][j]);
// }
// }
//
// State[] states = new State[MAX_COLOR + 1];
//
// for (int color = 1; color <= MAX_COLOR; ++color)
// if (hasCycle(graph, color, states))
// return false;
//
// return true;
// }
//
// private boolean hasCycle(Set<Integer>[] graph, int u, State[] states) {
// if (states[u] == State.VISITING)
// return true;
// if (states[u] == State.VISITED)
// return false;
// states[u] = State.VISITING;
// for (int v : graph[u])
// if (hasCycle(graph, v, states))
// return true;
// states[u] = State.VISITED;
// return false;
// }
// }
# Accepted solution for LeetCode #1591: Strange Printer II
from enum import Enum
class State(Enum):
INIT = 0
VISITING = 1
VISITED = 2
class Solution:
def isPrintable(self, targetGrid: list[list[int]]) -> bool:
MAX_COLOR = 60
m = len(targetGrid)
n = len(targetGrid[0])
# graph[u] := {v1, v2} means v1 and v2 cover u
graph = [set() for _ in range(MAX_COLOR + 1)]
for color in range(1, MAX_COLOR + 1):
# Get the rectangle of the current color.
minI = m
minJ = n
maxI = -1
maxJ = -1
for i in range(m):
for j in range(n):
if targetGrid[i][j] == color:
minI = min(minI, i)
minJ = min(minJ, j)
maxI = max(maxI, i)
maxJ = max(maxJ, j)
# Add any color covering the current as the children.
for i in range(minI, maxI + 1):
for j in range(minJ, maxJ + 1):
if targetGrid[i][j] != color:
graph[color].add(targetGrid[i][j])
states = [State.INIT] * (MAX_COLOR + 1)
def hasCycle(u: int) -> bool:
if states[u] == State.VISITING:
return True
if states[u] == State.VISITED:
return False
states[u] = State.VISITING
if any(hasCycle(v) for v in graph[u]):
return True
states[u] = State.VISITED
return False
return not (any(hasCycle(i) for i in range(1, MAX_COLOR + 1)))
// Accepted solution for LeetCode #1591: Strange Printer II
struct Solution;
use std::collections::HashMap;
use std::collections::HashSet;
use std::collections::VecDeque;
impl Solution {
fn is_printable(target_grid: Vec<Vec<i32>>) -> bool {
let n = target_grid.len();
let m = target_grid[0].len();
let mut color: HashMap<i32, usize> = HashMap::new();
for i in 0..n {
for j in 0..m {
let size = color.len();
color.entry(target_grid[i][j]).or_insert(size);
}
}
let c = color.len();
let mut left: Vec<usize> = vec![m - 1; c];
let mut right: Vec<usize> = vec![0; c];
let mut top: Vec<usize> = vec![n - 1; c];
let mut bottom: Vec<usize> = vec![0; c];
for i in 0..n {
for j in 0..m {
let u = color[&target_grid[i][j]];
left[u] = left[u].min(j);
right[u] = right[u].max(j);
top[u] = top[u].min(i);
bottom[u] = bottom[u].max(i);
}
}
let mut adj: Vec<HashSet<usize>> = vec![HashSet::new(); c];
for i in 0..n {
for j in 0..m {
let u = color[&target_grid[i][j]];
for v in 0..c {
if v == u {
continue;
}
if left[v] <= j && j <= right[v] && top[v] <= i && i <= bottom[v] {
adj[v].insert(u);
}
}
}
}
let mut indegree = vec![0; c];
for u in 0..c {
for &v in &adj[u] {
indegree[v] += 1;
}
}
let mut queue: VecDeque<usize> = VecDeque::new();
let mut visited = 0;
for u in 0..c {
if indegree[u] == 0 {
visited += 1;
queue.push_back(u);
}
}
while let Some(u) = queue.pop_front() {
for &v in &adj[u] {
indegree[v] -= 1;
if indegree[v] == 0 {
visited += 1;
queue.push_back(v);
}
}
}
visited == c
}
}
#[test]
fn test() {
let target_grid = vec_vec_i32![[1, 1, 1, 1], [1, 2, 2, 1], [1, 2, 2, 1], [1, 1, 1, 1]];
let res = true;
assert_eq!(Solution::is_printable(target_grid), res);
let target_grid = vec_vec_i32![[1, 1, 1, 1], [1, 1, 3, 3], [1, 1, 3, 4], [5, 5, 1, 4]];
let res = true;
assert_eq!(Solution::is_printable(target_grid), res);
let target_grid = vec_vec_i32![[1, 2, 1], [2, 1, 2], [1, 2, 1]];
let res = false;
assert_eq!(Solution::is_printable(target_grid), res);
let target_grid = vec_vec_i32![[1, 1, 1], [3, 1, 3]];
let res = false;
assert_eq!(Solution::is_printable(target_grid), res);
}
// Accepted solution for LeetCode #1591: Strange Printer II
// Auto-generated TypeScript example from java.
function exampleSolution(): void {
}
// Reference (java):
// // Accepted solution for LeetCode #1591: Strange Printer II
// enum State { INIT, VISITING, VISITED }
//
// class Solution {
// public boolean isPrintable(int[][] targetGrid) {
// final int MAX_COLOR = 60;
// final int m = targetGrid.length;
// final int n = targetGrid[0].length;
// // graph[u] := {v1, v2} means v1 and v2 cover u
// Set<Integer>[] graph = new HashSet[MAX_COLOR + 1];
//
// for (int color = 1; color <= MAX_COLOR; ++color) {
// // Get the rectangle of the current color.
// int minI = m;
// int minJ = n;
// int maxI = -1;
// int maxJ = -1;
// for (int i = 0; i < m; ++i)
// for (int j = 0; j < n; ++j)
// if (targetGrid[i][j] == color) {
// minI = Math.min(minI, i);
// minJ = Math.min(minJ, j);
// maxI = Math.max(maxI, i);
// maxJ = Math.max(maxJ, j);
// }
// // Add any color covering the current as the children.
// graph[color] = new HashSet<>();
// for (int i = minI; i <= maxI; ++i)
// for (int j = minJ; j <= maxJ; ++j)
// if (targetGrid[i][j] != color) {
// graph[color].add(targetGrid[i][j]);
// }
// }
//
// State[] states = new State[MAX_COLOR + 1];
//
// for (int color = 1; color <= MAX_COLOR; ++color)
// if (hasCycle(graph, color, states))
// return false;
//
// return true;
// }
//
// private boolean hasCycle(Set<Integer>[] graph, int u, State[] states) {
// if (states[u] == State.VISITING)
// return true;
// if (states[u] == State.VISITED)
// return false;
// states[u] = State.VISITING;
// for (int v : graph[u])
// if (hasCycle(graph, v, states))
// return true;
// states[u] = State.VISITED;
// return false;
// }
// }
Use this to step through a reusable interview workflow for this problem.
Repeatedly find a vertex with no incoming edges, remove it and its outgoing edges, and repeat. Finding the zero-in-degree vertex scans all V vertices, and we do this V times. Removing edges touches E edges total. Without an in-degree array, this gives O(V × E).
Build an adjacency list (O(V + E)), then either do Kahn's BFS (process each vertex once + each edge once) or DFS (visit each vertex once + each edge once). Both are O(V + E). Space includes the adjacency list (O(V + E)) plus the in-degree array or visited set (O(V)).
Review these before coding to avoid predictable interview regressions.
Wrong move: Loop endpoints miss first/last candidate.
Usually fails on: Fails on minimal arrays and exact-boundary answers.
Fix: Re-derive loops from inclusive/exclusive ranges before coding.