模板串匹配问题 | 豆包MarsCode AI刷题

47 阅读5分钟

问题描述

小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 小的数字。

思路解析

  1. 回溯算法(Backtracking)

    • 回溯算法是解决此类问题的自然选择。我们可以通过递归的方式,在每个 '?' 的位置尝试替换为 09 的数字,从而生成所有可能的数字串。
    • 在递归过程中,如果当前替换后的字符串是合法的(没有前导零),则将其加入结果集中。
  2. 前导零处理

    • 如果模板串的第一个字符是 '?',我们不能将其替换为 0,除非字符串本身的长度为 1。例如,"0" 是合法的,但 "01" 则是不合法的。
  3. 字典序排序

    • 在所有合法的数字串生成之后,我们需要对这些字符串进行字典序排序。然后直接返回第 k 小的数字。
  4. 边界条件

    • 如果 k 大于生成的合法数字的数量,返回 -1
    • 如果模板串中没有 '?',那么直接检查模板是否是合法的数字,并返回该数字。

解题步骤

  1. 递归回溯

    • 从模板串的第一个字符开始,如果是 '?',则尝试替换成 09 之间的数字,递归处理下一个字符。
    • 如果当前字符不是 '?',直接递归处理下一个字符。
    • 每次递归完成后,检查生成的数字是否合法,合法则加入结果集。
  2. 合法性判断

    • 判断字符串的第一个字符是否为 '0',如果是且字符串长度大于 1,则视为非法。
  3. 返回第 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;
}

代码详解

  1. 回溯函数 backtrack(int index)

    • backtrack 是一个递归函数,它尝试替换模板串中每个 '?' 字符,并生成所有可能的数字字符串。每当递归到一个有效字符串时,就将其加入 results 列表中。
    • 在递归过程中,我们确保第一个字符不能是 '0',除非该字符就是 '0'(即字符串为 "0")。这是通过检查 s[0] != '0' || s.length() == 1 来完成的。
  2. 递归终止条件

    • index == s.length() 时,表示已经处理完了整个字符串。此时检查字符串是否合法,并加入结果列表。
  3. 合法性检查

    • 我们通过检查首位字符是否为 '0' 来确保数字字符串不含前导零。除非字符串的长度为 1(即仅为 '0'),该字符串才是合法的。
  4. 排序与返回结果

    • 在回溯完成后,我们将所有合法的数字字符串按字典序排序,并返回第 k 小的数字。如果合法的数字串个数不足 k,返回 -1

时间复杂度分析

  1. 回溯过程

    • 在最坏情况下,每个 '?' 字符可能生成 10 种不同的替换(从 '0''9'),因此回溯的时间复杂度大约是 O(10^m),其中 m 是字符串中 '?' 的个数。
  2. 排序

    • 在生成所有合法的数字串后,我们需要对结果进行排序。假设生成了 n 个合法字符串,排序的时间复杂度是 O(n log n)
  3. 总复杂度

    • 综合考虑,时间复杂度为 O(10^m + n log n),其中 m'?' 的个数,n 是合法字符串的个数。

边界情况

  • 输入字符串没有 '?' :直接判断该字符串是否是合法的数字。如果合法,则返回该字符串;如果不合法,则返回 -1
  • 没有足够的合法数字:如果合法的数字串少于 k 个,返回 -1
  • 字符串为零的特殊情况:例如 "0""000" 等。

总结

本题通过回溯算法生成所有符合条件的数字字符串,并且通过排序和合法性检查,保证了能高效地找到字典序中的第 k 小的数字。回溯算法非常适合这类“枚举所有可能”并且要筛选合法解的题目,同时合理处理了前导零的问题。