专题练习——二分查找

二分模板

left = 0;
right = array.length - 1;
while(left <= right) {
	int mid = left + (right - left) / 2;//取left和right的中间值
	if (array[mid] == target) {
		//find the target
		break or return result
	} else if (array[mid] < target){
		left = mid + 1;
	}else {
		right = mid + 1;
	}
}

一、LeetCode69. x 的平方根

题目描述

链接
实现 int sqrt(int x) 函数。

计算并返回 x 的平方根,其中 x 是非负整数。

由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。

示例 1:

输入: 4
输出: 2

示例 2:

输入: 8
输出: 2
说明: 8 的平方根是 2.82842..., 
     由于返回类型是整数,小数部分将被舍去。

二分法

class Solution {
    public int mySqrt(int x) {
        int left = 0;
        int right = x;
        int ans = -1;
        while (left <= right) {
            int mid = left + ((right - left) >> 1);     
            if ((long)mid * mid <= x) {
                ans = mid;
                left = mid + 1;        
            }
            else
                right = mid - 1;
        }
        return ans; 
    }
}

二、LeetCode367. 有效的完全平方数

题目描述

链接
给定一个正整数 num,编写一个函数,如果 num 是一个完全平方数,则返回 True,否则返回 False。

说明:不要使用任何内置的库函数,如 sqrt。

示例 1:

输入:16
输出:True

示例 2:

输入:14
输出:False

二分法

tips:测试数据较大,需要定义为long

class Solution {
    public boolean isPerfectSquare(int num) {
        if (num == 1)
            return true;
        long left = 0;
        long right = num >> 1;//一个数的平方根必然不会大于该数的1/2
        while (left <= right) {
            long mid = left + ((right - left) >> 1);
            long temp = mid * mid;
            if (temp == num)
                return true;
            else if(temp > num)
                right = mid - 1;
            else
                left = mid + 1;
        }
        return false;
    }
}

或者,处理一下中间结果,让其值不超过 i n t int int的最大范围

class Solution {
    public boolean isPerfectSquare(int num) {
        if (num == 1)
            return true;
        int left = 0;
        int right = num >> 1;
        while (left <= right) {
            int mid = left + ((right - left) >> 1);
            long temp = mid * mid;
            if (num / mid == mid && num % mid == 0)//相当于 mid * mid == num
                return true;
            else if(num / mid < mid) //相当于 mid * mid > num
                right = mid - 1;
            else
                left = mid + 1;
        }
        return false;
    }
}

三、LeetCode33. 搜索旋转排序数组

题目描述

链接
假设按照升序排序的数组在预先未知的某个点上进行了旋转。

( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。

搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回 -1 。

你可以假设数组中不存在重复的元素。

你的算法时间复杂度必须是 O(log n) 级别。

示例 1:

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

示例 2:

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

二分法

  1. 将旋转数组从中间分为两部分的时候,一定有一部分的数组是有序的。比如 n u m s = [ 4 , 5 , 6 , 7 , 0 , 1 , 2 ] nums = [4,5,6,7,0,1,2] nums=[4,5,6,7,0,1,2],从中间分开为 [ 4 , 5 , 6 ] [4,5,6] [4,5,6] [ 7 , 0 , 1 , 2 ] [7,0,1,2] [7,0,1,2]两部分,其中左边部分 [ 4 , 5 , 6 ] [4,5,6] [4,5,6]是有序的。
  2. 那么,我们可以在每次查看mid为分割位置分割出来的两个部分 [ l e f t , m i d ] [left, mid] [left,mid] [ m i d + 1 , r i g h t ] [mid + 1, right] [mid+1,right]哪个部分是有序的,并根据有序的那个部分确定下一次二分搜索的上下界:
    • 如果 [ l e f t , m i d ] [left, mid ] [left,mid]是有序数组,并且 t a r g e t target target在区间 [ n u m s [ l e f t ] , n u m s [ m i d ] ) [ nums[left], nums[mid] ) [nums[left],nums[mid]),则将搜索范围缩小至 [ l e f t , m i d − 1 ] [left, mid - 1] [left,mid1],否则缩小至 [ m i d + 1 , r i g h t ] [mid + 1, right] [mid+1,right]
    • 如果 ( m i d , r i g h t ] (mid,right] (midright]是有序数组,并且 t a r g e t target target在区间 ( n u m s [ m i d ] , n u m s [ r i g h t ] ] ( nums[mid], nums[right] ] (nums[mid],nums[right]],则将搜索范围缩小至 [ m i d + 1 , r i g h t ] [mid + 1, right] [mid+1,right],否则缩小至 [ l e f t , m i d − 1 ] [left,mid - 1] [left,mid1]
class Solution {
    public int search(int[] nums, int target) {
        int n = nums.length;
        if (n <= 0)
            return -1;
        if (n == 1)
            return nums[0] == target ? 0 : -1;
        int left = 0;
        int right = n - 1;
        while (left <= right) {
            int mid = left + ((right - left) >> 1);
            if (nums[mid] == target)//找到了target
                return mid;
            if (nums[left] <= nums[mid]) {//这一部分(前半部分)是有序的
                if (nums[left] <= target && target < nums[mid]) {//target在这一部分
                    right = mid - 1;
                } else {
                    left = mid + 1;
                }
            } else {//nums[left] > nums[mid],这一部分(后半部分)是有序的
                if (nums[right] >= target && target > nums[mid]) {//target在这一部分
                    left = mid + 1;
                } else {
                    right = mid - 1;
                }
            }
        }
        return -1;//搜索结束还没找到,说明不存在
    }
}

四、LeetCode74. 搜索二维矩阵

题目描述

链接
编写一个高效的算法来判断 m x n 矩阵中,是否存在一个目标值。该矩阵具有如下特性:

每行中的整数从左到右按升序排列。
每行的第一个整数大于前一行的最后一个整数。
示例 1:

输入:
matrix = [
  [1,   3,  5,  7],
  [10, 11, 16, 20],
  [23, 30, 34, 50]
]
target = 3
输出: true

示例 2:

输入:
matrix = [
  [1,   3,  5,  7],
  [10, 11, 16, 20],
  [23, 30, 34, 50]
]
target = 13
输出: false

方法一、缩小领域法

  1. 因为每一行从左往右递增,每一列从上往下递增,那么可以从右上角或者左下角开始找,每次比较可以排除一行或者一列。
  2. 我这里写的代码是从右上角开始往左下角找。
  3. 示例1的搜索路径是:7->5>3
  4. 示例2的搜索路径是: 7->20->16->11->30->23->结束,没找到
class Solution {
    public boolean searchMatrix(int[][] matrix, int target) {
        if(matrix == null || matrix.length == 0)
            return false;
        int rows = matrix.length;
        int cols = matrix[0].length;
        int row = 0;
        int col = cols - 1;
        while (row < rows && col >= 0) {//从矩阵右上角开始搜索
            if (target == matrix[row][col])
                return true;
            else if (target > matrix[row][col])
                row++;
            else
                col--;
        }
        return false;
    }
}

时间复杂度为O(n)。

方法二、二分查找

因为每一行从左往右递增,每一列从上往下递增,那么将这个二维矩阵拖为一维矩阵,就是一个有序的一维数组了,从而进行二分查找。

class Solution {
    public boolean searchMatrix(int[][] matrix, int target) {
        if(matrix == null || matrix.length == 0)
            return false;
        int rows = matrix.length;
        int cols = matrix[0].length;
        int left = 0;
        int right = rows * cols - 1;
        while (left <= right) {
            int mid = left + ((right - left) >> 1);
            int midValue = matrix[mid / cols][mid % cols];
            if (midValue == target)
                return true;
            else if(target < midValue)
                right = mid - 1;
            else
                left  = mid + 1;
        }
        return false;
    }
}

时间复杂度为O(log(n))


五、LeetCode153. 寻找旋转排序数组中的最小值

题目描述

链接
假设按照升序排序的数组在预先未知的某个点上进行了旋转。

( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。

请找出其中最小的元素。

你可以假设数组中不存在重复元素。

示例 1:

输入: [3,4,5,1,2]
输出: 1

示例 2:

输入: [4,5,6,7,0,1,2]
输出: 0

二分法

class Solution {
    public int findMin(int[] nums) {
        if (nums == null || nums.length == 0)
            return -1;
        int n = nums.length; 
        if (n == 1 || nums[0] < nums[n - 1])//数组只有一个元素或者未发生旋转
            return nums[0];
        int left  = 0;
        int right = n - 1;
        while (left < right) {
            int mid = left + ((right - left) >> 1);
            if (nums[mid] > nums[right]) {          // 中值 > 右值,最小值一定在右半边,收缩左边界 
                left = mid + 1;                     // 因为中值 > 右值,中值肯定不是最小值,左边界可以跨过mid 
            } else if (nums[mid] < nums[right]) {   // 中值 < 右值,最小值在左半边,收缩右边界 
                right = mid;                        // 因为中值 < 右值,中值也可能是最小值,右边界只能取到mid处 
            }
        }
        return nums[left];    // 循环结束,left == right 
    }
}

六、LeetCode154. 寻找旋转排序数组中的最小值 II

题目描述

链接
假设按照升序排序的数组在预先未知的某个点上进行了旋转。

( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。

请找出其中最小的元素。

注意数组中可能存在重复的元素。

示例 1:

输入: [1,3,5]
输出: 1

示例 2:

输入: [2,2,2,0,1]
输出: 0

二分法

同上一题一样的解法,不同点在于需要在 n u m s [ m i d ] = = n u m s [ r i g h t ] nums[mid] == nums[right] nums[mid]==nums[right]时挪动右边界进行去重就行。

class Solution {
    public int findMin(int[] nums) {
       if (nums == null || nums.length == 0)
            return -1;
        int n = nums.length; 
        int left = 0;
        int right = n - 1;
        while (left < right) {
            int mid = left + ((right - left) >> 1);
            if (nums[mid] == nums[right]) { //中值 = 右值,更新右边界,进行去重
                right--;
            }else if (nums[mid] > nums[right]) {//中值 > 右值,最小值在右半边,收缩左边界
                left = mid + 1; // 因为中值 > 右值,中值肯定不是最小值,左边界可以跨过mid 
            }else {             // 中值 < 右值,最小值在左半边,收缩右边界 
                right  = mid;   // 因为中值 < 右值,中值也可能是最小值,右边界只能取到mid处 
            }
        }
        return nums[left];
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值