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; } } }