问题描述
我们有一个由数字和 '?' 组成的特殊字符串,其中 '?' 可以被替换为任何数字字符('0'-'9'),以构造出多个正整数。需要注意的是,生成的正整数不能有前导零。我们的目标是找到所有可能的匹配数中,按字典序排列的第 k 小的数。如果不存在这样的第 k 小数,则返回 -1。
解决思路
解决这个问题的核心思路分为三个步骤:
- 生成所有可能的匹配数:通过递归地替换 '?' 为 '0'-'9',生成所有可能的字符串组合。
- 筛选有效的匹配数:从生成的字符串中筛选出有效的正整数(没有前导零)。
- 排序并找到第 k 小的数:将筛选出的有效匹配数进行字典序排序,然后找到第 k 小的数。
解决代码分析
public static String solution(String s, int k) {
List<String> numbers = new ArrayList<>();
generateNumbers(s.toCharArray(), 0, numbers);
Collections.sort(numbers);
if (k > numbers.size()) {
return "-1";
}
return numbers.get(k - 1);
}
private static void generateNumbers(char[] chars, int index, List<String> numbers) {
if (index == chars.length) {
String number = new String(chars);
if (isValid(number)) {
numbers.add(number);
}
return;
}
if (chars[index] == '?') {
for (char digit = '0'; digit <= '9'; digit++) {
chars[index] = digit;
generateNumbers(chars, index + 1, numbers);
}
chars[index] = '?'; // backtrack
} else {
generateNumbers(chars, index + 1, numbers);
}
}
private static boolean isValid(String number) {
if (number.startsWith("0") && number.length() > 1) {
return false;
}
return true;
}
代码详解
- solution 方法:这是解决问题的入口方法,它首先调用
generateNumbers方法生成所有可能的匹配数,然后对这些数进行排序,并返回第 k 小的数。如果 k 大于生成数的数量,则返回 "-1"。 - generateNumbers 方法:这个方法递归地替换 '?' 为 '0'-'9',生成所有可能的字符串组合。它使用回溯算法的思想,每次替换一个 '?',然后递归调用自身。完成后,将字符还原为 '?',以便进行下一次替换。
- isValid 方法:该方法检查生成的字符串是否为有效的正整数。如果字符串以 '0' 开头且长度大于 1,则不是有效的正整数。
深入理解问题和解决方案
为什么选择递归和回溯?
递归是解决此类问题的自然选择,因为我们面对的是一个可以分解为更小子问题的大问题:每个 '?' 可以独立地替换为任何数字,而不影响其他位置。这种“分而治之”的思想是递归的核心。
回溯则提供了一种系统地探索所有可能性的方法。通过修改当前位置的字符,然后递归地处理下一个字符,我们能够遍历所有可能的字符串。如果当前选择不满足条件,我们则“回溯”到上一个状态,尝试另一个字符,这就是回溯算法的精髓。
字符串有效性的重要性
在生成所有可能的字符串后,我们需要从中筛选出有效的正整数。这一步是必不可少的,因为某些字符串可能以 '0' 开头,或者在替换 '?' 后形成了非法的整数(比如 "00"、"01" 等)。isValid 方法确保了每一个加入到最终列表中的字符串都是一个有效的正整数。
排序和查找
一旦我们有了所有有效的匹配数,下一步就是进行排序。这里使用了 Java 的 Collections.sort() 方法,它基于归并排序或 Timsort 算法,提供了稳定的排序性能。排序后,第 k 小的数就简单地位于列表的第 k - 1 个位置(因为列表索引从 0 开始)。