*内容来自leetcode
1.数组中的第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-1次,再获取得到的最大数就是第k个最大元素。
这种方法的问题在于时间复杂度较高,使用内置的max()和remove()函数需要多次遍历列表,无法满足本题要求。
class Solution:
def findKthLargest(self, nums: List[int], k: int) -> int:
for i in range(1,k):
nums.remove(max(nums))
return max(nums)
。。。让newbing对这段代码进行优化:这段代码可以通过使用heapq.nlargest()
函数来优化。heapq.nlargest()
函数可以返回列表中的前k个最大元素,而不会改变原始列表。
就是将列表当成堆来看待,能够顺利通过,时间复杂度为O(nlogk)。
#import heapq
class Solution:
def findKthLargest(self, nums: List[int], k: int) -> int:
return heapq.nlargest(k, nums)[-1]
可以考虑用上一篇中提到的堆来解决,遍历数组建立大根堆,然后再在堆中进行查找。相较于上面的直接用nlargest()函数,时间复杂度和空间复杂度表现有所提升。
#python中heapq默认为小根堆,所以需要做出相应变化
class Solution:
def findKthLargest(self, nums: List[int], k: int) -> int:
heap = []
for i in range(len(nums)):
heappush(heap,nums[i])
for i in range(1,len(nums)-k+1):
heappop(heap)
return heappop(heap)
考虑到在python 中堆有现成的容器可以直接用,但是堆的实现方式也很重要。在这里用python对官解中自建堆的解法进行复现
class Solution:
def findKthLargest(self, nums: List[int], k: int) -> int:
#自建堆来完成
def maxHeapify(arr,i,heapSize):
left, right = i*2 + 1, i*2 + 2
largest = i
if left < heapSize and arr[left] > arr[largest]:
largest = left
if right < heapSize and arr[right] > arr[largest]:
largest = right
if largest != i:
arr[i], arr[largest] = arr[largest], arr[i]
maxHeapify(arr, largest,heapSize)
def buildMaxHeap(arr, heapSize):
build = heapSize // 2
while build >= 0 :
maxHeapify(arr,build,heapSize)
build -=1
heapSize = len(nums)
buildMaxHeap(nums, heapSize)
for i in range(len(nums)-1,len(nums)-k,-1):
nums[0], nums[i] = nums[i], nums[0]
heapSize -=1
maxHeapify(nums,0,heapSize)
return nums[0]
然后第二种方法是基于快速排序实现,是基于官方给的C++版本修改得到的。
import random
class Solution:
def quickSelect(self,arr,left,right,index):
q = self.randomPartition(arr,left,right)
if q == index:
return arr[q]
else:
if q < index:
return self.quickSelect(arr,q+1,right,index)
else:
return self.quickSelect(arr,left,q-1,index)
def randomPartition(self,arr,left,right):
i = random.randint(left,right)
arr[i], arr[right] = arr[right], arr[i]
return self.partition(arr, left, right)
def partition(self,arr,left,right):
x = arr[right]
i = left - 1
for j in range(left,right):
if arr[j] < x:
i +=1
arr[i], arr[j] = arr[j], arr[i]
i +=1
arr[i], arr[right] = arr[right], arr[i]
return i
def findKthLargest(self, nums: List[int], k: int) -> int:
return self.quickSelect(nums,0,len(nums)-1,len(nums)-k)
2.寻找峰值
题目描述
峰值元素是指其值严格大于左右相邻值的元素。
给你一个整数数组 nums,找到峰值元素并返回其索引。数组可能包含多个峰值,在这种情况下,返回 任何一个峰值 所在位置即可。
你可以假设 nums[-1] = nums[n] = -∞ 。
你必须实现时间复杂度为 O(log n) 的算法来解决此问题。
示例 1:
输入:nums = [1,2,3,1]
输出:2
解释:3 是峰值元素,你的函数应该返回其索引 2。
示例 2:
输入:nums = [1,2,1,3,5,6,4]
输出:1 或 5
解释:你的函数可以返回索引 1,其峰值元素为 2;
或者返回索引 5, 其峰值元素为 6。
提示:
1 <= nums.length <= 1000
-231 <= nums[i] <= 231 - 1
对于所有有效的 i 都有 nums[i] != nums[i + 1]
思路
简单来说,一个数比左右的数都大,那就是一个峰值。最为简单的办法就是用遍历数组来完成。性能也还可以。
class Solution:
def findPeakElement(self, nums: List[int]) -> int:
n = len(nums)
if n <= 1 or nums[0] > nums[1]:
return 0
for i in range(1,n-1):
if nums[i] > nums[i+1] and nums[i] > nums[i-1]:
return i
if nums[n-1] > nums[n-2]:
return n-1
对于所有有效的 i 都有 nums[i] != nums[i + 1],由于有这个条件存在,所以可以直接找到数组中的最大值即可。就不进行具体实现了。
直接来到迭代爬坡的算法,思路就是不断的往高处走,总能走到最高峰。基于此思路用二分查找进行优化,即可达到题目要求的O(logn)时间复杂度。具体为
对于当前可行的下标范围 [l,r],我们随机一个下标 i;
如果下标 i 是峰值,我们返回 i 作为答案;
如果 nums[i]<nums[i+1],那么我们抛弃 [l,i]的范围,在剩余 [i+1,r]的范围内继续随机选取下标;
如果 nums[i]>nums[i+1],那么我们抛弃 [i,r]的范围,在剩余 [l,i−1]的范围内继续随机选取下标。
在上述算法中,如果我们固定选取 i 为 [l,r]的中点,那么每次可行的下标范围会减少一半,成为一个类似二分查找的方法,时间复杂度为 O(logn)。
写了一个二分查找的函数来递归完成,在返回坐标时其实有些麻烦。因为每次递归的是一个子数组,得到的坐标也是相对于子数组的坐标。可以传递左右边界来替代传递子数组来让代码更为直观。
class Solution:
def erfenSearch(self,nums):
n = len(nums)
ans = 0
def get(index):
if index == -1 or index == n:
return float('-inf')
return nums[index]
i = (0 + n)//2
if get(i) > get(i-1) and get(i) > get(i+1):
return i
if get(i) < get(i+1):
return i + self.erfenSearch(nums[i+1:]) + 1
if get(i) > get(i+1):
return self.erfenSearch(nums[:i])
def findPeakElement(self, nums: List[int]) -> int:
if len(nums) == 1:
return 0
return self.erfenSearch(nums)
以下为官方用循环完成的解法
class Solution:
def findPeakElement(self, nums: List[int]) -> int:
n = len(nums)
# 辅助函数,输入下标 i,返回 nums[i] 的值
# 方便处理 nums[-1] 以及 nums[n] 的边界情况
def get(i: int) -> int:
if i == -1 or i == n:
return float('-inf')
return nums[i]
left, right, ans = 0, n - 1, -1
while left <= right:
mid = (left + right) // 2
if get(mid - 1) < get(mid) > get(mid + 1):
ans = mid
break
if get(mid) < get(mid + 1):
left = mid + 1
else:
right = mid - 1
return ans