leetcode每周3道(六)二叉树

240 搜索二维矩阵Ⅱ

题目描述

编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性:
每行的元素从左到右升序排列。
每列的元素从上到下升序排列。
240描述

暴力法

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 高的元素。你可以按 任意顺序 返回答案。
题目2

暴力法

用字典记录数字出现次数,对字典的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

结果2

手写小顶堆优先队列法

  • 处理海量数据的 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:]]       

结果2-2

库函数小顶堆

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        

结果2-3

374 猜数字大小

题目描述

题目3
示例3

二分法

不断缩小区间范围,初始化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

结果3

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值