【专题三】二分查找

111 阅读3分钟

前言

不是只有数组有序才可以使用二分查找的,只要满足二段性即可

二段性:可以取一个点,然后这个点可以将数组按照题目要求划分成两个部分,然后可以直接过滤掉其中一部分,紧接着在剩下的一部分中继续使用二段性

代码模板【不要死记,理解原理】

朴素二分模板

简单,但是有局限性,只能解决部分题目【不存在重复值】

image.png

while(left <= right) {
    int mid = left + ((right-left)>>>1);

    if(…………) {
        right = mid-1;
    } else if(…………) {
        left = mid+1;
    } else {
        return …………;
    }
};

查找左边界的二分模板

左右边界的查找思路基本一样,就是处理边界不一样

image.png

while (left < right) {
    int mid = left + (right-left)/2;
    if(…………) {
        left = mid + 1;
    } else {
        right = mid;
    }
}

查找右边界的二分模板

image.png

while (left < right) {
    int mid = left + (right-left + 1)/2;
    if(…………) {
        left = mid;
    } else {
        right = mid - 1;
    }
}

704. 二分查找

题目

image.png

思路

  1. 暴力 O(N)
  2. 二分查找 O(logN)

代码

public int search(int[] nums, int target) {
    int n = nums.length;

    int left = 0, right = n-1;

    while(left <= right) {
        int mid = left + ((right-left)>>>1);

        if(nums[mid] > target) {
            right = mid-1;
        } else if(nums[mid] < target) {
            left = mid+1;
        } else {
            return mid;
        }
    }

    return -1;
}

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

题目

image.png

思路

  1. 暴力
  2. 二分查找左右端点 - 见模板推导

代码

public int[] searchRange(int[] nums, int target) {
    int[] ret = new int[2];
    ret[0] = ret[1] = -1;
    // 处理边界
    if(nums.length == 0) return ret;

    // 二分左端点
    int left = 0, right = nums.length-1;
    while (left < right) {
        int mid = left + (right-left)/2;
        if(nums[mid] < target) {
            left = mid + 1;
        } else {
            right = mid;
        }
    }

    // 处理细节 - 不包含情况
    if(nums[left] != target) {
        return ret;
    }
    ret[0] = left;
    // 找右端点
    right = nums.length-1;
    while (left < right) {
        int mid = left + (right-left+1)/2;
        if(nums[mid] <= target) {
            left = mid;
        } else {
            right = mid - 1;
        }
    }
    ret[1] = left;
    return ret;
}

69. x 的平方根

题目

image.png

思路

  1. 暴力
  2. 二分

image.png

代码

public int mySqrt(int x) {
    if(x < 1) {
        return 0;
    }

    long left = 1, right = x;
    while (left < right) {
        long mid = left + (right - left + 1) / 2;
        if(mid * mid <= x) {
            left = mid;
        } else {
            right = mid - 1;
        }
    }

    return (int)left;
}

35. 搜索插入位置

题目

image.png

解析

image.png

代码

public int searchInsert(int[] nums, int target) {
    int left = 0, right = nums.length-1;

    while(left < right) {
        int mid = left + (right - left) / 2;
        if(nums[mid] < target) {
            left = mid+1;
        } else {
            right = mid;
        }
    }

    if(target > nums[nums.length-1]) {
        return left + 1;
    }

    return left;
}

852. 山脉数组的峰顶索引

题目

image.png

解析

image.png

代码

public int peakIndexInMountainArray(int[] arr) {
    int left = 0, right = arr.length-1;

    while(left < right) {
        int mid = left + (right - left) / 2;
        if(arr[mid] < arr[mid+1]) {
            left = mid+1;
        } else {
            right = mid;
        }
    }

    return left;
}

public int peakIndexInMountainArray1(int[] arr) {
    int left = 0, right = arr.length-1;

    while(left < right) {
        int mid = left + (right - left + 1) / 2;
        if(arr[mid] > arr[mid-1]) {
            left = mid;
        } else {
            right = mid - 1;
        }
    }

    return left;
}

162. 寻找峰值

题目

image.png

解析

image.png

代码

public int findPeakElement(int[] nums) {
    int left = 0, right = nums.length-1;

    while(left < right) {
        int mid = left + (right - left + 1) / 2;
        if(nums[mid] > nums[mid-1]) {
            left = mid;
        } else {
            right = mid - 1;
        }
    }

    return left;
}

LCR 173. 点名

题目

image.png

解析

方法很多,基本都是O(N),但是二分可以达到O(logN)

  1. 遍历
  2. 按位与
  3. 高斯求和
  4. 哈希表
  5. 二分查找

image.png

代码

public int missingNumber(int[] nums) {
    int left = 0, right = nums.length-1;

    while(left < right) {
        int mid = left + (right - left) / 2;
        if(nums[mid] == mid) {
            left = mid+1;
        } else {
            right = mid;
        }
    }

    if(left == nums[nums.length - 1]) {
        return left + 1;
    }

    return left;
}

153. 寻找旋转排序数组中的最小值

题目

image.png

解析

image.png

代码

// 比如【1,2】【2,1】 都是在if来处理的
public int findMin(int[] nums) {
    int left = 0, right = nums.length-1;
    int x = nums[0];
    while (left < right) {
        int mid = left + (right - left) / 2;

        if(nums[mid] >= x) {
            left = mid + 1;
        } else {
            right = mid;
        }
    }

    // 旋转后仍然有序,特殊处理
    if(x < nums[left]) {
        return x;
    }

    return nums[left];
}

public int findMin1(int[] nums) {
    int left = 0, right = nums.length-1;
    int x = nums[right];
    while (left < right) {
        int mid = left + (right - left) / 2;
        if(nums[mid] > x) {
            left = mid + 1;
        } else {
            right = mid;
        }
    }

    return nums[left];
}