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:
Input: n = 1, k = 3
Output: "c"
Explanation: The list ["a", "b", "c"] contains all happy strings of length 1. The third string is "c".
Example 2:
Input: n = 1, k = 4
Output: ""
Explanation: There are only 3 happy strings of length 1.
Example 3:
Input: n = 3, k = 9
Output: "cab"
Explanation: There are 12 different happy string of length 3 ["aba", "abc", "aca", "acb", "bab", "bac", "bca", "bcb", "cab", "cac", "cba", "cbc"]. You will find the 9th string = "cab"
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
Java
Using the mutable string:
class Solution {
public String getHappyString(int n, int k) {
List<String> ans = new ArrayList<>(); // Using ans instead of res
StringBuilder curr = new StringBuilder(); // Replace String with StringBuilder
backtrack(n, curr, ans);
return k <= ans.size() ? ans.get(k - 1) : "";
}
private void backtrack(int n, StringBuilder curr, List<String> ans) {
if (curr.length() == n) { // Base case: Valid happy string of length n
ans.add(curr.toString()); // Convert StringBuilder to String and add to ans
return;
}
for (char ch : new char[] {'a', 'b', 'c'}) { // Using ch instead of c for clarity
if (curr.length() == 0 || curr.charAt(curr.length() - 1) != ch) { // No consecutive identical chars
curr.append(ch); // Add to StringBuilder
backtrack(n, curr, ans); // Recurse
curr.deleteCharAt(curr.length() - 1); // Undo the change (backtrack)
}
}
}
}
Using the string:
class Solution {
public String getHappyString(int n, int k) {
List<String> ans = new ArrayList<>();
backtrack("", n, ans);
return k <= res.size() ? res.get(k - 1) : "";
}
private void backtrack(String curr, int n, List<String> ans) {
if (curr.length() == n) { // Base case: Valid happy string of length n
res.add(curr);
return;
}
for (char ch : new char[] {'a', 'b', 'c'}) { // Try each character
if (curr.isEmpty() || curr.charAt(curr.length() - 1) != ch) { // Ensure no consecutive identical chars
backtrack(curr + ch, n, ans);
}
}
}
}
Python
class Solution:
def getHappyString(self, n: int, k: int) -> str:
def backtrack(curr: str, res: List[str]) -> None:
if len(curr) == n: # Base case: Valid happy string of length n
res.append(curr)
return
for c in 'abc': # Try each character
if not curr or curr[-1] != c: # Ensure no consecutive identical chars
backtrack(curr + c, res)
res = []
backtrack("", res)
return res[k - 1] if k <= len(res) else "" # Return kth or empty string
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
Java
class Solution {
public String getHappyString(int n, int k) {
int total = 3 * (1 << (n - 1)); // Total happy strings of length n
if (k > total) {
return ""; // k exceeds the number of valid strings
}
StringBuilder ans = new StringBuilder();
char prev = '\0'; // Tracks the previous character
char[] chars = {'a', 'b', 'c'};
for (int i = 0; i < n; i++) {
int groupSize = 1 << (n - i - 1); // Size of each group at this level
for (char ch : chars) {
if (ch != prev) { // Valid choice (no consecutive identical letters)
if (k <= groupSize) {
ans.append(ch);
prev = ch; // Update previous character
break;
}
k -= groupSize; // Move to the next group
}
}
}
return ans.toString();
}
}
Python
class Solution:
def getHappyString(self, n: int, k: int) -> str:
total = 3 * (2 ** (n - 1)) # Total happy strings of length n
if k > total:
return "" # k exceeds the number of valid strings
res = []
chars = ['a', 'b', 'c']
prev = None # Tracks the previous character
for i in range(n):
group_size = 2 ** (n - i - 1) # Size of each group at this level
for c in chars:
if c != prev: # Valid choice (no consecutive identical letters)
if k <= group_size:
res.append(c)
prev = c # Update previous character
break
k -= group_size # Move to the next group
return "".join(res)
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"