第4章—二分查找

4.1 算法解释

二分查找又称折半查找,每次取区间的二分之一继续查找

4.2 求开方

题目代号: 69 x的平方根

题目描述:

实现 int sqrt(int x) 函数。

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

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

测试用例:

示例 1:

输入: 4
输出: 2
示例 2:

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

我的分析:

折半查找,一旦mid² <=x;来右边找
反之来左边找

代码:

class Solution {
        public int mySqrt(int x) {
            //其实就是在0到x之间找个数,满足n*n == x
            //如果找不到,那就找它左边的值
            int left = 0,right = x,ans = -1;
            while (left <= right){
                int mid = (left + right)/2;
                if((long)mid * mid <= x){
                    ans = mid;//为啥要写在小的这边呢,是因为最后小数点被舍去,也是找左边的数
                    left = mid+1;
                }else {

                    right = mid -1;
                }
            }
            return ans;
        }
    }

4.3 查找区间

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

题目描述:

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

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

测试用例:

示例 1:

输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]
示例 2:

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

输入:nums = [], target = 0
输出:[-1,-1]

我的分析:

这里面还是要考虑详细一点的,如果是空就返回[-1,-1]
如果长度为1的数组,且值等于目标值,那就返回[0,0]

正常的数组的话,就先找到一个,找不到的时候就向左或者右折半查找
等找到一个的时候,那就好办了,一步一步递归就好

代码:

class Solution {
        public int[] searchRange(int[] nums, int target) {
            //现在已经排好序了,那肯定就用二分查找了
            int[] zhaobudao = {-1,-1};
            int[] ling ={0,0};
            if(nums.length == 0){
                return zhaobudao;
            }
            if(nums.length == 1){
                return nums[0] == target ? ling:zhaobudao;
            }
            int left = 0,right = nums.length-1;
            while (left <= right){
                int[] list =new int[2];
                int mid = (left + right)/2;
                int midValue = nums[mid];
                if(midValue > target){
                    //在左边来递归
                    right = mid-1;
                }else if(midValue < target){
                    left = mid+1;
                }else {
                    //现在相等了

                    while ((mid-1) >= 0 && nums[mid-1] == target){//往左递归
                        mid--;
                    }

                    list[0] = mid;

                    while ((mid+1) <= (nums.length-1) && nums[mid+1] == target){
                        mid++;
                    }
                    list[1] = mid;
                    return list;
                }
            }
            return zhaobudao;
        }
    }

4.4 旋转数组查找数字

题目代号: 81 搜索旋转排序数组 II

题目描述:

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

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

编写一个函数来判断给定的目标值是否存在于数组中。若存在返回 true,否则返回 false。

测试用例:

示例 1:

输入: nums = [2,5,6,0,0,1,2], target = 0
输出: true
示例 2:

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

我的分析:

里面有种特殊情况,就是中间值和左边界值相等,这个问题必须解决一下,否则会陷入死循环的

这里面肯定有一半是有序的,就要判断是左还是右了
在有序的前提下,就看处于左半部分还是右半部分了

代码:

class Solution {
    public boolean search(int[] nums, int target) {
        int left = 0,right = nums.length-1;
        while (left <= right){
            int mid = (left + right)/2;
            if(nums[mid] == target) return true;

            if(nums[left] == nums[mid]){//最怕的就是这种情况,一直死循环了
                left++;
            }else if(nums[left] < nums[mid]){//左半部分有序
                if(nums[left] <= target && target < nums[mid]) right = mid - 1;//处于左半部分
                else left = mid + 1;//处于右半部分
            }else {//右半部分有序
                if(nums[mid] < target && target <= nums[right]) left = mid + 1;//处于右半部分
                else right = mid - 1;//处于左半部分
            }
        }
        return false;
    }
}

4.5 练习题

题目代号: 153 寻找旋转排序数组中的最小值

题目描述:

假设按照升序排序的数组在预先未知的某个点上进行了旋转。例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] 。

请找出其中最小的元素。

测试用例:

示例 1:

输入:nums = [3,4,5,1,2]
输出:1
示例 2:

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

输入:nums = [1]
输出:1

我的分析:

就看哪边有序了,左边有序,去右边找,右边有序,来左边找

代码:

class Solution {
    public int findMin(int[] nums) {
        int left = 0,right = nums.length-1;
        int mid = 0;
        while (left <= right){
            mid = (left+right)/2;
            if(nums[right] > nums[mid]){//mid也可以是最小的,右边有序,来左边找
                right = mid;
            }else {//左边有序,去右边找
                left = mid+1;
            }
        }
        return nums[mid];
    }
}

题目代号: 154 寻找旋转排序数组中的最小值 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

我的分析:

只要旋转过,最左边一定 > 最右边,所以一旦出现左 < 右,就代表找到了

之后就看有序数组在哪边了,有序数组在左边,则最小值一定处于右端
有序数组在右边,则最小值一定处于左端
当中间值和左边值相等的时候,让左端值++,在缩小的区间里来寻找

代码:

class Solution {
    public int findMin(int[] nums) {
        int left = 0,right = nums.length - 1;
        int mid = 0;
        if(nums[left] < nums[right]) return nums[left];//你记住,只要旋转过,那就永远头大尾小;所以只要左边<右边,代表有序了
        while (left  <= right){
            if(nums[left] < nums[right]) return nums[left];//如果二分后是有序数组,则返回最左端的值

            mid = (left + right)/2;
            if(nums[left] < nums[mid]){//左边有序,那说明左边严格单调递增,最小元素必定在mid之后
                left = mid + 1;
            }else if(nums[left] > nums[mid]){//右边有序,那最小元素肯定处于左边
                right = mid;
            }else{//既然相等,比如0 1 2 3 3 3 3 变成3 3 3 3 0 1 2,那最小值肯定处于右边
                left++;
            }
        }
        return nums[mid];
    }
}

题目代号: 540有序数组中的单一元素

题目描述:

给定一个只包含整数的有序数组,每个元素都会出现两次,唯有一个数只会出现一次,找出这个数。

测试用例:

示例 1:

输入: [1,1,2,3,3,4,4,8,8]
输出: 2
示例 2:

输入: [3,3,7,7,10,11,11]
输出: 10

输入:[1,1,2,3,3,4,4,8,8]
输出:2
我的分析:

这道题需要注意的是mid是奇数还是偶数,若是奇数的时候,要转化成偶数(因为情况恰好相反)

当nums[mid] == nums[mid + 1],那就说明结果处于右边
反之就去左边找
最后的结果一定是left =right的时候,所以在while的时候,就不要等于号

代码:

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

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

题目代号: 4 寻找两个正序数组的中位数

题目描述:

给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。

测试用例:

示例 1:

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

输入:nums1 = [1,2], nums2 = [3,4]
输出:2.50000
解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5
示例 3:

输入:nums1 = [0,0], nums2 = [0,0]
输出:0.00000
示例 4:

输入:nums1 = [], nums2 = [1]
输出:1.00000
示例 5:

输入:nums1 = [2], nums2 = []
输出:2.00000

我的分析:

首先考虑特殊情况,如果一个指针为空,那另一个指针就看是齐数个还是偶数个了

然后就两个指针按照顺序来合并成一个大的指针,怎么合并呢?就按
看谁小了,小的就往大数组里放

当放着放着,肯定就有一个数组放完了,那就该另一个数组来放了,另一个数组全都放上去

放完后,就看这个总的数组是奇数个还是偶数个来分了,然后计算结果即可

代码:

class Solution {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        int m = nums1.length,n = nums2.length;
        int[] hebing = new int[m+n];
        //如果有一个数组是空数组
        if(m == 0){
            //那就来另一个数组找
            if(n % 2 == 1){
                return nums2[n/2];
            }else {
                return (nums2[n/2-1]+nums2[n/2])/2.0;
            }
        }
        if(n == 0){
            //那就来另一个数组找
            if(m % 2 == 1){
                return nums1[m/2];
            }else {
                return (nums1[m/2-1]+nums1[m/2])/2.0;
            }
        }
        //现在两个数组里面都有元素了,那就开始合并吧

        int count = 0,index1 = 0,index2 = 0;//三个不断移动的指针
        while (count != (m+n)){
            //合并主要要考虑一个合并完,另一个还没结束
            if(nums1[index1] < nums2[index2]){
                hebing[count++] = nums1[index1++];
            }else {
                hebing[count++] = nums2[index2++];
            }
            if(index1 == m){
                while (index2 != n){
                    hebing[count++] = nums2[index2++];
                }
                break;
            }
            if(index2 == n){
                while (index1 != m){
                    hebing[count++] = nums1[index1++];
                }
                break;
            }
            
        }
        //合并好之后就好弄了
        if(count % 2 == 1){
            return hebing[count/2];
        }else {
            return (hebing[count/2 - 1] + hebing[count/2])/2.0;
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值