【字节跳动高频面试题】不超过 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 状态转移+缓存 可扩展,适合复杂限制条件 学习门槛略高,状态较多

相关推荐
yanlele26 分钟前
我用爬虫抓取了 25 年 5 月掘金热门面试文章
前端·javascript·面试
小兵张健2 小时前
武汉拿下 23k offer 经历
java·面试·ai编程
爱莉希雅&&&2 小时前
技术面试题,HR面试题
开发语言·学习·面试
天天扭码3 小时前
《很全面的前端面试题》——HTML篇
前端·面试·html
zhuiQiuMX5 小时前
脉脉maimai面试死亡日记
数据仓库·sql·面试
独行soc5 小时前
2025年渗透测试面试题总结-2025年HW(护网面试) 33(题目+回答)
linux·科技·安全·网络安全·面试·职场和发展·护网
库森学长5 小时前
面试官:发生OOM后,JVM还能运行吗?
jvm·后端·面试
然我5 小时前
面试必问:JS 事件机制从绑定到委托,一篇吃透所有考点
前端·javascript·面试
爱学习的茄子6 小时前
React Hooks进阶:从0到1打造高性能Todo应用
前端·react.js·面试