mid = low + ((high - low) / 2)
,而不是 mid = (low + high) / 2
,这是由于当数组较大时,可能会出现溢出的状况right = mid -1
, left = mid + 1
配合使用right = mid
,left = mid + 1
或 right = mid - 1
,left = mid
配合使用right = mid
和 left = mid + 1
和 mid = left + (right - left) / 2
必定是配对出现的right = mid - 1
和 left = mid
和 mid = left + (right - left + 1) / 2
必定是配对出现的思路java
代码数组
public int mySqrt(int x) { if (x <= 1) { return x; } if (x < 4) { return 1; } int left = 2; int right = x / 2; int mid = 0; while (left <= right) { mid = left + (right - left) / 2; long temp = (long)mid * (long)mid; if (temp < x) { left = mid + 1; } else if (temp > x) { right = mid - 1; } else { return mid; } } return (long)mid * (long)mid > x ? mid - 1 : mid; }
注意:优化
int
除 int
会致使直接向下取整,这里致使了精度上的损失。因此,最后要多判断一下 mid - 1
的平方int
乘 int
会先得出int
类型的结果,而后将这个结果转换成long
,这回致使溢出,因此在乘以前要先转成long
类型或者统一使用 long
最后在返回的时候转成 int
分析编码
代码指针
public int[] searchRange(int[] nums, int target) { if (nums == null || nums.length == 0) { return new int[]{-1, -1}; } if (nums.length <= 1) { return nums[0] == target ? new int[]{0, 0} : new int[] {-1, -1}; } int[] res = new int[]{-1, -1}; int left = 0; int right = nums.length - 1; // 找到第一次出现的位置 while (left < right) { int mid = left + (right - left) / 2; if (nums[mid] >= target) { right = mid; } else { left = mid + 1; } } res[0] = nums[right] != target ? -1 : right; if (res[0] == -1) { return new int[] {-1, -1}; } // 找最后一次出现的位置 left = right; right = nums.length - 1; while (left < right) { int mid = left + (right - left) / 2; if (nums[mid] > target) { right = mid; } else { left = mid + 1; } } res[1] = nums[left] == target ? left : left - 1; return res; }
注意:code
分析排序
0,1,...,i,i+1,...,n-1
,其中 nums[i+1] < nums[i]
[0,i]
和 [i+1, n-1]
这两块分别进行二分搜索便可,那么只须要找到 i
便可
i
必然要对数组进行遍历,而且由于数组中存在重复的元素,因此必需要遍历整个数组,才能肯定出 i
。所以,这一部分的时间复杂度为 O(n)i
时间复杂度为 O(n) ,二分查找时间复杂度为 O(log n),总的时间复杂度为 O(n)代码element
public boolean search(int[] nums, int target) { if (nums.length == 0) { return false; } if (nums.length == 1) { return nums[0] == target; } int flag = 0; for (int i = 0; i < nums.length - 1; i++) { if (nums[i] > nums[i + 1]) { flag = i; } } return binarySearch(0, flag, nums, target) || binarySearch(flag + 1, nums.length - 1, nums, target); } public boolean binarySearch(int left, int right, int[] nums,int target) { while (left <= right) { int mid = left + (right - left) / 2; if (nums[mid] > target) { right = mid - 1; } else if (nums[mid] < target) { left = mid + 1; } else { return true; } } return false; }
改进(来自LeetCode官方题解)leetcode
[left, mid]
和 [mid + 1, right]
哪一个部分是有序的,并根据有序的那个部分肯定咱们该如何改变二分搜索的上下界,由于咱们可以根据有序的那部分判断出 target
在不在这个部分:
[left, mid]
是有序数组,且 target
的大小知足 [nums[left],nums[mid])
,则咱们应该将搜索范围缩小至 [left, mid-1]
,不然在 [mid + 1, right]
中寻找。[mid, right]
是有序数组,且 target 的大小知足 (nums[mid+1],nums[right]]
,则咱们应该将搜索范围缩小至 [mid + 1, right]
,不然在 [left, mid - 1]
中寻找。实现get
public boolean search(int[] nums, int target) { if (nums.length == 0) { return false; } if (nums.length == 1) { return nums[0] == target; } int left = 0; int right = nums.length - 1; while (left <= right) { int mid = left + (right - left) / 2; if (nums[mid] == target) { return true; } if (nums[left] == nums[mid]) { left++; } else if (nums[mid] <= nums[right]) { // 右边是有序的 if (target > nums[mid] && target <= nums[right]) { // target落在有序区间内 left = mid + 1; } else { right = mid - 1; } } else { // 左边是有序的 if (target >= nums[left] && target < nums[mid]) { // target落在有序区间内 right = mid - 1; } else { left = mid + 1; } } } return false; }
要点:如何判断哪边有序?
nums[mid] == nums[left]
时,咱们没法肯定哪边是递增的,这时须要将 left
左移一个位置分析
[小数区间][大数区间]
,那么旋转以后,就是[大数区间][小数区间]
。咱们要找的这个最小值,就是大数区间变成小数区间的转折点
i
[0, i-1]
中的全部元素所有大于等于该元素i
小于等于 [i+1, ... ,n-1]
中的全部元素nums[mid] < nums[right]
,那么转折点在 [left, mid-1]
范围内nums[mid] == nums[right]
,没法肯定转折点所在的区间,right
左移一位nums[mid] > nums[right]
,那么转折点在 [mid + 1, right]
范围内right
进行判断而不是使用 left
?
left
没法正确得出结果。(通过屡次尝试后发现,百思不得其解,参考了一下官方解法才发现关键在这里)代码
public int findMin(int[] nums) { if (nums.length <= 2) { return nums.length == 1 ? 0 : Math.min(nums[0], nums[1]); } int left = 0; int right = nums.length - 1; int mid = 0; while (left < right) { mid = left + (right - left) / 2; if (nums[mid] < nums[right]) { right = mid; } else if (nums[right] == nums[mid]) { right--; } else { left = mid + 1; } } return nums[left]; }
注意
left < right
做为循环结束的条件分析
i
对元素的下标是 2i-2
和 2i-1
i
对元素的下标是 2i-1
和 2i
mid
的 左边仍是右边
nums{mid]
和先后元素都不相同,则 nums{mid]
就是单一元素mid
是奇数,且 nums{mid]
和后面元素相同,单一元素在 [left, mid-1]
范围内,否则就在 [mid+1, right]
范围内mid
是偶数,且 nums{mid]
和后面元素相同,单一元素在 [mid+1, right]
范围内,否则就在 [left, mid-1]
范围内代码
public int singleNonDuplicate(int[] nums) { if (nums.length == 0) { return 0; } if (nums.length == 1 || nums[0] != nums[1]) { return nums[0]; } if (nums[nums.length - 1] != nums[nums.length -2]) { return nums[nums.length - 1]; } int left = 0; int right = nums.length - 1; while (left <= right) { int mid = left + (right - left) / 2; if (nums[mid] != nums[mid-1] && nums[mid] != nums[mid+1]) { return nums[mid]; } if (mid % 2 != 0) { // mid是奇数 if (nums[mid] == nums[mid + 1]) { right = mid - 1; } else { left = mid + 1; } } else { // mid是偶数 if (nums[mid] == nums[mid + 1]) { left = mid + 1; } else { right = mid - 1; } } } return 0; }
注意
mid-1
和 mid+1
的操做,因此要对首尾的元素进行特殊操做,避免数组越界分析
(m+n)/2
次后便可获得中位数,不过这样并无优化时间复杂度,只是把空间复杂度从 O(m+n) 下降到了 O(1)m+n
是奇数时,中位数是两个有序数组中的第 (m+n)/2
个元素,当 m+n
是偶数时,中位数是两个有序数组中的第 (m+n)/2
个元素和第 (m+n)/2+1
个元素的平均值。所以,这道题能够转化成寻找两个有序数组中的第 k
小的数,其中 k 为 (m+n)/2
或 (m+n)/2+1
k
个元素,咱们能够比较 A[k/2−1]
和 B[k/2−1]
。因为 A[k/2−1]
和 B[k/2−1]
的前面分别有 A[0..k/2−2]
和 B[0..k/2−2]
,即 k/2-1
个元素,对于 A[k/2−1]
和 B[k/2−1]
中的较小值,最多只会有 (k/2−1)+(k/2−1)≤k−2
个元素比它小,那么它就不能是第 k
小的数了。A[k/2−1]<B[k/2−1]
,则比 A[k/2−1]
小的数最多只有 A
的前 k/2-1
个数和 B
的前 k/2−1
个数,即比 A[k/2−1]
小的数最多只有 k-2
个,所以 A[k/2−1]
不多是第 k
个数,A[0]
到 A[k/2−1]
也都不多是第 k
个数,能够所有排除。A[k/2−1]>B[k/2−1]
,则能够排除 B[0]
到 B[k/2−1]
。A[k/2−1]=B[k/2−1]
,则能够纳入第一种状况处理。A[k/2−1]
或者 B[k/2−1]
越界,那么咱们能够选取对应数组中的最后一个元素。在这种状况下,咱们必须根据排除数的个数减小 k
的值,而不能直接将 k
减去 k/2
。k
小的元素。k=1
,咱们只要返回两个数组首元素的最小值便可。代码
public double findMedianSortedArrays(int[] nums1, int[] nums2) { int len = nums1.length + nums2.length; int k = len / 2; if (len % 2 == 0) { int r1 = findKth(nums1, nums2, k); int r2 = findKth(nums1, nums2, k + 1); return (r1 + r2) / 2.0; } else { return findKth(nums1, nums2, k+1); } } private int findKth(int[] nums1, int[] nums2, int k) { int length1 = nums1.length; int length2 = nums2.length; // index表示的是排除元素以后,“新数组” 的开始位置 int index1 = 0; int index2 = 0; while (true) { // 若是其中一个数组为空,则返回另一个数组的中位数 if (index1 == length1) { return nums2[index2 + k - 1]; } if (index2 == length2) { return nums1[index1 + k - 1]; } // 当 k=1 时退出循环 if (k == 1) { return Math.min(nums1[index1], nums2[index2]); } int half = k / 2; // 这步操做保证了newIndex不会超出数组长度 int newIndex1 = Math.min(index1 + half, length1) - 1; int newIndex2 = Math.min(index2 + half, length2) - 1; int pivot1 = nums1[newIndex1], pivot2 = nums2[newIndex2]; // k要减去每次排除的元素个数 if (pivot1 <= pivot2) { k -= (newIndex1 - index1 + 1); index1 = newIndex1 + 1; } else { k -= (newIndex2 - index2 + 1); index2 = newIndex2 + 1; } } }