跳至主要內容

1415. 长度为 n 的开心字符串中字典序第 k 小的字符串


1415. 长度为 n 的开心字符串中字典序第 k 小的字符串

🟠   🔖  字符串 回溯  🔗 力扣open in new window LeetCodeopen in new window

题目

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 of i from 1 to s.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'].
  • 对所有在 1s.length - 1 之间的 i ,满足 s[i] != s[i + 1] (字符串的下标从 1 开始)。

比方说,字符串 " abc"" ac","b"" abcbabcbcb" 都是开心字符串,但是 " aa""baa"" ababbc" 都不是开心字符串。

给你两个整数 nk ,你需要将长度为 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' 排列的,因此,按顺序回溯构造的字符串天然是字典序排列的。
  1. 回溯法构造字符串

    • 每次尝试 'a', 'b', 'c',但不能选择与上一个字符相同的字符。
    • 递归进入下一层。
    • 回溯(撤销选择)。
    • 终止条件track.length == n
  2. 剪枝优化

    • 使用 count 变量计数,当 count == k 时,保存当前字符串并直接返回,避免不必要的计算。
  3. 不存储所有字符串

    • 传统回溯会存储 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` 个字符串
};