九章算法01:二分法

九章算法01:二分法

二分法第一重境界: 套模板

public class Solution {
    /**
     * @param nums   an integer array sorted in ascending order
     * @param target an integer
     * @return an integer
     */
    public int findPosition(int[] nums, int target) {
        if (nums == null || nums.length == 0) {
            return -1;
        }

        int start = 0, end = nums.length - 1;
        while (start + 1 < end) {                   // 要点1
            int mid = start + (end - start) / 2;    // 要点2
            if (nums[mid] == target) {              // 要点3
                end = mid;	// 找第一个出现位置时      // 要点4
                // or end = start 找最后一个出现位置时
            } else if (nums[mid] < target) {		// 要点3
                start = mid;
                // or start = mid + 1
            } else {								// 要点3
                end = mid;
                // or end = mid - 1
            }
        }

        if (nums[start] == target) {				// 要点4
            return start;
        }
        if (nums[end] == target) {
            return end;
        }
        return -1;
    }
}

上面模板,有四点需要注意:

  1. while循环的条件是start + 1 < end,这样写是为了避免死循环: 循环体内startend永不相邻,导致mid不至于跟startend之一重合,造成bug.

    考虑到一个情况:start == end+1,此时若要求推出条件为start == end,则根据后面的代码,永远不可能退出.

  2. 取中点的操作mid = start + (end - start) / 2,这个是为了防止大数相加溢出.

  3. ==,>,<三种情况分开来写,不要合并.因为写完之前,没人知道这三种情况是否会有些许的不同.

    另外在<>两种情况下,适当放宽条件,将下个区间起始位置指定在不可能是答案的end上,在不降低时间复杂度的同时减少了bug发生的可能.

  4. 在循环中不进行任何返回,循环的作用是缩小区间,将答案限制在我们可以数的过来的两个值上.

    另外根据我们要找的具体条件,这个模板还要在两个地方进行改写.

    • 若题目要求找到任何一个出现位置即可,我们代码可以直接运行.
    • 若题目要求找到第一个出现位置时: 16行==情况下区间左缩,且27行先判断start在判断end.
    • 若题目要求找到最后一个出现位置时: 16行==情况下区间右缩,且27行先判断end在判断start.

上面一种模板是对应于后文OOXX情况的,也就是没有明确分界点的情况,如果有准确的分界点target的话,也可以用while (start <= end)且在while循环中直接return,但这样的话就需要把子区间转移的条件写的准确一些,防止死循环(start=end+1,end=mid-1).

public class Solution {
    /**
     * @param nums   an integer array sorted in ascending order
     * @param target an integer
     * @return an integer
     */
    public int findAnyPosition(int[] nums, int target) {
        if (nums == null || nums.length == 0) {
            return -1;
        }

        int start = 0, end = nums.length - 1;
        while (start + 1 < end) {                   
            int mid = start + (end - start) / 2;    
            if (nums[mid] == target) {              
                return mid;	
            } else if (nums[mid] < target) {
                start = mid + 1
            } else {						
                end = mid - 1
            }
        }

        return -1;
    }
}

题目:

  1. 704. Binary Search
  2. 34. Find First and Last Position of Element in Sorted Array

二分法第二重境界: 找OOXX

所谓OOXX的造型,就是把所有小于target的项标记为O,大于等于target的项标记为X,然后去找第一个X即可.

题目:

  1. 278. First Bad Version: 将good version视为O,将bad version视为X.

  2. 153. Find Minimum in Rotated Sorted Array:

    对于旋转有序数组nums,两个threshold分别为nums[0]nums[-1],应该取哪一个作为区分OX的threshold呢?

    应该取nums[-1]作为threshold: 将nums[i]>nums[-1]视为O;将nums[i]<=nums[-1]视为X.(若取nums[0]作为threshold的话,就无法满足单调递增数组的情况,单调递增数组也是旋转有序数组).

    对于154. Find Minimum in Rotated Sorted Array II这道题,有一个特点在于原数组中的数据不是严格递增(相邻的可能会相等).这种非严格性在一般情况下不会产生问题,但是在旋转数组的两侧会产生问题:当nums[start]==nums[end]时,难以确定OX的分界依据,因此需要我们在程序开始时将start向右划过直到nums[start]<numsend],就可以按照153. Find Minimum in Rotated Sorted Array来做.

  3. 852. Peak Index in a Mountain Array:

    根据比较nums[i]nums[i+1]的关系来区分OX:

    • nums[i]<nums[i+1]O
    • nums[i]>nums[i+1]X

二分法第二重境界: 二分位置

在这类题目中,无法形成OOXX的造型,但是仍然能够根据具体情况硬核分析出保留哪一半,去掉哪一半.

  1. 162. Find Peak Element: 在一个有多个峰的数组中找到任意一个峰

    这道题目不能构成OOXX的造型,然而可以根据mid所处的位置来具体分析应该留下哪一半作为解:

    造型1造型2造型3造型4
    在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述
    • 造型1中,mid落在了峰上,直接返回即可.
    • 造型2中,mid落在了谷上,两边都有峰,留哪一边都行.
    • 造型3中,mid右侧必有一峰,留右边.
    • 造型4中,mid左侧必有一峰,留左边.
  2. 33. Search in Rotated Sorted Array: 在循环有序数组中查询

    这道题中的数组不严格有序,不能直接二分查找,可以仿效153. Find Minimum in Rotated Sorted Array的思路,先找两段数组的分界点,再在两段有序数组上分别进行二分查找.

    但是如果题目要求只进行一次二分查找的话,就要根据mid所处的位置来具体分析应该留下哪一半作为解:

    造型1造型2
    在这里插入图片描述在这里插入图片描述

    通过比较nums[mid]nums[-1]值的大小关系,可以确定mid具体位于哪个区间,然后根据target值所在区间来判断留下哪一半.

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值