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.
A complete visual walkthrough of the look-and-say sequence — building each term by describing the previous one.
The count-and-say sequence is a sequence of digit strings defined by the recursive formula:
countAndSay(1) = "1"countAndSay(n) is the run-length encoding of countAndSay(n - 1).Run-length encoding (RLE) is a string compression method that works by replacing consecutive identical characters (repeated 2 or more times) with the concatenation of the character and the number marking the count of the characters (length of the run). For example, to compress the string "3322251" we replace "33" with "23", replace "222" with "32", replace "5" with "15" and replace "1" with "11". Thus the compressed string becomes "23321511".
Given a positive integer n, return the nth element of the count-and-say sequence.
Example 1:
Input: n = 4
Output: "1211"
Explanation:
countAndSay(1) = "1" countAndSay(2) = RLE of "1" = "11" countAndSay(3) = RLE of "11" = "21" countAndSay(4) = RLE of "21" = "1211"
Example 2:
Input: n = 1
Output: "1"
Explanation:
This is the base case.
Constraints:
1 <= n <= 30The count-and-say sequence starts with "1". Each subsequent term is created by reading aloud the previous term — describing what you see:
Read it out loud! Term 4 is "1211". Reading it: "one 2, one 1" — which becomes "1211". Wait, that's the same! No: reading "1211" gives "one 1, one 2, two 1s" = "111221". Each term describes the previous, not itself.
To build term n from term n-1, we scan left-to-right and group consecutive identical digits. For each group, we output [count][digit].
Previous term: "1211" — let's group it:
The algorithm is iterative: Start with "1", repeat n-1 times: scan the current string, count consecutive duplicates, build the next string. No recursion needed — just a loop within a loop.
Let's trace the algorithm step by step, showing how each term generates the next:
Current: "1"
Scan: one group of "1" (count=1). Output: "1" + "1" = "11"
Current: "11"
Scan: one group of "1,1" (count=2). Output: "2" + "1" = "21"
Current: "21"
Scan: group "2" (count=1), group "1" (count=1). Output: "1""2" + "1""1" = "1211"
Current: "1211"
Scan: group "1" (count=1), group "2" (count=1), group "1,1" (count=2). Output: "1""1" + "1""2" + "2""1" = "111221"
class Solution { public String countAndSay(int n) { String result = "1"; // base case: term 1 for (int i = 1; i < n; i++) { // build terms 2..n StringBuilder sb = new StringBuilder(); int count = 1; // count of current digit for (int j = 1; j < result.length(); j++) { if (result.charAt(j) == result.charAt(j - 1)) { count++; // same digit — extend group } else { sb.append(count) // different digit — emit group .append(result.charAt(j - 1)); count = 1; // start new group } } // Don't forget the last group! sb.append(count) .append(result.charAt(result.length() - 1)); result = sb.toString(); } return result; } }
func countAndSay(n int) string { result := "1" // base case: term 1 for i := 1; i < n; i++ { // build terms 2..n var sb []byte count := 1 // count of current digit for j := 1; j < len(result); j++ { if result[j] == result[j-1] { count++ // same digit — extend group } else { // different digit — emit group sb = append(sb, byte('0'+count), result[j-1]) count = 1 // start new group } } // Don't forget the last group! sb = append(sb, byte('0'+count), result[len(result)-1]) result = string(sb) } return result }
class Solution: def count_and_say(self, n: int) -> str: result = "1" # base case: term 1 for _ in range(1, n): # build terms 2..n sb: list[str] = [] count = 1 # count of current digit for j in range(1, len(result)): if result[j] == result[j - 1]: count += 1 # same digit — extend group else: sb.append(str(count) + result[j - 1]) count = 1 # start new group # Don't forget the last group! sb.append(str(count) + result[-1]) result = "".join(sb) return result
impl Solution { pub fn count_and_say(n: i32) -> String { let mut result = String::from("1"); // base case: term 1 for _ in 1..n { // build terms 2..n let chars: Vec<char> = result.chars().collect(); let mut sb = String::new(); let mut count = 1usize; // count of current digit for j in 1..chars.len() { if chars[j] == chars[j - 1] { count += 1; // same digit — extend group } else { // different digit — emit group sb.push_str(&count.to_string()); sb.push(chars[j - 1]); count = 1; // start new group } } // Don't forget the last group! sb.push_str(&count.to_string()); sb.push(*chars.last().unwrap()); result = sb; } result } }
function countAndSay(n: number): string { let result = "1"; // base case: term 1 for (let i = 1; i < n; i++) { // build terms 2..n let sb = ""; let count = 1; // count of current digit for (let j = 1; j < result.length; j++) { if (result[j] === result[j - 1]) { count++; // same digit — extend group } else { sb += count + result[j - 1]; // different digit — emit group count = 1; // start new group } } // Don't forget the last group! sb += count + result[result.length - 1]; result = sb; } return result; }
Common pitfall: The inner loop starts at j=1 and compares with j-1. After the loop exits, the last group hasn't been appended yet. The final sb.append(count).append(lastChar) handles this. Forgetting it is the most frequent bug.
Enter a value for n, then step through the generation process. Watch how each term is built from the previous by grouping consecutive digits.
Each term is generated from the previous by a single linear scan. However, the string length grows roughly exponentially -- each term can be up to 2x the length of the previous. The total work to generate the nth term sums across all iterations to O(2n). Space is dominated by the largest string stored (the final term).
There is no shortcut to skip terms -- each must be built from the previous. The outer loop runs n-1 times and the inner loop scans the current string, which roughly doubles in length each iteration. Total work sums to O(2n).
Iterative generation with a single linear scan per term using a StringBuilder. Each term is processed in one pass by counting consecutive identical digits. Space is dominated by the final string, which grows as Ln where L ~ 1.30 (Conway's constant).
This is the Morris number sequence. The sequence length grows roughly as Ln where L ~ 1.303577 (Conway's constant). Each term is at most 2x the previous. Since n is capped at 30, the final string has at most ~5,800 characters -- well within practical limits.
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.