问题描述
小U有一个特殊的“模板串”,这个串由数字字符和 '?' 组成。她可以通过将 '?' 替换成数字字符,来构造出多个正整数。不过有一个重要的限制条件:匹配出来的正整数不能有前导零。现在,小U想知道,按照字典序排列后,第 k 小的匹配数是多少?如果没有满足条件的第 k 小数,则返回 -1。
测试样例
样例1:
输入:
s = "??1",k = 1
输出:'101'
样例2:
输入:
s = "2??",k = 3
输出:'202'
样例3:
输入:
s = "000???",k = 1
输出:'-1'
问题分析与解法
在本题中,我们需要从一个包含数字和 '?' 的模板串中生成所有符合条件的数字串,并根据字典序排序,找出第 k 小的数字。如果不存在第 k 小的数字,则返回 -1。生成的数字不能有前导零,除非数字本身就是 '0'。这意味着需要特别注意如何处理前导零的情况,并且我们需要对生成的结果进行排序,以确保能够正确返回字典序中的第 k 小的数字。
思路解析
-
回溯算法(Backtracking) :
- 回溯算法是解决此类问题的自然选择。我们可以通过递归的方式,在每个
'?'的位置尝试替换为0到9的数字,从而生成所有可能的数字串。 - 在递归过程中,如果当前替换后的字符串是合法的(没有前导零),则将其加入结果集中。
- 回溯算法是解决此类问题的自然选择。我们可以通过递归的方式,在每个
-
前导零处理:
- 如果模板串的第一个字符是
'?',我们不能将其替换为0,除非字符串本身的长度为 1。例如,"0"是合法的,但"01"则是不合法的。
- 如果模板串的第一个字符是
-
字典序排序:
- 在所有合法的数字串生成之后,我们需要对这些字符串进行字典序排序。然后直接返回第
k小的数字。
- 在所有合法的数字串生成之后,我们需要对这些字符串进行字典序排序。然后直接返回第
-
边界条件:
- 如果
k大于生成的合法数字的数量,返回-1。 - 如果模板串中没有
'?',那么直接检查模板是否是合法的数字,并返回该数字。
- 如果
解题步骤
-
递归回溯:
- 从模板串的第一个字符开始,如果是
'?',则尝试替换成0到9之间的数字,递归处理下一个字符。 - 如果当前字符不是
'?',直接递归处理下一个字符。 - 每次递归完成后,检查生成的数字是否合法,合法则加入结果集。
- 从模板串的第一个字符开始,如果是
-
合法性判断:
- 判断字符串的第一个字符是否为
'0',如果是且字符串长度大于 1,则视为非法。
- 判断字符串的第一个字符是否为
-
返回第
k小的结果:- 将所有合法的数字字符串进行排序,返回第
k小的字符串。如果没有足够的数字,返回-1。
- 将所有合法的数字字符串进行排序,返回第
代码实现
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
std::string solution(std::string s, int k) {
std::vector<std::string> results; // 用于存储所有合法的数字字符串
// 回溯递归函数
std::function<void(int)> backtrack = [&](int index) {
if (index == s.length()) { // 如果遍历到了字符串的末尾
// 检查是否为合法数字(首位不能是'0',除非字符串为"0")
if (s[0] != '0' || s.length() == 1) {
results.push_back(s);
}
return;
}
if (s[index] == '?') { // 如果当前字符是'?'
for (char digit = '0'; digit <= '9'; ++digit) {
s[index] = digit;
backtrack(index + 1); // 递归到下一个字符
}
s[index] = '?'; // 恢复原值,回溯
} else {
backtrack(index + 1); // 递归到下一个字符
}
};
// 从第一个字符开始回溯生成所有可能的数字字符串
backtrack(0);
// 如果没有合法的结果,或者k超出范围,返回"-1"
if (results.empty() || k > results.size()) {
return "-1";
}
// 排序所有合法的结果
std::sort(results.begin(), results.end());
// 返回第k小的数字
return results[k - 1];
}
int main() {
std::cout << (solution("??1", 1) == "101") << std::endl; // 输出:101
std::cout << (solution("2??", 3) == "202") << std::endl; // 输出:202
std::cout << (solution("000???", 1) == "-1") << std::endl; // 输出:-1
return 0;
}
代码详解
-
回溯函数
backtrack(int index):backtrack是一个递归函数,它尝试替换模板串中每个'?'字符,并生成所有可能的数字字符串。每当递归到一个有效字符串时,就将其加入results列表中。- 在递归过程中,我们确保第一个字符不能是
'0',除非该字符就是'0'(即字符串为"0")。这是通过检查s[0] != '0' || s.length() == 1来完成的。
-
递归终止条件:
- 当
index == s.length()时,表示已经处理完了整个字符串。此时检查字符串是否合法,并加入结果列表。
- 当
-
合法性检查:
- 我们通过检查首位字符是否为
'0'来确保数字字符串不含前导零。除非字符串的长度为 1(即仅为'0'),该字符串才是合法的。
- 我们通过检查首位字符是否为
-
排序与返回结果:
- 在回溯完成后,我们将所有合法的数字字符串按字典序排序,并返回第
k小的数字。如果合法的数字串个数不足k,返回-1。
- 在回溯完成后,我们将所有合法的数字字符串按字典序排序,并返回第
时间复杂度分析
-
回溯过程:
- 在最坏情况下,每个
'?'字符可能生成 10 种不同的替换(从'0'到'9'),因此回溯的时间复杂度大约是O(10^m),其中m是字符串中'?'的个数。
- 在最坏情况下,每个
-
排序:
- 在生成所有合法的数字串后,我们需要对结果进行排序。假设生成了
n个合法字符串,排序的时间复杂度是O(n log n)。
- 在生成所有合法的数字串后,我们需要对结果进行排序。假设生成了
-
总复杂度:
- 综合考虑,时间复杂度为
O(10^m + n log n),其中m是'?'的个数,n是合法字符串的个数。
- 综合考虑,时间复杂度为
边界情况
- 输入字符串没有
'?':直接判断该字符串是否是合法的数字。如果合法,则返回该字符串;如果不合法,则返回-1。 - 没有足够的合法数字:如果合法的数字串少于
k个,返回-1。 - 字符串为零的特殊情况:例如
"0"或"000"等。
总结
本题通过回溯算法生成所有符合条件的数字字符串,并且通过排序和合法性检查,保证了能高效地找到字典序中的第 k 小的数字。回溯算法非常适合这类“枚举所有可能”并且要筛选合法解的题目,同时合理处理了前导零的问题。