导语
leetcode刷题笔记记录,本篇博客记录字栈与队列部分的题目,主要题目包括:
- 239 滑动窗口最大值
- 347 前 K 个高频元素
Leetcode 239 滑动窗口最大值
题目描述
给你一个整数数组 nums,有一个大小为 k **的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。 返回 滑动窗口中的最大值 。
示例 1:
输入: nums = [1,3,-1,-3,5,3,6,7], k = 3
输出: [3,3,5,5,6,7]
解释:
滑动窗口的位置 最大值
--------------- -----
[1 3 -1] -3 5 3 6 7 3
1 [3 -1 -3] 5 3 6 7 3
1 3 [-1 -3 5] 3 6 7 5
1 3 -1 [-3 5 3] 6 7 5
1 3 -1 -3 [5 3 6] 7 6
1 3 -1 -3 5 [3 6 7] 7
示例 2:
输入: nums = [1], k = 1
输出: [1]
提示:
1 <= nums.length <= 10^5-10^4 <= nums[i] <= 10^41 <= k <= nums.length
解法
这是一道Hard级别的题目,如果使用暴力法(复杂度为)会超时。这里使用一个队列结构来解决这道问题。
假设存在这样一个队列,放进去窗口里的元素,然后随着窗口的移动,队列也一进一出,每次移动之后,队列告诉我们里面的最大值是什么。这样,每次窗口移动的时候,调用que.pop(滑动窗口中移除元素的数值),que.push(滑动窗口添加元素的数值),然后que.front()就返回我们要的最大值。
所以这里先实现这样一个队列,代码如下:
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):
que.push(nums[i])
result.append(que.front())
for i in range(k, len(nums)):
que.pop(nums[i-k])
que.push(nums[i])
result.append(que.front())
return result
Leetcode 347 前 K 个高频元素
题目描述
给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。
示例 1:
输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]
示例 2:
输入: nums = [1], k = 1
输出: [1]
提示:
1 <= nums.length <= 105k的取值范围是[1, 数组中不相同的元素的个数]- 题目数据保证答案唯一,换句话说,数组中前
k个高频元素的集合是唯一的
进阶: 你所设计算法的时间复杂度 必须 优于 O(n log n) ,其中 n **是数组大小。
解法
这个题的直观解法就是使用map来记录次数,然后快排,这样复杂度就是,代码如下:
class Solution:
def topKFrequent(self, nums: List[int], k: int) -> List[int]:
num2cnt = defaultdict(int)
for num in nums:
num2cnt[num] += 1
sorted_dict = sorted(num2cnt.items(), key= lambda item: item[1], reverse=True)
return list(dict(sorted_dict[:k]).keys())
需要注意的是,我们在对字典中值进行排序时,传入sorted函数的key是一个lambda函数,表示我们按照value值进行排序。
进阶要求希望我们使用由于的代码,这里使用小顶堆可以实现的代码,所谓小顶堆就是一个根节点值最小的二叉树,每次pop元素都是pop根节点。通过设置一个大小为K的小顶堆,遍历数组后,小顶堆就剩下了最大的Top-K个元素(因为最小的都被pop了),所以这时候取出这些元素即可(题目对顺序无要求,最后可以不排序)。
实现代码如下:
import heapq
class Solution:
def topKFrequent(self, nums: List[int], k: int) -> List[int]:
num2cnt = defaultdict(int)
for num in nums:
num2cnt[num] += 1
pri_que = [] # 小顶堆
for key, freq in num2cnt.items():
heapq.heappush(pri_que, (freq, key))
if len(pri_que) > k:
heapq.heappop(pri_que)
result = [0] * k
for i in range(k-1, -1, -1):
result[i] = heapq.heappop(pri_que)[1]
return result
易错点
Python中的lambda函数
在Python中,lambda函数是一种匿名函数,也称为"lambda表达式"。它是一种简洁的函数定义方式,可以用于快速定义简单的函数,通常用于一些简单的函数功能,而不适用于复杂的函数逻辑。
lambda函数的语法形式如下:
lambda arguments: expression
其中,arguments是lambda函数的参数列表,可以是0个或多个参数,但不能包含默认参数或可变参数(*args和**kwargs)。expression是一个单一的表达式,用于计算lambda函数的返回值。
lambda函数的特点:
- 匿名性:lambda函数没有函数名,仅存在于定义的上下文中,因此被称为匿名函数。
- 简洁:由于只能包含一个表达式,所以lambda函数通常比普通函数更简洁。
使用lambda函数的例子:
# 使用普通函数定义求和函数
def add(x, y):
return x + y
print(add(3, 5)) # 输出:8
# 使用lambda函数定义求和函数
add_lambda = lambda x, y: x + y
print(add_lambda(3, 5)) # 输出:8
在这个例子中,我们定义了一个普通函数add()和一个lambda函数add_lambda,它们的功能是一样的:对两个参数求和。注意,lambda函数没有函数名,直接通过赋值给变量来使用。
lambda函数通常在函数式编程中频繁使用,例如在map()、filter()、sorted()等函数的参数中,用于定义简单的功能。在复杂的函数逻辑和多行代码的情况下,建议使用普通的def语句来定义函数,以提高代码的可读性和可维护性。
sort与sorted函数
在Python中,sorted()和list.sort()是两个用于排序的函数,它们有一些区别:
-
返回值:
sorted(): 返回一个新的已排序的列表,原始列表不受影响。list.sort(): 在原始列表上进行排序,没有返回值。它直接修改原列表。
-
原地排序:
sorted(): 不会修改原始列表,而是返回一个新的排序后的列表。list.sort(): 在原列表上进行原地排序,不返回新的列表。
-
可用于不同类型的可迭代对象:
sorted(): 可以对任何可迭代对象(如列表、元组、集合等)进行排序,返回一个新的列表。list.sort(): 只能用于列表,并且直接在原列表上进行排序。
-
参数:
sorted(iterable, key=None, reverse=False): 可以指定key函数用于排序的依据,以及reverse参数用于控制升序或降序,默认为升序。list.sort(key=None, reverse=False): 与sorted()类似,可以指定key函数和reverse参数,但不作为参数传递,而是直接在方法内部使用。
下面是一个简单的示例:
# 使用sorted()函数对列表进行排序
my_list = [3, 1, 5, 2, 4]
sorted_list = sorted(my_list)
print("Sorted list:", sorted_list) # 输出:Sorted list: [1, 2, 3, 4, 5]
print("Original list:", my_list) # 输出:Original list: [3, 1, 5, 2, 4]
# 使用list.sort()方法对列表进行原地排序
my_list = [3, 1, 5, 2, 4]
my_list.sort()
print("Sorted list:", my_list) # 输出:Sorted list: [1, 2, 3, 4, 5]
print("Original list:", my_list) # 输出:Original list: [1, 2, 3, 4, 5]
总结:如果希望对一个可迭代对象进行排序,而且不希望修改原始对象,使用sorted()函数;如果想直接在原始列表上进行排序,可以使用list.sort()方法。
使用lambda作为sorted函数的key值
在Python中,sorted()函数的key参数用于指定一个函数,该函数将用于提取可迭代对象中的每个元素的排序依据。该函数接受一个参数并返回一个值,这个返回值将被用于排序。lambda函数正是一个可以很方便地创建匿名函数的方式,因此在sorted()函数中使用lambda函数非常常见。
lambda函数非常适合用于简单的功能,因为它们可以在一行内定义,并且不需要单独命名函数。由于key参数接受的是一个函数,而不是一个普通的值,所以使用lambda函数非常方便。它可以让我们快速定义简单的函数逻辑,而不必单独定义一个完整的函数。以下示例使用sorted()函数对一个包含字符串的列表按照字符串长度进行排序:
my_list = ['apple', 'orange', 'banana', 'kiwi', 'pear']
# 使用普通函数定义排序依据函数
def get_length(word):
return len(word)
sorted_list = sorted(my_list, key=get_length)
print(sorted_list) # 输出:['kiwi', 'pear', 'apple', 'orange', 'banana']
# 使用lambda函数进行同样的排序
sorted_list_lambda = sorted(my_list, key=lambda word: len(word))
print(sorted_list_lambda) # 输出:['kiwi', 'pear', 'apple', 'orange', 'banana']
上面的例子首先使用普通函数get_length()来定义排序依据函数,然后使用这个函数作为key参数传递给sorted()函数进行排序。然后,使用lambda函数来实现同样的功能,将lambda函数作为key参数传递给sorted()函数。可以看到,使用lambda函数使代码更为简洁和紧凑。