🚀【字节跳动高频面试题】不超过 N 的最大数拼接
✨ 题目描述
给定一个按 非递减顺序 排列的数字字符数组 digits
(如 ["1","3","5","7"]
),你可以任意次数 使用这些字符,拼接成一个正整数。
请返回:所有能拼出且 ≤ 给定整数 n
的正整数中,值最大的那个。
📌 示例
txt
输入:digits = ["1","3","5","7"], n = 100
输出:77
💡 解法一:暴力回溯(Backtrack 贪心)
🔍 思路概览
我们希望构造一个不大于 n
的数,且尽可能大。每一位从高位到低位逐位尝试:
-
若该位能找到一个
≤ s[i]
的最大字符d
:- 如果
d == s[i]
,继续匹配下一位; - 如果
d < s[i]
,则当前为转折点,后面全部填digits
中最大值,直接构成答案;
- 如果
-
如果该位没有合法字符(即所有字符都 >
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 原题推荐
- 类型:数位 DP
- 难度:中等
- 高频企业题:字节跳动、美团、阿里
🔚 总结
方法 | 思路 | 优点 | 缺点 |
---|---|---|---|
回溯贪心法 | 模拟构造 | 逻辑直观,易于调试 | 实现较繁琐,回退处理需细致 |
数位 DP | 状态转移+缓存 | 可扩展,适合复杂限制条件 | 学习门槛略高,状态较多 |