【字节跳动高频面试题】不超过 N 的最大数拼接

🚀【字节跳动高频面试题】不超过 N 的最大数拼接

✨ 题目描述

给定一个按 非递减顺序 排列的数字字符数组 digits(如 ["1","3","5","7"]),你可以任意次数 使用这些字符,拼接成一个正整数

请返回:所有能拼出且 ≤ 给定整数 n 的正整数中,值最大的那个


📌 示例

txt 复制代码
输入:digits = ["1","3","5","7"], n = 100  
输出:77

💡 解法一:暴力回溯(Backtrack 贪心)

🔍 思路概览

我们希望构造一个不大于 n 的数,且尽可能大。每一位从高位到低位逐位尝试:

  1. 若该位能找到一个 ≤ s[i] 的最大字符 d

    • 如果 d == s[i],继续匹配下一位;
    • 如果 d < s[i],则当前为转折点,后面全部填 digits 中最大值,直接构成答案;
  2. 如果该位没有合法字符(即所有字符都 > s[i]),则需要回退(Backtrack):

    • 回退到前一位,寻找一个更小字符,之后所有位都填最大字符。

若所有位都成功对齐 n 的每一位字符,则答案为 n 本身。否则返回构造出的最大值。

🧮 时间复杂度分析

  • 最坏情况每一位都需要回退多次;

  • 时间复杂度约为 O(L × |digits|),其中:

    • L = log10(n) + 1 为位数;
    • |digits| ≤ 9

即便 n 非常大(如 10^18),依然能在毫秒级别内完成。

✅ C++ 实现

cpp 复制代码
int bytedance1(vector<string>& digits, int n) {
    bool flag = false; // 是否已经满足 <,之后可以直接填最大digit
    string s = to_string(n);
    n = s.size();
    unordered_map<char, int> mp;
    for(int i = 0; i < digits.size(); i++) mp[digits[i][0]] = i + 1;

    vector<char> res(n, digits[0][0]);

    for(int i = 0; i < n && !flag; i++) {
        for(auto& d : digits) {
            if(d[0] <= s[i]) res[i] = d[0];
        }
        if(res[i] < s[i]) {
            flag = true;
            for(int j = i + 1; j < n; j++) res[j] = digits.back()[0];
        } else if(res[i] > s[i]) { // 无法继续匹配
            int j = i;
            while(j >= 0 && res[j] == digits[0][0]) j--;
            if(j == -1) {
                res.resize(n - 1, digits.back()[0]);
            } else {
                for(auto& d : digits) {
                    if(d[0] < s[j]) res[j] = d[0];
                }
                j++;
                for(; j < n; j++) res[j] = digits.back()[0];
            }
            flag = true;
        }
    }

    for(auto& ch : res) cout << ch;
    cout << endl;
    return 0;
}

💡 解法二:数位 DP(Digit DP)

🎯 解题框架

使用数位 DP 框架模板,通过参数限制合法性。

  • L:下界,补齐成和 R 同样的位数(例如 "001");

  • R:上界,即 n

  • digits:枚举所有合法的数字;

  • 状态参数:

    • i:当前位;
    • isZero:是否还处于前导零;
    • isLow:当前是否仍受下界限制;
    • isHigh:当前是否仍受上界限制。

🔢 关键转移逻辑

cpp 复制代码
int dfs(int i, int isZero, int isLow, int isHigh);
  • isZero && l == 0 时,可以跳过当前位;
  • !isZero && !isLow && !isHigh 时,结果可以记忆化缓存;
  • 尝试填入所有合法的 digits 中的数字,满足边界限制即可。

✅ C++ 实现

cpp 复制代码
string L, R;
vector<int> digits;
int n, f[10];
string tmp = "", res = "";

int dfs(int i, int isZero, int isLow, int isHigh) {
    if(i == n) {
        if(res == "") res = tmp; // 保存最大值
        return !isZero;
    }

    if(!isZero && !isLow && !isHigh && ~f[i]) return f[i];

    int l = isLow ? L[i] - '0' : 0;
    int r = isHigh ? R[i] - '0' : 9;
    int ans = 0;

    for(int& d : digits) {
        if(d >= l && d <= r) {
            tmp.push_back(d + '0');
            ans += dfs(i + 1, 0, isLow && d == l, isHigh && d == r);
            tmp.pop_back();
        }
    }

    if(isZero && l == 0) ans += dfs(i + 1, 1, 1, isHigh && r == 0);

    if(!isZero && !isLow && !isHigh) f[i] = ans;
    return ans;
}

int bytedance2(vector<string>& digitsStr, int n) {
    R = to_string(n);
    L = string(R.size() - 1, '0') + "1";
    ::n = R.size();

    digits.clear();
    for(auto& x : digitsStr) digits.emplace_back(x[0] - '0');
    ranges::reverse(digits); // 从大到小尝试

    memset(f, -1, sizeof f);
    dfs(0, 1, 1, 1);
    cout << res << endl;
    return 0;
}

📚 LeetCode 原题推荐

LeetCode 902. 最大为 N 的数字组合

  • 类型:数位 DP
  • 难度:中等
  • 高频企业题:字节跳动、美团、阿里

🔚 总结

方法 思路 优点 缺点
回溯贪心法 模拟构造 逻辑直观,易于调试 实现较繁琐,回退处理需细致
数位 DP 状态转移+缓存 可扩展,适合复杂限制条件 学习门槛略高,状态较多

相关推荐
boonya2 小时前
Elasticsearch核心原理与面试总结
大数据·elasticsearch·面试
蒹葭玉树7 小时前
【C++上岸】C++常见面试题目--算法篇(第二十期)
c++·算法·面试
CoderYanger10 小时前
MySQL数据库——3.2.1 表的增删查改-查询部分(全列+指定列+去重)
java·开发语言·数据库·mysql·面试·职场和发展
雲墨款哥10 小时前
一个前端开发者的救赎之路-JS基础回顾(五)-数组
前端·javascript·面试
我是哪吒12 小时前
分布式微服务系统架构第170集:Kafka消费者并发-多节点消费-可扩展性
后端·面试·github
一只叫煤球的猫14 小时前
2025年基于Java21的的秒杀系统要怎么设计?来点干货
后端·面试·性能优化
yanlele14 小时前
前端面试第 78 期 - 2025.09.07 更新 Nginx 专题面试总结(12 道题)
前端·javascript·面试
yh云想16 小时前
《Java线程池面试全解析:从原理到实践的高频问题汇总》
jvm·面试·职场和发展
橙序员小站16 小时前
搞定系统面试题:如何实现分布式Session管理
java·后端·面试
元闰子18 小时前
怎么用CXL加速数据库?· SIGMOD'25
数据库·后端·面试