算法-二分查找

1. 搜索插入的位置(LeetCode 35)

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

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

注意:

  • 使用left + (right-left) / 2,而不是(left+right) / 2能够避免出现相加溢出的情况
class Solution {
    public int searchInsert(int[] nums, int target) {
        int left = 0;
        int right = nums.length-1;

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

        return right+1;
    }
}

2. 在排序数组中查找元素的第一个和最后一个位置(LeetCode 34,易错)

给你一个非递减的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target,返回 [-1, -1]。
要求:时间复杂度为O(log n)

输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]
class Solution {
    public int[] searchRange(int[] nums, int target) {
        if(nums.length == 0)    
            return new int[]{-1, -1};

        int start = lowerBound(nums, target);
        if(start == nums.length || nums[start] != target)
            return new int[]{-1, -1};

        // 最大的小于 target+1 的下标,又存在 nums[i] == target
        // 所以end必定是最大的等于target的下标
        int end = lowerBound(nums, target+1) - 1;
        return new int[]{start, end};
    }

    // 返回最小的满足 nums[i] >= target 的下标i
    public int lowerBound(int[] nums, int target) {
        int left = 0;
        int right = nums.length-1;

        while(left <= right) {
            int mid = left + (right - left)/2;
            // 注意这里是:大于等于 target
            if(nums[mid] >= target) {
                right = mid-1;
            } else {
                left = mid+1;
            }
        }

        // 最终结束时 left = right+1
        // 即 nums[left] < target, nums[left] = nums[right+1] >= target
        // 如果nums都小于target,则left = nums.length
        return left;
    }
}

3. 搜索旋转排序数组(LeetCode 33,有难度)

整数数组 nums 按升序排列,数组中的值 互不相同 。

在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行了 向左旋转,使数组变为 [nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]](下标 从 0 开始 计数)。
例如, [0,1,2,4,5,6,7] 下标 3 上向左旋转后可能变为 [4,5,6,7,0,1,2] 。

给你旋转后的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的下标,否则返回 -1 。

输入:nums = [4,5,6,7,0,1,2], target = 0
输出:4
class Solution {
    public int search(int[] nums, int target) {
        if(nums.length==1)
            return (nums[0] == target) ? 0 : -1;
        
        int left = 0;
        int right = nums.length-1;

        while(left <= right) {
            int mid = left + (right - left)/2;
            if(nums[mid] == target)
                return mid;
            
            // 只在有序的半区上进行二分查找
            // 一定是大于等于,否则处理不了左边只有一个元素的情况
            if(nums[mid] >= nums[left]) {
                // target 落在左半区内
                if(target >= nums[left] && target < nums[mid])
                    right = mid-1;
                else
                    left = mid+1;
            }
            // nums[mid] < nums[left],左半区不是单调的。考察target和右半区的关系 
            else {
                if(target > nums[mid] && target <= nums[right])
                    left = mid+1;
                else
                    right = mid-1;
            }
        }
        
        return -1;
    }
}

4. 寻找旋转排序数组中的最小值(LeetCode 153,有难度)

给你一个元素值互不相同的数组 nums ,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。
请你找出并返回数组中的 最小元素 。

输入:nums = [4,5,6,7,0,1,2]
输出:0
class Solution {
    public int findMin(int[] nums) {
        if(nums.length==1)  return nums[0];

        int left = 0;
        int right = nums.length-1;

        while(left < right) {
            int mid = left + (right - left)/2;

            // 以最右边元素作为参照
            if(nums[mid] < nums[right]) {
                right = mid;
            } else {
                left = mid+1;
            }
        }

        return nums[left];

    }
}

5. 寻找两个正序数组的中位数(LeetCode 4, 难!!!)

给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。
要求:时间复杂度为O(log (m+n))

输入:nums1 = [1,3], nums2 = [2]
输出:2.00000
解释:合并数组 = [1,2,3] ,中位数 2

输入:nums1 = [1,2], nums2 = [3,4]
输出:2.50000
解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5
  • 数组长度k为奇数,中位数为nums[k/2 + 1];k为偶数,中位数为(nums[k/2] + nums[k/2+1]) / 2.
  • 因此,找中位数可以转化为找第k个元素
  • getKthElement的思路见注释,每次都能够删除nums[index] ~ nums[newIndex],即newIndex - index + 1个元素,大概是k/2-1个,从而对k实现二分。
  • 边界情况是一个数组为空,则只需考虑另一个数组即可;如果k=1,只需要考虑两个数组中的较小值即可。
class Solution {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        int length1 = nums1.length;
        int length2 = nums2.length;
        int totalLength = length1 + length2;
        if (totalLength % 2 == 1) {
            int midIndex = totalLength / 2;
            double median = getKthElement(nums1, nums2, midIndex + 1);
            return median;
        } else {
            int midIndex1 = totalLength / 2 - 1, midIndex2 = totalLength / 2;
            double median = (getKthElement(nums1, nums2, midIndex1 + 1) + getKthElement(nums1, nums2, midIndex2 + 1)) / 2.0;
            return median;
        }
    }

    public int getKthElement(int[] nums1, int[] nums2, int k) {
        /* 主要思路:要找到第 k (k>1) 小的元素,那么就取 pivot1 = nums1[k/2-1] 和 pivot2 = nums2[k/2-1] 进行比较
         * 这里的 "/" 表示整除
         * nums1 中小于等于 pivot1 的元素有 nums1[0 .. k/2-2] 共计 k/2-1 个
         * nums2 中小于等于 pivot2 的元素有 nums2[0 .. k/2-2] 共计 k/2-1 个
         * 取 pivot = min(pivot1, pivot2),两个数组中小于等于 pivot 的元素共计不会超过 (k/2-1) + (k/2-1) <= k-2 个
         * 这样 pivot 本身最大也只能是第 k-1 小的元素
         * 如果 pivot = pivot1,那么 nums1[0 .. k/2-1] 都不可能是第 k 小的元素。把这些元素全部 "删除",剩下的作为新的 nums1 数组
         * 如果 pivot = pivot2,那么 nums2[0 .. k/2-1] 都不可能是第 k 小的元素。把这些元素全部 "删除",剩下的作为新的 nums2 数组
         * 由于我们 "删除" 了一些元素(这些元素都比第 k 小的元素要小),因此需要修改 k 的值,减去删除的数的个数
         */

        int length1 = nums1.length;
        int length2 = nums2.length;
        int index1 = 0, index2 = 0;
        int kthElement = 0;

        while (true) {
            // 边界情况
            if (index1 == length1) {
                return nums2[index2 + k - 1];
            }
            if (index2 == length2) {
                return nums1[index1 + k - 1];
            }
            if (k == 1) {
                return Math.min(nums1[index1], nums2[index2]);
            }
            
            // 正常情况
            int half = k / 2;
            int newIndex1 = Math.min(index1 + half, length1) - 1;
            int newIndex2 = Math.min(index2 + half, length2) - 1;
            int pivot1 = nums1[newIndex1], pivot2 = nums2[newIndex2];
            if (pivot1 <= pivot2) {
                k -= (newIndex1 - index1 + 1);
                index1 = newIndex1 + 1;
            } else {
                k -= (newIndex2 - index2 + 1);
                index2 = newIndex2 + 1;
            }
        }
    }
}
posted @ 2026-03-28 15:54  Frank23  阅读(14)  评论(0)    收藏  举报