一、题目描述
给定一个由纯数字组成以字符串表示的数值,现要求字符串中的每个数字最多只能出现2次,超过的需要进行删除;删除某个重复的数字后,其它数字相对位置保持不变。
如"34533″,数字3重复超过2次,需要删除其中一个3,删除第一个3后获得最大数值"4533″
请返回经过删除操作后的最大的数值,以字符串表示。
二、输入输出描述
输入描述
- 第一行:纯数字字符串,长度范围 [1, 100000]。
输出描述
- 删除重复数字后的最大数值字符串(无前置零,空串返回空)。
三、示例
|----|-------|
| 输入 | 34533 |
| 输出 | 4533 |
| 说明 | |
|----|------------|
| 输入 | 5445795045 |
| 输出 | 5479504 |
| 说明 | |
四、解题思路
1. 核心思想
通过 "贪心 + 栈" 策略重构数字字符串:优先让高位保留更大的数字,同时通过unused和reserve两个哈希表保证 "每个数字最终最多保留 2 个" 的约束 ------ 核心是 "栈顶优化(替换更小数字)+ 约束校验(保证数字保留数量)"。
2. 问题本质分析
- 表层问题:在 "每个数字最多出现 2 次" 的约束下,将原数字字符串重构为尽可能大的新字符串;
- 深层问题:带约束的贪心排序,核心矛盾是 "追求高位更大" 与 "保证数字出现次数不超 2 次" 的平衡;
- 关键难点:优化栈顶时,需确保被弹出的数字仍能通过 "剩余未遍历数 + 已保留数" 满足 "最多 2 个" 的约束,避免某数字最终出现次数不足(或超量)。
3. 核心逻辑
- 约束跟踪:用
unused(未遍历的数字数量)和reserve(已保留的数字数量)跟踪每个数字的可用状态,确保最终出现次数≤2; - 贪心优化:遍历每个数字时,若当前数字比栈顶大,且弹出栈顶后该数字仍能满足 "保留≤2 个" 的约束,则弹出栈顶(让高位更大);
- 边界控制:若某数字已保留 2 个,后续该数字直接跳过,不加入栈。
4. 步骤拆解
-
状态初始化:
- 统计每个数字的总个数,初始化
unused(总个数)和reserve(0); - 初始化空栈,用于存储最终结果序列。
- 统计每个数字的总个数,初始化
-
遍历处理每个数字字符:
- 跳过已保留 2 个的数字(直接减少
unused,不处理); - 循环优化栈顶:若当前数字更大,且弹出栈顶后该数字仍能满足约束,则弹出栈顶;
- 保留当前数字:入栈,更新
unused(-1)和reserve(+1)。
- 跳过已保留 2 个的数字(直接减少
-
结果生成:
- 拼接栈中所有字符,得到最终的最大数字字符串。
五、代码实现
java
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String str = sc.next();
System.out.println(getResult(str));
}
public static String getResult(String str) {
// 每个数字字符的可用个数
HashMap<Character, Integer> unused = new HashMap<>();
// 每个数字字符的保留个数
HashMap<Character, Integer> reserve = new HashMap<>();
// 初始时,每个数字有多少个,就可用多少个,由于还未使用,因此保留个数为0
for (int i = 0; i < str.length(); i++) {
char c = str.charAt(i);
unused.put(c, unused.getOrDefault(c, 0) + 1);
reserve.putIfAbsent(c, 0);
}
// 定义一个栈
LinkedList<Character> stack = new LinkedList<>();
// 遍历输入字符串的每个数字字符c
for (int i = 0; i < str.length(); i++) {
char c = str.charAt(i);
// 如果该字符已经保留了2个了,则后续再出现该数字字符可以不保留
if (reserve.get(c) == 2) {
// 则可用c数字个数--
unused.put(c, unused.get(c) - 1);
continue;
}
// 比较当前数字c和栈顶数字top,如果c>top,那么需要考虑将栈顶数字弹出
while (stack.size() > 0) {
char top = stack.getLast();
// 如果栈顶数字被弹出后,已保留的top字符数量和未使用的top字符数量之和大于等于2,则可以弹出,否则不可以
if (top < c && unused.get(top) + reserve.get(top) - 1 >= 2) {
stack.removeLast();
reserve.put(top, reserve.get(top) - 1);
} else {
break;
}
}
// 选择保留当前遍历的数字c
stack.add(c);
// 则可用c数字个数--
unused.put(c, unused.get(c) - 1);
// 已保留c数字个数++
reserve.put(c, reserve.get(c) + 1);
}
StringBuilder sb = new StringBuilder();
for (Character c : stack) {
sb.append(c);
}
return sb.toString();
}
}