Cost of a loop, next perm, parse then count
Say the honest Big-O. Do not concatenate in a loop. Next perm is a pivot from the right. Encoded frequency is a grammar, then a count array. Job 10454435 still none.
+ copies the whole prefix. Quadratic in a loop.Honest Big-O
This is a table, not a Live Code problem. Two tiny pictures: HashMap get is not always O(1), and recursion is not O(1) extra just because you wrote no array.
| Operation | Average / typical | Honest worst |
|---|---|---|
Array index a[i] | O(1) | O(1) |
| Array insert / delete at front | O(n) copy | O(n) |
| ArrayList append (back) | O(1) amortized | O(n) on a grow copy |
| HashMap get / put | O(1) | O(n) if every key lands in one bucket (Java 8+ treeifies comparable keys → O(log n) in that bin) |
| TreeMap get / put | O(log n) | O(log n) |
| Binary search on sorted array | O(log n) | O(log n) |
| DFS / recursion | time as the recurrence | stack = depth of the call tree — that is space |
String + in a loop of n | people say O(n) | O(n²) character copies — next card |
Example 1 — simple
HashMap average vs a collision chain. Same three puts; different bucket layouts.
Map<Integer, Integer> m = new HashMap<>(); m.put(2, 0); m.put(7, 1); m.get(7); // typical: hash 7 lands in its bin, one entry — O(1) // worst picture: 2 and 7 (and n other keys) all hash to one bin; // get walks that chain — O(n) until the table treeifies comparable keys
Answer: say “O(1) average, O(n) if everything collides” — not “HashMap is always O(1).”
Example 2 — every step
Recursion stack space. Sum 1..4 with a call per n. Time is O(n); extra memory is also O(n) frames, even though you allocated no array.
int sum(int n) {
if (n == 0) return 0; // one frame still sits here until the child returns
return n + sum(n - 1); // n frames live at the bottom: sum(4)→sum(3)→sum(2)→sum(1)→sum(0)
}
| Event | Frames on the stack (top on the right) | Space live |
|---|---|---|
| enter sum(4) | [4] | 1 |
| enter sum(3) | [4, 3] | 2 |
| enter sum(2) | [4, 3, 2] | 3 |
| enter sum(1) | [4, 3, 2, 1] | 4 |
| enter sum(0) | [4, 3, 2, 1, 0] | 5 |
| return 0, then 1, then 3, then 6, then 10 | unwinds to empty | peak 5 = O(n) |
Answer: result 10. Peak stack 5 frames. Tail-recursion does not save you in Java — there is no tail-call elimination on this pattern.
- Writing O(1) for HashMap without the collision sentence.
- Counting only heap arrays and ignoring the call stack.
- Saying ArrayList get is O(n) — it is O(1); insert at 0 is O(n).
StringBuilder vs + in a loop
Recognize it from — build a string of length n from pieces, add two huge digit-strings, serialize a count key, reverse a character buffer. Java String is immutable; each + allocates a new one.
What it actually does — s = s + ch copies every character already in s into a new object, then appends ch. After n appends of one char, you copied 1+2+…+n characters — quadratic. StringBuilder keeps one growing buffer (amortized doubling) and copies each character a constant number of times.
Simple — read this first
String repeatPlus(int n) {
String s = "";
for (int i = 0; i < n; i++) {
s = s + "a"; // new String object every time; copies s.length() chars
}
return s;
}
Interview — type this
String repeatSb(int n) {
StringBuilder sb = new StringBuilder(n); // capacity hint — one buffer
for (int i = 0; i < n; i++) sb.append('a'); // amortized O(1) per char
return sb.toString();
}
String addLong(String a, String b) { // schoolbook add: same builder habit
StringBuilder sb = new StringBuilder();
int i = a.length() - 1, j = b.length() - 1, c = 0;
while (i >= 0 || j >= 0 || c > 0) {
int s = c;
if (i >= 0) s += a.charAt(i--) - '0';
if (j >= 0) s += b.charAt(j--) - '0';
sb.append(s % 10);
c = s / 10;
}
return sb.reverse().toString();
}
Example 1 — simple
n = 3 concatenations. Count the objects and the character copies.
| i | Old s | New object | Chars copied into the new object | Old object |
|---|---|---|---|---|
| 0 | "" | "a" | 1 | "" garbage |
| 1 | "a" | "aa" | 2 | "a" garbage |
| 2 | "aa" | "aaa" | 3 | "aa" garbage |
Answer: 3 new String objects (plus the empty start). Character copies 1+2+3 = 6, not 3.
Example 2 — every step
n = 5. Quadratic copies vs StringBuilder.
| After i | String + : length of new object | Chars copied this step (+) | Total copies so far (+) | StringBuilder copies this step |
|---|---|---|---|---|
| 0 | 1 | 1 | 1 | 1 |
| 1 | 2 | 2 | 3 | 1 |
| 2 | 3 | 3 | 6 | 1 |
| 3 | 4 | 4 | 10 | 1 |
| 4 | 5 | 5 | 15 | 1 |
Answer: + copied 15 characters (1+2+3+4+5). Builder copied 5 into its buffer (plus a possible grow, none if capacity was n). For general n, + is Θ(n²).
+ safe and slow in a loop. The compiler may rewrite a single expression a + b + c into a builder; it does not save a loop. Digit-string addition in the notebook is the same habit: append remainders, reverse once.
- Building the answer with
+insideforand quoting O(n). - Calling
Integer.parseInton a 40-digit string. Walk digits. - Forgetting
sb.reverse()after appending from the right.
| Problem | Pattern | Links |
|---|---|---|
| Sum of a very long string | Schoolbook add from the right, StringBuilder — not the 1226# parser | cheat card |
| Count good-review words; sort reviews | HashSet of good words, then their comparator — ask the key | cheat card |
Next permutation — pivot, swap, reverse suffix
Recognize it from — next lexicographic permutation of an array; wrap to sorted-ascending if this is already the last. “Next greater number with the same digits” is the same walk on a digit array. Next greater element is a stack — different chapter.
What it actually does — from the right, find the first ascent a[i] < a[i+1] (the pivot: the rightmost place we can increase). If none, the array is non-increasing — reverse all of it. Otherwise find the rightmost a[j] > a[i] (the suffix is non-increasing, so that j is the smallest successor), swap i and j, then reverse the suffix after i so it becomes the smallest tail.
Simple — read this first
void nextPermutation(int[] a) {
int n = a.length;
int i = n - 2;
while (i >= 0 && a[i] >= a[i + 1]) i--; // walk right → left until an ascent (pivot)
if (i >= 0) {
int j = n - 1;
while (a[j] <= a[i]) j--; // rightmost successor strictly greater than pivot
int t = a[i]; a[i] = a[j]; a[j] = t;
}
reverse(a, i + 1, n - 1); // suffix was decreasing; reverse → smallest tail
}
void reverse(int[] a, int l, int r) {
while (l < r) {
int t = a[l]; a[l] = a[r]; a[r] = t;
l++; r--;
}
}
Interview — type this
void nextPermutation(int[] a) {
if (a == null || a.length <= 1) return;
int n = a.length, i = n - 2;
while (i >= 0 && a[i] >= a[i + 1]) i--;
if (i >= 0) {
int j = n - 1;
while (a[j] <= a[i]) j--;
int t = a[i]; a[i] = a[j]; a[j] = t; // in-place swap
}
for (int l = i + 1, r = n - 1; l < r; l++, r--) { // in-place reverse, no helper if they want one method
int t = a[l]; a[l] = a[r]; a[r] = t;
}
}
Example 1 — simple
[1, 2, 3] → [1, 3, 2].
| Step | i / j | Array | Why |
|---|---|---|---|
| i starts at 1 | i = 1 | [1, 2, 3] | a[1]=2 < a[2]=3, ascent found, stop |
| j from the right | j = 2 | [1, 2, 3] | 3 > 2, successor |
| swap i, j | — | [1, 3, 2] | pivot increased |
| reverse suffix i+1..end | l=2,r=2 | [1, 3, 2] | one element, nothing to flip |
Answer: [1, 3, 2].
Example 2 — every step
[1, 3, 2] → [2, 1, 3]. Every index from the right.
| Look at | Compare | Decision |
|---|---|---|
| i = 1 (value 3) | 3 ≥ 2 | not an ascent; i-- |
| i = 0 (value 1) | 1 < 3 | pivot found; stop walking i |
| j = 2 (value 2) | 2 > 1 | rightmost successor; stop walking j (suffix decreasing ⇒ first hit from the right is the smallest > pivot) |
| swap 0 and 2 | — | [2, 3, 1] |
| reverse indices 1..2 | 3 ↔ 1 | [2, 1, 3] |
Answer: [2, 1, 3]. Do not stamp an LC id on the AUTA “similar to next permutation” write-up.
- Finding the leftmost ascent. Next perm is controlled by the rightmost.
- Sorting the suffix instead of reversing. It is already decreasing — reverse is O(n) and correct; sort is the same result, slower, and looks like you missed the invariant.
- Mixing this with next-greater-element (stack).
| Problem | Pattern | Links |
|---|---|---|
| Next permutation | Pivot from the right; wrap = reverse all. No LC id stamp on 6806195 “similar.” | Java in ch.05 · cheat card · clue |
Encoded string frequency — index walk
Recognize it from — a string of digits, #, and (k) that encodes letters; output letter counts. The notebook sample is 1226#24#(2). The grammar in that IE was not fully specified — say the assumption out loud, then parse. This is not “sum two long digit strings.”
Assumed grammar (same as chapter 05, from the sample): a single digit d is letter a..i (1→a … 9→i). Two digits followed by # is letter j..z (10#→j … 26#→z). Greedy: try the two-digit # token first. An optional (k) immediately after a token multiplies that token’s count. Unknown letters / other symbols: ask; this booklet does not invent extra productions.
What it actually does — walk an index i. At each step parse one token, then an optional multiplier, then add into a count array. Do not expand (k) into k copies of the letter when they only asked for frequencies — that blows up huge k.
Simple — read this first
Map<Character, Integer> freqEncoded(String s) {
Map<Character, Integer> map = new HashMap<>();
int i = 0, n = s.length();
while (i < n) {
int code, j;
if (i + 2 < n && Character.isDigit(s.charAt(i))
&& Character.isDigit(s.charAt(i + 1)) && s.charAt(i + 2) == '#') {
code = Integer.parseInt(s.substring(i, i + 2)); // 10#..26# before a lone digit
j = i + 3; // skip the two digits and '#'
} else {
code = s.charAt(i) - '0'; // 1..9 → a..i
j = i + 1;
}
int times = 1;
if (j < n && s.charAt(j) == '(') { // optional (k) multiplies this token
int k = j + 1;
while (k < n && s.charAt(k) != ')') k++;
times = Integer.parseInt(s.substring(j + 1, k));
j = k + 1;
}
map.merge((char) ('a' + code - 1), times, Integer::sum);
i = j; // next token
}
return map;
}
Interview — type this
int[] freqEncodedCounts(String s) {
int[] cnt = new int[26]; // no HashMap, no expanded string
int i = 0, n = s.length();
while (i < n) {
int code, j;
if (i + 2 < n && Character.isDigit(s.charAt(i))
&& Character.isDigit(s.charAt(i + 1)) && s.charAt(i + 2) == '#') {
code = (s.charAt(i) - '0') * 10 + (s.charAt(i + 1) - '0');
j = i + 3;
} else {
code = s.charAt(i) - '0';
j = i + 1;
}
int times = 1;
if (j < n && s.charAt(j) == '(') {
int k = j + 1, val = 0;
while (k < n && s.charAt(k) != ')') val = val * 10 + (s.charAt(k++) - '0');
times = val; // add times into cnt — do not loop times appending a letter
j = k + 1;
}
cnt[code - 1] += times;
i = j;
}
return cnt;
}
Example 1 — simple
Locked tiny string: 12#. Under the grammar above, that is one token: two digits plus # → letter 12 → l. It is not 1 then 2 then leftover #, because the two-digit rule is greedy.
| i | Lookahead | Token | code | letter | times | i after |
|---|---|---|---|---|---|---|
| 0 | s[0..2] = 1, 2, # | 12# | 12 | l | 1 | 3 = n, done |
Answer: l once. Four-token drill defined next, still under this grammar.
Example 2 — every step
Four-token string we define: 10#11#2(3)3 → tokens 10#, 11#, 2(3), 3. Letters j, k, b×3, c. The notebook family sample 1226#24#(2) uses the same grammar — dry-run lives on the ch.05 card; do not invent extra productions.
| i before | Greedy dd#? | Token | (k)? | letter | cnt add | i after |
|---|---|---|---|---|---|---|
| 0 | yes, 10# | 10# | no | j (10) | cnt['j'-'a'] += 1 | 3 |
| 3 | yes, 11# | 11# | no | k (11) | k += 1 | 6 |
| 6 | s[6..8] is 2,(,3 — not digit-digit-# | 2 | (3) | b | b += 3 (not bbb string) | 10 |
| 10 | last char 3 | 3 | no | c | c += 1 | 11 = n |
Answer: j=1, k=1, b=3, c=1. Family sample on ch.05 encoded-frequency: 1→a, 2→b, 26#→z, 24#(2)→x×2. Confirm grammar before coding if they hand you a different string.
(1000000) into a million-character string is how you run out of time and heap when they only wanted frequencies.
- Taking a single digit before checking two digits +
#.12#becomes a, b, leftover hash. - Inventing an LC id for
1226#24#(2). - Building the expanded string when the output is a count map.
| Problem | Pattern | Links |
|---|---|---|
| Encoded string frequency 1226#24#(2) | Parse then count. Grammar assumed from the sample — say that. Mapped our R1 analogue (OA-as-R1) on that IE, not a Job 10454435 item. | Java in ch.05 · cheat card · clue |