1. 问题背景与需求分析
"删除重复数字后的最大数字"是一道经典的算法问题,题目要求给定一个数字字符串,在删除所有重复出现的数字后,返回可能得到的最大数字。这个问题看似简单,但蕴含着字符串处理、贪心算法和栈数据结构等核心算法思想。
在实际应用中,这类问题常见于数据清洗、金融交易ID处理等场景。比如在证券交易系统中,每笔交易都有唯一的交易ID,当系统需要处理大量交易记录时,可能会遇到重复ID的情况。这时就需要在不改变原始顺序的前提下,删除重复项并保留最大可能的ID值。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 问题理解与示例分析
2.1 题目要求详解
给定一个由数字组成的字符串,我们需要:
- 从左到右遍历字符串
- 对于每个数字,如果它之前已经出现过,则跳过(即删除)
- 如果它之前没出现过,则保留
- 最终在所有可能的去重结果中,选择数值最大的那个
例如:
- 输入:"2023" → 输出:"203"(删除第二个'2')
- 输入:"332211" → 输出:"321"(删除所有重复数字)
- 输入:"12321" → 输出:"123"(删除第二个'2'和'1')
2.2 边界条件考虑
在实际编码前,我们需要考虑各种边界情况:
- 空字符串输入如何处理
- 所有数字都相同的情况(如"999")
- 已经是不含重复数字的字符串(如"1234")
- 包含前导零的情况(如"0023")
- 非常大的数字字符串(长度超过10^5)
3. 算法设计与实现
3.1 暴力解法分析
最直观的解法是生成所有可能的去重子序列,然后比较它们的数值大小。这种方法的时间复杂度是O(2^n),对于n=20的字符串就需要处理约100万种子序列,显然不可行。
3.2 贪心算法思路
更高效的解法是使用贪心算法结合栈数据结构:
- 维护一个栈来存储结果
- 使用哈希表记录每个数字最后出现的位置
- 遍历字符串时:
- 如果当前数字已经在栈中,跳过
- 否则,弹出栈顶比当前数字小且后面还会出现的数字
- 将当前数字压入栈中
3.3 代码实现示例
python复制def removeDuplicateDigits(s):
last_occurrence = {c: i for i, c in enumerate(s)}
stack = []
seen = set()
for i, c in enumerate(s):
if c in seen:
continue
while stack and c > stack[-1] and i < last_occurrence[stack[-1]]:
seen.remove(stack.pop())
stack.append(c)
seen.add(c)
return ''.join(stack)
3.4 复杂度分析
- 时间复杂度:O(n),每个元素最多入栈出栈一次
- 空间复杂度:O(n),用于存储栈和哈希表
4. 算法优化与变种
4.1 处理前导零问题
原始问题中数字字符串可能包含前导零,我们需要在结果中去掉前导零:
python复制result = removeDuplicateDigits(s)
result = result.lstrip('0') or '0'
4.2 保留k个重复数字的变种
如果题目改为"最多保留k个重复数字",算法需要相应调整:
python复制def removeDuplicateDigitsKeepK(s, k):
count = {}
stack = []
for c in s:
count[c] = count.get(c, 0) + 1
if count[c] > k:
continue
while stack and c > stack[-1] and count[stack[-1]] > k:
stack.pop()
stack.append(c)
return ''.join(stack)
5. 实际应用案例
5.1 交易ID去重
在金融系统中处理交易记录时,可以使用此算法确保交易ID唯一性:
python复制def process_transaction_ids(ids):
unique_ids = removeDuplicateDigits(ids)
return [int(id) for id in unique_ids.split()]
5.2 数据压缩存储
对于某些数字序列存储场景,可以在保证最大数值的前提下减少存储空间:
python复制def compress_number_sequence(seq):
compressed = removeDuplicateDigits(''.join(map(str, seq)))
return list(map(int, compressed))
6. 常见错误与调试技巧
6.1 栈操作顺序错误
常见的实现错误是弹出栈顶元素的时机不对。正确的逻辑应该是:
- 当前字符大于栈顶字符
- 栈顶字符在后面还会出现
6.2 哈希表更新问题
另一个易错点是忘记更新字符的最后出现位置。必须在预处理阶段完整遍历字符串记录每个字符的最后位置。
6.3 测试用例设计建议
完整的测试应该包含:
- 全相同数字("1111")
- 递增序列("1234")
- 递减序列("4321")
- 混合序列("12321")
- 包含零的序列("10203")
- 长随机序列
7. 性能优化实践
7.1 内存优化
对于特别长的字符串,可以使用位运算替代哈希表来记录字符出现:
python复制seen = 0
for c in s:
mask = 1 << (ord(c) - ord('0'))
if seen & mask:
continue
# ...其余逻辑
seen |= mask
7.2 并行处理
对于超长字符串,可以分块处理然后合并结果:
python复制def parallel_remove_dup(s, chunk_size=10000):
chunks = [s[i:i+chunk_size] for i in range(0, len(s), chunk_size)]
with concurrent.futures.ThreadPoolExecutor() as executor:
results = list(executor.map(removeDuplicateDigits, chunks))
return removeDuplicateDigits(''.join(results))
8. 扩展思考
8.1 字典序最小版本
如果将问题改为求字典序最小的去重结果,算法需要反向处理:
python复制def removeDuplicateDigitsMin(s):
stack = []
seen = set()
last_occurrence = {c: i for i, c in enumerate(s)}
for i, c in enumerate(s):
if c in seen:
continue
while stack and c < stack[-1] and i < last_occurrence[stack[-1]]:
seen.remove(stack.pop())
stack.append(c)
seen.add(c)
return ''.join(stack)
8.2 多数字删除限制
如果限制最多删除k个数字(不一定是重复的),问题会变得更加复杂,需要结合动态规划解决。
在实际编码面试中,这类问题考察的是对数据结构的灵活运用和对问题本质的理解。我建议在理解基础解法后,多思考各种变种问题,并尝试用不同的方法解决。
