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 }
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 }
练习:
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 }
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 }
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 }
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 }
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 }
7、寻找峰值
问题:
示例 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 }
8、数组中的逆序对
问题:
示例 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 }