算法基础:二分查找及相关题目

二分查找

二分查找解析

注:①二分查找个人感觉分为查找边界和具体值两种情况,相比来说具体值就是将nums[mid] == target作为截止条件跳出,查找边界就按头尾指针相遇为截止。
②普通二分使用mid = (head + tail) / 2;有可能产生溢出,可以用mid = head + ((tail - head) / 2);防止溢出情况(除二操作还可用>>1右移位运算优化)
③二分查找内while循环判断条件通常有<和<=,这点可从最后跳出条件决定(取值区间是否存在,例如:用<时,跳出条件为头尾相遇,即取值区间内只存在一个值;而<=跳出是区间内无值)

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

题目实际就是在有序数组中,查找目标元素起始位置和终点位置。
查看目标元素的始末,可以看做查找目标元素的起始和目标元素+1的起始之差。

public int[] searchRange(int[] nums, int target) {
        int start = findMid(nums, target);
        int end = findMid(nums, target + 1) - 1;

        if (start <= end) {
            return new int[] {start, end};
        }
        return new int[] {-1, -1};
    }

    private int findMid(int[] nums, int target) {
        int head = 0;
        int tail = nums.length - 1;
        int mid;
        while (head <= tail) {
            mid = head + ((tail - head) / 2);
            if (nums[mid] < target) {
                head = mid + 1;
            } else {
                tail = mid - 1;
            }
        }
        return head;
    }

普通二分查找具体数不一定能定位到相同数中的起始位置,所以不需要将nums[mid] == target作为截止条件,查找边界即可。
在结果中,只要有目标数存在,start必然小于tail,

搜索旋转排序数组

给定一个排序但按某位旋转后的数组(如[4,5,6,7,0,1,2]),查找目标值。

public int search(int[] nums, int target) {
        int head = 0;
        int tail = nums.length - 1;
        int mid;
        while (head <= tail) {
            mid = (head + tail) / 2;
            if (nums[mid] == target) {
                return mid;
            }
            //在有序段落内进行二分判断
            //故先查找有序部分相对中值位置
            if (nums[mid] >= nums[head]) {
            	//中值比头值大(中值必在头之后),说明中值以左为有序段落
            	//当目标值比头值大,比中值小,说明目标值在头和中之间
                if (target >= nums[head] && target < nums[mid]) {
                    tail = mid - 1;
                } else {
                //当目标值不符合上述条件,就说明在中值之后
                    head = mid + 1;
                }
            } else {
           		//中值比头值小,说明中值以右是有序段落
            	//当目标值比中值大,比尾值小,说明目标值在中和尾之间
                if (target > nums[mid] && target <= nums[tail]) {
                    head = mid + 1;
                } else {
                    tail = mid - 1;
                }
            }
        }
        return -1;
    }

采取查找具体值的写法,但在更改基准头尾时,需要额外判断(见注释)。
主要思维是中值划分出来的两部分一部分必然有序,另一部分部分有序,而二分查找思路只能在有序部分实行,所以需要判断有序部分的位置,若有序在中值左侧,则依据目标值在头-中之间进行判断;右侧则依据目标值在中-尾之间判断。

public int search(int[] nums, int target) {
        int head = 0;
        int tail = nums.length - 1;
        int mid;
        while (head <= tail) {
            mid = (head + tail) / 2;
            if (nums[mid] == target) {
                return mid;
            }
            //当目标值比0值大,说明在旋转点以左
            if (target >= nums[0]) {
                if (nums[mid] < nums[0]) {
                	//当中值比0值小,说明中值位于旋转点以右,故中值右侧不用再判断
                    nums[mid] = Integer.MAX_VALUE;
                }
            } else {
            //当目标值比0值小,说明在旋转点以右
                if (nums[mid] >= nums[0]) {
                	//当中值比0值大,说明中值位于旋转点以左,故中值左侧不用再判断
                    nums[mid] = Integer.MIN_VALUE;
                }
            }
            if (nums[mid] < target) {
                head = mid + 1;
            } else {
                tail = mid - 1;
            }
        }
        return -1;
    }

依旧采用查找具体值情况写二分查找,该思路目的将目标值依据旋转点进行判断,并且依据结果改变mid值,例如位于目标值位于旋转点以右,而中值位于旋转点以左,那么将当前中值改为无限小,强制向右方查找即可。

搜索二维矩阵

二维有序矩阵查找对应值
在这里插入图片描述

public boolean searchMatrix(int[][] matrix, int target) {
        int row = matrix.length;
        int col = matrix[0].length;
        int head = 0;
        int tail = row * col - 1;
        int mid;
        while (head <= tail) {
            mid = (head + tail) / 2;
            if (matrix[mid / col][mid % col] == target) {
                return true;
            }
            if (matrix[mid / col][mid % col] < target) {
                head = mid + 1;
            } else {
                tail = mid - 1;
            }
        }
        return false;
    }

想象将二维展开成一维处理即可,注意行数用除,列数用取余即可。

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

public int findMin(int[] nums) {
        int head = 0;
        int tail = nums.length - 1;
        int mid;
        while (head < tail) {
            mid = (head + tail) / 2;
            if (nums[mid] < nums[tail]) {
                tail = mid;
            } else {
                head = mid + 1;
            }
        }

        return nums[head];
    }

不同旋转数组实际都是由两个开头数不同的递增数组组成,那么头、中、尾三值可能有如下情况:
①头值<中值,中值<尾值:说明处于一个递增数组中
说明最小值在左侧,应调节右边界
②头值<中值,中值>尾值:说明中值处于两个数组交界
说明最小值在右侧(因为旋转后的数组右侧为小值,左侧为大值),应调节左边界
③头值>中值,中值>尾值:该情况不发生
④头值>中值,中值<尾值:说明中值处于左侧部分
说明最小值在左侧,应调整右边界

综上:
中值与尾值进行比较即可得出哪个边界需要调整,中值<尾值:调整右边界 中值>尾值:调整左边界

寻找峰值

查找数组中比其两侧值都大的数。

public int findPeakElement(int[] nums) {
        int head = 0;
        int tail = nums.length - 1;
        int mid;
        while (head < tail) {
            mid = (head + tail + 1) / 2;
            if (nums[mid] < nums[mid - 1]) {
                tail = mid - 1;
            } else {
                head = mid;
            }
        }
        return head;
    }

该问题思路依据中值分析峰值所在位置。
若中值比左侧值小,则该中值及其右侧不可能成为峰值,且其左侧值可能为峰值,故需要调节右边界。
否则(中值>=左侧值)包含中值在内的右侧数可能称为峰值,故调节左边界。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

魔幻音

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值