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

相关推荐
Lee川11 小时前
优雅进化的JavaScript:从ES6+新特性看现代前端开发范式
javascript·面试
Lee川14 小时前
从异步迷雾到优雅流程:JavaScript异步编程与内存管理的现代化之旅
javascript·面试
晴殇i16 小时前
揭秘JavaScript中那些“不冒泡”的DOM事件
前端·javascript·面试
绝无仅有17 小时前
Redis过期删除与内存淘汰策略详解
后端·面试·架构
绝无仅有17 小时前
Redis大Key问题排查与解决方案全解析
后端·面试·架构
AAA梅狸猫18 小时前
Looper.loop() 循环机制
面试
AAA梅狸猫18 小时前
Handler基本概念
面试
Wect18 小时前
浏览器缓存机制
前端·面试·浏览器
掘金安东尼19 小时前
Fun with TypeScript Generics:玩转 TS 泛型
前端·javascript·面试
掘金安东尼19 小时前
Next.js 企业级落地
前端·javascript·面试