算法打卡第十一天 牛客BM17 二分查找-I 和BM18 二维数组中的查找、BM19 寻找峰值

        今天是秋招预备队算法篇打卡第十一天,已经状态不太好,先写两道题压压惊

        问题1:二分查找-Ⅰ

        描述:        

         解题方法:

        1、暴力遍历(仅思路)

        遍历一遍数组元素,和目标值作比较,若能找到,则返回数组下标,反之,则返回-1
            1)遍历数组
            2)比较数组当前值与目标值
            3)若存在与目标值相等的元素,则返回当前值的下标,若遍历完整个数组都不存在,则返回-1
 

        时间复杂度:O(n),需要遍历一遍数组
        空间复杂度:O(1),不需要额外空间

        2、二分查找(推荐使用)

        因为数组为无重复升序的,所以若存在目标值,那么它一定位于数组的某个区间,若不存在,则在整个区间中都找不到。我们每次以当前区间的中点划分区间,若目标值小于中点值,那么目标值位于中点前半部分,若目标值大于中点值,那么目标值位于中点后半部分,反复递归,直到确定目标值是否存在
            1)从数组中取出当前区间的中点值
            2)比较中点值和目标值,如果中间值等于目标即找到了,可返回下标,如果中点值大于目标,说明中点以后的都大于目标,因此目标在中点左半区间,如果中点值小于目标,则相反
            3)根据第二步的比较调整当前区间,直到不存在元素,即左边界大于右边界

import java.util.*;

public class Solution {

    public int search (int[] nums, int target) {
        //确定数组区间,即左右边界
        int left = 0;
        int right = nums.length - 1;
        //寻找目标值
        while(left <= right){
            //中点
            int middle = (right - left) / 2 + left;
            //存在,返回目标值下标
            if(target == nums[middle]){
                return middle;
            }
            //目标值的前半部分
            else if(target < nums[middle]){
                right = middle - 1;
            }
            //目标值在后半部分
            else{
                left = middle + 1;
            }
        }
        return -1;
    }
}

        时间复杂度:O(log2n),对长度为nnn的数组进行二分,最坏情况就是取2的对数

        空间复杂度:O(1),常数级变量,无额外辅助空间

        

        问题2:二维数组中的查找

        描述:

         解题方法:

        1、暴力遍历法(仅思路)

        遍历二维数组中每个一维数组的元素值,与目标值进行比较,若存在,则返回true,若不存在,则返回false
    
        时间复杂度:O(mn),遍历二维数组所有元素m*n
        空间复杂度:O(1),不需要辅助空间

        2、二分法(仅思路)

        因为二维数组每一行和列都按照从左到右递增的顺序排序,所以可以利用列排除一维数组第一个值大于目标值的,然后在剩余的一维数组中使用二分法查找目标值

        时间复杂度:O(mlogn),遍历二维数组所有元素遍历一遍二维数组,并且同时在每个一维数组中使用二分法查找目标值
        空间复杂度:O(1),不需要辅助空间

        3、线性查找(推荐使用)        

        首先看四个角,左上与右下必定为最小值与最大值,而左下与右上就有规律了:左下元素大于它上方的元素,小于它右方的元素,右上元素与之相反。既然左下角元素有这么一种规律,相当于将要查找的部分分成了一个大区间和小区间,每次与左下角元素比较,我们就知道目标值应该在哪部分中,于是可以利用分治思维来做
            1)首先获取矩阵的两个边长,判断特殊情况。
            2)首先以左下角为起点,若是它小于目标元素,则往右移动去找大的,若是它大于目标元素,则往上移动去找小的。
            3)若是移动到了矩阵边界也没找到,说明矩阵中不存在目标值。

public class Solution {
    public boolean Find(int target, int [][] array) {
        //判断特殊
        if(array.length == 0 || array[0].length == 0){
            return false;
        }
        //获取二维数组边界
        int r = array.length - 1;
        int c = array[0].length - 1;
        //向左移动步数
        int left = 0;
        //左下元素值
        int index = array[r][left];
        //在二维数组内
        while(r >= 0 && c >= left){
            //找到目标值
            if(index == target){
                return true;
            }
            //来到第一行或者最后一列,即边界,且当前值与目标值不等,返回false
            //此处的目的是防止数组越界,第一行或者最后一列查找后,没其他可找的行或列了
            else if(r == 0 || left == c){
                return false;
            }
            //当前值大于目标值
            else if(index > target){
                //向上移动当前值
                r--;
                index = array[r][left];
            }
            //当前值小于目标值
            else{
                //右移当前值
                left++;
                index = array[r][left];
            }
        }
        //不存在目标值
        return false;
    }
}

         上述的while循环体较为冗余,可以进行一定的优化,如下所示:

        因为是先获取了下标值,再判断下标值是否合法,所以不用担心数组越界问题,而上面的while循环体内,虽然会先判断是否在数组范围内,但是在内部又改变了数组下标,并且直接取获取数组下标值,有可能会导致数组越界

while(r >= 0 && c >= left){
            index = array[r][left];
            //找到目标值
            if(index == target){
                return true;
            }
            //当前值大于目标值
            else if(index > target){
                //向上移动当前值
                r--;
            }
            //当前值小于目标值
            else{
                //右移当前值
                left++;
            }
        }

         或者:

for(int i = r , j = 0; i >= 0 && j <= c; ){ 
    //元素较大,往上走
    if(array[i][j] > target)   
        i--;
    //元素较小,往右走
    else if(array[i][j] < target) 
        j++;
    else
        return true;
    }

        时间复杂度:O(m+n),遍历矩阵的时候,最多经过矩阵的一行一列

        空间复杂度:O(1),常数级变量,无额外辅助空间

        

        问题3:寻找峰值

        描述:

 

 

       解题方法:

        1、二分法(推荐使用)

        首先想到的就是遍历数组,获取最大值,或者比较当前元素值和其左右元素值,若大于相邻元素,则是一个峰值,反之,则不是,但是这样的效率很低,需要将整个链表遍历完,并且比较其左右两边的值。那么有什么办法可以提高效率呢?

        由题目对于所有有效的 i 都有 nums[i] != nums[i + 1]可知,在数组中相邻的元素一定存在大小之分,并且我们发现峰值一定会出现在比当前元素值大的相邻元素一边(如果 1 2 3 为数组中相邻的三个元素,其中2为当前元素,那么一定能在元素3这边找到一个峰值),也就是说我们随机指定一个当前节点,那么就只需要找比它的相邻元素一边,但是随机节点可以每次都是边界元素,造成最坏的情况(遍历整个数组),所以我们通过指定随机节点的位置,即数组的中点,可以避免随机的最坏情况,且便于我们收敛区间于一点,退出循环。

        每次取当前区间的中间元素,判断中间元素与相邻元素的大小情况,若左相邻元素大于中间元素,则一定有峰值在左边区间,反之,则右边区间一定存在峰值,直到区间收敛到一个元素值,这个元素就是峰值

import java.util.*;


public class Solution {

    public int findPeakElement (int[] nums) {
        //区间
        int left = 0, right = nums.length - 1;
        //二分缩小区间
        while(left < right){
            //中点
            int middle = (right - left) / 2 + left;
            //这段注释内的方法错误,无法解决问题,需要添加一定的条件
//             //左邻元素大于中间元素,峰值存在左半区间
//             if(nums[middle] > nums[middle - 1]){
//                 left = middle;
//             }
//             //左邻元素小于中间元素,峰值存在右半区间
//             else{
//                 right = middle -1;
//             }
            //右相邻元素较小,不一定有峰值
            if(nums[middle] > nums[middle + 1]){
                right = middle;
            }
            //右边相邻元素较大,一定存在峰值
            else{
                left = middle + 1;
            }
            
        }
        return left;
    }
}

        在解题过程中,我发现若是用左边的邻接元素来作为比较的参考值(即下面代码被注释的部分),那么可能会进入死循环或者数组越界,我猜测原因应该是当和不管为奇数还是偶数时,取它的中间值要么刚好为中间元素(奇数),要么是中点的前一个元素(偶数),这个时候左邻元素可能不存在(middle = 0),此时会产生数组越界,而且当区间只剩下两个元素时(假设:2 3),中间元素为前一个元素(2),那么此时比较的一直都是前一个元素(2)和元素(2)的前一个元素(此元素不在区间内了),若元素(2)前面的元素小于元素(2),那么if条件恒成立,此时middle一直等于left(left=middle,那么这两个元素计算出的middle恒等于left),无法比较到剩下的两个元素,形成死循环,而如果使用右邻接元素就可以避免这个问题,不管如何middl都会小于right,所以不会数组越界,并且能保证每次都是和区间内(middle+1一定在区间内)的元素进行比较,所以推荐直接使用右邻元素进行比较

        时间复杂度:O(log2n),二分法最坏情况对整个数组连续二分,最多能分log2nlog_2nlog2​n次

        空间复杂度:O(1),常数级变量,无额外辅助空间

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值