字节面试常考算法类型-单调栈

本文旨在对于个人知识的梳理以及知识的分享,如果有不足的地方,欢迎大家在评论区指出


Question One
题目描述

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

例如,给定一个列表 temperatures = [73, 74, 75, 71, 69, 72, 76, 73],你的输出应该是 [1, 1, 4, 2, 1, 1, 0, 0]。

提示:气温 列表长度的范围是 [1, 30000]。每个气温的值的均为华氏度,都是在 [30, 100] 范围内的整数。

题目链接
题目分析

在分析题目之前我先描述一下什么是单调栈

  • 概念: 单调栈顾名思义就是一个栈,栈中的元素在某个方面具有单调性
  • 解决问题: 单调栈解决的问题一般都是找到某个元素左边或者右边最近的比它大或者小的元素

那么我们回来看这道题,他要求的虽然是等待的天数,但是抽象问题之后我们发现其实就只需要求出每一个元素右边比它大的最近的元素的下标即可,那么这个问题自然就可以使用单调栈求解了

解题代码

Python

class Solution:
    def dailyTemperatures(self, T: List[int]) -> List[int]:
        n = len(T)
        stack = []
        res = [0] * n

        for i in range(n-1, -1, -1):
            # 如果当前元素的值大于等于栈顶元素,则弹栈
            while stack and T[i] >= T[stack[-1]]:
                stack.pop()
            # 如果栈为空,则说明当前元素右边没有比它大的元素了
            if not stack:
                pass
            else:
                res[i] = stack[-1]-i
            stack.append(i)
        return res

Java

class Solution {
    public int[] dailyTemperatures(int[] T) {
        int n = T.length;

        int[] res = new int[n];
        Stack<Integer> sk = new Stack<Integer>();
        for(int i=n-1; i>=0; i--){
            while(sk.size()>0 && T[i]>=T[sk.peek()]) sk.pop();
            if(sk.size() == 0) res[i] = 0;
            else res[i] = sk.peek()-i;
            sk.push(i);
        }
        return res;
    }
}

Question Two
题目描述

给定一个以字符串表示的非负整数 num,移除这个数中的 k 位数字,使得剩下的数字最小。

注意:
num 的长度小于 10002 且 ≥ k。
num 不会包含任何前导零。
示例1:

输入: num = "1432219", k = 3
输出: "1219"
解释: 移除掉三个数字 4, 3, 和 2 形成一个新的最小的数字 1219。

示例2:

输入: num = "10200", k = 1
输出: "200"
解释: 移掉首位的 1 剩下的数字为 200. 注意输出不能有任何前导零。

示例3:

输入: num = "10", k = 2
输出: "0"
解释: 从原数字移除所有的数字,剩余为空就是0。
题目链接
题目分析

之前分析过了单调栈,这里就不作分析了,直接说一下这道题的思路,我们想要让数字最小,其实也就是让数字的高位尽可能的小,那么在不考虑k的情况下,如果当前的数字比之前的一些相邻数字要小,那么必然之前的那些数字都是要删除的,加上k的限制之后,也就是我们最多只能删除k次,那么这k次必然是尽量删除高位上较大的,使得高位上的数字变小,由此我们想到了单调栈,也就是如果栈顶元素小于当前元素,则进行弹栈操作,可以看出这道题似乎没有上一道题说的那些性质(最左/最右找最大/最小),我个人感觉也是这样的(这道题是贪心的思想,但是维护了一个单调栈),只是这道题最终元素的顺序不能发生变化,因为栈适合于处理相邻元素之间的关系,所以使用栈这种数据结构是最优的

解题代码

Python

class Solution:
    def removeKdigits(self, num: str, k: int) -> str:
        n = len(num)
        remain = n - k
        stack = []

        for cur in num:
            while k and stack and stack[-1] > cur:
                stack.pop()
                k -= 1
            stack.append(cur)
        
        return "".join(stack[:remain]).lstrip('0') or '0'

Java

class Solution {
    public String removeKdigits(String num, int k) {
        int n = num.length();

        int remain = n - k; // 计算应该剩下的数字

        Stack<Integer> stack = new Stack<Integer>();
        for(int i=0; i<n; i++){
            // 如果当前元素比栈顶元素大,则执行弹栈操作
            while(stack.size()>0 && k > 0 && num.charAt(i)<num.charAt(stack.peek())){
                stack.pop();
                k -= 1;
            }
            stack.push(i);
        }

        while(stack.size() > remain) stack.pop();
        StringBuilder res = new StringBuilder();
        while(stack.size() > 0) res.append(num.charAt(stack.pop()));
        res = res.reverse();
        int t = 0; 
        while(t<res.length()-1 && res.charAt(t)=='0') t += 1;
        return res.substring(t).equals("")? "0":res.substring(t);
    }
}

另外还有一道和这道题非常类似的题目,这里只给出题目链接,分析方法与上一道题大体相同


Question Three
题目描述

给你一个整数数组 nums ,数组中共有 n 个整数。132 模式的子序列 由三个整数 nums[i]、nums[j] 和 nums[k] 组成,并同时满足:i < j < k 和 nums[i] < nums[k] < nums[j] 。

如果 nums 中存在 132 模式的子序列 ,返回 true ;否则,返回 false 。
进阶:
很容易想到时间复杂度为 O(n^2) 的解决方案,你可以设计一个时间复杂度为 O(n logn) 或 O(n) 的解决方案吗?
示例一:

输入:nums = [1,2,3,4]
输出:false
解释:序列中不存在 132 模式的子序列。

示例二:

输入:nums = [3,1,4,2]
输出:true
解释:序列中有 1 个 132 模式的子序列: [1, 4, 2] 。

示例三:

输入:nums = [-1,3,2,0]
输出:true
解释:序列中有 3 个 132 模式的的子序列:[-1, 3, 2]、[-1, 3, 0] 和 [-1, 2, 0] 。

提示:

  • n == nums.length
  • 1 <= n <= 10^4
  • -10^9 <= nums[i] <= 10^9
题目链接
题目分析

首先先说暴力解,我们知道nums[i]对应的数字一定是最小的,所以我们可以这样枚举,也就是枚举j和k的所有情况,然后在枚举j和k的同时,每一次都让nums[i]为j之前最小的,到此判断nums[i]、nums[j]以及nums[k]的关系即可,时间复杂度为 O ( n 2 ) O(n^2) O(n2),Java是可以通过的,Python估计有些卡常,不过 1 0 4 10^4 104的数据量使用 O ( n 2 ) O(n^2) O(n2)一般是通过不了的


之后说优化的解法,我们发现对于nums[j]与nums[k]的关系,我们可以使用一个单调栈来维护,具体的思路就是从后向前的遍历所有的数字,如果当前数字比栈顶的元素要大,那么说明当前数字是i, j, k中的j,而栈顶的元素为k,此时我们要更新k的值,将它更新为k中的最大值,如果在某个时刻当前数字比k代表的数字小,则说明该序列中存在132模式,因为后面有比k代表的值大的数字,那么这三个数字正好可以构成合法的132模式

解题代码
解法1(暴力解):

Python

class Solution:
    def find132pattern(self, nums: List[int]) -> bool:
        i = 0
        for j in range(len(nums)):
            for k in range(j, len(nums)):
                if nums[k]>nums[i] and nums[j]>nums[k]:
                    return True
            if nums[j]<nums[i]:
                i = j
        return False

Java

class Solution {
    public boolean find132pattern(int[] nums) {
        int n = nums.length;

        int i = 0;
        for(int j=1; j<n; j++){
            for(int k=j; k<n; k++){
                if(nums[k]>nums[i] && nums[j]>nums[k]) return true;
            }
            if(nums[j]<nums[i]) i = j;
        }
        return false;
    }
}

解法二(单调栈):

Python

class Solution:
    def find132pattern(self, nums: List[int]) -> bool:
        right = -2e9
        stack = []
        for i in range(len(nums)-1, -1, -1):
            if nums[i]<right: return True
            while stack and nums[i]>nums[stack[-1]]:
                right = max(right, nums[stack.pop()])
            stack.append(i)
        return False

Java

class Solution {
    public boolean find132pattern(int[] nums) {
        int n = nums.length;

        Stack<Integer> stack = new Stack<Integer>();
        int right = Integer.MIN_VALUE;
        for(int i=n-1; i>=0; i--){
            if(right>nums[i]) return true;
            while(stack.size()>0 && nums[i]>nums[stack.peek()]){
                right = Math.max(right, nums[stack.pop()]);
            }
            stack.push(i);
        }
        return false;
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值