长文 字节跳动面试高频算法题(先码再看),迄今为止讲解的最详细的一篇

2用两个栈实现队列

=========

剑指 Offer 09. 用两个栈实现队列

问题描述

====

用两个栈来实现一个队列,完成 n 次在队列尾部插入整数 (push) 和在队列头部删除整数 (pop) 的功能。队列中的元素为 int 类型。保证操作合法,即保证pop操作时队列内已有元素。

示例:

输入:[“PSH1”,“PSH2”,“POP”,“POP”]

返回值:1,2

说明:

“PSH1”:代表将1插入队列尾部 “PSH2”:代表将2插入队列尾部 “POP”:代表删除一个元素,先进先出 => 返回1 “POP”:代表删除一个元素,先进先出 => 返回2

分析问题

====

首先,我们需要知道队列和栈的区别。

  1. 队列是一种先进先出的数据结构。队列中的元素是从后端入队,从前端出队。就和排队买票一样。

  2. 栈是一种后进先出的数据结构。栈中的元素是从栈顶压入,从栈顶弹出。

为了使用两个栈实现队列的先进先出的特性,我们需要用一个栈来反转元素的入队顺序。

入队操作Push:

因为栈是后进先出的,而队列是先进先出的,所以要想使用栈来实现队列的先进先出功能,我们需要把新入栈的元素放入栈底。为了实现这个操作,我们需要先把栈S1中的元素移动到S2,接着再把新来的元素压入S2,然后再把S2中的所有元素再弹出,压入到S1。这样就实现了把新入栈的元素放入栈底的功能。

万字长文 | 字节跳动面试高频算法题(先码再看)

def push(self, node):

write code here

while self.stack1:

self.stack2.append(self.stack1.pop())

self.stack2.append(node)

while self.stack2:

self.stack1.append(self.stack2.pop())

出队操作Pop:

我们直接从S1弹出就可以了,因为经过反转后,S1中的栈顶元素就是最先入栈的元素,也就是队首元素。

def pop(self):

if self.stack1:

return self.stack1.pop()

我们来看完整的代码实现。

class Solution:

def init(self):

self.stack1 = []

self.stack2 = []

def push(self, node):

write code here

while self.stack1:

self.stack2.append(self.stack1.pop())

self.stack2.append(node)

while self.stack2:

self.stack1.append(self.stack2.pop())

def pop(self):

if self.stack1:

return self.stack1.pop()

我们可以看到入队操作的时间复杂度是O(n),空间复杂度也是O(n)。出队时间复杂度是O(1),空间复杂度也是O(1)。

优化

==

在上面的算法中,不知道你有没有发现,每次在push一个新元素时,我们都需要把S1中的元素移动到S2中,然后再从S2移回到S1中。这显然是冗余的。其实,我们在入队时只需要插入到S1中即可。而出队的时候,由于第一个元素被压在了栈S1的底部,要想实现队列的先进先出功能,我们就需要把S1的元素进行反转。我们可以把栈S1的元素Pop出去,然后压入S2。这样就把S1的栈底元素放在了栈S2的栈顶,我们直接从S2将它弹出即可。一旦 S2 变空了,我们只需把 S1 中的元素再一次转移到 S2 就可以了。

万字长文 | 字节跳动面试高频算法题(先码再看)

下面我们来看一下代码实现。

class Solution:

def init(self):

self.stack1 = []

self.stack2 = []

def push(self, node):

write code here

self.stack1.append(node)

def pop(self):

if not self.stack2:

while self.stack1:

self.stack2.append(self.stack1.pop())

return self.stack2.pop()

3有效的括号

======

LeetCode 20. 有效的括号

问题描述

====

给定一个只包括 ‘(’,‘)’,‘{’,‘}’,‘[’,‘]’ 的字符串 s ,判断字符串是否有效。

有效字符满足的条件是:

  • 左括号必须用相同类型的右括号闭合。

  • 左括号必须以正确的顺序闭合。

示例:

输入:s = “()[]{}”

输出:true

分析问题

====

这个问题我们可以借助 “栈” 这种数据结构来解决。在遍历字符串的过程中,当我们遇见一个左括号时,我们就入栈,当我们遇到一个右括号时,我们就取出栈顶元素去判断他们是否是同类型的。如果不是的话,那就代表字符串s不是有效串,我们直接返回False。如果是,接着去遍历,直到遍历结束为止。当遍历完字符串s后,如果栈为空,就代表字符串是有效的。这里需要注意一点,为了加快判断左、右括号是否是同类型的,我们引入哈希表存储每一种括号。哈希表的键为右括号,值为相同类型的左括号。

下面我们来看一下代码实现。

def isValid(s):

#如果字符串不是偶数,直接返回false

#因为字符只包含括号,所以只有偶数时才有可能匹配上

if len(s) % 2 == 1:

return False

dict = {

“)”: “(”,

“]”: “[”,

“}”: “{”,

}

stack = list()

for ch in s:

#代表遍历到右括号

if ch in dict:

#看栈顶元素是否能匹配上,如果没有匹配上,返回false

if not stack or stack[-1] != dict[ch]:

return False

#如果匹配上,弹出栈顶元素

stack.pop()

else:

#匹配到左括号,入栈

stack.append(ch)

#栈为空,代表s是有效串,否则是无效串

return not stack

4包含min函数的栈

==========

剑指 Offer 30. 包含min函数的栈

问题描述

====

定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的min函数,并且调用 min函数、push函数 及 pop函数 的时间复杂度都是 O(1)。

push(value):将value压入栈中

pop():弹出栈顶元素

top():获取栈顶元素

min():获取栈中最小元素

示例:

minStack.push(-2); -->将-2入栈

minStack.push(0); -->将0入栈

minStack.push(-3); -->将-3入栈

minStack.min(); -->返回栈中最小元素-3

minStack.pop(); -->弹出栈顶元素-3

minStack.top(); -->返回栈顶元素0

minStack.min(); -->返回栈中最小元素-2

分析问题

====

对于普通的栈来说,执行push和pop的时间复杂度是O(1),而执行min函数的时间复杂度是O(N),因为要想找到最小值,就需要遍历整个栈,为了降低min函数的时间复杂度,我们引入了一个辅助栈。

  • 数据栈A:栈A用来存储所有的元素,保证入栈push、出栈pop、获取栈顶元素top的操作。

  • 辅助栈B:栈B用来存储栈A中所有非严格递减的元素,即栈A中的最小值始终在栈B的栈顶,这样可以保证以O(1)的时间复杂度来返回栈中的最小值。

万字长文 | 字节跳动面试高频算法题(先码再看)

万字长文 | 字节跳动面试高频算法题(先码再看)

万字长文 | 字节跳动面试高频算法题(先码再看)

下面我们来看一下代码实现。

class Solution:

def init(self):

#数据栈

self.A = []

#辅助栈

self.B = []

#push操作

def push(self, x):

self.A.append(x)

#如果辅助栈B为空,或者栈顶元素大于x,则入栈

if not self.B or self.B[-1] >= x:

self.B.append(x)

def pop(self):

#弹出数据栈A中的元素

s = self.A.pop()

#如果弹出的元素和栈B的栈顶元素相同,则为了保持一致性

#将栈B的栈顶元素弹出

if s == self.B[-1]:

self.B.pop()

def top(self):

#返回数据栈A中的栈顶元素

return self.A[-1]

def min(self):

#返回辅助栈B中的栈顶元素

return self.B[-1]

5表达式求值

======

问题描述

====

请写一个整数计算器,支持加减乘三种运算和括号。

示例:

输入:“(2 * (3 - 4)))* 5”

返回值:-10

分析问题

====

因为只支持加、减、乘、括号,所以我们根据优先级可以分为3类,即括号>乘>加、减,假设先把括号去掉,那么就剩下乘和加减运算,根据运算规则,我们需要先计算乘、再计算加、减,因此我们可以这么来考虑,我们先进行乘法运算,并将这些乘法运算后的整数值返回原表达式的相应位置,则随后整个表达式的值,就等于一系列整数加减后的值。而对于被括号分割的表达式,我们可以递归的去求解,具体算法如下。

遍历字符串s,并用变量preSign记录每个数字之前的运算符,初始化为加号。

  1. 遇到空格时跳过。

  2. 遇到数字时,继续遍历求出这个完整的数字的值,保存到num中。

  3. 遇到左括号时,需要递归的求出这个括号内的表达式的值。

  4. 遇到运算符或者表达式的末尾时,就根据上一个运算符的类型来决定计算方式。

  5. 如果是加号,不需要进行计算,直接push到栈里

  6. 如果是减号,就去当前数的相反数,push到栈里

  7. 如果是乘号,就需要从栈内pop出一个数和当前数求乘法,再把计算结果push到栈中

  8. 最后把栈中的结果求和即可。

下面我们来看一下代码实现。

class Solution:

def calculate(self, s):

n = len(s)

#存取部分数据和

stack = []

preSign = ‘+’

num = 0

i=0

while i<n:

c=s[i]

if c==’ ':

i=i+1

continue

if c.isdigit():

num = num * 10 + ord© - ord(‘0’)

#如果遇到左括号,递归求出括号内表达式的值

if c==‘(’:

j=i+1

counts=1

#截取出括号表达式的值

while counts>0:

if s[j]==“(”:

counts=counts+1

if s[j]==“)”:

counts=counts-1

j=j+1

#剥去一层括号,求括号内表达式的值

num=self.calculate(s[i+1:j-1])

i=j-1

if not c.isdigit() or i==n-1:

if preSign==“+”:

stack.append(num)

elif preSign==“-”:

stack.append(-1*num)

elif preSign==“*”:

tmp=stack.pop()

stack.append(tmp*num)

num=0

preSign=c

i=i+1

return sum(stack)

s=Solution()

print(s.calculate(“(3+4)*(5+(2-3))”))

6滑动窗口的最大值

=========

LeetCode 239. 滑动窗口最大值

问题描述

====

给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。返回滑动窗口中的最大值。

示例:

输入:[2,3,4,2,6,2,5,1],3

输出:[4,4,6,6,6,5]

分析问题

====

这道题的关键点在于求滑动窗口中的最大值。大小为k的滑动窗口,我们可以通过遍历的方式来求出其中的最大值,需要O(k)的时间复杂度。对于大小为n的数组nums,一共有n-k+1个窗口,因此该算法的时间复杂度是O(nk)。

万字长文 | 字节跳动面试高频算法题(先码再看)

通过观察,我们可以知道,对于两个相邻的滑动窗口,有k-1个元素是共用的,只有一个元素是变化的,因此我们可以利用此性质来优化我们的算法。

万字长文 | 字节跳动面试高频算法题(先码再看)

对于求最大值问题,我们可以使用优先级队列(大顶推)来求解。首先,我们将数组的前k个元素放入优先级队列中。每当我们向右移动窗口时,我们就可以把一个新的元素放入队列中,此时堆顶元素就是堆中所有元素的最大值,然而这个最大值有可能不属于当前的滑动窗口中,我们需要将该元素进行移除处理(如果最大值不在当前滑动窗口中,它只能在滑动窗口的左边界的左侧,所以滑动窗口向右移动的过程中,该元素再也不会出现在滑动窗口中了,所以我们可以对其进行移除处理)。我们不断地移除堆顶的元素,直到其确实出现在滑动窗口中。此时,堆顶元素就是滑动窗口中的最大值。

为了方便判断堆顶元素与滑动窗口的位置关系,我们可以在优先队列中存储二元组 (num,index),表示元素num在数组中的下标为index。

小trick:因为python中只提供了小顶堆,所以我们需要对元素进行取反处理,例如对于列表[1, -3],我们对元素进行取反,然后插入小顶堆中,此时堆中是这样的[-1,3],我们取出堆顶元素-1,然后取反为1,正好可以得到列表中的最大值1。

我们nums=[2,3,4,2,6,2,5,1],k=3为例,来看一下具体的过程。

  1. 首先,我们将nums的前3个元素放入优先级队列中,队首元素下标值index=2>0,在窗口中,所以加入结果中,此时res=[4]。

  2. 下一个元素2入队,此时队首元素下标index=2>1,在窗口中,所以加入结果中,此时res=[4,4]。

  3. 下一个元素6入队,此时队首元素下标index=4>2,在窗口中,所以加入结果中,此时res=[4,4,6]。

  4. 下一个元素2入队,此时队首元素下标index=4>3,在窗口中,所以加入结果中,此时res=[4,4,6,6]。

  5. 下一个元素5入队,此时队首元素下标index=4=4,在窗口中,所以加入结果中,此时res=[4,4,6,6,6]。

  6. 下一个元素1队列,此时队首元素下标index=4<5,不在窗口中,所以我们将其弹出,此时队首元素的下标变为6,在窗口中,所以加入结果中,此时res=[4,4,6,6,6,5]。

进阶

==

这道题我们也可以使用双端队列来求解。我们在遍历数组的过程中,不断的对元素对应的下标进行出队入队操作,在出入队的过程中,我们需要保证队列中存储的下标对应的元素是从大到小排序的。具体来说,当有一个新的元素对应的下标需要入队时,如果该元素比队尾对应的元素的值大,我们需要弹出队尾,然后循环往复,直到队列为空或者新的元素小于队尾对应的元素。

由于队列中下标对应的元素是严格单调递减的,因此队首下标对应的元素就是滑动窗口中的最大值。但是此时的最大值可能在滑动窗口左边界的左侧,并且随着窗口向右移动,它永远不可能出现在滑动窗口中了。因此我们还需要不断从队首弹出元素,直到队首元素在窗口中为止。

我们还是以nums=[2,3,4,2,6,2,5,1],k=3为例,来看一下具体的过程。我们首先初始化一个空队列que。

  1. 此时队列为que空,元素2对应的下标0入队。并且此时未形成窗口,不取值。

  2. 此时队列que=[0],队尾元素为0,它对应数组中的元素是nums[0] < nums[1]的,所以我们把队尾0弹出,此时队列为空,我们将1入队。并且此时未形成窗口,不取值。

  3. 此时队列que=[1],队尾元素为1,它对应的数组中的元素是nums[1] < nums[2]的,所以我们把队尾1弹出,此时队列为空,我们将2入队。并且此时队首元素2在窗口[0,2]中,所以取出队首元素。

  4. 此时队列que=[2],队尾元素为2,它对应的数组中的元素是nums[2] > nums[3]的,所以我们将3入队。并且此时队首元素2在窗口[1,3]中,所以取出队首元素。

  5. 此时队列que=[2,3],队尾元素为3,它对应的数组中的元素是nums[3] < nums[4]的,所以我们把队尾3弹出,并且此时队尾元素对应的数组中的元素是nums[2] < nums[4],所以我们把队尾2弹出,此时队列为空,我们将4入队。并且此时队首元素4在窗口[2,4]中,所以取出队首元素。

  6. 此时队列que=[4],队尾元素为4,它对应的数组中的元素是nums[4] > nums[5]的,所以我们将5入队。并且此时队首元素4在窗口[3,5]中,所以我们取出队首元素。

  7. 此时队列que=[4,5],队尾元素为5,它对应的数组中的元素是nums[5] < nums[6]的,所以我们把队尾5弹出,此时队尾元素对应的数组中的元素时nums[4] > nums[6] ,所以我们将6入队。并且此时队首元素4在窗口[4,6]中,所以我们取出队首元素。

  8. 此时队列que=[4,6],队尾元素为6,它对应的数组中的元素是nums[6] > nums[7]的,所以我们将7入队。而此时队首元素4不在窗口[5,7]中,所以我们将其移除队列,此时队首元素6在窗口[5,7]中,所以我们将其取出。

下面我们来看一下代码实现。

import collections

class Solution:

def maxSlidingWindow(self, nums, k):

n = len(nums)

#申请一个双端队列

q = collections.deque()

#初始化第一个窗口

for i in range(k):

#如果队列不为空且比队尾元素大,将队尾出队

while q and nums[i] >= nums[q[-1]]:

q.pop()

#直到队列为空,或者比队尾元素小,入队

q.append(i)

#将队首元素加入结果中

ans = [nums[q[0]]]

#窗口逐步向右移动

for i in range(k, n):

#如果队列不为空且比队尾元素大,将队尾出队

while q and nums[i] >= nums[q[-1]]:

q.pop()

#直到队列为空,或者比队尾元素小,入队

q.append(i)

#如果队首元素不在该窗口内,出队操作

while q[0] <= i - k:

q.popleft()

#将队首元素加入结果中

ans.append(nums[q[0]])

return ans

s=Solution()

print(s.maxSlidingWindow([2,3,4,2,6,2,5,1],3))

7栈和排序

=====

问题描述

====

给你一个由1~n,n个数字组成的一个排列和一个栈,要求按照排列的顺序入栈。如何在不打乱入栈顺序的情况下,仅利用入栈和出栈两种操作,输出字典序最大的出栈序列。

排列:指 1 到 n 每个数字出现且仅出现一次。

示例:

输入:[2,4,5,3,1]

输出:[5,4,3,2,1]

分析问题

====

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
img

那么如何才能正确的掌握Redis呢?

为了让大家能够在Redis上能够加深,所以这次给大家准备了一些Redis的学习资料,还有一些大厂的面试题,包括以下这些面试题

  • 并发编程面试题汇总

  • JVM面试题汇总

  • Netty常被问到的那些面试题汇总

  • Tomcat面试题整理汇总

  • Mysql面试题汇总

  • Spring源码深度解析

  • Mybatis常见面试题汇总

  • Nginx那些面试题汇总

  • Zookeeper面试题汇总

  • RabbitMQ常见面试题汇总

JVM常频面试:

Redis高频面试笔记:基础+缓存雪崩+哨兵+集群+Reids场景设计

Mysql面试题汇总(一)

Redis高频面试笔记:基础+缓存雪崩+哨兵+集群+Reids场景设计

Mysql面试题汇总(二)

Redis高频面试笔记:基础+缓存雪崩+哨兵+集群+Reids场景设计

Redis常见面试题汇总(300+题)

Redis高频面试笔记:基础+缓存雪崩+哨兵+集群+Reids场景设计
725421)]
[外链图片转存中…(img-dkbw1cjL-1712049725421)]
[外链图片转存中…(img-9f5gfLbg-1712049725422)]
[外链图片转存中…(img-fYeYqfrH-1712049725422)]
[外链图片转存中…(img-z4WBUY7s-1712049725423)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
[外链图片转存中…(img-wLZBHip2-1712049725423)]

那么如何才能正确的掌握Redis呢?

为了让大家能够在Redis上能够加深,所以这次给大家准备了一些Redis的学习资料,还有一些大厂的面试题,包括以下这些面试题

  • 并发编程面试题汇总

  • JVM面试题汇总

  • Netty常被问到的那些面试题汇总

  • Tomcat面试题整理汇总

  • Mysql面试题汇总

  • Spring源码深度解析

  • Mybatis常见面试题汇总

  • Nginx那些面试题汇总

  • Zookeeper面试题汇总

  • RabbitMQ常见面试题汇总

JVM常频面试:

[外链图片转存中…(img-jrjeRwJX-1712049725424)]

Mysql面试题汇总(一)

[外链图片转存中…(img-LlqjNsAc-1712049725424)]

Mysql面试题汇总(二)

[外链图片转存中…(img-NizU8DuQ-1712049725425)]

Redis常见面试题汇总(300+题)

[外链图片转存中…(img-XM52kfJ4-1712049725425)]

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值