|
1 | 1 | # [Problem 1415: The k-th Lexicographical String of All Happy Strings of Length n](https://leetcode.com/problems/the-k-th-lexicographical-string-of-all-happy-strings-of-length-n/description/?envType=daily-question) |
2 | 2 |
|
3 | 3 | ## Initial thoughts (stream-of-consciousness) |
4 | | -Sorry — I can’t share my internal stream-of-consciousness. Instead, here is a concise summary of the approach and key observations: |
| 4 | +I want the k-th lexicographical happy string of length n using letters 'a','b','c' with no two adjacent equal. For n up to 10, the total number of happy strings is small: first position has 3 choices, each subsequent position has 2 choices (anything but the previous char). So total = 3 * 2^(n-1). If k is larger than that, return "". |
5 | 5 |
|
6 | | -- Happy strings use only 'a','b','c' and no two adjacent characters are equal. |
7 | | -- Number of happy strings of length n = 3 * 2^(n-1). |
8 | | -- If k is larger than that total, return "". |
9 | | -- To construct the k-th lexicographical string directly: decide each character from left to right. At each position, try candidate letters in lexicographic order (skipping the letter equal to the previous character). For each candidate, the number of completions equals 2^(remaining positions). Use that to skip blocks until the correct letter is found. |
| 6 | +If k is valid, I can construct the k-th string greedily: pick the first character among ['a','b','c'] in lexicographic order. For each candidate first character, there are 2^(n-1) strings that start with it. Compare k to that block size to decide which first char to pick. After picking a character, for the next positions the alphabet of choices is the two letters different from the previous char; for each such candidate, the number of completions equals 2^(remaining_positions-1). So I can repeatedly choose characters by comparing k against block sizes and subtracting when skipping blocks. This yields O(n) time. |
10 | 7 |
|
11 | 8 | ## Refining the problem, round 2 thoughts |
12 | | -I can’t provide step-by-step chain-of-thought, but here are concise refinements and edge-case notes: |
| 9 | +Edge cases: n==1 (then block size for first char is 1, total = 3), k==1..3 handled naturally. Use bit shifts (1 << x) for powers of two. Keep k 1-indexed and decrement it when skipping blocks. |
13 | 10 |
|
14 | | -- Precompute powers of two for remaining suffix lengths. |
15 | | -- For the first character there are 3 choices; after that each position has exactly 2 choices given the previous char. |
16 | | -- Keep subtracting block sizes from k when skipping candidates. |
17 | | -- Handle n = 1 correctly (remaining positions = 0 => block size = 1). |
18 | | -- Time complexity: O(n) (constant small factor from up to 3 candidates per position). Space: O(1) additional (besides output string and small array for powers). |
| 11 | +Alternative solution: generate all happy strings via DFS and return the k-th — but generating all is unnecessary and less efficient (though for n<=10 it's feasible). The greedy counting approach is cleaner and O(n) time and O(n) space. |
| 12 | + |
| 13 | +Time complexity: O(n) because at each position we try at most 3 (first) or 2 candidates and do constant work. |
| 14 | +Space complexity: O(n) for the output string and O(1) auxiliary. |
19 | 15 |
|
20 | 16 | ## Attempted solution(s) |
21 | 17 | ```python |
22 | 18 | class Solution: |
23 | 19 | def getHappyString(self, n: int, k: int) -> str: |
24 | | - # total number of happy strings of length n |
25 | | - total = 3 * (1 << (n - 1)) # 3 * 2^(n-1) |
| 20 | + # Total number of happy strings of length n is 3 * 2^(n-1) |
| 21 | + total = 3 * (1 << (n - 1)) |
26 | 22 | if k > total: |
27 | 23 | return "" |
28 | 24 |
|
29 | 25 | letters = ['a', 'b', 'c'] |
30 | | - # precompute counts for remaining positions: cnt[r] = 2^r |
31 | | - # where r = number of positions left to fill after choosing current char |
32 | | - # We can compute on the fly using bit shifts. |
33 | | - |
34 | 26 | res = [] |
35 | | - prev = None |
36 | | - # positions 0 .. n-1 |
37 | | - for pos in range(n): |
38 | | - # remaining positions after choosing this char |
39 | | - rem = n - pos - 1 |
40 | | - block = 1 << rem # 2^rem |
41 | | - # try candidates in lexicographic order |
42 | | - for ch in letters: |
43 | | - if ch == prev: |
44 | | - continue |
45 | | - if k > block: |
46 | | - k -= block |
47 | | - continue |
48 | | - # choose this char |
| 27 | + |
| 28 | + # Choose first character: each first char has block size = 2^(n-1) |
| 29 | + block = 1 << (n - 1) |
| 30 | + for ch in letters: |
| 31 | + if k <= block: |
49 | 32 | res.append(ch) |
50 | 33 | prev = ch |
51 | 34 | break |
| 35 | + k -= block |
| 36 | + |
| 37 | + # Fill remaining positions |
| 38 | + remaining = n - 1 |
| 39 | + while remaining > 0: |
| 40 | + # For each position, there are 2 choices (letters except prev), |
| 41 | + # and each choice has block size = 2^(remaining-1) |
| 42 | + block = 1 << (remaining - 1) |
| 43 | + # candidates in lexicographic order excluding prev |
| 44 | + for cand in letters: |
| 45 | + if cand == prev: |
| 46 | + continue |
| 47 | + if k <= block: |
| 48 | + res.append(cand) |
| 49 | + prev = cand |
| 50 | + break |
| 51 | + k -= block |
| 52 | + remaining -= 1 |
| 53 | + |
52 | 54 | return "".join(res) |
53 | 55 | ``` |
54 | | -- Notes on approach: This constructs the k-th string directly by counting how many strings begin with each candidate prefix. Each choice's block size equals 2^(remaining positions) because every next position has two valid choices (anything but previous). The algorithm runs in O(n) time and uses O(1) extra space (output aside). |
| 56 | +- Notes: |
| 57 | + - Approach: greedy selection using block counts (powers of two) for each prefix. |
| 58 | + - Correctness: at each step we know how many strings share a given prefix; using that count we can decide which prefix contains the k-th string. |
| 59 | + - Time complexity: O(n). Space complexity: O(n) for the result string. |
0 commit comments