查看原文
其他

从酒桌游戏看二分查找算法

霍语佳 前端食堂 2021-01-14
这是前端食堂的第34篇原创 

(给前端食堂加星标,吃好每一顿)

「观感度:🌟🌟🌟🌟🌟」

「口味:孜然牛肉」

「烹饪时间:5min」

本文已收录在Github github.com/Geekhyt,感谢Star。

酒桌上曾经玩过这样一个小游戏,游戏规则是:主持人每次随机从 1-1000 中选择一个数字,比如是 171。只有主持人自己知道并事先写在纸条上留存,然后分别让大家来猜,能够用最少次数猜到的人获胜并拥有指定一个人罚酒的权利。

  • 童欧巴:500
  • 主持人:大了
  • 童欧巴:250
  • 主持人:大了
  • 童欧巴:125
  • 主持人:小了
  • 童欧巴:187
  • 主持人:大了
  • 童欧巴:156
  • 主持人:小了
  • 童欧巴:171

主持人再次挑选数字,让扒蒜小妹去猜...

最后,童欧巴用的次数最少,童欧巴获胜!指定扒蒜小妹罚酒。

这个游戏就是看谁能使用最少的次数猜到主持人选的数字,谁就获胜。这种在有序数据集合中的查找用二分查找再合适不过了。

二分查找 Binary Search

二分查找,顾名思义。(看上文欧巴熟练的灌酒操作也可以知道)每次的查找都是和区间的中间元素对比,将待查找的区间缩小为一半,直到找到目标元素,或者区间被缩小为 0 (没找到)。二分查找的时间复杂度是 O(logn)。对比常量级时间复杂度,当常量很大时 O(999999),就会比 O(1) 的算法要高效。

二分算法虽然高效,但也存在一定的局限性。想要使二分查找发挥威力,需要满足几个前置条件才行。

  • 有序(单调递增/递减)
  • 数组(能够通过索引访问)
  • 数据量不能太大(数组内存空间连续,对内存要求严格)也不能太小(遍历即可)

LeetCode 真题

33. 搜索旋转排序数组 

https://leetcode-cn.com/problems/search-in-rotated-sorted-array/solution/pythonjs-er-fen-fa-33-sou-suo-xuan-zhuan-pai-xu-sh/

假设按照升序排序的数组在预先未知的某个点上进行了旋转。

( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。

搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回 -1 。

你可以假设数组中不存在重复的元素。

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

示例 1:

输入: nums = [4,5,6,7,0,1,2], target = 0 输出: 4

示例 2:

输入: nums = [4,5,6,7,0,1,2], target = 3 输出: -1

关键点

进行旋转后的数组一定有一部分是有序的。而且,题目要求时间复杂度为 O(logn),暗示我们使用二分搜索。

如上图中的两种情况,观察旋转后的数组:

  • nums[mid] >= nums[start] 时,mid 在左边且左边有序 5 >= 2
  • nums[mid] < nums[start] 时,mid 在右边且右边有序 2 < 6

接着我们来判断 target 在哪一个部分,舍弃另一部分即可。如上图的第二种情况,我们假设 target黑色的 3mid 在右边也就是 [mid, end]target > nums[mid] && target <= nums[end],所以舍弃左边,start = mid + 1

const search = function(nums, target) {
    let start = 0;
    let end = nums.length - 1;
    
    while (start <= end) {
        const mid = start + ((end - start) >> 1);
        if (nums[mid] === target) {
            return mid;
        }
        // 左侧有序
        if (nums[mid] >= nums[start]) {
            // target 在 [start, mid] 之间
            if (target >= nums[start] && target < nums[mid]) {
                end = mid - 1;
            } else {
                start = mid + 1;
            }
        } else { // 右侧有序
            // target 在 [mid, end] 之间
            if (target > nums[mid] && target <= nums[end]) {
                start = mid + 1;
            } else {
                end = mid - 1;
            }
        }
    }
    return -1;
}

复杂度分析

  • 时间复杂度:O(logn)
  • 空间复杂度:O(1) 只需要常量级别的空间存放变量

❤️爱心三连击

1.看到这里了就点个在看支持下吧,你的「在看」是我创作的动力。

2.关注公众号前端食堂,「你的前端食堂,记得按时吃饭」

3.本文已收录在前端食堂Github github.com/Geekhyt,求个小星星,感谢Star。

公众号:前端食堂


掘金:童欧巴


知乎:童欧巴


这是一个终身学习的男人,他在坚持自己热爱的事情,欢迎加入前端食堂,和这个男人一起开心的变胖~


推荐阅读:

「种树专业户」“树”业有专攻

你不知道的 TypeScript 泛型(万字长文,建议收藏)

  在看和转发是莫大鼓励❤️

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存