二分法

1.局部最小值

给定一个数组,相邻的数不相等, 返回一个局部最小值的索引。

func getLessIndex(arr []int) int {
    n := len(arr)
    if n == 0 {
        return -1 // no exist
    }
    if n == 1 || arr[0] < arr[1] {
        return 0
    }
    if arr[n - 1] < arr[n - 2] {
        return n - 1
    }

    // 说明在1...n-2之间是这样趋势:\.../
    l, r := 1, n - 2
    var mid int
    for l <= r {
        mid = l + ((r - l) >> 1)
        if arr[mid] > arr[mid + 1] {
            l = mid + 1
        } else if arr[mid] > arr[mid - 1] {
            r = mid - 1
        } else {
            return mid
        }
    }
    return -1
}

2.寻找两个有序数组的中位数

LeetCode4. 寻找两个有序数组的中位数
给定两个大小为 m 和 n 的有序数组 nums1nums2
请你找出这两个有序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n))。
你可以假设 nums1nums2 不会同时为空。

class Solution {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        int n = nums1.length; // 短
        int m = nums2.length; // 长
        if(n > m) {
            return findMedianSortedArrays(nums2, nums1);
        }
        int len = n + m;
        
        // i : nums1前半部分的size
        // j : nums2前半部分的size
        // 保持 : i + j == len / 2
        int i, j; 
        int l = 0;
        int r = n;
        
        // 找到合适的i
        while(l <= r) {
            i = l + ((r - l) >> 1);
            j = len/2 - i;
            if(i != 0 && nums1[i - 1] > nums2[j]) {
                // 说明nums1[i]太大了,需要向左边移动
                r = i - 1;
            } else if(i < n && nums2[j - 1] > nums1[i]) {
                // 说明nums1[i]太小了,需要向右边移动
                l = i + 1;
            } else {
                // 找到答案
                
                // 如果是奇数的话返回 min(num1[i], nums2[j])
                double odd;
                if(i == n) {
                    odd = nums2[j];
                } else if(j == m) {
                    odd = nums1[i];
                } else {
                    odd = Math.min(nums1[i], nums2[j]);
                }
                
                if((len & 1) == 1) {
                    return odd;
                }
                
                double even;
                if(i == 0) {
                    even = nums2[j - 1];
                } else if(j == 0) {
                    even = nums1[i - 1];
                } else {
                    even = Math.max(nums1[i - 1], nums2[j - 1]);
                }
                
                return (odd + even) / 2.0;
            }
        }
        return 0.0;
    }
}

以下几题, 切记首选右侧比较

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

153.寻找旋转排序数组中的最小值
假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。
请找出其中最小的元素。
你可以假设数组中不存在重复元素。

class Solution {
    public int findMin(int[] nums) {
        int l = 0;
        int r = nums.length - 1;
        int mid;
        
        while(l <= r) {
            mid = l + ((r - l) >> 1);
            
            if(nums[mid] < nums[r]) {
                r = mid; // mid 是有可能是答案,所以不能r = mid - 1
            } else { // nums[mid] > nums[r], 说明右边存在谷
                l = mid + 1;
            }
        }
        // 最后答案一定出现在mid位置,但是mid赋给了r,所以r位置就是答案
        return nums[r];
    }
}

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

154. 寻找旋转排序数组中的最小值 II
假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。
请找出其中最小的元素。
注意数组中可能存在重复的元素

class Solution {
    public int findMin(int[] nums) {
        int l = 0;
        int r = nums.length - 1;
        int mid;
        
        while(l <= r) {
            mid = l + ((r - l) >> 1);
            
            if(nums[mid] < nums[r]) { //说明右边有序, mid可能是答案,所以不可以r = mid + 1
                r = mid;
            } else if(nums[mid] > nums[r]) { // 说明右边存在山谷, 但是此处的mid不可能是答案
                l = mid + 1;
            } else { // nums[mid] == nums[r], 之间的数据状况无法判断,可能是平地,也可能是山谷,也可能是山峰, 所以只能一个一个试   
                r--;
            }
        }
        
        // 这里为什么返回的是l, 而不是r呢?
        // 因为在最后一次进入循环也就是"l == r"的时候,
        // 会进入"r--"分支, 所以应该返回 "l",
        // 或者循环条件写成 l < r, 那么最后的情况就是l == r,
        // 此时就是答案,返回l或者r都行,就不用纠结了
        return nums[l];
    }
}

5.旋转数组

LeetCode33. 搜索旋转排序数组
假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。
搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回 -1
你可以假设数组中不存在重复的元素。
你的算法时间复杂度必须是 O(log n) 级别。

class Solution {
    public int search(int[] nums, int target) {
        int l = 0;
        int r = nums.length - 1;
        int mid;
        // 左:[l, mid], 右 : [mid, r]
        // 一定要先判断右,再判断左,否则[3,1], target == 1会出现问题
        while(l <= r) {
            mid = l + ((r - l) >> 1);
            
            if(nums[mid] == target) {
                return mid;
            } else if(nums[mid] < nums[r]) { // 说明右半部分有序
                if(target > nums[mid] && target <= nums[r]) {
                    l = mid + 1;
                } else {
                    r = mid - 1;
                }
            } else { // nums[l] < nums[mid], 说明左半部分有序
                if(target >= nums[l] && target < nums[mid]) {
                    r = mid - 1;
                } else {
                    l = mid + 1;
                }
            }
        }
        
        return  -1;
    }
}

6.旋转数组2

81. 搜索旋转排序数组 II
假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组 [0,0,1,2,2,5,6] 可能变为 [2,5,6,0,0,1,2] )。
编写一个函数来判断给定的目标值是否存在于数组中。若存在返回 true,否则返回 false

class Solution {
    public boolean search(int[] nums, int target) {
        int l = 0;
        int r = nums.length - 1;
        int mid;
        
        while(l <= r) {
            mid = l + ((r - l) >> 1);
            
            if(target == nums[mid]) {
                return true;
            } else if(nums[mid] < nums[r]) { // 右边有序
                if(target > nums[mid] && target <= nums[r]) {
                    l = mid + 1;
                } else {
                    r = mid - 1;
                }
            } else if(nums[mid] > nums[r])  { // 左边有序
                if(target >= nums[l] && target < nums[mid]) {
                    r = mid - 1;
                } else {
                    l = mid + 1;
                }
            } else { // nums[mid] == nums[r], 说明[mid, r]数据状况无法判断,可能相等,可能存在峰和谷
                r--;
            }
        }
        return false;
    }
}

你可能感兴趣的:(二分法)