1415. 长度为 n 的开心字符串中字典序第 k 小的字符串
1415. 长度为 n 的开心字符串中字典序第 k 小的字符串
题目
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
.
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
题目大意
一个 「开心字符串」定义为:
- 仅包含小写字母
['a', 'b', 'c']
. - 对所有在
1
到s.length - 1
之间的i
,满足s[i] != s[i + 1]
(字符串的下标从 1 开始)。
比方说,字符串 " abc"," ac","b" 和 " abcbabcbcb" 都是开心字符串,但是 " aa","baa" 和 " ababbc" 都不是开心字符串。
给你两个整数 n
和 k
,你需要将长度为 n
的所有开心字符串按字典序排序。
请你返回排序后的第 k 个开心字符串,如果长度为 n
的开心字符串少于 k
个,那么请你返回 空字符串 。
示例 1:
输入: n = 1, k = 3
输出: "c"
解释: 列表 ["a", "b", "c"] 包含了所有长度为 1 的开心字符串。按照字典序排序后第三个字符串为 "c" 。
示例 2:
输入: n = 1, k = 4
输出: ""
解释: 长度为 1 的开心字符串只有 3 个。
示例 3:
输入: n = 3, k = 9
输出: "cab"
解释: 长度为 3 的开心字符串总共有 12 个 ["aba", "abc", "aca", "acb", "bab", "bac", "bca", "bcb", "cab", "cac", "cba", "cbc"] 。第 9 个字符串为 "cab"
示例 4:
输入: n = 2, k = 7
输出: ""
示例 5:
输入: n = 10, k = 100
输出: "abacbabacb"
提示:
1 <= n <= 10
1 <= k <= 100
解题思路
回溯(Backtracking)是生成组合或排列的一种常见方法,可以使用 回溯 + 剪枝 来生成第 k
个快乐字符串:
- 递归构造:从空字符串开始,每次添加
'a'
、'b'
、'c'
中不同于上一个字符的选项。 - 字典序排列:字典序是按照 'a' < 'b' < 'c' 排列的,因此,按顺序回溯构造的字符串天然是字典序排列的。
回溯法构造字符串:
- 每次尝试
'a'
,'b'
,'c'
,但不能选择与上一个字符相同的字符。 - 递归进入下一层。
- 回溯(撤销选择)。
- 终止条件:
track.length == n
。
- 每次尝试
剪枝优化:
- 使用
count
变量计数,当count == k
时,保存当前字符串并直接返回,避免不必要的计算。
- 使用
不存储所有字符串:
- 传统回溯会存储
O(3^n)
个字符串,再排序并取第k
个,但这样空间消耗大。 - 这里使用计数法,只找第
k
个,不存储额外结果,时间复杂度降低到O(k)
,空间复杂度降低到O(n)
(递归栈)。
- 传统回溯会存储
复杂度分析
- 时间复杂度:
O(k)
,剪枝优化,只找到k
个就终止。 - 空间复杂度:
O(n)
,回溯栈的开销,递归深度最多为n
。
代码
/**
* @param {number} n
* @param {number} k
* @return {string}
*/
var getHappyString = function (n, k) {
let result = '';
let count = 0;
// 回溯函数,track 记录当前构造的字符串
const backtrack = (track) => {
if (track.length == n) {
count++;
// 终止条件
if (count == k) result = track.join('');
return;
}
for (let char of ['a', 'b', 'c']) {
// 遍历所有可能字符
if (track.length == 0 || track[track.length - 1] !== char) {
// 不能和前一个字符相同
track.push(char); // 选择
backtrack(track); // 递归
track.pop(); // 回溯
if (count >= k) return; // 剪枝优化,找到 `k` 之后直接返回
}
}
};
backtrack([]); // 递归构造所有快乐字符串
return result; // 返回第 `k` 个字符串
};