LeetCode 数组专题 1:二分查找

二分查找法

说明:二分查找法在代码实现上有模板方法,一定要掌握。

1、二分查找法的使用前提:数组一定要是排好序的,如果数组不是排好序的,二分查找法便不能使用。

2、实现二分查找法的关键之处:维护循环不变量的定义。如果修改了区间边界的定义,算法得相应改变。

技巧:可以使用小数据量进行测试。

下面给出的问题不是标准的二分查找问题,但是可以用二分查找的思想来解决,稍微要绕一点弯子。其中,第 300 题要用到第 35 题。

LeetCode 第 34 题:在排序数组中查找元素的第一个和最后一个位置

传送门:34. 在排序数组中查找元素的第一个和最后一个位置

给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。

你的算法时间复杂度必须是 O(log n) 级别。

如果数组中不存在目标值,返回 [-1, -1]

示例 1:

输入: nums = [5,7,7,8,8,10], target = 8
输出: [3,4]

示例 2:

输入: nums = [5,7,7,8,8,10], target = 6
输出: [-1,-1]

思路:使用二分查找,我使用的是二分查找法我认为最好用的模板写法。

1、循环可以继续的条件是 l < r,这样退出循环的时候 l == r 成立,因此就不用纠结返回 l 还是 r 了;

不过要特别注意一点:我们是通过夹逼的方式把搜索的范围逼到一个点,那么这个点是不是符合要求还要单独做判断。

2、循环体比较简单,真正地做到了“二分”,即“写两个分支”作判断,只要分支条件写正确,其中一个分支一定可以排除掉中点,而另一个分支则保留了中点;

3、取“中点”的操作有 2 种,根据循环体的收缩情况,采用合适的中点方法,这一点很重要,否则会出现死循环。

(1)mid = l + (r - l) // 2,特点:在只有两个数的时候,靠近左边。

(2)mid = l + (r - l + 1) // 2,特点:在只有两个数的时候,靠近右边。

例如:循环体是 l = mid + 1r = mid 的时候,表示左端点不断右移,则选择(1),否则会出现死循环;

循环体是 l = midr = mid - 1 的时候,表示右端点不断左移,则选择(2),否则会出现死循环。

Python 代码:

class Solution:
    def searchRange(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: List[int]
        """
        left = self.__find_lower_bound(nums, target)
        if left == -1:
            return [-1, -1]

        right = self.__find_up_bound(nums, target)
        return [left, right]

    def __find_lower_bound(self, nums, target):
        # 找到小于等于 target 的第 1 个元素的索引
        size = len(nums)
        if size == 0:
            return -1
        l = 0
        r = size - 1
        while l < r:
            mid = l + (r - l) // 2
            if nums[mid] < target:
                l = mid + 1
            else:
                assert nums[mid] >= target
                r = mid
        # 最后还要单独判断一下
        if nums[l] != target:
            return -1
        return l

    def __find_up_bound(self, nums, target):
        # 找到大于等于 target 的最后 1 个元素的索引
        size = len(nums)
        if size == 0:
            return -1
        l = 0
        r = size - 1
        while l < r:
            mid = l + (r - l + 1) // 2
            if nums[mid] > target:
                r = mid - 1
            else:
                assert nums[mid] <= target
                l = mid
        # 最后还要单独判断一下
        if nums[l] != target:
            return -1
        return l


if __name__ == '__main__':
    solution = Solution()
    nums = [5, 7, 7, 8, 8, 10]
    target = 8
    result = solution.searchRange(nums, target)
    print(result)

LeetCode 第 35 题:搜索插入位置

传送门:35. 搜索插入位置

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。

你可以假设数组中无重复元素。

示例 1:

输入: [1,3,5,6], 5
输出: 2

示例 2:

输入: [1,3,5,6], 2
输出: 1

示例 3:

输入: [1,3,5,6], 7
输出: 4

示例 4:

输入: [1,3,5,6], 0
输出: 0

分析:仔细分析题意,返回的是大于等于 target 的索引,有可能是最后一个。

关键之一:如果 target 比 nums 所有的数都大,则最后一个数的索引 + 1 就是候选值,因此,右边界应该是数组的长度。

关键之二:二分的逻辑一定要写对,否则会出现死循环或者数组下标越界。

Python 代码:

class Solution:

    # 返回的是大于等于 target 的索引,有可能是最后一个

    def searchInsert(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: int
        """

        size = len(nums)
        if size == 0:
            return 0
        l = 0
        r = size

        while l < r:
            mid = l + (r - l) // 2
            if nums[mid] >= target:
                r = mid
            else:
                assert nums[mid] < target
                l = mid + 1
        return l


if __name__ == '__main__':
    nums = [1, 3, 5, 6]
    target = 5
    solution = Solution()
    result = solution.searchInsert(nums, target)
    print(result)

LeetCode 第 300 题:最长上升子序列

传送门:300. 最长上升子序列

给定一个无序的整数数组,找到其中最长上升子序列的长度。

示例:

输入: [10,9,2,5,3,7,101,18]
输出: 4 
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。

说明:

  • 可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。
  • 你算法的时间复杂度应该为 O(n2) 。

进阶: 你能将算法的时间复杂度降低到 O(n log n) 吗?

思路:自己写一个辅助数组,用二分查找完成数组的覆盖或者插入,遍历完整个输入数组,辅助数组的长度就是所求。其实这道题的一个子过程就是 LeetCode 第 35 题:搜索插入位置。这个思路用到的策略是贪心算法,技巧和二分查找

关键:找大于等于“当前遍历的那个数”的第 1 个索引,将它替换成“当前遍历的那个数”,这样使得这个数变小,后面才有可能接一个更大的数。

注意:辅助数组不一定是一个最长上升子序列,但辅助数组的长度一定是最长上升子序列的长度。

LeetCode 第 300 题:最长上升子序列-1
LeetCode 第 300 题:最长上升子序列-2
LeetCode 第 300 题:最长上升子序列-3

Python 代码:

class Solution:
    def lengthOfLIS(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """

        size = len(nums)
        if size < 2:
            return size
        # 最长上升子序列
        lis = []
        for num in nums:
            # 找到大于等于 target 的第 1 个数
            l = 0
            r = len(lis)
            while l < r:
                mid = l + (r - l) // 2
                if lis[mid] >= num:
                    r = mid
                else:
                    l = mid + 1
            if l == len(lis):
                lis.append(num)
            else:
                lis[l] = num
        return len(lis)


if __name__ == '__main__':
    nums = [10, 9, 2, 5, 3, 7, 101, 18]
    solution = Solution()
    result = solution.lengthOfLIS(nums)
    print(result)

说明:这道题还可以用动态规划来完成。

(本节完)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容