第三章:二分查找


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 }

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 }

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 }

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 }

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 }