leetcode分类刷题:二分查找(Binary Search)(四、基于值域的数组/矩阵类型)

2023-11-14

基于值域的二分法与基于定义域的题型不同,它的目标是从一“特殊排序序列”中确定“第k个元素值”,而不像基于定义域的题型是从排序序列中找小于等于特定target值的第一个索引;同时,针对“特殊排序序列”,往往需要嵌套使用双指针法进行操作,进一步增加了对应题型的难度。

378. 有序矩阵中第 K 小的元素

from typing import List
'''
378. 有序矩阵中第 K 小的元素
题目描述:给你一个n x n矩阵matrix ,其中每行和每列元素均按升序排序,找到矩阵中第 k 小的元素。
请注意,它是 排序后 的第 k 小元素,而不是第 k 个 不同 的元素。
你必须找到一个内存复杂度优于O(n2) 的解决方案。
示例 1:
    输入:matrix = [[1,5,9],[10,11,13],[12,13,15]], k = 8
    输出:13
    解释:矩阵中的元素为 [1,5,9,10,11,12,13,13,15],第 8 小元素是 13
题眼:每行和每列元素均按升序排序 有序矩阵
思路:(有点难度,看了官方解答才明白)值域的二分法:确定左右两侧边界为matrix[0][0]=leftVal和matrix[n-1][n-1]=rightVal,二分后统计小于等于midVal的数量,
      讨论与k的大小关系,保证第k小的数始终位于leftVal~rightVal之间(即保证在左闭右闭区间内[leftVal,rightVal]),当leftVal==rightVal时,第k小的数即被找出
      这道题就是在“二分式”缩小目标值的值域区间范围!也可以看到,这个题的target值不像之前一样是给定然后确定存在位置了,是要确定其值。
'''


class Solution:
    def kthSmallest(self, matrix: List[List[int]], k: int) -> int:
        n = len(matrix)
        leftVal, rightVal = matrix[0][0], matrix[n-1][n-1]
        # 确定target所在的区间[leftVal, rightVal],对矩阵中小于等于中间值midVal的元素数目进行统计
        # 关键思路:如果数目小于k,说明target>midVal;即左边界leftVal = midVal + 1
        # 如果数目等于k,说明target<=midVal;即右边界rightVal = midVal
        # 如果数目大于k,说明target<=midVal;即右边界rightVal = midVal
        while leftVal < rightVal:
            midVal = leftVal + (rightVal-leftVal) // 2
            count = self.checkSmallEqualMid(matrix, midVal)  # 表示小于等于mid的数量
            if count < k:  # 第k小的数在midVal的右侧,且不包含midVal
                leftVal = midVal + 1
            elif count >= k:  # 第k小的数在midVal的左侧,可能包含midVal
                rightVal = midVal
        return leftVal

    def checkSmallEqualMid(self, matrix: List[List[int]], midVal: int) -> int:
        n = len(matrix)
        # 双指针:根据矩阵的特殊性质,从左下角开始统计
        i, j = n - 1, 0
        count = 0
        while i >= 0 and j <= n - 1:  # 索引必须在边界之内
            if matrix[i][j] <= midVal:
                count += i + 1  # 对应行及行以上全部统计
                j += 1  # 并更新列数
            else:
                i -= 1  # 更新行数
        return count


if __name__ == '__main__':
    obj = Solution()
    while True:
        try:
            in_line = input().strip().split('=')
            matrix = []
            for row in in_line[1].strip()[1: -4].split(']')[:-1]:
                matrix.append([int(n) for n in row.split('[')[1].split(',')])
            k = int(in_line[2])
            # print(matrix, k)
            print(obj.kthSmallest(matrix, k))
        except EOFError:
            break

373. 查找和最小的 K 对数字

from typing import List
'''
373. 查找和最小的 K 对数字
题目描述:给定两个以 升序排列 的整数数组 nums1 和 nums2,以及一个整数 k。
定义一对值(u,v),其中第一个元素来自nums1,第二个元素来自 nums2。
请找到和最小的 k个数对(u1,v1), (u2,v2) ... (uk,vk)。
示例 1:
    输入: nums1 = [1,7,11], nums2 = [2,4,6], k = 3
    输出: [1,2],[1,4],[1,6]
    解释: 返回序列中的前 3 对数:
        [1,2],[1,4],[1,6],[7,2],[7,4],[11,2],[7,6],[11,4],[11,6]
题眼:组合num1为行索引,num2为列索引,可实现 每行和每列元素均按升序排序 有序矩阵,类似于”378. 有序矩阵中第 K 小的元素“
思路:值域二分法,注意返回的不是第k小,而是前k小(看了官网解析也觉得很难,这个题应该更适合其它算法来解)
'''


class Solution:
    def kSmallestPairs(self, nums1: List[int], nums2: List[int], k: int) -> List[List[int]]:
        result = []
        m, n = len(nums1), len(nums2)
        # 第一步,值域二分法找到第k小的数,确定pairSum所在的区间[leftVal, rightVal],对矩阵中小于等于中间值midVal的元素数目进行统计
        leftVal, rightVal = nums1[0]+nums2[0], nums1[m-1]+nums2[n-1]
        while leftVal < rightVal:
            midVal = (leftVal + rightVal) // 2
            count = self.checkSmallEqualMid(nums1, nums2, midVal)  # 表示小于等于中间值midVal的数量
            if count < k:  # 第k小的数在midVal的右侧,且不包含midVal
                leftVal = midVal + 1
            elif count >= k:  # 第k小的数在midVal的左侧,可能包含midVal
                rightVal = midVal
        pairSum = leftVal

        # 第二步,将小于pairSum的数对升序添加到result中
        # 分为两步添加是为了避免等于pairSum的数对和太多,导致所有小于的没有被添加到result中
        # 双指针:根据两个数组的特殊性质,i取nums1的最小值位置,j取nums2的最大值位置(以nums1为基准进行)
        i, j = 0, len(nums2) - 1
        while i < len(nums1) and j >= 0:  # 索引必须在边界之内
            if nums1[i] + nums2[j] < pairSum:
                for t in range(j + 1):
                    result.append([nums1[i], nums2[t]])
                    # if len(result) == k:  # 这里可以不用判断,小于pairSum的数对一定小于k个
                    #     return result
                i += 1
            elif nums1[i] + nums2[j] >= pairSum:
                j -= 1
        # 以下过程是上述双指针法 的等价形式
        # i2 = n - 1
        # for i1 in range(m):
        #     while i2 >= 0 and nums1[i1] + nums2[i2] >= pairSum:  # 用while直到小于pairSum的数对出现
        #         i2 -= 1
        #     for j in range(i2 + 1):  # 将该索引之前的nums2构成的数对,包括本身,全部添加到result
        #         result.append([nums1[i1], nums2[j]])
        #         if len(result) == k:
        #             return result

        # 第三步,将等于pairSum的数对升序添加到result中
        # 双指针:根据两个数组的特殊性质,i取nums1的最小值位置,j取nums2的最大值位置(以nums1为基准进行)
        i, j = 0, len(nums2) - 1
        while i < len(nums1) and j >= 0:  # 索引必须在边界之内
            if nums1[i] + nums2[j] < pairSum:
                i += 1
            elif nums1[i] + nums2[j] > pairSum:
                j -= 1
            else:
                t = j  # 添加nums2元素时考虑重复情况,同时j的位置要被记录而不能被更新
                while t >= 0 and nums1[i] + nums2[t] == pairSum:
                    result.append([nums1[i], nums2[t]])
                    if len(result) == k:
                        return result
                    t -= 1
                i += 1
        # 以下过程是上述双指针法 的等价形式
        # i2 = n - 1
        # for i1 in range(m):
        #     while i2 >= 0 and nums1[i1] + nums2[i2] > pairSum:  # 用while过滤掉大于pairSum的数对
        #         i2 -= 1
        #     j = i2
        #     while j >= 0 and nums1[j] + nums2[i2] == pairSum:  # 用while是为了将nums2中的全部重复元素添加上
        #         result.append([nums1[i1], nums2[j]])
        #         if len(result) == k:
        #             return result
        #         j -= 1
        return result

    def checkSmallEqualMid(self, nums1: List[int], nums2: List[int], midVal: int) -> int:
        # 双指针:根据两个数组的特殊性质,i取nums1的最小值位置,j取nums2的最大值位置
        i, j = 0, len(nums2) - 1
        count = 0
        while i < len(nums1) and j >= 0:  # 索引必须在边界之内
            if nums1[i] + nums2[j] <= midVal:
                count += j + 1  # 对应nums2位置元素及之前的全部统计
                i += 1
            else:
                j -= 1
        return count


if __name__ == '__main__':
    obj = Solution()
    while True:
        try:
            in_line = input().strip().split('=')
            nums1 = [int(n) for n in in_line[1].strip().split('[')[1].split(']')[0].split(',')]
            nums2 = [int(n) for n in in_line[2].strip().split('[')[1].split(']')[0].split(',')]
            k = int(in_line[3])
            # print(nums1, nums2, k)
            print(obj.kSmallestPairs(nums1, nums2, k))
        except EOFError:
            break

719. 找出第 K 小的数对距离

from typing import List
'''
719. 找出第 K 小的数对距离
题目描述:数对 (a,b) 由整数 a 和 b 组成,其数对距离定义为 a 和 b 的绝对差值。
给你一个整数数组 nums 和一个整数 k ,数对由 nums[i] 和 nums[j] 组成且满足 0 <= i < j < nums.length 。
返回 所有数对距离中 第 k 小的数对距离。
示例 1:
    输入:nums = [1,3,1], k = 1
    输出:0
    解释:数对和对应的距离如下:
    (1,3) -> 2
    (1,1) -> 0
    (3,1) -> 2
    距离第 1 小的数对是 (1,1) ,距离为 0 。
题眼:第k小,必然是要经过排序的
思路:排序+值域二分法(完全想不到和“378. 有序矩阵中第 K 小的元素”、“373. 查找和最小的 K 对数字”是类似题型:
注意最小取值为0,最大为末端减去首端)+双指针
'''


class Solution:
    def smallestDistancePair(self, nums: List[int], k: int) -> int:
        # 第一步,将数组升序排列
        nums.sort()
        n = len(nums) - 1
        # 第二步,值域二分法:确保第k小的 数对距离,确定所在的区间[leftVal, rightVal]
        leftVal, rightVal = 0, nums[n] - nums[0]  # 注意最小取值为0,最大为末端减去首端
        while leftVal < rightVal:
            midVal = (leftVal + rightVal) // 2
            count = self.countSmallEqual(nums, midVal)  # 表示小于等于中间值midVal的数量
            if count < k:  # 第k个数对距离 一定大于midVal
                leftVal = midVal + 1
            elif count >= k:  # 第k个数对距离 可能小于或等于midVal
                rightVal = midVal
        return leftVal

    def countSmallEqual(self, nums: List[int], midVal: int) -> int:
        # 双指针统计小于等于midVal的数对距离个数
        count = 0
        i, j = 0, 1  # i,j指向数组第一、第二位置
        while i < len(nums) - 1:  # i<j所以i不能取到最后一个索引
            if j < len(nums) and nums[j] - nums[i] <= midVal:
                j += 1
            elif j < len(nums) and nums[j] - nums[i] > midVal:  # 满足统计条件:索引全部有效时,第一次出现超过midVal的距离时,
                # 把前面满足小于等于midVal的距离对全部添加上
                count += (j - i - 1)
                i += 1
            elif j == len(nums):  # 当j已经遍历到头时,与上面满足统计条件的操作一致,可以合并
                count += (j - i - 1)
                i += 1
        return count


if __name__ == "__main__":
    obj = Solution()
    while True:
        try:
            in_line = input().strip().split('=')
            nums = [int(n) for n in in_line[1].split('[')[1].split(']')[0].split(',')]
            k = int(in_line[2])
            print(obj.smallestDistancePair(nums, k))
        except EOFError:
            break

个人总结体会

通过刷上述几道题目,除了掌握了基于值域的二分法步骤和模板,也同时掌握了对于行列递增的矩阵、两个递增的数组求和、单个递增的数组求元素对距离 这种“特殊序列”里用双指针法确定小于等于某一数值的元素或组合个数。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

leetcode分类刷题:二分查找(Binary Search)(四、基于值域的数组/矩阵类型) 的相关文章

  • iOS 关于NSNotificationCenter

    通常我们在 iOS 中发生什么事件时该做什么是由 Delegate 实现的 Apple 还为我们提供了另一种通知响应方式 那就是 NSNotification NSNotificationCenter 较之于 Delegate 可以实现更大

随机推荐

  • 计算机视觉OpenCV-图像直方图

    欢迎来到本博客 作者简介 目前计算机研究生在读 主要研究方向是人工智能和群智能算法方向 目前熟悉python网页爬虫 机器学习 计算机视觉 OpenCV 群智能算法 然后正在学习深度学习的相关内容 以后可能会涉及到网络安全相关领域 毕竟这是
  • 100家企业近年面试题整理

    打造最受企业欢迎的iOS开发者 一直都存在的问题 什么样的员工最受企业欢迎 一直也有人在努力提升自己 成为受企业欢迎的员工 然而 我们应该往方向去提升自己呢 100家知名企业今年来iOS面试题合集 你要的这里都有 企业要的这里也有 从基础开
  • Unity Resources资源管理的优点和痛点

    1 1 Resources详解 我觉得 Resources之所以能被广泛的使用 是因为它的使用非常简单 并且是同步加载 一般来说 正式的商业项目 对外发布资源的时候都是使用AssetBundles的方式进行 AssetBundles的方式有
  • re.compile(pattern,flags=0)中flags的用法

    re正则表达式模块还包括一些有用的操作正则表达式的函数 下面主要介绍compile函数 定义 compile pattern flags 根据包含正则表达式的字符串创建模式对象 通过python的help函数查看compile含义 1 he
  • 【数电】如何使用74LS112(或74LS74)构成一个十四分频器(模七计数器)

    IT精英们 大家都学过数字电子技术吧 尽管这东西没用 不过这些基础课程对思维的培养还是很有好处的 我不爱上课 但不代表我不喜欢数电 我们实验课老师为了加强实验难度 把实验题改掉了 用74LS112 或者74LS74 设计一个十四分频器 原来
  • PHP文件包含

    本地文件包含 打开PHPstudy 打开网站根目录 创建文件 文件内容为 在浏览器上查看所包含文件 远程文件包含 文件include php文件内容 print txt文件内容 远程查看print txt 远程包含shell shell t
  • zookeeper入门到精通03——zookeeper集群搭建

    zookeeper集群搭建 3 1 多虚拟机环境搭建 3 2 zookeeper集群搭建 3 1 多虚拟机环境搭建 我们需要搭建zookeeper集群 而由于zookeeper的的服务器数量需要设置为单数 前文介绍了原因 一个zookeep
  • 2023年第47届(第二届)浙江技能大赛网络安全项目 (世赛省选拔赛)A模块解析

    2023年第47届 第二届 浙江技能大赛网络安全项目 世赛省选拔赛 A模块解析 模块A 企业基础设施安全 1 竞项赛目简介 1 1 介绍 1 2 任务描述 1 3 竞赛说明 2 竞赛项目工作任务 2 2 操作系统安全加固 2 2 1 Win
  • OpenCV3.4.13+OpenCV_contrib 双摄像头实时拼接 环境配置

    如题 基于OpenCV3 4 13 VS2015做了个双摄像头实时拼接的代码 是一个大项目的一个baseline的一部分 下面先说配环境再给代码 环境配置 关于OpenCV VS的环境配置网上已经有很多了 因为这份代码用到了OpenCV C
  • 【微信小程序】实现根据某一属性值分类渲染数组内容

    需求与效果 实现根据某一属性值分类渲染数组 需求是 数组如下 渲染在页面上时 根据p num值进行分组渲染 p num相同的放在同一容器里 容器外包裹边框 array content 内容1 id 1 p num 1 content 内容2
  • RabbitMQ系列(十一)RabbitMQ进阶-Queue队列详解-延时队列

    RabbitMQ进阶 Queue队列详解 延迟队列 文章目录 RabbitMQ进阶 Queue队列详解 延迟队列 1 延迟队列场景 1 1 场景 2 延迟队列实现方式 3 TTL Exchange实现延迟队列 3 1 初始化死信交换机 3
  • 正则匹配html内容中的图片路径

    正则匹配html内容中的图片路径 let imgReg
  • 事不避难,知难不难

    My first article
  • Qt 中引入ffmpeg 动态库

    1 前期准备 在qt引入ffmpeg动态库的时候 需要准备ffmpeg的动态库和头文件 2 打开qt项目 在qt项目的 pro文件中添加以下几行代码 INCLUDEPATH PWD thirtLib ffmpeg4 2 include wi
  • 使用R语言添加抖动数据点

    使用R语言添加抖动数据点 在数据可视化中 抖动 jitter 是一种常用的技术 用于在散点图中添加一定程度的随机扰动 以解决数据重叠的问题 本文将介绍如何使用R语言添加抖动数据点 并提供相应的源代码 首先 我们需要准备一组数据用于绘制散点图
  • HTTP的演变

    这个问题之前一直没有关注过 后来在面试的过程中 面试官总喜欢问http1 0和http1 1之间的区别是啥 改进是啥以及优缺点 在今天进行一个总结 Http1 0和Http1 1的对比 这里讲俩放在一起进行对比学习 相较于Http1 0而言
  • Java调用Python脚本报错cv2.error: OpenCV(4.8.0) D:\a\opencv-python\opencv-python\opencv\modules\imgproc\src

    Java调用python脚本报错cv2 error OpenCV 4 8 0 D a opencv python opencv python opencv modules imgproc src resize cpp 4062 error
  • Android开机动画

    Android开机动画 1 BootLoader开机图片 2 Kernel开机图片 3 系统启动时 BootAnimation 动画 3 1 bootanimation zip位置 3 2 bootanimation启动 3 3 Surfa
  • linux保存git用户名密码

    1 创建git credentials gt vim git credentials https username password github com gitlab或github地址 2 执行git命令 gt git config gl
  • leetcode分类刷题:二分查找(Binary Search)(四、基于值域的数组/矩阵类型)

    基于值域的二分法与基于定义域的题型不同 它的目标是从一 特殊排序序列 中确定 第k个元素值 而不像基于定义域的题型是从排序序列中找小于等于特定target值的第一个索引 同时 针对 特殊排序序列 往往需要嵌套使用双指针法进行操作 进一步增加