算法系列-单调栈集锦,一套题弄懂单调栈!

177 阅读3分钟

大家好,这里是白十七,今天给大家带来单调栈集锦,本集锦参考

大家一起进步!!有什么好的资源都可以发在评论区嗷!

402. 移掉 K 位数字 - 力扣(LeetCode)

这道题参考leetcode 402 Remove K Digits_哔哩哔哩_bilibili

大体上就是,先理解什么事单调栈。

他维持一个单调递增或单调递减的栈。

看这道题,移掉k位数字,返回最小的。

那我们就可以整一个单调递增的栈,这里要注意一个问题,就是什么时候添加,什么删除。

什么时候添加呢?当num[i]>num[i+1]的时候,这个时候,num[i+1]<num[i],维持不了单调递增的条件,就需要把num[i]删掉。

注意:这里删除还有一个条件限制,你总不能删的比移掉k位数字还多吧。就是stack.size()+nums.length()-1-index>=nums.length()-k 这句话什么意思呢,就是,当前的栈的大小+我们后面剩的数,是否大于我们要保留的位数。

比如 1432219 当我们遍历到第二位的时候,也就是4,添加。

遍历到第三位的时候,发现他不是单调递增的了,要不要删除呢?看看我们后面的位数+我们栈里面有的位数,是否大于我们,也就是stack.size()+nums.length()-index-1 (index是我们当前遍历到的,因为是从0开始算的,所以在-1) >=nums.length()-k (也就是我们要保存的位数)

也即stack.size()-1+k>=index

再然后就是考虑去0,因为你不能返回0200把,肯定都是200.

去0的话,就把头的0去掉,如果都是0,最后在给队列里加个0就ok。

class Solution {
      public String removeKdigits(String num, int k) {
        Deque<Character> deque = new ArrayDeque<>();

        int len = num.length();
        int index = 0;
        int count = len-k;
        if (count==0) {
            return "0";
        }
        while (index < len) {
            //单调递增 添加
            while (index<len && (deque.isEmpty() || deque.peekLast()<=num.charAt(index))){
                deque.addLast(num.charAt(index++));
            }
            if (index==len){break;}

            //删除
            while(!deque.isEmpty() &&deque.peekLast()>num.charAt(index) &&
                    deque.size()+len-index-1>=count){
                deque.removeLast();
            }
            deque.addLast(num.charAt(index++));
        }
        //针对10001做的处理
        while(deque.size()>count){
            deque.removeLast();
        }
        //去0
        while (!deque.isEmpty()&& deque.peekFirst()=='0'){
            deque.removeFirst();
        }if (deque.isEmpty()) {
            deque.add('0');
        }
        StringBuilder builder = new StringBuilder();

        index=0;
        while(!deque.isEmpty()&& index<count){
            builder.append(deque.removeFirst());
            index++;
        }


        return builder.toString();
    }
}

316. 去除重复字母 - 力扣(LeetCode)

首先是这道题的描述,他要求返回结果的字典序最小(要求不能打乱其他字符的相对位置)。

那这个怎么解决呢?

输入:"bcabc"


返回 "abc"

首先,我们得知道后面有没有这个字母,也就是字母最后一次出现的位置,如果有,我们就可以替换掉,如果没有,那么我们就不能动。

这个字母最后一次出现的位置怎么保存呢?用一个int[26],里面保存26个小字母,正好。

然后就是单调栈,如果遍历的字母正好比原先的大,那就添加进去,维持一个单调递增的数组,如果小呢,就去比较他们的位置,如果在后面还有出现,那我们就可以直接扔掉,然后在看前面的,如果还是小,接着这一操作,也就是一个while循环。

然后,我们还需要整一个set数组用来保存当前的字母,看有没有重复,如果重复了,就直接跳过。

大体思路搞明白之后,就可以写代码。

public String removeDuplicateLetters(String s){

    //首先保存一下这个字符串最后出现的位置
    int[] index=new int[26];
    for(int i = 0;i<s.length();i++){
        index[s.charAt(i)-'a']=i;
    }
    //然后一个queue用来保存单调递增的序列
    Deque<Character>queue = new ArrayDeque<>();
    //一个set用来保存出现的字母
     HashSet<Character> set = new HashSet<Character>();
    //整个哨兵节点 a,用来比大小
    queue.addLast('a');
    for(int i =0;i<s.length();i++){
        char curChar = s.charAt(i);
        //如果包含,直接下一次
        if(set.contains(curChar)){
            continue;
        }
        //不满足条件,删除
        while(curChar<queue.peekLast() && index[queue.peekLast()-'a']>i){  //      index[queue.peekLast()-'a']>i
           //把set和queue中都删掉
           set.remove(queue.peekLast());
           queue.pollLast();
        }
        //删除完之后,再把后面的加进来
        queue.addLast(curChar);
        set.add(curChar);
    }
    //删除哨兵节点
    queue.removeFirst();
    StringBuilder sb= new StringBuilder();
    while(!queue.isEmpty()){
        sb.append(queue.pollFirst());
    }
    return sb.toString();
}



321. 拼接最大数 - 力扣(LeetCode)

参考:leetcode 321 Create Maximum Number_哔哩哔哩_bilibili

这个题目的描述就是,从两个数组中,取到k位的最大值。

也就是从num1中选取i位,从num2中选取k-i位,然后混合,取最大值。

思路有了之后,开始写代码。

首先是从从nums1和num2中取出最大的k位数,这个就可以用单调栈

首先大体框架

public int[] maxNumber(int[] nums1, int[] nums2, int k) {
    int len1 = nums1.length;
    int len2 = nums2.length;
    int[] res= null;
    //从数组中取k个数
    for(int i = 0;i<=k;i++){
        if (i>nums1.length|| k-i>nums2.length) {
            continue;
        }
        //从nums1和nums2中取值
        int[] max1 = getMax(nums1,i);
        int[] max2 = getMax(nums2,k-i);
        //混合两个数组,返回两个数组混合的最大值
        int[] tmp=merge(max1,max2);
        if(greater(tmp,0,res,0)){
            res=tmp;
        }
    }
    return res;
}
public int[] getMax(int[]nums,int num){
    int[] res = new int[num];
    //取nums中最大的num位数,用单调栈
    Deque<Integer> queue = new ArrayDeque<>();
    int index = 0;
    int len = nums.length;
    while(index<len){
        //单调栈添加  这是一个单调递减的单调栈
        while(index<len && (queue.isEmpty() || queue.peekLast() > nums[index])){
            queue.addLast(nums[index++]);
        }
        //单调栈删除
        if (index==nums.length) {
            break;
        }
        while(!queue.isEmpty() && queue.peekLast()<nums[index] && queue.size()+len-index-1>=num){
            queue.removeLast();
        }
        //最后元素的添加
        queue.addLast(nums[index++]);
    }
    //最后元素的返回
    for(int i = 0;i<num;i++){
        if (!queue.isEmpty()){
            res[i]=queue.pollFirst();
        }

    }
    return res;
}

取得最大值之后,就是混合,然后在比较

混合的话就是看取出的max1和max2里面谁的数大就取谁,最后返回一个数组

public int[] merge(int[] nums1,int[] nums2){
    int len1 = nums1.length;
    int len2 = nums2.length;
    int len = len1 + len2;
    int[] nums = new int[len];
    int index1 = 0;
    int index2 = 0;
    int index = 0;
    while(index1<len1 || index2<len2){
        if(greater(nums1,index1,nums2,index2)){
            nums[index++]=nums1[index1++];
        }else{
            nums[index++]=nums2[index2++];
        }
    }
    return nums;
}

最后就是比较

public boolean greater(int[] nums1, int index1, int[] nums2, int index2){
    //因为nums2 是k-i,所以上来会是null,为了防止空指针异常,我们直接返回一个true
    if(nums2==null) {
        return true;
    }
    int len1 = nums1.length;
    int len2 = nums2.length;
    //这个是如果两个个数相等的情况
    while(index1<len1 && index2<len2 && nums1[index1]==nums2[index2]){
        index1++;
        index2++;
    }
    //index2==nums2.length是为了防止数组下标越界的情况  index1!=nums1.length&&(nums1[index1]>nums2[index2])比较大小   index1!=nums1.length是为了防止第一次传的nums1空数组异常
    return index2==nums2.length ||(index1!=nums1.length&&(nums1[index1]>nums2[index2]));
}