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;
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章