240 搜索二维矩阵Ⅱ
题目描述
编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性:
每行的元素从左到右升序排列。
每列的元素从上到下升序排列。
暴力法
class Solution(object):
def searchMatrix(self, matrix, target):
"""
:type matrix: List[List[int]]
:type target: int
:rtype: bool
"""
m =len(matrix)
n = len(matrix[0])
if target<matrix[0][0]:
return False
elif target>matrix[m-1][n-1]:
return False
else:
for i in range(m):
for j in range(n):
if target == matrix[i][j]:
return True
return False
二叉搜索树性质法
- 从最右上角开始搜索,如果target=当前值,return True;target<当前值,就向左走;否则向下走。
- 这样在搜索的过程中,如果我们没有找到 }target,那么我们要么将 y 减少 1,要么将 x 增加 1。由于(x,y) 的初始值分别为(0,n−1),因此 y 最多能被减少 nn次,x 最多能被增加 mm次,总搜索次数为 m + n。在这之后,x和 y就会超出矩阵的边界。
- 时间O(m+n) 空间O(1)
class Solution(object):
def searchMatrix(self, matrix, target):
"""
:type matrix: List[List[int]]
:type target: int
:rtype: bool
"""
n =len(matrix)
m = len(matrix[0])
row = 0
col = m-1
while(row<n and col>=0):
if target == matrix[row][col]:
return True
elif target<matrix[row][col]:
col -= 1
else:
row += 1
return False
347 前k个高频元素
题目描述
给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。
暴力法
用字典记录数字出现次数,对字典的value进行排序得到list,再取list的前k个key值。
时间: 存入字典O(n)+字典排序O(nlogn)+获取前k个O(k) 总体:O(nlogn)
空间: O(n)
重点:
- 对字典排序:
- 按照value的值从大到小的顺序来排序,得到的是一个list。
- dic1 = sorted(dic.items(), key=lambda d:d[1], reverse = True)
- 按键(key)排序
- dict2 = sorted(dic.items(), key=lambda d:d[0])
- sort 与 sorted 区别:
- sort 是应用在 list 上的方法,sorted 可以对所有可迭代的对象进行排序操作。
- list 的 sort 方法返回的是对已经存在的列表进行操作,无返回值,而内建函数 sorted 方法返回的是一个新的 list,而不是在原来的基础上进行的操作。
class Solution(object):
def topKFrequent(self, nums, k):
"""
:type nums: List[int]
:type k: int
:rtype: List[int]
"""
dic = {}
res = []
for i in nums:
dic.setdefault(i,0)
dic[i] += 1
dic1 = sorted(dic.items(), key=lambda d:d[1], reverse = True)
for i in range(k):
res.append(dic1[i][0])
return res
手写小顶堆优先队列法
- 堆 处理海量数据的 topK,分位数 非常合适,优先队列 应用在元素优先级排序,比如本题的频率排序非常合适。与基于比较的排序算法 时间复杂度 O(nlogn)相比,使用 堆,优先队列 复杂度可以下降到 O(nlogk),在总体数据规模 n 较大,而维护规模 k 较小时,时间复杂度优化明显。
- 堆,优先队列 的本质其实就是个完全二叉树,有其下重要性质
ps: 堆 heap[0] 插入一个占位节点,此时堆顶为 index 为 1 的位置,可以更方便的运用位操作。[1,2,3] -> [0,1,2,3]
1.父节点 index 为 i
2.左子节点 index 为 i << 1
3.右子节点 index 为 i << 1 | 1
4.大顶堆中每个父节点大于子节点,小顶堆每个父节点小于子节点
5.优先队列以优先级为堆的排序依据
因为性质 1,2,3,堆可以用数组直接来表示,不需要通过链表建树。
- 堆,优先队列 有两个重要操作,时间复杂度均是 O(logk)O(logk)。以小顶锥为例:
- 上浮 sift up: 向堆尾新加入一个元素,堆规模 +1,依次向上与父节点比较,如小于父节点就交换。
- 下沉 sift down: 从堆顶取出一个元素(堆规模 -1,用于堆排序)或者更新堆中一个元素(本题),依次向下与子节点比较,如大于子节点就交换。
对于 topk 问题:最大堆求topk小,最小堆求 topk 大。
- topk小:构建一个 k 个数的最大堆,当读取的数小于根节点时,替换根节点,重新塑造最大堆
- topk大:构建一个 k 个数的最小堆,当读取的数大于根节点时,替换根节点,重新塑造最小堆
这一题的总体思路 总体时间复杂度 O(nlogk)
- 遍历统计元素出现频率 O(n)
- 前k个数构造 规模为 k+1 的最小堆 minheap, O(k)O(k), 注意 +1 是因为占位节点。
- 遍历规模k之外的数据,大于堆顶则入堆,下沉维护规模为k的最小堆 minheap. O(nlogk)O(nlogk)
- (如需按频率输出,对规模为k的堆进行排序)
参考:
作者:xxinjiee
链接:https://leetcode.cn/problems/top-k-frequent-elements/solution/python-dui-pai-xu-by-xxinjiee/
class Solution(object):
def topKFrequent(self, nums, k):
"""
:type nums: List[int]
:type k: int
:rtype: List[int]
"""
def sift_down(arr, root, k):
"""下沉log(k),如果新的根节点>子节点就一直下沉"""
val = arr[root] # 用类似插入排序的赋值交换
while root<<1 < k:
child = root << 1
# 选取左右孩子中小的与父节点交换
if child|1 < k and arr[child|1][1] < arr[child][1]:
child |= 1
# 如果子节点<新节点,交换,如果已经有序break
if arr[child][1] < val[1]:
arr[root] = arr[child]
root = child
else:
break
arr[root] = val
def sift_up(arr, child):
"""上浮log(k),如果新加入的节点<父节点就一直上浮"""
val = arr[child]
while child>>1 > 0 and val[1] < arr[child>>1][1]:
arr[child] = arr[child>>1]
child >>= 1
arr[child] = val
stat = collections.Counter(nums)
stat = list(stat.items())
heap = [(0,0)]
# 构建规模为k+1的堆,新元素加入堆尾,上浮
for i in range(k):
heap.append(stat[i])
sift_up(heap, len(heap)-1)
# 维护规模为k+1的堆,如果新元素大于堆顶,入堆,并下沉
for i in range(k, len(stat)):
if stat[i][1] > heap[1][1]:
heap[1] = stat[i]
sift_down(heap, 1, k+1)
return [item[0] for item in heap[1:]]
库函数小顶堆
import heapq
class Solution(object):
def topKFrequent(self, nums, k):
"""
:type nums: List[int]
:type k: int
:rtype: 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
374 猜数字大小
题目描述
二分法
不断缩小区间范围,初始化left,right为1,n。然后mid=(left+right)//2
# The guess API is already defined for you.
# @param num, your guess
# @return -1 if my number is lower, 1 if my number is higher, otherwise return 0
# def guess(num):
class Solution(object):
def guessNumber(self, n):
"""
:type n: int
:rtype: int
"""
left = 1
right = n
while 1:
mid = (left+right)//2
tmp = guess(mid)
if tmp == 0:
return mid
elif tmp<0:
right = mid-1
else:
left = mid+1