String · 7 of 43
Count and Say problem
Build each new number by reading the last one out loud in groups, like three ones, two twos, one one, and writing down what you hear.
- Simple way
- time O(L^2)
room O(L) - Clever way
- time O(L)
room O(L)
The problem, simply
The count and say list starts with 1. Every next term is made by reading the term before it out loud, group by group.
1 is read as "one 1", so term 2 is 11. 11 is "two 1s", so term 3 is 21. 21 is "one 2, one 1", so term 4 is 1211. Given n, give back term n as a string.
A story to remember it
Picture it
The first six terms. Each one is the previous one read out loud: 111221 is three 1s, two 2s, one 1, which gives 312211.
- looking now
- answer
Examples
The start
- In
n = 1- Out
1
Term 1 is given. Nothing to read.
A few steps in
- In
n = 4- Out
1211
1, then 11, then 21, then "one 2, one 1" which is 1211.
Longer runs
- In
n = 6- Out
312211
Term 5 is 111221: three 1s, two 2s, one 1.
Bigger n
- In
n = 10- Out
13211311123113112211
The terms get longer, about 30 percent longer each step.
Way 1: the simple way
Read each term with a counter, and glue the result together with + on a plain string.
- Start with
cur = "1". - Repeat
n - 1times: walkcur, counting how many times the same digit repeats. When the digit changes, glue the count and the digit ontonextwithnext = next + count + digit. curbecomesnext.
class Solution {
public String countAndSay(int n) {
String cur = "1";
for (int step = 2; step <= n; step++) {
String next = "";
int count = 1;
for (int i = 1; i <= cur.length(); i++) {
if (i < cur.length() && cur.charAt(i) == cur.charAt(i - 1)) {
count++; // same run goes on
} else {
next = next + count + cur.charAt(i - 1); // copies next every time
count = 1;
}
}
cur = next;
}
return cur;
}
}How long it takes: Java strings cannot change, so every next + ... builds a brand new string and copies all of next into it. For a term of length L that is about L^2 copying, so O(L^2), where L is the length of the last term. Extra room: the current and next strings, O(L).
Way 2: the clever way
Same reading, two better habits. Use a StringBuilder, which grows in place instead of copying. And read each run with two fingers: i at the start of the run and j sliding right while the digit stays the same. The run length is j - i.
- Start with
cur = "1". - For each step, set
i = 0. Slidejfromiwhilecur[j] == cur[i]. Appendj - iandcur[i]. Seti = jand repeat until the end. curbecomes what you built.
Reading 111221 out loud to make term 6
01 / 05
Term 5 is 111221. To get term 6 we read it out loud in groups of the same digit.
- looking now
- done
- answer
- just added
class Solution {
public String countAndSay(int n) {
String cur = "1";
for (int step = 2; step <= n; step++) {
StringBuilder next = new StringBuilder();
int i = 0;
while (i < cur.length()) {
int j = i;
while (j < cur.length() && cur.charAt(j) == cur.charAt(i)) j++; // slide over the run
next.append(j - i).append(cur.charAt(i)); // say count, then digit
i = j; // next run starts here
}
cur = next.toString();
}
return cur;
}
}How long it takes: each step reads its term once. The terms grow by about 30 percent each time, so all the earlier terms together are only a few times longer than the last one. In total that is O(L), where L is the length of term n. Extra room: O(L) for the strings.
Watch out for
n = 1must return"1"without reading anything.- Append the count as a number, not as a
char.append((char) count)would add a strange symbol. - The terms grow fast. Term 30 already has more than 4000 digits, so never turn them into
intorlong.
What interviewers ask next
- Which digits can appear? Only 1, 2 and 3. A run of four equal digits would need an earlier term to say the same thing twice in a row, which cannot happen.
- How fast do the terms grow? Each term is about 1.3036 times longer than the one before. This is Conway's constant, and it is a fun fact quant firms like.
- Write the recursive version?
countAndSay(n)readscountAndSay(n - 1). Same work, but it keepsncalls on the stack. - Run length encode a general string, like
aaabcctoa3b1c2? The same two finger loop. Compress in place in an array (LeetCode 443) by writing with a third finger.
Remember this
Grown-up words: reading in runs is run length encoding, the two fingers are two pointers, StringBuilder avoids the copying that comes from immutable strings, and the growth rate of about 1.3 is Conway's constant.