力扣解题思路:二分法 纠错记录

本文详细介绍了二分法在解决力扣(LeetCode)题目中的应用,强调了二分法的边界条件判断和计算中防止溢出的技巧。通过分析多个具体题目,如寻找旋转排序数组中的最小值、平方根等,揭示了二分法的思考过程和解题策略。同时,提醒在处理旋转数组和山脉数组时,注意重复元素和边界条件的特殊处理。
摘要由CSDN通过智能技术生成

二分法

每次做题,只要看到题目要求说时间复杂度要不能大于O(logn)我就脑瓜子疼,因为一般这种情况就是需要用到二分法了,二分法的原理其实很简单,就是一次次的找数组中间的mid数,验证其是否满足要求,然后根据实际情况来移动low或者high。然而二分法最头疼的不在于他的原理,而在于他的边界条件的取值,例如while循环的条件中应该是l<h呢还是l<=h呢,以及当mid不满足条件后是h = mid还是h=mid+1还是l = mid还是l=mid+1呢?

其实判断这个也是有一定规律的,比如当mid不满足条件后是h = mid(因为mid可能是答案),那这时候while循环中的条件一般为l<h,因为如果此时l<=h的话会陷入一个死循环的状态,同样的道理,h=mid+1(l=mid-1)往往与l<=h配合使用,当然,具体情况应该具体分析,接下来我会列举几道使用二分法解题的例题。

另外,还有一个比较重要的点就是,我们写mid的时候要养成一个良好的习惯,不要使用mid = (h+l)/2,尽量使用mid = l + (h-l)>>2,这是因为l + h 可能出现加法溢出,也就是说加法的结果大于整型能够表示的范围。但是 l 和 h 都为正数,因此 h - l 不会出现加法溢出问题。所以,最好使用第二种计算法方法。

先上一个比较简单的题目:

69. x 的平方根


思路:这一题题目没有给出时间复杂度的要求,所以我是用的暴力法哈哈:

public int mySqrt(int x) {
   
        long i = 0;
        long n = 0;        
        while(n<=x){
               
	        i++;            
	        n = i*i;        
        }        
        return (int)i-1;
}

接下来使用二分:一个数 x 的开方 sqrt 一定在 0 ~ x 之间,并且满足 sqrt == x / sqrt。可以利用二分查找在 0 ~ x 之间查找 sqrt。

    if (x <= 1) {
   
        return x;
    }
    int l = 1, h = x;
    while (l <= h) {
   
        int mid = l + (h - l) >> 2;
        int sqrt = x / mid;
        if (sqrt == mid) {
   
            return mid;
        } else if (mid > sqrt) {
   
            h = mid - 1;
        } else {
   
            l = mid + 1;
        }
    }

二分法是写好了,那么我们是return h还是return l呢?不如我们举个例子:对于 x = 8,它的开方是 2.82842…,最后应该返回 2 而不是 3。在循环条件为 l <= h 并且循环退出时,h 总是比 l 小 1,也就是说 h = 2,l = 3,因此最后的返回值应该为 h 而不是 l。

744. 寻找比目标字母大的最小字母


思路:显然题目也没有做任何规定,暴力法也是可行的。这里我们还是主要看二分法:由题目,给定一个有序的字符数组 letters 和一个字符 target,要求找出 letters 中大于 target 的最小字符,如果找不到就返回第 1 个字符。

public char nextGreatestLetter(char[] letters, char target) {
           
	int l = 0, h = letters.length;        
	while (l < h) {
               
		int m = l + (h - l) >> 2;            
		if (letters[m] <= target) l = m + 1;            
		else h = m;        
	}        
	return letters[l % letters.length];
}

或者:

    public char nextGreatestLetter(char[] letters, char target) {
   
        int n = letters.length;
        int l = 0, h = n - 1;
        while (l <= h) {
   
            int m = l + (h - l) / 2;
            if (letters[m] <= target) {
   
                l = m + 1;
            } else {
   
                h = m - 1;
            }
        }
        return letters[l%n];
    }

一般情况下letters[l]是返回值,当目标字母比所有字母都大,需要返回letters[0]即letters[l % len]。

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


思路:我们只看二分法,若nums[m] <= nums[h]则旋转点在左边,可能就是mid,所以h = m,反之,旋转点在右边则l = m + 1,代码如下:

public int findMin(int[] nums) {
   
    int l = 0, h = nums.length - 1;
    while (l < h) {
   
        int m = l + (h - l) / 2;
        if (nums[m] <= nums[h]) {
   
            h = m;
        } else {
   
            l = m + 1;
        }
    }
    return nums[l];
}

因为退出循环时h = l,所以,此时return nums[h] 也是可以的,结果都是对的。

然后我试了一下和左边界相比较,发现有各种错误。。。我还是放弃了,以后记住一定要尽量和右边界比较!!!!!

但是其实用下面这一题的答案,左边界又是可以的!继续往后看:

    public int findMin(int[] numbers) {
   
        int l = 0, r = numbers.length - 1;
        while (l < r) {
   
            if(numbers[l]<numbers[r]){
   
                return numbers[l];
            }
            int mid = ((r - l) >> 1) + l;
            //只要右边比中间大,那右边一定是有序数组
            if (numbers[l] < numbers[mid]) {
   
                l = mid + 1;
            } else if (numbers[l] > numbers[mid]) {
   
                r = mid;
             //去重
            } else l++;
        }
        return numbers[l];
    }

剑指 Offer 11. 旋转数组的最小数字

思路:这一题和上一题基本一样,区别在于这一题是含有重复数字的!所以直接用上面的方法会导致错误。所以需要去重,比如[3,3,1,3]重复数字被换到两边会导致右边界判断无效!因为一定要和非重复数字比较才会时有意义的!所以我们移动右边界指针:

    public int minArray(int[] numbers) {
   
        int l = 0, r = numbers.length - 1;
        while (l < r) {
   
            int mid = ((r - l) >> 1) + l;
            //只要右边比中间大,那右边一定是有序数组
            if (numbers[r] > numbers[mid]) {
   
                r = mid;
            } else if (numbers[r] < numbers[mid]) {
   
                l = mid + 1;
             //去重
            } else r--;
        }
        return numbers[l];
    }

实际上也可以用左边界:

    public int minArray(int[] numbers) {
   
        int l = 0, r = numbers.length - 1;
        while (l < r) {
   
            if(numbers[l]<numbers[r]){
   
                return numbers[l];
            }
            int mid = ((r - l) >> 1) + l;
            //只要右边比中间大,那右边一定是有序数组
            if (numbers[l] < numbers[mid]) {
   
                l = mid + 1;
            } else if (numbers[l] > numbers[mid]) {
   
                r = mid;
             //去重
            } else l++;
        }
        return numbers[l];
    }

但是一定要注意;

if(numbers[l]<numbers[r]){
   
       return numbers[l];
}

这句一定不可以掉!!因为有非逆序数组的测试用例!

面试题 10.05. 稀疏数组搜索

思路:
在这里插入图片描述
这一题和上面一题比较类似,但这一题不是去重,而是去除“”空串。注意字符串比较大小时用cpmpareTo,当遇到mid的位置为空我们就要移动边界,但要注意需要先判断边界是否为正确答案,如果不判断会导致为正确答案的边界被移除:


                
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值