九章算法01:二分法

九章算法01:二分法

  • 九章算法01:二分法
    • 二分法第一重境界: 套模板
    • 二分法第二重境界: 找OOXX
    • 二分法第二重境界: 二分位置

九章算法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]==start[end]时,难以确定OX的分界依据,因此需要我们在程序开始时将start向右划过直到nums[start],就可以按照153. Find Minimum in Rotated Sorted Array来做.

  3. 852. Peak Index in a Mountain Array:

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

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

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

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

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

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

    造型1 造型2 造型3 造型4
    九章算法01:二分法_第1张图片 九章算法01:二分法_第2张图片 九章算法01:二分法_第3张图片 九章算法01:二分法_第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
    九章算法01:二分法_第5张图片 九章算法01:二分法_第6张图片

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

你可能感兴趣的:(#,算法,二分法,算法,面试,数据结构,九章算法)