150. 逆波兰表达式求值
题目
给你一个字符串数组 tokens
,表示一个根据 逆波兰表示法 表示的算术表达式。
请你计算该表达式。返回一个表示表达式值的整数。
注意:
- 有效的算符为
'+'
、'-'
、'*'
和'/'
。 - 每个操作数(运算对象)都可以是一个整数或者另一个表达式。
- 两个整数之间的除法总是 向零截断 。
- 表达式中不含除零运算。
- 输入是一个根据逆波兰表示法表示的算术表达式。
- 答案及所有中间计算结果可以用 32 位 整数表示。
尝试解答
什么东西,题目都没看懂
然后看了代码随想录的视频——栈,来写一下试试
class Solution(object):
def evalRPN(self, tokens):
"""
:type tokens: List[str]
:rtype: int
"""
stack=[]
for i in tokens:
if i not in {'+', '-', '*', '/'}:
stack.append(i)
elif i=='+'and len(stack)!=1:
temp1=int(stack.pop())
temp2=int(stack.pop())
stack.append(temp1+temp2)
elif i=='-'and len(stack)!=1:
temp1=int(stack.pop())
temp2=int(stack.pop())
stack.append(temp1-temp2)
elif i=='*'and len(stack)!=1:
temp1=int(stack.pop())
temp2=int(stack.pop())
stack.append(temp1*temp2)
elif i=='/'and len(stack)!=1:
temp1=int(stack.pop())
temp2=int(stack.pop())
stack.append(temp2//temp1)
return int(stack[-1])
不对,应该是 向零截断的问题
代码随想录的代码
from operator import add, sub, mul
def div(x, y):
# 使用整数除法的向零取整方式
return int(x / y) if x * y > 0 else -(abs(x) // abs(y))
class Solution(object):
op_map = {'+': add, '-': sub, '*': mul, '/': div}
def evalRPN(self, tokens: List[str]) -> int:
stack = []
for token in tokens:
if token not in {'+', '-', '*', '/'}:
stack.append(int(token))
else:
op2 = stack.pop()
op1 = stack.pop()
stack.append(self.op_map[token](op1, op2)) # 第一个出来的在运算符后面
return stack.pop()
整段代码首先定义了向零截断,然后用栈来计算。其中对于判断是否是数字,我一开始想用的是isdigit来判断,但是它不能判断负数。这个直接判断不是加减乘除就行,然后就是计算。创建一个字典存入加减乘除,然后括号起来计算。这都是值得学习的。
239. 滑动窗口最大值
题目
给你一个整数数组 nums
,有一个大小为 k
的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k
个数字。滑动窗口每次只向右移动一位。
返回 滑动窗口中的最大值 。
代码随想录的代码
代码
from collections import deque
class MyQueue: #单调队列(从大到小
def __init__(self):
self.queue = deque() #这里需要使用deque实现单调队列,直接使用list会超时
#每次弹出的时候,比较当前要弹出的数值是否等于队列出口元素的数值,如果相等则弹出。
#同时pop之前判断队列当前是否为空。
def pop(self, value):
if self.queue and value == self.queue[0]:
self.queue.popleft()#list.pop()时间复杂度为O(n),这里需要使用collections.deque()
#如果push的数值大于入口元素的数值,那么就将队列后端的数值弹出,直到push的数值小于等于队列入口元素的数值为止。
#这样就保持了队列里的数值是单调从大到小的了。
def push(self, value):
while self.queue and value > self.queue[-1]:
self.queue.pop()
self.queue.append(value)
#查询当前队列里的最大值 直接返回队列前端也就是front就可以了。
def front(self):
return self.queue[0]
class Solution:
def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
que = MyQueue()
result = []
for i in range(k): #先将前k的元素放进队列
que.push(nums[i])
result.append(que.front()) #result 记录前k的元素的最大值
for i in range(k, len(nums)):
que.pop(nums[i - k]) #滑动窗口移除最前面元素
que.push(nums[i]) #滑动窗口前加入最后面的元素
result.append(que.front()) #记录对应的最大值
return result
正如代码随想录的思路是设置三个函数pop,push和front(找最大值)。 整体思路是按照滑动窗口将元素push进队列里,当push进的新元素比前面的元素都要大时,就把前面的元素都移除。当push进的新元素没有前面元素大时,就正常放进里面。此时位于列表第一位的始终就是最大值,这样找最大值时就只需要把队列第一个取出来就行。但是当滑动窗口移动时,需要把目前的最大值移去,然后push进新的元素。所以在进行pop操作的时候,就是首先保证要不能对空队列进行操作。然后就是对需要移动表头操作的一个判断。这是pop函数,然后对于push函数,除了push进新元素外,还要对小于该元素的前面元素进行移除操作。然后对于maxSlidingWindow函数,在移动滑动窗口时,这个pop函数还是要写的,但是具体判断是在pop函数里面的。所以这个思路还是要学习的,再push进新函数。
类
python中,class是用来定义类的关键字。类是面向对象编程的核心概念。它允许创建自定义的数据类型,并将数据(属性)和行为(方法)结合在一起。
关键组成部分:
1.类定义:使用class关键字定义类,后面跟类名。
2.构造函数:__init__方法是构造函数,当创建对象时会自动调用。它通常用于初始化对象的属性。
3.属性:使用self关键字定义实例属性。
4.方法:类中定义的函数,通常第一个参数是self,指向调用该方法的对象本身。
创建对象:
使用类来创建对象
继承:
python类还支持继承,可以从一个类继承属性和方法。
类是面向对象编程(OOP)的核心概念,因为它提供了一个结构化的方式来组织和管理代码,模拟现实世界中的对象及其行为。
1.抽象:类允许程序员定义新的数据类型,将相关的数据和操作组合在一起。通过这种抽象,可以简化复杂系统的设计,使其更易于理解和管理。例如,一个Car类可以抽象出汽车的属性(如颜色、品牌)和行为(如启动、停止)。
2.封装:类提供了一种将数据和方法封装在一个对象中的机制,确保对象的内部状态只能通过对象的方法访问和修改。这有助于隐藏实现细节,只暴露必要的接口,提升代码的安全性和可靠性。例如,银行账户类可以封装账户余额,确保只有通过存取款方法才能修改余额。
3.继承:继承是OOP是一个重要特性,它允许一个类从另一个类继承属性和方法,从而实现代码重用和层次化设计。继承使得可以创建一个通用类(基类),并在此基础上拓展出更具体的类(派生类),减少代码重复。
4.多态:多态性允许不同的对象以相同的方式响应相同的消息,即方法调用。通过多态,程序可以处理不同类型的对象,而不需要在编写时知道这些对象的具体类型。这使得代码更加灵活和可拓展。
python中,类的属性和方法是通过类定义中的语句和函数来定义的。实例是指某个类的具体对象。实例是类的一个具体实现,包含类定义的所有属性和方法,每个实例都有自己的状态和行为。
类的属性分为类属性和实例属性:
类属性是类本身拥有的属性,所有实例共享同一个类属性。它在类定义内部、方法外部定义。
class MyClass:
class_attribute = "I am a class attribute"
# 访问类属性
print(MyClass.class_attribute) # 输出: I am a class attribute
# 所有实例共享类属性
obj1 = MyClass()
obj2 = MyClass()
print(obj1.class_attribute) # 输出: I am a class attribute
print(obj2.class_attribute) # 输出: I am a class attribute
实例属性是由每个实例单独拥有的属性,通过构造函数__init__来定义。每个实例都有自己独立的实例属性。
class MyClass:
def __init__(self, value):
self.instance_attribute = value
# 创建实例并访问实例属性
obj1 = MyClass(10)
obj2 = MyClass(20)
print(obj1.instance_attribute) # 输出: 10
print(obj2.instance_attribute) # 输出: 20
类的方法分为实例方法、类方法和静态方法。
实例方法是属于实例的,必须包含self参数,用于访问和操作实例属性
class MyClass:
def __init__(self, value):
self.instance_attribute = value
def instance_method(self):
return f"Instance attribute value is {self.instance_attribute}"
# 创建实例并调用实例方法
obj = MyClass(10)
print(obj.instance_method()) # 输出: Instance attribute value is 10
类方法使用@classmethod装饰器,并且第一个参数是cls,表示类本身。类方法可以访问类属性和其他类方法
class MyClass:
class_attribute = "I am a class attribute"
@classmethod
def class_method(cls):
return f"Class attribute value is {cls.class_attribute}"
# 调用类方法
print(MyClass.class_method())
# 输出: Class attribute value is I am a class attribute
静态方法使用@staticmethod装饰器,不需要self或cls参数。静态方法通常不依赖于类或实例的数据。
class MyClass:
@staticmethod
def static_method():
return "I am a static method"
# 调用静态方法
print(MyClass.static_method()) # 输出: I am a static method
347.前 K 个高频元素
题目
给你一个整数数组 nums
和一个整数 k
,请你返回其中出现频率前 k
高的元素。你可以按 任意顺序 返回答案。
尝试解答
没有思路
代码随想录的代码
import heapq
class Solution:
def topKFrequent(self, nums: List[int], k: int) -> List[int]:
#要统计元素出现频率
map_ = {} #nums[i]:对应出现的次数
for i in range(len(nums)):
map_[nums[i]] = map_.get(nums[i], 0) + 1
#对频率排序
#定义一个小顶堆,大小为k
pri_que = [] #小顶堆
#用固定大小为k的小顶堆,扫描所有频率的数值
for key, freq in map_.items():
heapq.heappush(pri_que, (freq, key))
if len(pri_que) > k: #如果堆的大小大于了K,则队列弹出,保证堆的大小一直为k
heapq.heappop(pri_que)
#找出前K个高频元素,因为小顶堆先弹出的是最小的,所以倒序来输出到数组
result = [0] * k
for i in range(k-1, -1, -1):
result[i] = heapq.heappop(pri_que)[1]
return result
思路
主要涉及到如下三块内容:
- 要统计元素出现频率
- 对频率排序
- 找出前K个高频元素
首先统计元素出现的频率,这一类的问题可以使用map来进行统计。
然后是对频率进行排序,这里可以使用一种容器适配器就是优先级队列。
优先级队列是一个披着队列外衣的堆,因为优先级队列对外接口只是从队头取元素,从队尾添加元素,再无其他取元素的方式,看起来就是一个队列。而且优先级队列内部元素是自动依照元素的权值排列。关键是如何有序排列的。缺省情况(默认状态)下priority_queue利用max-heap(大顶堆)完成对元素的排序,这个大顶堆是以vector为表现形式的complete binary tree(完全二叉树)。
堆是一棵完全二叉树,树中每个结点的值都不小于(或不大于)其左右孩子的值。 如果父亲结点是大于等于左右孩子就是大顶堆,小于等于左右孩子就是小顶堆。大顶堆的堆头是最大元素,小顶堆的堆头是最小元素。底层实现都是一样的,从小到大排就是小顶堆,从大到小排就是大顶堆。
不使用快排的原因是,使用快排要将map转换为vector的结构,然后对整个数组进行排序, 而这种场景下,我们其实只需要维护k个有序的序列就可以了,所以使用优先级队列是最优的。
但是是使用小顶堆还是大顶堆,直觉上看到题目要求K个高频元素,所以使用大顶堆。但是如果定义了一个大小为k的大顶堆,在每次移动更新大顶堆的时候,每次弹出都把最大的元素弹出去了,那么无法保留下来前K个高频元素,而且使用大顶堆就要把所有元素都进行排序。
所以要使用小顶堆,因为要统计最大前k个元素,只有小顶堆每次将最小的元素弹出,最后小顶堆里积累的才是前k个最大元素。
寻找前k个最大元素流程如图所示:(图中的频率只有三个,所以正好构成一个大小为3的小顶堆,如果频率更多一些,则用这个小顶堆进行扫描)
统计元素出现的概率
map_={}
for i in range(len(nums)):
map_[nums[i]]=map_.get(nuts[I],0)+1
heapq模块是python中使用列表进行堆操作
heapq.heapify(x)是将list x转换为堆
heapq.heappush(heap,item),将item的值加入heap中
heapq.heappop(heap)弹出并返回heap的最小的元素,如果堆为空,抛出IndexError
heapq.heappushpop(heap,item)将item放入堆中,然后弹出并返回heap的最小元素
heapq.heapreplace(heap,item)弹出并返回heap中最小的一项,同时推入新的item
构建小顶堆:
pri_que=[]
for key, freq in map_.items():
heapq.heappush(pri_que,(freq,key))
if len(pri_que)>k:
heapq.heappop(pri_que)
找出前K个高频元素,因为小顶堆先弹出最小的,所以倒叙来输出数组。
result=[0]*k
for i in range(k-1,-1,-1):
result[i]=heapq.pop(pri_que)[1]
return result