Problem
A happy string is a string that:
- consists only of letters of the set
['a', 'b', 'c']
. s[i] != s[i + 1]
for all values ofi
from1
tos.length - 1
(string is 1-indexed).
For example, strings “abc”, “ac”, “b” and “abcbabcbcb” are all happy strings and strings “aa”, “baa” and “ababbc” are not happy strings.
Given two integers n
and k
, consider a list of all happy strings of length n
sorted in lexicographical order.
Return the kth string of this list or return an empty string if there are less than k
happy strings of length n
.]()
Examples
Example 1:
|
|
Example 2:
|
|
Example 3:
|
|
Constraints:
1 <= n <= 10
1 <= k <= 100
Solution
Method 1 - Backtracking
The goal is to generate all “happy strings” of length n
, sort them lexicographically, and return the k
th string. A “happy string” adheres to the following rules:
- It contains only the characters
['a', 'b', 'c']
. - Consecutive characters are different.
Given the constraints, generating all strings using recursion is a suitable approach. Using backtracking, we’ll attempt to build strings character by character, ensuring they are “happy” at every step. Once we have all happy strings, we can access the k
th string directly or return an empty string if k
exceeds the count.
Approach
- Backtracking: Start with an empty string and try appending each character
['a', 'b', 'c']
, ensuring no two consecutive characters are the same. - Base Case: Stop when the string reaches the desired length
n
, and add it to a result array. - Lexicographical Order: Backtracking naturally produces strings in lexicographical order due to the order of character choices.
- Check if the number of generated happy strings is at least
k
. If yes, return thek
th string; otherwise, return an empty string.
Video explanation
Here is the video explaining this method in detail. Please check it out:
Code
|
|
|
|
|
|
|
|
Complexity
- ⏰ Time complexity:
O(2^n)
- Generating all happy strings of length
n
involvesO(2^n)
operations as, for each position, we have at most 2 valid choices (excluding the previous character). - Accessing the
k
th string from the stored list isO(1)
, so the overall time complexity isO(2^n)
.
- Generating all happy strings of length
- 🧺 Space complexity:
O(2^n)
- Storing all happy strings requires
O(2^n)
space. - The recursive call stack takes
O(n)
space. - Hence, the overall space complexity is
O(2^n)
.
- Storing all happy strings requires
Dry Runs
n
= 3, k
= 4
For n = 3, k = 4
:
- Generate All Happy Strings of Length 3:
- Backtracking generates:
["aba", "abc", "aca", "acb", "bab", "bac", "bca", "bcb", "cab", "cac", "cba", "cbc"]
(lexicographical order).
- Backtracking generates:
- Access the 4th string:
"acb"
.
graph TD S["Start (Empty String)"] --> |a| A["a"] S --> |b| B["b"] S --> |c| C["c"] A --> |b| AA["ab"] A --> |c| AB["ac"] B --> |a| BA["ba"] B --> |c| BB["bc"] C --> |a| CA["ca"] C --> |b| CB["cb"] AA --> |a| AAA["aba"] AA --> |c| AAB["abc"] AB --> |a| ABA["aca"] AB --> |b| ABB["acb"] BA --> |b| BAA["bab"] BA --> |c| BAB["bac"] BB --> |a| BBA["bca"] BB --> |b| BBB["bcb"] CA --> |b| CAA["cab"] CA --> |c| CAB["cac"] CB --> |a| CBA["cba"] CB --> |c| CBB["cbc"]
n
= 2, k
= 8
For n = 2, k = 8
(when k
exceeds count):
- Generated Happy Strings:
["ab", "ac", "ba", "bc", "ca", "cb"]
. - Since
k = 8
exceeds the count, return an empty string.
Method 2 - Using Maths
If you observe, the recursion tree is very symmetric.
Instead of generating all the happy strings explicitly (which results in exponential time complexity), we can use the ordered and structured nature of the happy strings to directly compute the k
th happy string without generating all the others.
Symmetry observation
- For a given
n
, the strings branch at every position (a
,b
,c
), resulting in a tree structure. - Each valid branch creates half (
2/3
) of the total possibilities at each step due to the restriction (no consecutive characters are the same
). - For example, at every level:
- If the previous character is
'a'
, the valid choices are'b'
and'c'
. - If the previous character is
'b'
, the valid choices are'a'
and'c'
. - This reduces the problem to selecting a character based on the
k
value and recursively narrowing down.
- If the previous character is
Mathematical Optimisation
- The total number of happy strings of length
n
is3 * 2^(n-1)
(derived from 3 choices for the first character and 2 choices thereafter). - If we know the
k
value, we can choose the next character at each level based on which regionk
falls into without generating all possibilities.
Algorithm
- Start with an empty result string.
- Compute the total number of happy strings of length
n
(let it betotal
). - For each position in the string (from
0
ton-1
):- Divide the space of possibilities equally among valid branches (depending on the previous character).
- Narrow down to the correct branch based on
k
. - Append the corresponding character to the result.
- Update
k
to represent the relative position within the current branch.
Code
|
|
|
|
Complexity
- ⏰ Time complexity:
O(n)
as at each step, we compute the valid choices and reduce the problem size in constant time. - 🧺 Space complexity:
O(1)
Dry Run
Here are the steps:
Step 1:
- Total happy strings:
3 * 2^(3-1) = 12
. - First character has 3 groups (
a
,b
,c
), each with size2^(3-1) = 4
. k = 4
falls in the first group (a
).
Step 2 (fix the first character to a
):
- Within the
a
group (ab
,ac
), each second-level group has size2^(3-2) = 2
. k = 4
is on the second group (ac
).
Step 3 (fix the first two characters to ac
):
- Within the
ac
group (aca
,acb
), each group has size2^(3-3) = 1
. k = 4
picks the second string (acb
).
Result: "acb"