Facebook Pixel

38. Count and Say

MediumString
Leetcode Link

Problem Description

The count-and-say sequence is a special sequence of strings where each term describes the previous term using run-length encoding.

The sequence starts with "1" and follows these rules:

  • countAndSay(1) = "1" (base case)
  • For n > 1, countAndSay(n) is obtained by "reading" countAndSay(n-1) aloud

To "read" a string aloud means to count consecutive groups of the same digit and say the count followed by the digit itself. This process is called run-length encoding.

For example:

  • "1" is read as "one 1" → "11"
  • "11" is read as "two 1s" → "21"
  • "21" is read as "one 2, then one 1" → "1211"
  • "1211" is read as "one 1, one 2, then two 1s" → "111221"
  • "111221" is read as "three 1s, two 2s, then one 1" → "312211"

Another example with the string "3322251":

  • "33" becomes "23" (two 3s)
  • "222" becomes "32" (three 2s)
  • "5" becomes "15" (one 5)
  • "1" becomes "11" (one 1)
  • Combined result: "23321511"

Given a positive integer n, return the n-th term of the count-and-say sequence.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

Since each term in the sequence is built from the previous term, we need to simulate the process iteratively. Starting from "1", we generate each subsequent term by "reading" the current term.

The key insight is that we need to group consecutive identical digits together. When we scan through a string like "111221", we need to identify groups: "111", "22", and "1". For each group, we record two pieces of information: the count of digits and the digit itself.

To implement this grouping, we can use two pointers:

  • One pointer i marks the start of a group
  • Another pointer j moves forward to find where the group ends (when we encounter a different digit)

For each group found, we append the count (j - i) followed by the digit at position i to our result. This naturally builds the run-length encoding.

After processing all groups in the current term, we have our next term. We repeat this process n - 1 times to get from the first term to the n-th term.

The beauty of this approach is its simplicity - we're essentially implementing exactly what the problem describes: counting consecutive digits and saying what we see. No complex data structures or algorithms needed, just careful tracking of where each group of identical digits begins and ends.

Solution Approach

The solution uses a simulation approach with two-pointer technique to implement the run-length encoding for each iteration.

Algorithm Steps:

  1. Initialize: Start with s = '1' as the first term of the sequence.

  2. Iterate n - 1 times: Since we already have the first term, we need n - 1 iterations to reach the n-th term.

  3. For each iteration, process the current string s:

    • Initialize an empty list t to build the next term
    • Use pointer i to track the current position in string s
  4. Group consecutive identical digits:

    • While i < len(s):
      • Set j = i as a second pointer
      • Move j forward while s[j] == s[i] (same digit)
      • The count of consecutive digits is j - i
      • Append the count and the digit to list t:
        • t.append(str(j - i)) - add the count
        • t.append(str(s[i])) - add the digit itself
      • Move i to j to process the next group
  5. Update the string: Join all elements in t to form the new string: s = ''.join(t)

  6. Return: After n - 1 iterations, s contains the n-th term.

Example Walkthrough for n = 4:

  • Start: s = "1"
  • Iteration 1: "1" → one 1 → "11"
  • Iteration 2: "11" → two 1s → "21"
  • Iteration 3: "21" → one 2, one 1 → "1211"
  • Return: "1211"

Time Complexity: O(n × m) where n is the input parameter and m is the maximum length of any string in the sequence. Each iteration processes all characters in the current string.

Space Complexity: O(m) for storing the temporary list t and the string s.

Ready to land your dream job?

Unlock your dream job with a 3-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's trace through the algorithm with n = 5 to find the 5th term of the count-and-say sequence.

Initial State: s = "1"

Iteration 1 (to get 2nd term):

  • Current string: "1"
  • Set i = 0, create empty list t = []
  • Group starting at i = 0:
    • j = 0, advance j while s[j] == s[0] (both are '1')
    • j stops at 1 (end of string)
    • Count = j - i = 1 - 0 = 1
    • Append: t = ["1", "1"] (one '1')
  • Update s = "11"

Iteration 2 (to get 3rd term):

  • Current string: "11"
  • Set i = 0, create empty list t = []
  • Group starting at i = 0:
    • j = 0, advance j while s[j] == s[0] (both are '1')
    • j stops at 2 (end of string)
    • Count = j - i = 2 - 0 = 2
    • Append: t = ["2", "1"] (two '1's)
  • Update s = "21"

Iteration 3 (to get 4th term):

  • Current string: "21"
  • Set i = 0, create empty list t = []
  • Group 1 starting at i = 0:
    • j = 0, advance j while s[j] == s[0] ('2')
    • j stops at 1 (different digit found)
    • Count = 1 - 0 = 1
    • Append: t = ["1", "2"] (one '2')
    • Move i to 1
  • Group 2 starting at i = 1:
    • j = 1, advance j while s[j] == s[1] ('1')
    • j stops at 2 (end of string)
    • Count = 2 - 1 = 1
    • Append: t = ["1", "2", "1", "1"] (one '1')
  • Update s = "1211"

Iteration 4 (to get 5th term):

  • Current string: "1211"
  • Set i = 0, create empty list t = []
  • Group 1 at i = 0: '1' appears once → append ["1", "1"]
  • Group 2 at i = 1: '2' appears once → append ["1", "2"]
  • Group 3 at i = 2: '1' appears twice → append ["2", "1"]
  • Final: t = ["1", "1", "1", "2", "2", "1"]
  • Update s = "111221"

Result: The 5th term is "111221"

The two-pointer technique efficiently identifies consecutive groups: pointer i marks the group start, pointer j finds the group end, and j - i gives us the count. This process naturally builds the run-length encoding required for each term.

Solution Implementation

1class Solution:
2    def countAndSay(self, n: int) -> str:
3        """
4        Generate the nth term of the count-and-say sequence.
5      
6        The count-and-say sequence describes the previous term by counting
7        consecutive identical digits.
8        Example: "1" -> "11" (one 1) -> "21" (two 1s) -> "1211" (one 2, one 1)
9      
10        Args:
11            n: The term number to generate (1-indexed)
12          
13        Returns:
14            The nth term of the count-and-say sequence as a string
15        """
16        # Initialize with the first term of the sequence
17        current_term = '1'
18      
19        # Generate each subsequent term up to the nth term
20        for _ in range(n - 1):
21            # Index to traverse the current term
22            index = 0
23            # List to build the next term
24            next_term_parts = []
25          
26            # Process all characters in the current term
27            while index < len(current_term):
28                # Find the end of the current run of identical digits
29                run_end = index
30                while run_end < len(current_term) and current_term[run_end] == current_term[index]:
31                    run_end += 1
32              
33                # Count of consecutive identical digits
34                count = run_end - index
35                # The digit being counted
36                digit = current_term[index]
37              
38                # Append count and digit to build the next term
39                next_term_parts.append(str(count))
40                next_term_parts.append(digit)
41              
42                # Move to the next different digit
43                index = run_end
44          
45            # Join all parts to form the next term
46            current_term = ''.join(next_term_parts)
47      
48        return current_term
49
1class Solution {
2    /**
3     * Generates the nth term of the Count-and-Say sequence.
4     * The sequence starts with "1" and each subsequent term describes the previous term.
5     * 
6     * @param n The position in the sequence to generate (1-indexed)
7     * @return The nth term of the Count-and-Say sequence
8     */
9    public String countAndSay(int n) {
10        // Initialize with the first term of the sequence
11        String currentSequence = "1";
12      
13        // Generate each subsequent term until we reach the nth term
14        while (--n > 0) {
15            StringBuilder nextSequence = new StringBuilder();
16          
17            // Process the current sequence to generate the next one
18            for (int currentIndex = 0; currentIndex < currentSequence.length();) {
19                // Find the end position of consecutive identical digits
20                int endIndex = currentIndex;
21                while (endIndex < currentSequence.length() && 
22                       currentSequence.charAt(endIndex) == currentSequence.charAt(currentIndex)) {
23                    endIndex++;
24                }
25              
26                // Append the count of consecutive digits
27                int count = endIndex - currentIndex;
28                nextSequence.append(count);
29              
30                // Append the digit itself
31                nextSequence.append(currentSequence.charAt(currentIndex));
32              
33                // Move to the next group of digits
34                currentIndex = endIndex;
35            }
36          
37            // Update the current sequence for the next iteration
38            currentSequence = nextSequence.toString();
39        }
40      
41        return currentSequence;
42    }
43}
44
1class Solution {
2public:
3    string countAndSay(int n) {
4        // Initialize the first term of the sequence
5        string currentSequence = "1";
6      
7        // Generate the sequence n-1 times to get the nth term
8        while (--n) {
9            string nextSequence = "";
10          
11            // Process the current sequence to generate the next one
12            for (int i = 0; i < currentSequence.size();) {
13                // Find the end position of consecutive identical digits
14                int j = i;
15                while (j < currentSequence.size() && currentSequence[j] == currentSequence[i]) {
16                    ++j;
17                }
18              
19                // Append count of consecutive digits
20                nextSequence += to_string(j - i);
21                // Append the digit itself
22                nextSequence += currentSequence[i];
23              
24                // Move to the next group of digits
25                i = j;
26            }
27          
28            // Update current sequence for next iteration
29            currentSequence = nextSequence;
30        }
31      
32        return currentSequence;
33    }
34};
35
1/**
2 * Generates the nth term in the Count-and-Say sequence.
3 * The sequence begins with "1" and each subsequent term describes the previous term.
4 * For example: "1" -> "11" (one 1) -> "21" (two 1s) -> "1211" (one 2, one 1)
5 * @param n - The position in the sequence to generate (1-indexed)
6 * @returns The nth term of the Count-and-Say sequence
7 */
8function countAndSay(n: number): string {
9    // Initialize with the first term of the sequence
10    let currentSequence: string = '1';
11  
12    // Generate each subsequent term up to the nth term
13    for (let iteration = 1; iteration < n; iteration++) {
14        // Build the next term by reading the current sequence
15        let nextSequence: string = '';
16      
17        // Track the current character being counted
18        let currentChar: string = currentSequence[0];
19        let charCount: number = 1;
20      
21        // Iterate through the rest of the current sequence
22        for (let charIndex = 1; charIndex < currentSequence.length; charIndex++) {
23            // Check if we've encountered a different character
24            if (currentSequence[charIndex] !== currentChar) {
25                // Append the count and character to the next sequence
26                nextSequence += `${charCount}${currentChar}`;
27              
28                // Reset for the new character
29                currentChar = currentSequence[charIndex];
30                charCount = 0;
31            }
32            // Increment count for the current character
33            charCount++;
34        }
35      
36        // Append the final group of characters
37        nextSequence += `${charCount}${currentChar}`;
38      
39        // Update the current sequence for the next iteration
40        currentSequence = nextSequence;
41    }
42  
43    return currentSequence;
44}
45

Time and Space Complexity

Time Complexity: O(n * m) where n is the input parameter and m is the maximum length of the string generated during the process.

The algorithm performs n-1 iterations. In each iteration, it processes every character of the current string s. The length of the string grows with each iteration, but the growth rate is bounded. In the worst case, the string length can grow by a factor of 2 (when all digits are different), but typically the growth is less dramatic due to run-length encoding compression. The processing of each character involves constant-time operations (comparisons and appends). Therefore, the overall time complexity is O(n * m) where m represents the length of the longest intermediate string generated.

Space Complexity: O(m) where m is the maximum length of the string generated.

The algorithm uses additional space for:

  • The string s which stores the current result: O(m)
  • The list t which temporarily stores the components of the next iteration's string: O(m)
  • Variables i and j for iteration: O(1)

Since we're building a new string in each iteration and the old string is replaced, we only need to consider the space for the current and next string at any point. The maximum space used is proportional to the length of the longest string generated during the process, which gives us O(m) space complexity.

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Off-by-One Error in Loop Count

A frequent mistake is running the loop n times instead of n-1 times. Since we start with the first term "1" already initialized, we only need n-1 iterations to reach the nth term.

Incorrect:

for _ in range(n):  # This would give us the (n+1)th term
    # process...

Correct:

for _ in range(n - 1):  # Properly gives us the nth term
    # process...

2. String Concatenation Performance Issue

Building the next term using string concatenation in a loop can be inefficient due to string immutability in Python.

Inefficient approach:

next_term = ""
while index < len(current_term):
    # ... counting logic ...
    next_term += str(count) + digit  # Creates new string objects repeatedly

Efficient solution:

next_term_parts = []
while index < len(current_term):
    # ... counting logic ...
    next_term_parts.append(str(count))
    next_term_parts.append(digit)
current_term = ''.join(next_term_parts)  # Single join operation

3. Incorrect Boundary Check in Inner Loop

When finding consecutive identical digits, forgetting to check the array boundary can cause an IndexError.

Problematic:

run_end = index
while current_term[run_end] == current_term[index]:  # Missing boundary check
    run_end += 1

Fixed:

run_end = index
while run_end < len(current_term) and current_term[run_end] == current_term[index]:
    run_end += 1

4. Not Handling Edge Case n=1

Forgetting that when n=1, the loop should not execute at all, and the function should return "1" immediately.

Solution: The code correctly handles this by using range(n - 1), which produces an empty range when n=1, causing the loop to be skipped entirely and returning the initial value "1".

Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

Given a sorted array of integers and an integer called target, find the element that equals to the target and return its index. Select the correct code that fills the ___ in the given code snippet.

1def binary_search(arr, target):
2    left, right = 0, len(arr) - 1
3    while left ___ right:
4        mid = (left + right) // 2
5        if arr[mid] == target:
6            return mid
7        if arr[mid] < target:
8            ___ = mid + 1
9        else:
10            ___ = mid - 1
11    return -1
12
1public static int binarySearch(int[] arr, int target) {
2    int left = 0;
3    int right = arr.length - 1;
4
5    while (left ___ right) {
6        int mid = left + (right - left) / 2;
7        if (arr[mid] == target) return mid;
8        if (arr[mid] < target) {
9            ___ = mid + 1;
10        } else {
11            ___ = mid - 1;
12        }
13    }
14    return -1;
15}
16
1function binarySearch(arr, target) {
2    let left = 0;
3    let right = arr.length - 1;
4
5    while (left ___ right) {
6        let mid = left + Math.trunc((right - left) / 2);
7        if (arr[mid] == target) return mid;
8        if (arr[mid] < target) {
9            ___ = mid + 1;
10        } else {
11            ___ = mid - 1;
12        }
13    }
14    return -1;
15}
16

Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More