题目描述

  • 给定一个由纯数字组成以字符串表示的数值,现要求字符串中的每个数字最多只能出现2次,超过的需要进行删除;
  • 删除某个重复的数字后,其它数字相对位置保持不变如“34533”
  • 数字3重复超过2次,需要删除其中一个3,删除第一个3后获得最大数值“4533”
  • 请返回经过删除操作后的最大的数值,以字符串表示

输入描述

  • 第一行为一个纯数字组成的字符串,长度范围:[1, 100 000]

输出描述

  • 输出经过删除操作后的最大的数值

用例

用例1

--输入
34533

--输出
4533

用例2

--输入
5445795045

--输出
5479504

题目解析

错误的思考过程

  • 一个容易观察到的点是:在经过删除重复数字的操作之后,得到的字符串的长度是固定了
  • 那么就需要找到一个通用的删除规则
  • 从头往后删,还是从后往前删?
ex:   219222111
很显然,这个例子用上帝视角观察的话,删除之后能够得到的最大的数字是   92211
似乎从后往前删除更靠谱一些

ex:   34533
以这个例子而言,最优的结果是删除 第一个 3  最终得到  4533
似乎从后往前又不太靠谱了
  • 遍历一遍字符串可以得到每一个字符出现的次数
  • 要想得到最大的数字,那么肯定要让 “第一位最大”
  • 那么好,似乎有了一个方法
ex:
219222111
34533

对于 219222111 从头开始遍历
2 出现的次数是 4,满足可以删除的条件,那么向后找,找有没有比 2 大的且可以保留的数字,那么这样的话,开头是 1 的话,肯定可以删除。
2 向后走,遇到了 1,这里归属于小于当前数字,如果 1 不满足数字移除条件,那么 当前数字 2 就不能移除。
反之,继续向后找。如果找到了,那么当前数字可以删除,如果找不到,那么当前数字不能移除

这样子,从前往后遍历,遍历到的位置均认为自己是 数字的开始位置。
  • 继续总结一下整体的过程
  • 首先遍历一遍字符串,记录每一个数字出现的次数
  • 然后从字符串的第一个字符开始遍历。拿到其出现的次数。那么现在考虑判断的顺序。
  • 首先判断其下一个数字是否和当前数字相同,如果相同,且出现次数大于2,则当前数字可以被删除.
  • 如果下一个数字和当前数字不相同,这里可能存在特殊情况。
  • 如果当前数字是 9,那么一定不可以被移除。但是存在一种例外情况,啧啧啧。写到这里这个思路似乎是错误的
  • 因为没办法处理 818181818181这种情况

新的思考过程

  1. 从第一个字符开始遍历,那么假如仅仅只有一个字符的情况下的话,那么第一个字符肯定会被保留
  2. 那么遍历到第二个字符,第二个字符出现了可以和第一个字符做比较,来决定第一个字符是否可以保留
  3. 依照这个思路
  4. 可以首先遍历一次字符串,记录下每一个字符数字出现的次数
  5. 然后记录一下保留的 字符数字 的个数
  6. 当遍历到一个新的字符位置。
  1. 让其和已经保留的字符进行相比较,如果当前字符比已经保留的前一个字符大,那么可以继续考虑是否可以移除前一个字符。
  2. 这里如何判断呢?可以利用 保留的字符个数 和 目前等待使用的字符数字的个数

show code

public static void main(String[] args) {
    Scanner in = new Scanner(System.in);
    while (in.hasNextLine()) {
        String line = in.nextLine();

        maxNumber(line);
    }
}

private static void maxNumber(String num) {
    int n = num.length();
// 统计每一个 字符数字 出现的次数
Map<Character, Integer> sts = new HashMap<>();
// 统计已经保留的  字符数字 的个数
Map<Character, Integer> keep = new HashMap<>();

// 用一个队列保留已经保存的 字符数字
LinkedList<Character> queue = new LinkedList<>();


for (int i = 0; i < n; i++) {
    char at = num.charAt(i);

    sts.put(at, sts.getOrDefault(at, 0) + 1);
    // 初始化一个 字符数字 都不保留
    keep.putIfAbsent(at, 0);
}

//开始遍历 字符串
for (int i = 0; i < n; i++) {
    char at = num.charAt(i);

    //  这里还需要首先判断一下已经保留的字符数字个数有没有两个,如果有,那么跳过
    if(keep.get(at) == 2) {
        // 表示当前的字符可以被移除了,对应的 可用的 字符数字 个数减1
        sts.put(at, sts.get(at) - 1);
        continue;
    }

    // 如果队列不为空
    while(!queue.isEmpty()) {
        // 取出队列最后入队的一个字符
        Character lastInQueue = queue.getLast();

        // 判断 最后入队的字符是否可以移除
        // 第二个判断是:判断移除之后,保留的字符个数和剩余的字符个数的总数量是否够 2 个
        // 两个判断条件都满足了,则  最后入队的字符是否可以移除
        if(at > lastInQueue && sts.get(lastInQueue) + keep.get(lastInQueue) - 1 >= 2) {
            //  删除之后的操作
            queue.removeLast();
            //  对应的保留字符的个数减少1
            keep.put(lastInQueue, keep.get(lastInQueue) - 1);
        } else {
            // 不满足条件,直接跳过
            break;
        }
    }

    // 这里保留当前字符
    queue.addLast(at);

    // 可使用的 字符数字个数 减少1
    sts.put(at, sts.get(at) - 1);

    // 保留的 字符数字的个数 增加 1
    keep.put(at, keep.get(at) + 1);
}

// 输出结果
StringBuilder ans = new StringBuilder();
for (Character at : queue) {
    ans.append(at);
}

System.out.println(ans);
}