一个二分查找的模版,解决leetcode好几道题目

二分查找

常见困惑

当我们在刷leetcode关于二分查找的算法题的时候,经常会遇到下面的一些困惑:

  1. 边界搞不清,太闹心了
 while (left <= right)  Or  while (left < right)
  1. 不知道新查找区间是mid 还是mid+1 还是mid-1
 binarySearch(nums, target, mid+1, right)  Or binarySearch(nums, target, mid, right)
  1. 二分查找一变形,就啥也不会了!

这篇博客解决什么问题

  1. 统一边界的写法,记住一种就够了,不用纠结left <= right 还是 left < right
  2. 最简单的方式来理解和写二分查找的题目
  3. 介绍一个二分查找的模版,来解决在排序数组中查找元素的第一个和最后一个位置

一般的二分查找

非递归方式 (大家都会)

int binarySearch(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 (nums[mid] < target)
            left = mid + 1; 
        else if (nums[mid] > target)
            right = mid - 1; 
    }
    return -1;
}

上面就是一个二分查找非递归方式最最常见的一个模版,基本上大家都会,也不是这篇博客的重点。

递归方式(对!!就是它)

大家写递归的时候,容易陷入一个误区,总是有意识的“跟着递归函数”走,殊不知,一下子就把自己绕晕了。毕竟对于复杂一点的递归,压栈太深了,实在难以理解。

**正确的方式:**我学习网上大佬的方式,我们写递归的时候,只要关注递归函数本身的功能,不要去纠结函数怎么走,只想着递归函数能干什么,同时想一想这个递归的结束条件是什么。只要明白了上面两点,那么其他的就交给递归函数自己来完成吧。

public static void main(String args[]) {
    int[] nums = {1,2,3,4,5};
    target = 3;
    
    // 调用函数
    searchIndex(nums, target, 0, nums.length-1);
}



public void searchIndex(int[] nums, int target, int low, int high) {
    // 1. 递归结束的条件
    if (low > high)
        return ;    
        
    // 2. 递归函数功能体
    int mid = (low + high) / 2;
    if (nums[mid] == target) {
        return mid;
    } else if (target < nums[mid]) {
        searchIndex(nums, target, low, mid -1); 
    } else if (target > nums[mid]) {
        searchIndex(nums, target, mid+1, high);
    }       
}

注意:这种写法,不用考虑结left <= right 还是 left < right问题。

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

假设有这样的情况:

nums = {1, 2, 2, 3, 4} 中查找 target = 2 并且下标最小的元素所在的索引。

又或者:

nums = {1, 2, 3, 3, 3, 4} 中查找 target = 3 并且下标最大的元素所在的索引。

又或者,看这个题目:在排序数组中查找元素的第一个和最后一个位置

给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。

如果数组中不存在目标值 target,返回 [-1, -1]。

进阶:你可以设计并实现时间复杂度为 O(log n) 的算法解决此问题吗?
 
示例 1:
输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]

示例 2:
输入:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1]

递归模版只加一句代码,就可以完美解决上面的问题

查找 target 并且下标最小的元素所在的索引

int ans = -1;

// 寻找第一个位置
public void searchLeft(int[] nums, int target, int low, int high) {
	if (low > high)
            return ;    
    int mid = (low + high) / 2;
    if (nums[mid] == target) {
        ans = mid;
        // *****  只增加这一行代码 *******
        searchLeft(nums, target, low, mid -1);
    } else if (target < nums[mid]) {
        searchLeft(nums, target, low, mid -1); 
    } else if (target > nums[mid]) {
        searchLeft(nums, target, mid+1, high);
    }       
}
怎么理解?

对于 nums = {1, 2, 2, 3, 4} ,查找target = 2的索引

  1. 首先,一般形式的二分查找,我们很容易找到索引 index = 2;找到之后就直接return index;
  2. 但是我们希望找到的是index = 1 这个索引,那么index = 1 是不是可以通过 在 nums = {1, 2 }这一个范围找到呢?
  3. 所以这里就是用到了递归的方法了,我们递归函数要解决的是:查找 target 并且下标最小的元素所在的索引。所以在步骤1 找到index = 2 的时候,不要着急返回return index;我们不妨再去index = 2的左边看看,或许可以再找到一个更小的索引,这就是为什么只需要增加一行代码:searchLeft(nums, target, low, mid -1);
同理:查找 target = 2 并且下标最大的元素所在的索引。
int ans = -1;

// 寻找第一个位置
public void searchRight(int[] nums, int target, int low, int high) {
	if (low > high)
            return ;    
    int mid = (low + high) / 2;
    if (nums[mid] == target) {
        ans = mid;
        // *****  只增加这一行代码 *******
        searchRight(nums, target, low, mid -1);
    } else if (target < nums[mid]) {
        searchRight(nums, target, low, mid -1); 
    } else if (target > nums[mid]) {
        searchRight(nums, target, mid+1, high);
    }       
}

我们在找到index = 2 的时候,不妨那个 再去index = 2的右边看看,或许可以再找到一个=更大的索引==,这就是为什么只需要增加一行代码:searchRight(nums, target, mid+1, high);

写一道Leetcode的题目练练手吧!

看这个题目:在排序数组中查找元素的第一个和最后一个位置

给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。

如果数组中不存在目标值 target,返回 [-1, -1]。

进阶:你可以设计并实现时间复杂度为 O(log n) 的算法解决此问题吗?
 
示例 1:
输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]

示例 2:
输入:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1]

按照刚才的套路,代码如下:

class Solution {

    int[] ans = {-1,-1};
    
    public int[] searchRange(int[] nums, int target) {
        searchLeft(nums, target, 0, nums.length-1);
        searchRight(nums, target, 0, nums.length-1);
        return ans;
    }

    // 寻找第一个位置
    public void searchLeft(int[] nums, int target, int low, int high) {
      
        if (low > high)
            return ;    
        int mid = (low + high) / 2;
        if (nums[mid] == target) {
            ans[0] = mid;
            searchLeft(nums, target, low, mid -1);
        } else if (target < nums[mid]) {
            searchLeft(nums, target, low, mid -1); 
        } else if (target > nums[mid]) {
            searchLeft(nums, target, mid+1, high);
        }       
    }
    // 寻找最后一个位置
    public void searchRight(int[] nums, int target, int low, int high) {
      
        if (low > high)
            return ;    
        int mid = (low + high) / 2;
        if (nums[mid] == target) {
            ans[1] = mid;
            searchRight(nums, target, mid+1, high);
        } else if (target < nums[mid]) {
            searchRight(nums, target, low, mid -1); 
        } else if (target > nums[mid]) {
            searchRight(nums, target, mid+1, high);
        }      
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值