leetcode之二分查找算法(升序數組):閉合區間下解決查找 單個元素、左右側邊界元素

https://leetcode-cn.com/problems/find-first-and-last-position-of-element-in-sorted-array/solution/er-fen-cha-zhao-suan-fa-xi-jie-xiang-jie-by-labula/
做了幾個leetcode題,發現查找單個元素、左右側邊界元素的代碼不一樣,有時候mid 加一還是減一,while 裏到底用 <= 還是 <,這個很煩。
我就用mid加一和減一,<= 三種方式查找:單個元素、左側元素、右側元素

二分法查找數組一定要是升序的數組

思路分析:

第一個,最基本的二分查找算法:
因爲我們初始化 right = nums.length - 1
所以決定了我們的「搜索區間」是 [left, right]
所以決定了 while (left <= right)
同時也決定了 left = mid+1 和 right = mid-1
因爲我們只需找到一個 target 的索引即可
所以當 nums[mid] == target 時可以立即返回
第二個,尋找左側邊界的二分查找:
因爲我們初始化 right = nums.length
所以決定了我們的「搜索區間」是 [left, right)
所以決定了 while (left < =right)
同時也決定了 left = mid + 1 和 right = mid

因爲我們需找到 target 的最左側索引
所以當 nums[mid] == target 時不要立即返回
而要收緊右側邊界以鎖定左側邊界,right=mid-1,不斷減小下界
第三個,尋找右側邊界的二分查找:
因爲我們初始化 right = nums.length
所以決定了我們的「搜索區間」是 [left, right)
所以決定了 while (left <= right)
同時也決定了 left = mid + 1 和 right = mid

因爲我們需找到 target 的最右側索引
所以當 nums[mid] == target 時不要立即返回
而要收緊左側邊界以鎖定右側邊界,left=mid+1,不斷增大上界

又因爲收緊左側邊界時必須 left = mid + 1
所以最後無論返回 left 還是 right,必須減一

對於尋找左右邊界的二分搜索,常見的手法是使用左閉右開的「搜索區間」,我們還根據邏輯將「搜索區間」全都統一成了兩端都閉,便於記憶,只要修改兩處即可變化出三種寫法:

//查找單個元素
int binary_search(int[] nums, int target) {
    int left = 0, right = nums.length - 1; 
    while(left <= right) {
        int mid = left + (right - left) / 2;
        if (nums[mid] < target) {
            left = mid + 1;
        } else if (nums[mid] > target) {
            right = mid - 1; 
        } else if(nums[mid] == target) {
            // 直接返回
            return mid;
        }
    }
    // 直接返回
    return -1;
}
//左邊界查找元素
int left_bound(int[] nums, int target) {
    int left = 0, right = nums.length - 1;
    while (left <= right) {
        int mid = left + (right - left) / 2;
        if (nums[mid] < target) {
            left = mid + 1;
        } else if (nums[mid] > target) {
            right = mid - 1;
        } else if (nums[mid] == target) {
            // 別返回,收縮左側邊界
            right = mid - 1;
        }
    }
    // 最後要檢查 left 越界的情況
    if (left >= nums.length || nums[left] != target)
        return -1;
    return left;
}

//右邊界查找元素
int right_bound(int[] nums, int target) {
    int left = 0, right = nums.length - 1;
    while (left <= right) {
        int mid = left + (right - left) / 2;
        if (nums[mid] < target) {
            left = mid + 1;
        } else if (nums[mid] > target) {
            right = mid - 1;
        } else if (nums[mid] == target) {
            // 別返回,收縮右側邊界
            left = mid + 1;
        }
    }
    // 最後要檢查 right 越界的情況
    if (right < 0 || nums[right] != target)
        return -1;
    return right;
}

力摳33 單個元素查找的變形:分段升序二分查找

package _033搜索旋轉排序數組;
/**
 *假設按照升序排序的數組在預先未知的某個點上進行了旋轉。
 * ( 例如,數組 [0,1,2,4,5,6,7] 可能變爲 [4,5,6,7,0,1,2] )。
 * 搜索一個給定的目標值,如果數組中存在這個目標值,則返回它的索引,否則返回 -1 。
 * 你可以假設數組中不存在重複的元素。
 * 你的算法時間複雜度必須是 O(log n) 級別。
 * 示例 1:
 * 輸入: nums = [4,5,6,7,0,1,2], target = 0
 * 輸出: 4
 * 示例 2:
 * 輸入: nums = [4,5,6,7,0,1,2], target = 3
 * 輸出: -1
 * */
//二分查找
public class Solution {
    public int search(int[] nums, int target) {
        if(nums==null||nums.length==0) return -1;
        int left=0,right=nums.length-1;
        while (left<=right){//循環條件:只要left<=right,就一直循環。
            int mid=(left+right)/2;
            //找到就返回
            if (nums[mid]==target) return mid;
            //先判斷左半端,左半端是升序,右半段是降序,所以在左半端升序用二分查找
            if (nums[left]<=nums[mid]){
                //裏面繼續用二分查找的左右下標
                if(target>=nums[left]&&target<nums[mid]){
                    right=mid-1;
                }else {
                    left=mid+1;
                }
                //這裏是判斷右半段,右半段是升序,左半端降序,所以在右半段用二分查找。
            }else {
                //裏面繼續用二分查找的左右下標
                if (target<=nums[right]&&target>nums[mid]){
                    left=mid+1;
                }else {
                    right=mid-1;
                }
            }
        }
        return -1;
    }
}

力扣34 左右側邊界元素查找

package _034在排序數組中查找元素的第一個和最後一個位置;
/**
 * 給定一個按照升序排列的整數數組 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]
 * */
class Solution {
    public int[] searchRange(int[] nums, int target) {
        if(nums==null||nums.length==0) return new int[]{-1,-1};
        //尋找左側邊界的二分查找
        int left=left_search(nums,target);
        //尋找右側邊界的二分查找
        int right=right_search(nums,target);

        return new int[]{left,right};
    }
//找左邊界是不斷的往左壓縮,左右界一起動,直到left>=nums.lengh
    public int left_search(int[] nums,int target){
        int left=0,right=nums.length-1;
        while(left <= right){
            int mid = (left + right)/2;//這個一定要寫裏面
            if (nums[mid]<target){
                left=mid+1;
            }else if (nums[mid]>target){
                right=mid-1;
            }else if (nums[mid]==target){//當找到點時不要急着返回,而是讓他往左邊壓縮,找到左側邊界,所以下界不斷減小;
                right=mid-1;
            }
        }
        if(left >=nums.length||nums[left]!=target){ //注意處理下標越界的情況,如果target比nums數組的值要大,則越界
            return -1;
        }
        return left;//找到左邊界
    }
//找右邊界是不斷的往右壓縮,左右界一起動,直到right<0
    public int right_search(int[] nums,int target){
        int left=0,right=nums.length-1;
        while (left<=right){
            int mid=(left+right)/2;
            if (nums[mid]<target){
                left=mid+1;
            }else if (nums[mid]>target){
                right=mid-1;
            }else if (nums[mid]==target){//當找到點時不要急着返回,而是讓他往右邊壓縮,找到右側邊界,所以上界不斷增大;
                left=mid+1;
            }
        }
        if (right<0||nums[right]!=target){return -1;}//越界檢查,當target比nums值都小時,越界
        return right;
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章