代码随想录算法训练营第十一天|150. 逆波兰表达式求值 、 239. 滑动窗口最大值 、347.前 K 个高频元素 |纯小白python

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

思路

主要涉及到如下三块内容:

  1. 要统计元素出现频率
  2. 对频率排序
  3. 找出前K个高频元素

首先统计元素出现的频率,这一类的问题可以使用map来进行统计。

然后是对频率进行排序,这里可以使用一种容器适配器就是优先级队列

优先级队列是一个披着队列外衣的堆,因为优先级队列对外接口只是从队头取元素,从队尾添加元素,再无其他取元素的方式,看起来就是一个队列。而且优先级队列内部元素是自动依照元素的权值排列。关键是如何有序排列的。缺省情况(默认状态)下priority_queue利用max-heap(大顶堆)完成对元素的排序,这个大顶堆是以vector为表现形式的complete binary tree(完全二叉树)。

堆是一棵完全二叉树,树中每个结点的值都不小于(或不大于)其左右孩子的值。 如果父亲结点是大于等于左右孩子就是大顶堆,小于等于左右孩子就是小顶堆。大顶堆的堆头是最大元素,小顶堆的堆头是最小元素。底层实现都是一样的,从小到大排就是小顶堆,从大到小排就是大顶堆。

不使用快排的原因是,使用快排要将map转换为vector的结构,然后对整个数组进行排序, 而这种场景下,我们其实只需要维护k个有序的序列就可以了,所以使用优先级队列是最优的。

但是是使用小顶堆还是大顶堆,直觉上看到题目要求K个高频元素,所以使用大顶堆。但是如果定义了一个大小为k的大顶堆,在每次移动更新大顶堆的时候,每次弹出都把最大的元素弹出去了,那么无法保留下来前K个高频元素,而且使用大顶堆就要把所有元素都进行排序。

所以要使用小顶堆,因为要统计最大前k个元素,只有小顶堆每次将最小的元素弹出,最后小顶堆里积累的才是前k个最大元素。

寻找前k个最大元素流程如图所示:(图中的频率只有三个,所以正好构成一个大小为3的小顶堆,如果频率更多一些,则用这个小顶堆进行扫描)

347.前K个高频元素

统计元素出现的概率

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

                        

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值