Leetcode 剑指 Offer II 121.寻找目标值 - 二维数组

题目难度: 中等

原题链接

今天继续更新 Leetcode 的剑指 Offer(专项突击版)系列, 大家在公众号 算法精选 里回复 剑指offer2 就能看到该系列当前连载的所有文章了, 记得关注哦~

题目描述

m*n 的二维数组 plants 记录了园林景观的植物排布情况,具有以下特性:

  • 每行中,每棵植物的右侧相邻植物不矮于该植物;
  • 每列中,每棵植物的下侧相邻植物不矮于该植物。

请判断 plants 中是否存在目标高度值 target。

示例 1:

  • 输入:plants = [[2,3,6,8],[4,5,8,9],[5,9,10,12]], target = 8
  • 输出:true

示例 2:

  • 输入:plants = [[1,3,5],[2,5,7]], target = 4
  • 输出:false

提示:

  • 0 <= n <= 1000
  • 0 <= m <= 1000

题目思考

  1. 如何利用题目给出的条件进行优化?

解决方案

  • 分析题目, 相信大家都不难想出暴力遍历所有数字二分查找每行的方法, 前者时间复杂度为 O(RC), 后者有所优化, 达到了 O(RlogC). (RC 分别代表行和列的数目)
  • 当然, 对于二分查找, 我们还可以稍作优化. 因为行和列都是递增, 所以二分查找既可以横向查找, 也可以纵向查找, 具体选哪种就看行数还是列数更多: 行数多的话就纵向查找, 否则横向查找. 这样就充分利用了二分查找对数复杂度的特性, 减小较大项对速度的影响, 使得复杂度降低为O(min(R,C)*log(max(R,C)))
  • 但即使是上面的二分查找, 也只用到了行或列递增的单一条件, 没有把两个递增条件同时用上, 我们如何同时用上这两个条件呢?
  • 回顾两个递增性质, 假设当前我们遍历到的下标为(r,c), 那么它和 target 的关系可以分为下面三种情况:
    • 值等于 target, 直接返回 true 即可
    • 值小于 target, 那么以该点为右下角的左上矩形中的所有值都必然小于 target, 可以安全被排除, 而其余部分则说不好
    • 值大于 target, 正好与上面相反, 以该点为左上角端点的右下矩形都可以被排除
  • 重点关注上面的两个排除关系, 我们如果可以从某个起点开始, 一次排除一片区域, 然后朝一个方向继续线性遍历, 这样很快就能排查完整个二维数组
  • 而为了满足线性遍历的条件, 起点的可选值只能是左下角或者右上角, 这里可以用反证法证明: 假设我们从二维数组左上角或右下角或中间某个地方开始的话, 排除完一块区域后, 我们接下来总有两个方向选择: 排除了右下部分, 则可以向左或者向上继续遍历, 同理排除左上部分也会有向右和向下两个方向可选.
  • 而如果起点选在左下角或者右上角, 情况就不同了, 只会有一种选择. 这里以左下角起点为例, 我们可以通过递推来得出结论:
    • 如果初始值小于 target, 因为下边没元素, 所以只能向右不能向下;
    • 如果初始值大于 target, 因为左边没元素, 所以只能向上不能向左;
    • 如果第一步操作是向右, 那么第二个值如果小于 target, 还只能向右, 因为下边仍然没元素; 而如果大于 target, 还只能向上, 因为左边的整列都已经在上一次被排除掉了, 结果不可能存在于左边
    • 而如果第一步操作是向上, 按照同样的分析, 仍然可得还是只能向右或者向上继续遍历
    • 以此类推, 每次都是要么向右, 要么向上, 就能排查完二维数组所有的数字得出结果
  • 具体实现时, 我们初始化坐标为左下角(当然右上角也可以, 大家可以想想看代码要怎么改), 然后判断当前点与 target 的关系, 大于的话行号减 1, 小于的话列号+1, 直到找到等于 target 的点, 或者超出二维数组范围
  • 下面的代码对必要步骤有详细的解释, 方便大家理解
复杂度
  • 时间复杂度 O(R+C): 遍历时每次要么行号-1, 要么列号+1, 最多只用遍历 R+C 次
  • 空间复杂度 O(1): 只使用了常数额外空间
代码
class Solution:
    def findTargetIn2DPlants(self, plants: List[List[int]], target: int) -> bool:
        if not plants:
            return False
        rows, cols = len(plants), len(plants[0])
        # 起点为左下角
        r, c = rows - 1, 0
        while r >= 0 and c < cols:
            if plants[r][c] == target:
                # 找到target, 返回True
                return True
            elif plants[r][c] > target:
                # 大于target, 向上
                r -= 1
            else:
                # 小于target, 向右
                c += 1
        # 遍历整个二维数组都找不到, 返回False
        return False

大家可以在下面这些地方找到我~😊

我的 GitHub

我的 Leetcode

我的 CSDN

我的知乎专栏

我的头条号

我的牛客网博客

我的公众号: 算法精选, 欢迎大家扫码关注~😊

算法精选 - 微信扫一扫关注我

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值