DEMO

ps:https://segmentfault.com/a/1190000038594475

一、基本的二分搜索

因为我们初始化 right = nums.length - 1
所以决定了我们的「搜索区间」是 [left, right]
所以决定了 while (left <= right)
同时也决定了 left = mid+1 和 right = mid-1
因为我们只需找到一个 target 的索引即可
所以当 nums[mid] == target 时可以立即返回
int binarySearch(int[] nums, int target) {
    int left = 0; 
    int right = nums.length - 1; // 注意

    while(left <= right) {
        int mid = left + (right - left) / 2;
        if(nums[mid] == target)
            return mid; 
        else if (nums[mid] < target)
            left = mid + 1; // 注意
        else if (nums[mid] > target)
            right = mid - 1; // 注意
    }
    return -1;
}

二、寻找左侧边界的二分搜索

因为我们初始化 right = nums.length
所以决定了我们的「搜索区间」是 [left, right)
所以决定了 while (left < right)
同时也决定了 left = mid + 1 和 right = mid
因为我们需找到 target 的最左侧索引
所以当 nums[mid] == target 时不要立即返回
而要收紧右侧边界以锁定左侧边界
int left_bound(int[] nums, int target) {
    if (nums.length == 0) return -1;
    int left = 0;
    int right = nums.length; // 注意
    
    while (left < right) { // 注意
        int mid = (left + right) / 2;
        if (nums[mid] == target) {
            right = mid;
        } else if (nums[mid] < target) {
            left = mid + 1;
        } else if (nums[mid] > target) {
            right = mid; // 注意
        }
    }
    return left;
}

为什么没有返回 -1 的操作?如果 nums 中不存在 target 这个值,怎么办?

while (left < right) {
    //...
}
// target 比所有数都大
if (left == nums.length) return -1;
// 类似之前算法的处理方式
return nums[left] == target ? left : -1;

三、寻找右侧边界的二分搜索

因为我们初始化 right = nums.length
所以决定了我们的「搜索区间」是 [left, right)
所以决定了 while (left < right)
同时也决定了 left = mid + 1 和 right = mid
因为我们需找到 target 的最右侧索引
所以当 nums[mid] == target 时不要立即返回
而要收紧左侧边界以锁定右侧边界
又因为收紧左侧边界时必须 left = mid + 1
所以最后无论返回 left 还是 right,必须减一
int right_bound(int[] nums, int target) {
    if (nums.length == 0) return -1;
    int left = 0, right = nums.length;
    
    while (left < right) {
        int mid = (left + right) / 2;
        if (nums[mid] == target) {
            left = mid + 1; // 注意
        } else if (nums[mid] < target) {
            left = mid + 1;
        } else if (nums[mid] > target) {
            right = mid;
        }
    }
    return left - 1; // 注意
}

为什么没有返回 -1 的操作?如果 nums 中不存在 target 这个值,怎么办?

while (left < right) {
    // ...
}
if (left == 0) return -1;
return nums[left-1] == target ? (left-1) : -1;

示例:

1、爱吃香蕉的珂珂

问题:

珂珂喜欢吃香蕉。这里有 n 堆香蕉,第 i 堆中有 piles[i] 根香蕉。警卫已经离开了,将在 h 小时后回来。

珂珂可以决定她吃香蕉的速度 k (单位:根/小时)。每个小时,她将会选择一堆香蕉,从中吃掉 k 根。如果这堆香蕉少于 k 根,她将吃掉这堆的所有香蕉,然后这一小时内不会再吃更多的香蕉。  

珂珂喜欢慢慢吃,但仍然想在警卫回来前吃掉所有的香蕉。

返回她可以在 h 小时内吃掉所有香蕉的最小速度 k(k 为整数)。

示例 1:

输入:piles = [3,6,7,11], h = 8
输出:4
示例 2:

输入:piles = [30,11,23,4,20], h = 5
输出:30
示例 3:

输入:piles = [30,11,23,4,20], h = 6
输出:23

 1 class Solution {
 2     public int minEatingSpeed(int[] piles, int h) {
 3         int left = 1;
 4         int right = 1000000001; // 左侧
 5         while(left<right){
 6             int mid = left + ((right-left)>>1);
 7             if(f(piles,mid)==h){
 8                 right = mid;
 9             }else if(f(piles,mid)>h){
10                 left = mid + 1;
11             }else if(f(piles,mid)<h){
12                 right = mid;
13             }
14         }
15         return left;
16     }
17 
18     // 吃香蕉的速度为x根/小时,需要f(x)小时吃完所有香蕉(f(x)是单调递减的函数)
19     public int f(int[] piles,int x){
20         int hours = 0;
21         for(int i=0;i<piles.length;i++){
22             hours += piles[i]/x;
23             if(piles[i]%x>0){
24                 hours++;
25             }
26         }
27         return hours;
28     }
29 }
View Code

2、在D天内送达包裹的能力

问题:

传送带上的包裹必须在 days 天内从一个港口运送到另一个港口。

传送带上的第 i 个包裹的重量为 weights[i]。每一天,我们都会按给出重量(weights)的顺序往传送带上装载包裹。我们装载的重量不会超过船的最大运载重量。

返回能在 days 天内将传送带上的所有包裹送达的船的最低运载能力。

示例 1:

输入:weights = [1,2,3,4,5,6,7,8,9,10], days = 5
输出:15
解释:
船舶最低载重 15 就能够在 5 天内送达所有包裹,如下所示:
第 1 天:1, 2, 3, 4, 5
第 2 天:6, 7
第 3 天:8
第 4 天:9
第 5 天:10

请注意,货物必须按照给定的顺序装运,因此使用载重能力为 14 的船舶并将包装分成 (2, 3, 4, 5), (1, 6, 7), (8), (9), (10) 是不允许的。
示例 2:

输入:weights = [3,2,2,4,1,4], days = 3
输出:6
解释:
船舶最低载重 6 就能够在 3 天内送达所有包裹,如下所示:
第 1 天:3, 2
第 2 天:2, 4
第 3 天:1, 4
示例 3:

输入:weights = [1,2,3,1,1], days = 4
输出:3
解释:
第 1 天:1
第 2 天:2
第 3 天:3
第 4 天:1, 1

 1 class Solution {
 2     public int shipWithinDays(int[] weights, int days) {
 3         // 最小载重应该是weights数组中元素的最大值
 4         // 最大载重显然就是weights数组所有元素之和
 5         int left = 0;
 6         int right = 1;
 7         for(int i = 0;i<weights.length;i++){
 8             left = Math.max(left,weights[i]);
 9             right+=weights[i];
10         }
11         
12         while(left<right){
13             int mid = left+((right-left)>>1);
14             if(f(weights,mid)==days){
15                 right = mid;
16             }else if(f(weights,mid)>days){
17                 left = mid+1;
18             }else if(f(weights,mid)<days){
19                 right = mid;
20             }
21         }
22         return left;
23     }
24 
25     // 载重x   天数f(x)
26     public int f(int[] weights,int x){
27         int days = 0;
28         for(int i = 0;i<weights.length;){
29             int weight = x;
30             while(i<weights.length){
31                 if(weight<weights[i]){
32                     break;
33                 }else{
34                     weight -=weights[i];
35                 }
36                 i++;
37             }
38             days++;
39         }
40         return days;
41     }
42 }
View Code

练习:

1、Sqrt_x

问题:

给你一个非负整数 x ,计算并返回 x 的 算术平方根 。

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

注意:不允许使用任何内置指数函数和算符,例如 pow(x, 0.5) 或者 x ** 0.5 。

示例 1:

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

输入:x = 8
输出:2
解释:8 的算术平方根是 2.82842..., 由于返回类型是整数,小数部分将被舍去。

 1 package LeetCode.test3_erfenchazhao;
 2 
 3 public class ques_69_Sqrt_x {
 4     public static void main(String[] args) {
 5         System.out.println(mySqrt(2147395599)); //46339
 6 //        System.out.println(mySqrt(8));
 7     }
 8 
 9     public static int mySqrt(int x) {
10         if (x == 0) {
11             return 0;
12         }
13         int left = 1;
14         int right = x;
15         while (left <= right) {
16             int mid = left + (right - left) / 2;
17             int sqrt = x / mid;
18             if (sqrt == mid) {  // x/mid与mid相比  ->  x与mid*mid相比
19                 return mid;
20             } else if (sqrt < mid) {
21                 right = mid - 1;
22             } else {
23                 left = mid + 1;
24             }
25         }
26         return right;  // 等价于(left - 1)
27     }
28 }
View Code

2、在排序数组中查找元素的第一个和最后一个位置

问题:

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

如果数组中不存在目标值 target,返回 [-1, -1]。

进阶:你可以设计并实现时间复杂度为 O(log n) 的算法解决此问题吗?

示例 1:

输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]
示例 2:

输入:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1]
示例 3:

输入:nums = [], target = 0
输出:[-1,-1]

思路:要找到某个区间,肯定要进行两边的二分查找,可以定义一个函数binarySearch,找左边索引靠target,找右边索引靠target + 1即可很好的解决这个问题。

 1 package LeetCode.test3_erfenchazhao;
 2 
 3 import java.util.Arrays;
 4 
 5 public class ques_34_在排序数组中查找元素的第一个和最后一个位置 {
 6     public static void main(String[] args) {
 7         int[] nums = {5, 7, 7, 8, 8, 10};
 8         int target = 8;
 9         System.out.println(Arrays.toString(searchRange(nums, target)));
10 //        System.out.println(binarySearch(nums, 8));
11 //        System.out.println(binarySearch(nums, 9));
12 
13     }
14 
15     public static int[] searchRange(int[] nums, int target) {
16         if (nums.length == 0) {
17             return new int[]{-1, -1};
18         }
19         int a = binarySearch(nums, target);
20         int b = binarySearch(nums, target + 1);
21         if (nums.length==a||nums[a]!=target){
22             return new int[]{-1, -1};
23         }else {
24             if (nums[b]==target){
25                 return new int[]{a,b};
26             }else {
27                 return new int[]{a,b-1};
28             }
29         }
30     }
31 
32     public static int binarySearch(int[] nums, int target) {
33         int left = 0;
34         int right = nums.length - 1;
35         while (left < right) {
36             int mid = left + ((right - left) >> 1);
37             if (nums[mid] < target) {
38                 left = mid + 1;
39             } else {
40                 right = mid;
41             }
42         }
43         return right; // 等价于left
44     }
45 }
View Code
class Solution {
    public int[] searchRange(int[] nums, int target) {
        if(nums.length==0){
            return new int[]{-1,-1};
        }
        return new int[]{left_bound(nums,target),right_bound(nums,target)};
    }

    public int left_bound(int[] nums, int target){
        int left = 0;
        int right = nums.length;
        while(left<right){
            int mid = left + ((right-left)>>1);
            if(nums[mid]==target){
                right = mid;
            }else if(nums[mid] < target){
                left = mid + 1;
            }else if(nums[mid] > target){
                right = mid;
            }
        }
        if(left == nums.length){
            return -1;
        }
        return nums[left]==target?left:-1;
    }

    public int right_bound(int[] nums, int target){
        int left = 0;
        int right = nums.length;
        while(left<right){
            int mid = left + ((right-left)>>1);
            if(nums[mid]==target){
                left = mid + 1;
            }else if(nums[mid] < target){
                left = mid + 1;
            }else if(nums[mid] > target){
                right = mid;
            }
        }
        if(left == 0){
            return -1;
        }
        return nums[left-1]==target?(left-1):-1;
    }
}

3、搜索旋转排序数组II

问题:已知存在一个按非降序排列的整数数组 nums ,数组中的值不必互不相同。

在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行了旋转 ,使数组变为 [nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]](下标 从 0 开始 计数)。

例如, [0,1,2,4,4,4,5,6,6,7] 在下标 5 处经旋转后可能变为 [4,5,6,6,7,0,1,2,4,4] 。

给你旋转后的数组nums和一个整数 target ,请你编写一个函数来判断给定的目标值是否存在于数组中。如果 nums 中存在这个目标值 target ,则返回 true ,否则返回 false 。

示例 1:

输入:nums = [2,5,6,0,0,1,2], target = 0
输出:true
示例 2:

输入:nums = [2,5,6,0,0,1,2], target = 3
输出:false

 1 package LeetCode.test3_erfenchazhao;
 2 
 3 public class ques_81_搜索旋转排序数组II {
 4     public static void main(String[] args) {
 5 //        int[] nums = {2, 5, 6, 0, 0, 1, 2};
 6         int[] nums = {1, 1, 1, 1, 1, 1, 1, 2, 1};
 7 //        int target = 0;
 8         int target = 2;
 9         System.out.println(search(nums, target));
10     }
11 
12     public static boolean search(int[] nums, int target) {
13         int start = 0;
14         int end = nums.length - 1;
15         while (start <= end) {
16             int mid = start + ((end - start) >> 1);
17             if (nums[mid] == target) {
18                 return true;
19             }
20             if (nums[mid] == nums[start]) { //无法判断哪个区间是增序的
21                 start++;
22             } else if (nums[mid] <= nums[end]) { //右边是有序的
23                 if (target > nums[mid] && target <= nums[end]) {
24                     start = mid + 1;
25                 } else {
26                     end = mid - 1;
27                 }
28             } else { //左边是有序的
29                 if (target >= nums[start] && target < nums[mid]) {
30                     end = mid - 1;
31                 } else {
32                     start = mid + 1;
33                 }
34             }
35         }
36         return false;
37     }
38 }
View Code

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

问题:已知一个长度为 n 的数组,预先按照升序排列,经由 1 到 n 次 旋转 后,得到输入数组。例如,原数组 nums = [0,1,4,4,5,6,7] 在变化后可能得到:
若旋转 4 次,则可以得到 [4,5,6,7,0,1,4]    若旋转 7 次,则可以得到 [0,1,4,4,5,6,7]
注意,数组 [a[0], a[1], a[2], ..., a[n-1]] 旋转一次 的结果为数组 [a[n-1], a[0], a[1], a[2], ..., a[n-2]] 。

给你一个可能存在重复元素值的数组 nums ,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的最小元素 。

示例 1:

输入:nums = [1,3,5]
输出:1
示例 2:

输入:nums = [2,2,2,0,1]
输出:0

 1 package LeetCode.test3_erfenchazhao;
 2 
 3 public class ques_154_寻找旋转排序数组中的最小值II {
 4     public static void main(String[] args) {
 5         int[] nums = {1,3,5};
 6         System.out.println(findMin(nums));
 7     }
 8 
 9     public static int findMin(int[] nums) {
10         int start = 0;
11         int end = nums.length - 1;
12         int min = Integer.MAX_VALUE;
13         while (start <= end) {
14             int mid = start + ((end - start) >> 1);
15             if (nums[mid] == nums[start]) {
16                 start++;
17                 min = Math.min(min, nums[mid]);
18             } else if (nums[mid] <= nums[end]) {
19                 min = Math.min(min, nums[mid]);
20                 end = mid - 1;
21             } else {
22                 min = Math.min(min, nums[start]);
23                 start = mid + 1;
24             }
25         }
26         return min;
27     }
28 }
View Code
 1 import java.util.*;
 2 import java.util.ArrayList;
 3 public class Solution {
 4     public int minNumberInRotateArray(int [] array) {
 5         int left = 0;
 6         int right = array.length-1;
 7         while(left<right){
 8             int mid = left + ((right-left)>>1);
 9             if(array[mid]<array[right]){
10                 right = mid;
11             }else if(array[mid]>array[right]){
12                 left = mid + 1;
13             }else{
14                 right = right-1;
15             }
16         }
17         return array[left];
18     }
19 }

5、有序数组中的单一元素

问题:

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

示例 1:

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

输入: nums = [3,3,7,7,10,11,11]
输出: 10

 1 package LeetCode.test3_erfenchazhao;
 2 
 3 public class ques_540_有序数组中的单一元素 {
 4     public static void main(String[] args) {
 5         int[] nums1 = {1, 1, 2, 3, 3, 4, 4, 8, 8};
 6         int[] nums2 = {1, 1, 3, 3, 4, 4, 5, 8, 8};
 7         System.out.println(singleNonDuplicate(nums1));
 8         System.out.println(singleNonDuplicate(nums2));
 9     }
10 
11     public static int singleNonDuplicate(int[] nums) {
12         int start = 0;
13         int end = nums.length - 1;
14         while (start < end) {
15             int mid = start + ((end - start) >> 1);
16             if (mid % 2 == 1) { // 只遍历偶数下标,控制遍历的mid都是偶数下标
17                 mid--;
18             }
19             if (nums[mid] == nums[mid + 1]) { // 在未遇到目标元素的情况下,nums[mid] == nums[mid+1]
20                 start += 2;
21             } else {  // 如果不等于那说明目标元素在左侧
22                 end = mid;
23             }
24         }
25         return nums[end];   // 等价于nums[start];
26     }
27 }
View Code

6、二维数组中的查找

问题:

在一个二维数组array中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。

示例 1:

输入: 7,[[1,2,8,9],[2,4,9,12],[4,7,10,13],[6,8,11,15]]
输出: true
示例 2:

输入: 1,[[2]]
输出: false

示例 3:

输入: 3,[[1,2,8,9],[2,4,9,12],[4,7,10,13],[6,8,11,15]]
输出: false

 1 public class Solution {
 2     public boolean Find(int target, int [][] array) {
 3         for(int i=0;i<array[0].length;i++){
 4             int left = 0;
 5             int right = array.length - 1;
 6             while(left<=right){
 7                 int mid = left + ((right-left)>>1);
 8                 if(array[mid][i]==target){
 9                     return true;
10                 }else if(array[mid][i]<target){
11                     left = mid + 1;
12                 }else if(array[mid][i]>target){
13                     right = mid - 1;
14                 }
15             }
16         }
17         return false;      
18     }
19 }
View Code

7、寻找峰值

问题:

给定一个长度为n的数组nums,请你找到峰值并返回其索引。数组可能包含多个峰值,在这种情况下,返回任何一个所在位置即可。
1.峰值元素是指其值严格大于左右相邻值的元素。严格大于即不能有等于
2.假设 nums[-1] = nums[n] = -\infty
3.对于所有有效的 i 都有 nums[i] != nums[i + 1]
4.你可以使用O(logN)的时间复杂度实现此问题吗?

示例 1:

输入: [2,4,1,2,7,8,4]
输出: 1

示例 2:

输入: [1,2,3,1]
输出: 2

 1 import java.util.*;
 2 
 3 public class Solution {
 4     public int findPeakElement (int[] nums) {
 5         // write code here 
 6         // 上坡一定有波峰,下坡不一定有波峰
 7         int left = 0;
 8         int right = nums.length-1;
 9         while(left<right){
10             int mid = left + ((right-left)>>1);
11             //证明右边的路是下坡路,不一定有坡峰
12             if(nums[mid]>nums[mid+1]){
13                 right = mid;
14             }else{
15                 left = mid + 1;
16             }
17         }
18         return left;
19     }
20 }
View Code

8、数组中的逆序对

问题:

在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P mod 100000000
题目保证输入的数组中没有的相同的数字

示例 1:

输入: [1,2,3,4,5,6,7,0]
输出: 7

示例 2:

输入: [1,2,3]
输出: 0

 1 public class Solution {
 2     int count = 0;
 3     
 4     public int InversePairs(int [] array) {
 5         int[] temp = new int[array.length]; 
 6         merge_sort(array,temp,0,temp.length-1);
 7         return count;
 8     }
 9     
10     public void merge_sort(int[] array, int[] temp,int left,int right){
11         if(left<right){
12             int mid = left + ((right-left)>>1);
13             merge_sort(array,temp,left,mid);
14             merge_sort(array,temp,mid+1,right);
15             merge(array,temp,left,mid,right);
16         }
17     }
18     
19     public void merge(int[] array, int[] temp, int left, int mid, int right){
20         int t = 0;  // temp自增
21         int i = left;
22         int j = mid + 1;
23         while(i <= mid && j <= right){
24             if(array[i] <= array[j]){
25                 temp[t++] = array[i++];
26             }else{
27                 temp[t++] = array[j++];
28                 // [5,7] [ 0,6]  ->  [5,0] [7,0] 和 [7,6]
29                 count += mid-i+1;    
30                 count %= 1000000007;
31             }
32         }
33         while(i <= mid){
34             temp[t++] = array[i++];
35         }
36         while(j <= right){
37             temp[t++] = array[j++];
38         }
39         t = 0;
40         while(left <= right){
41             array[left++] = temp[t++];
42         }
43     }
44     
45 }
View Code

 

posted on 2021-11-29 20:01  晨曦生辉耀匕尖  阅读(56)  评论(0编辑  收藏  举报