【每日三题】去除重复字母,下一个更大元素Ⅱ

210 阅读4分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第13天,点击查看活动详情 >>


每日三刷,剑指千题

计划简介:

  • 每日三题,以中等题为主,简单题为辅进行搭配。保证质量题1道,数量题3道。
  • 每日早通勤在LeetCode手机端选题,思考思路,没答案的直接看题解。
  • 每日中午进行编码,时间控制在一小时之内。
  • 下班前半小时进行整理总结,并发布到掘金每日更文活动。

说明:

  • 基于以前的刷题基础,本次计划以中等题为主,大部分中等题都可以拆分为多个简单题,所以数量保证3,质量保证一道中等题即可。
  • 刷题顺序按照先刷链表、二叉树、栈、堆、队列等基本数据结构,再刷递归、二分法、排序、双指针等基础算法,最后是动态规划、贪心、回溯、搜索等复杂算法。
  • 刷题过程中整理相似题型,刷题模板。
  • 目前进度 139/1000

新阶段

因为周末女朋友过生日,耽误了刷题,会通过后面几天加量的方式补回来,所以二叉树暂时告一段落。

从今天开始正式进入单调栈的刷题,这里有一道经典的接雨水问题,之前也是有所了解,而且这部分题量较少。

[316]去除重复字母

给你一个字符串 s ,请你去除字符串中重复的字母,使得每个字母只出现一次。需保证 返回结果的字典序最小(要求不能打乱其他字符的相对位置)。

示例 1:

输入:s = "bcabc"
输出:"abc"

解析

这题看似很简单,一个哈希表去重就搞定了呀,但是因为末尾的限制条件,让它成为了中等题。

什么是字典序?

就是在字典中出现的顺序,什么意思呢?a在b的前面,ab在ac的前面。所以需要在重复的字母里面决定留哪个取哪个。

不能打乱顺序什么意思?

就是在字典序最小,但是你不能重新排序,如果第一个是b,且只有一个b,那即使有多余的a,也不能放在前面。

看一个案例:

若输入为bcacc
1. b 入栈
2. c 入栈时因为字典序靠后,且栈中没出现过c,直接压栈
3. a 入栈,因为 a 的字典序列靠前且a没有出现过,此时要考虑弹出栈顶元素.
元素 c 因为在之后还有 至少一次 出现次数,所以这里可以弹出.
元素 b 之后不再出现,为了保证至少出现一次这里不能再舍弃了.
4. c 入栈时依然因为字典序靠后且栈中没出现过直接压栈
5. c 入栈时栈中已经出现过c,跳过该元素
输出结果为 bac

所以需要两个判断:

  • 栈中是否有某个元素,如果已经有了,那就跳过。
  • 某个元素出现了多少次,得确保后面还有才能把当前的弹出来

因为是最小,所以有个贪心的思想,即弹出元素应该是while,不是if。

最后,将栈中的元素倒序才是返回的结果。

Code

class Solution {
        public String removeDuplicateLetters(String s) {
            char[] chars = s.toCharArray();
            Map<Character, Integer> count = new HashMap<>();
            // 生成出现次数的map
            for (char c : chars) {
                count.put(c, count.getOrDefault(c, 0) + 1);
            }
​
            Stack<Character> stack = new Stack<>();
            for (char c : chars) {
                if (!stack.contains(c)) {
                    while (!stack.isEmpty()
                            && count.get(stack.peek()) > 0
                            && c < stack.peek()
                    ) {
                        stack.pop();
                    }
                    stack.push(c);
                }
                count.put(c, count.get(c) - 1);
            }
​
            StringBuilder sb = new StringBuilder();
            while (!stack.empty()) {
                sb.append(stack.pop());
            }
            // 栈中元素插入顺序是反的,需要 reverse 一下
            return sb.reverse().toString();
        }
    }

[剑指 Offer II 038]每日温度

请根据每日 气温 列表 temperatures ,重新生成一个列表,要求其对应位置的输出为:要想观测到更高的气温,至少需要等待的天数。如果气温在这之后都不会升高,请在该位置用 0 来代替。

示例 1:

输入: temperatures = [73,74,75,71,69,72,76,73]
输出: [1,1,4,2,1,1,0,0]

解析

类似下一个最大元素,典型的单调栈,关键就在于下标的计算。

把下标入栈这操作绝了。

Code

class Solution {
        public int[] dailyTemperatures(int[] temperatures) {
            int[] ans = new int[temperatures.length];
            
            Stack<Integer> stack = new Stack<>();
            for (int i = 0; i < temperatures.length; i++) {
                while (!stack.isEmpty() && temperatures[i] > temperatures[stack.peek()]) {
                    int index = stack.pop();
                    ans[index] = i-index;
                }
                // 绝了
                stack.push(i);
            }
            return ans;
        }
    }

[503]下一个更大元素 II

给定一个循环数组 numsnums[nums.length - 1] 的下一个元素是 nums[0] ),返回 nums 中每个元素的 下一个更大元素

数字 x下一个更大的元素 是按数组遍历顺序,这个数字之后的第一个比它更大的数,这意味着你应该循环地搜索它的下一个更大的数。如果不存在,则输出 -1

示例 1:

输入: nums = [1,2,1]
输出: [2,-1,2]
解释: 第一个 1 的下一个更大的数是 2;
数字 2 找不到下一个更大的数; 
第二个 1 的下一个最大的数需要循环搜索,结果也是 2。

解析

本题主要思想在于如何处理循环数组:

一个朴素的思想是,我们可以把这个循环数组「拉直」,即复制该序列的前 n−1 个元素拼接在原序列的后面。这样我们就可以将这个新序列当作普通序列,用上文的方法来处理。

而在本题中,我们不需要显性地将该循环数组「拉直」,而只需要在处理时对下标取模即可。

Code

    class Solution {
        public int[] nextGreaterElements(int[] nums) {
            int n = nums.length;
            int[] ans = new int[n];
            Arrays.fill(ans, -1);
            Stack<Integer> stack = new Stack<>();
            for (int i = 0; i < n * 2 - 1; i++) {
                while (!stack.isEmpty() && nums[i % n] > nums[stack.peek()]) {
                    ans[stack.pop()] = nums[i % n];
                }
                stack.push(i % n);
            }
            return ans;
        }
    }

\