LeetCode有代表性的题解---应用二分查找(四)

1.求开方

69. x 的平方根

实现 int sqrt(int x) 函数。

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

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

示例 1:

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

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

解题思想:对于较大的数(2147483647)常规的解法可能会超出int范围,结合二分法进行可缩小计算范围

JAVA代码:

/**
 * Created by 高先森 on 2020/6/26.
 */
public class leetcode_69_sqrtX {
    public static void main(String[] args){
        System.out.println(mySqrt(2147483647));//2147483647
    }
    //1.常规解法(错误,超范围)
    public static int mySqrt(int x) {
        if (x <= 1)
            return x;
        int left = 1, right = x / 2, sum0, sum;
        while (left <= right) {
            sum0 = left * left; //当left相对大时sum超出int范围
            sum = (left + 1) * (left + 1);
            if (sum0 == x)
                return left;
            else if (sum0 < x && sum > x)
                return left;
            else
                left++;
        }
        return 0;
    }
    //2.二分查找帮助缩小范围
    public static int mySqrt1(int x) {
        if (x <= 1)
            return x;
        int left = 1, right = x / 2, mid;
        while (left <= right) {
            mid=left+(right-left)/2;//相对于mid=(left+right)/2 left+right可能超出int范围
            //如果mid值很大,直接用mid平方判断会超出int范围
            if (mid > x/mid)  //可以理解成:mid*mid>x ,mid就是x的平方值-过大,通过right缩小
                right=mid-1;
            else if(mid<x/mid)//可以理解成:mid*mid<x ,mid就是x的平方值-过小,通过left放大
                left=mid+1;
            else  //正好得到x的平方值
                return mid;
        }
        //在退出while循环后,right要小于left,而x的平方是舍去小数取整的,所以取较小的那个数right sqrt(8)=2
        return right;
    }
}

2.大于给定元素的最小元素

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

给你一个排序后的字符列表 letters ,列表中只包含小写英文字母。另给出一个目标字母 target,请你寻找在这一有序列表里比目标字母大的最小字母。在比较时,字母是依序循环出现的。举个例子:如果目标字母 target = 'z' 并且字符列表为 letters = ['a', 'b'],则答案返回 'a'

示例:

输入:
letters = ["c", "f", "j"]
target = "a"
输出: "c"

输入:
letters = ["c", "f", "j"]
target = "c"
输出: "f"

输入:
letters = ["c", "f", "j"]
target = "d"
输出: "f"

输入:
letters = ["c", "f", "j"]
target = "g"
输出: "j"

输入:
letters = ["c", "f", "j"]
target = "j"
输出: "c"

输入:
letters = ["c", "f", "j"]
target = "k"
输出: "c"
 

提示:

letters长度范围在[2, 10000]区间内。
letters 仅由小写字母组成,最少包含两个不同的字母。
目标字母target 是一个小写字母。

解题思想:由于字母已经排好序,通过二分查找法进行确定,当遍历完一遍不存在比目标字符大的字符时,返回letters[0]

JAVA代码:

/**
 * Created by 高先森 on 2020/6/26.
 */
public class leetcode_744_findSmallestLetterGreaterThanTarget {
    public static void main(String[] args){
        char[] letters={'c', 'f', 'j'};
        System.out.println(nextGreatestLetter(letters,'c'));
    }
    public static char nextGreatestLetter(char[] letters, char target) {
        int len=letters.length;
        int left=0,right=len-1,mid;
        while (left<=right){
            mid=left+(right-left)/2;//写成(left+right)/2,left+right容易溢出
            if(letters[mid]>target)//此处不能加等号,因为要找的是大于目标字符的最小字符
                right=mid-1;
            else
                left=mid+1;
        }
        //若通过二分法能找到大于目标字符的最小字符则返回left位置的字符,否则返回第一个字符
        return left>=len?letters[0]:letters[left];
    }

}

3.有序数组的SingleElement

540. 有序数组中的单一元素

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

示例 1:

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

输入: [3,3,7,7,10,11,11]
输出: 10
注意: 您的方案应该在 O(log n)时间复杂度和 O(1)空间复杂度中运行。

解题思想:此题的难点在于在 O(log n)时间复杂度和 O(1)空间复杂度中运行,因为数字有序,且要求在O(log n)时间复杂度内完成,则结合二分查找的思想进行查找。对于mid左右两端数字的个数,可以分为奇偶数两种情况,当mid左右两端的数字个数为奇数时,单一元素只能在mid左端或者右端;当mid两端数字的个数为偶数时,单一数字可能是mid也可能在mid左右端。

JAVA代码如下:

/**
 * Created by 高先森 on 2020/6/26.
 */
public class leetcode_540_singleElementInaSortedArray {
    public static void main(String[] args){
        int[] ints={1,1,2,3,3,4,4,8,8};
        int[] ints1={1,1,8};
        System.out.println(singleNonDuplicate(ints1));
    }
    public static int singleNonDuplicate(int[] nums) {
        int len=nums.length;
        int left=0,right=len-1,mid;
        int num=0;//标记mid左右两边数字个数(左右两边数字个数相同,因为总数字个数为奇数个)
        while (left<=right){
            mid=left+(right-left)/2;
            num=mid-left;
            if (left==right)
                return nums[left];
            if(num%2!=0) {//左右两端数字为奇数个,单一数字一定不是mid,只可能在mid左端或者右端
                if(nums[mid]==nums[mid-1]){//mid位置和mid-1位置数匹配,且mid左端有奇数个数字,所以单一数字一定在右端
                    left=mid+1;
                }else{//都在在左端
                    right=mid-1;
                }
            }else {//左右两端数字为偶数个(包括0个),单一数字可能是mid位置,也可能是mid左端或者右端
                if(nums[mid]==nums[mid-1]){//mid和mid-1位置数字相等,因为mid左右两端均为偶数个数字,所以单一数字在mid左端
                    right=mid-2;
                }else if(nums[mid]==nums[mid+1]){//mid和mid+1位置数字相等,因为mid左右两端均为偶数个数字,所以单一数字在mid右端
                    left=mid+2;
                }else //mid和mid-1和mid+1位置数字都不等,mid位置就是单一数字
                    return nums[mid];
            }
        }
        return 0;
    }
}

4.旋转数组的最小数字

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

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

( 例如,数组 [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) {
        int len=nums.length;
        if(nums==null||len<1)
            return -1;
        int left=0,mid,right=len-1;
        while(left<=right){
            mid=left+(right-left)/2;
            if(nums[mid]<nums[right]){//说明mid右侧顺序符合升序 eg:4 5 0 1 2 3 eg:3,4,5,1,2
                right=mid;//因为找最小值所以此处只能是mid,不能是mid-1
            }else{ //eg:  5 6 7 8 1 2 3 4  
                left=mid+1;//因为找最小值,所以此处只可能在mid+1--right之间
            }
        }
        return nums[right];
    }
}


5.查找区间
    34. 在排序数组中查找元素的第一个和最后一个位置

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

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

如果数组中不存在目标值,返回 [-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]

解题思路:由于数组有序且要求在O(log n)复杂度解决,所以可利用二分查找法解决,使用二分法进行搜索,可以使用一次二分或者使用两次二分分别查找首次和最后一次出现的位置。

JAVA代码:

    方法一:(个人认为方法一简单好理解)

class Solution {
    public static int[] searchRange(int[] nums, int target) {
        int[] index=new int[2];
        index[0]=findTargetFirstIndex(nums,target);
        index[1]=findTargetLastIndex(nums,target);
        return  index;
    }
    public static int findTargetFirstIndex(int[] nums, int target) {
        int len=nums.length;
        int left=0,right=len-1,mid,minIndex=len;//minIndex赋值为较大值
        while (left<=right){
            mid=left+(right-left)/2;
            if(nums[mid]<target)
                left=mid+1;
            else if(nums[mid]>target)
                right=mid-1;
            else{
                minIndex=minIndex<mid?minIndex:mid;
                right=mid-1;
            }
        }
        //如果没找到只能返回-1否则返回minIndex
        return minIndex==len?-1:minIndex;
    }
    public static int findTargetLastIndex(int[] nums, int target) {
        int len=nums.length;
        int left=0,right=len-1,mid,maxIndex=-1;//maxIndex赋值为较小值
        while (left<=right){
            mid=left+(right-left)/2;
            if(nums[mid]<target)
                left=mid+1;
            else if(nums[mid]>target)
                right=mid-1;
            else{
                maxIndex=maxIndex>mid?maxIndex:mid;
                left=mid+1;
            }
        }
        //如果没找到只能返回-1(maxIndex本身初始话就是-1)否则返回minIndex
        return maxIndex;
    }
}

    方法二:

class Solution {
    public static int[] searchRange(int[] nums, int target) {
        if(nums.length==0)
            return new int[]{-1,-1};
        int[] index=new int[2];
        //使用二分查找,先查目标数字第一次出现的位置,再查最后出现的位置
        index[0]=findTargetIndex(nums,target);
        index[1]=findTargetIndex(nums,target+1)-1;
        //index[0]==nums.length : 数组中没有目标数且target存放的位置为数组中最后一个数字之后
        //nums[index[0]]!=target:没在数组中找到目标数
        if(index[0]==nums.length||nums[index[0]]!=target)
            return new int[]{-1, -1};
        else
            return index;
    }
    public static int findTargetIndex(int[] nums, int target) {
        int len=nums.length;
        int left=0,right=len-1,mid,minIndex=len;
        while (left<=right){
            mid=left+(right-left)/2;
            if(nums[mid]<target)
                left=mid+1;
            else if(nums[mid]>target)
                right=mid-1;
            else{//找到目标字符,则记录位置下标,继续向前找
                minIndex=minIndex<mid?minIndex:mid;
                right=mid-1;
            }
        }
        //如果没找到只能返回-1否则返回minIndex
        return minIndex<left?minIndex:left;
    }
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值