74、数组中的第K个最大元素
给定整数数组 nums
和整数 k
,请返回数组中第 **k**
个最大的元素。
请注意,你需要找的是数组排序后的第 k
个最大的元素,而不是第 k
个不同的元素。
你必须设计并实现时间复杂度为 O(n)
的算法解决此问题。
示例 1:
输入: [3,2,1,5,6,4], k = 2
输出: 5
示例 2:
输入: [3,2,3,1,2,4,5,5,6], k = 4
输出: 4
提示:
1 <= k <= nums.length <= 105
-104 <= nums[i] <= 104
思路解答:
方法一:堆排序 构建最小堆 遍历数组每个元素 如果堆大小超过K就弹出堆顶元素 这样堆顶一定为第K大元素
def findKthLargest(nums: list[int], k: int) -> int:
heap = []
for num in nums:
heapq.heappush(heap, num)
if len(heap) > k:
heapq.heappop(heap)
return heap[0]
方法二:排序 切片即可
def findKthLargest(nums: list[int], k: int) -> int:
return sorted(nums)[len(nums) - k]
75、前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 <= 105
k
的取值范围是[1, 数组中不相同的元素的个数]
- 题目数据保证答案唯一,换句话说,数组中前
k
个高频元素的集合是唯一的
思路解答:
采用哈希表+排序的方式,哈希表key,value统计数字和频率,然后按哈希表的value值排序,最后返回前K个元素
def topKFrequent(nums: list[int], k: int) -> list[int]:
# 使用 Counter 统计每个元素的频率
counter = collections.Counter(nums)
# 对字典按值进行排序,获取前 k 高频率的元素
sorted_counter = sorted(counter.items(), key=lambda x: x[1], reverse=True)
# 获取前 k 高频率的元素
result = [x[0] for x in sorted_counter[:k]]
return result
76、数据流的中位数
中位数是有序整数列表中的中间值。如果列表的大小是偶数,则没有中间值,中位数是两个中间值的平均值。
- 例如
arr = [2,3,4]
的中位数是3
。 - 例如
arr = [2,3]
的中位数是(2 + 3) / 2 = 2.5
。
实现 MedianFinder 类:
MedianFinder()
初始化MedianFinder
对象。void addNum(int num)
将数据流中的整数num
添加到数据结构中。double findMedian()
返回到目前为止所有元素的中位数。与实际答案相差10-5
以内的答案将被接受。
示例 1:
输入
["MedianFinder", "addNum", "addNum", "findMedian", "addNum", "findMedian"]
[[], [1], [2], [], [3], []]
输出
[null, null, null, 1.5, null, 2.0]
解释
MedianFinder medianFinder = new MedianFinder();
medianFinder.addNum(1); // arr = [1]
medianFinder.addNum(2); // arr = [1, 2]
medianFinder.findMedian(); // 返回 1.5 ((1 + 2) / 2)
medianFinder.addNum(3); // arr[1, 2, 3]
medianFinder.findMedian(); // return 2.0
提示:
-105 <= num <= 105
- 在调用
findMedian
之前,数据结构中至少有一个元素 - 最多
5 * 104
次调用addNum
和findMedian
思路解答:
1、addNum
方法首先将元素插入最大堆,然后将最大堆的最大值弹出并插入最小堆。最后,如果最小堆的大小比最大堆大,将最小堆的最小值弹出并插入最大堆,以保持两个堆的平衡。
2、findMedian
方法根据两个堆的大小来返回中位数。如果两个堆的大小相等,取两个堆顶部元素的平均值作为中位数;如果最大堆的大小大于最小堆,直接返回最大堆的顶部元素作为中位数。
注:python中heap模块默认是小根堆
class MedianFinder:
def __init__(self):
# 最大堆,存储较小一半的元素
self.max_heap = []
# 最小堆,存储较大一半的元素
self.min_heap = []
def addNum(self, num: int) -> None:
# 先将元素插入最大堆
heapq.heappush(self.max_heap, -num)
# 将最大堆的最大值弹出并插入最小堆
heapq.heappush(self.min_heap, -heapq.heappop(self.max_heap))
# 如果最小堆的大小比最大堆大,将最小堆的最小值弹出并插入最大堆
if len(self.min_heap) > len(self.max_heap):
heapq.heappush(self.max_heap, -heapq.heappop(self.min_heap))
def findMedian(self) -> float:
if len(self.max_heap) == len(self.min_heap):
return (self.min_heap[0] - self.max_heap[0]) / 2
else:
return -self.max_heap[0]
77、买卖股票的最佳时机
给定一个数组 prices
,它的第 i
个元素 prices[i]
表示一支给定股票第 i
天的价格。
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0
。
示例 1:
输入:[7,1,5,3,6,4]
输出:5
解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。
示例 2:
输入:prices = [7,6,4,3,1]
输出:0
解释:在这种情况下, 没有交易完成, 所以最大利润为 0。
提示:
1 <= prices.length <= 105
0 <= prices[i] <= 104
思路解答:
min_price
表示到目前为止的最低价格,max_profit
表示当前的最大利润。在遍历过程中,不断更新这两个变量即可。
def maxProfit(prices: list[int]) -> int:
min_price = prices[0]
max_profit = 0
for price in prices:
if price < min_price:
min_price = price
else:
max_profit = max(max_profit,price - min_price)
return max_profit
78、跳跃游戏
给你一个非负整数数组 nums
,你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。
判断你是否能够到达最后一个下标,如果可以,返回 true
;否则,返回 false
。
示例 1:
输入:nums = [2,3,1,1,4]
输出:true
解释:可以先跳 1 步,从下标 0 到达下标 1, 然后再从下标 1 跳 3 步到达最后一个下标。
示例 2:
输入:nums = [3,2,1,0,4]
输出:false
解释:无论怎样,总会到达下标为 3 的位置。但该下标的最大跳跃长度是 0 , 所以永远不可能到达最后一个下标。
提示:
1 <= nums.length <= 104
0 <= nums[i] <= 105
思路解答:
从后向前遍历数组,不断更新能够到达最后一个位置的最左边的位置。如果最终这个位置是起始位置(第一个下标),则可以到达最后一个下标。
def canJump(nums: list[int]) -> bool:
last_position = len(nums) - 1
for i in range(len(nums) - 1, -1, -1):
#计算当前位置能够跳跃的最远距离
if i + nums[i] >= last_position:
last_position = i
return last_position == 0
79、跳跃游戏II
给定一个长度为 n
的 0 索引整数数组 nums
。初始位置为 nums[0]
。
每个元素 nums[i]
表示从索引 i
向前跳转的最大长度。换句话说,如果你在 nums[i]
处,你可以跳转到任意 nums[i + j]
处:
0 <= j <= nums[i]
i + j < n
返回到达 nums[n - 1]
的最小跳跃次数。生成的测试用例可以到达 nums[n - 1]
。
示例 1:
输入: nums = [2,3,1,1,4]
输出: 2
解释: 跳到最后一个位置的最小跳跃数是 2。
从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。
示例 2:
输入: nums = [2,3,0,1,4]
输出: 2
提示:
1 <= nums.length <= 104
0 <= nums[i] <= 1000
- 题目保证可以到达
nums[n-1]
思路解答:
- 初始化变量
curr_end
表示当前能够到达的最远位置,curr_farthest
表示在当前能到达的范围内,能够到达的最远位置,jumps
表示跳跃次数,初始值为 0。 - 遍历数组,对于当前位置
i
,更新curr_farthest
为max(curr_farthest, i + nums[i])
。 - 如果当前位置
i
等于curr_end
,表示已经到达当前能够到达的最远位置,需要进行一次跳跃,更新curr_end
为curr_farthest
,同时增加跳跃次数jumps
。 - 当
curr_end
大于等于n - 1
,即已经能够到达终点,返回跳跃次数jumps
。
def jump(nums: list[int]) -> int:
n = len(nums)
jumps = 0
curr_end = 0
curr_farthest = 0
for i in range(n - 1):
curr_farthest = max(curr_farthest, i + nums[i])
if i == curr_end:
jumps += 1
curr_end = curr_farthest
if curr_end >= n - 1:
return jumps
return jumps
80、划分字母区间
给你一个字符串 s
。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。
注意,划分结果需要满足:将所有划分结果按顺序连接,得到的字符串仍然是 s
。
返回一个表示每个字符串片段的长度的列表。
示例 1:
输入:s = "ababcbacadefegdehijhklij"
输出:[9,7,8]
解释:
划分结果为 "ababcbaca"、"defegde"、"hijhklij" 。
每个字母最多出现在一个片段中。
像 "ababcbacadefegde", "hijhklij" 这样的划分是错误的,因为划分的片段数较少。
示例 2:
输入:s = "eccbbbbdec"
输出:[10]
提示:
1 <= s.length <= 500
s
仅由小写英文字母组成
思路解答:
- 遍历字符串,记录每个字符最后出现的位置。
- 使用两个指针
start
和end
来表示当前片段的起始位置和结束位置。 - 遍历字符串,更新当前片段的结束位置
end
为当前字符的最后出现位置的最大值。 - 如果当前位置等于
end
,表示当前片段已经结束,记录当前片段的长度,并更新start
为end + 1
。 - 重复步骤 3 和 4,直到遍历完整个字符串。
def partitionLabels(s: str) -> list[int]:
last_occurrence = {char: i for i, char in enumerate(s)}
result = []
start = 0
end = 0
for i, char in enumerate(s):
end = max(end, last_occurrence[char])
if i == end:
result.append(end - start + 1)
start = end + 1
return result