先談談二分查找,二分查找是一種非常高效的查找算法,例題如下:
給定一個整形有序數組,如何找出某一整數是否在這個數組中,以及返回該整數所對應的下標?
此題最簡單的方法就是遍歷一遍原數組來判斷是否存在,這也是順序查找,時間複雜度爲O(n)。還可以建立一個哈希表,但是這種方法的空間複雜度爲O(n)。所以,此時最佳的算法便是二分查找,其時間複雜度爲O(logn),空間複雜度爲O(1)。
下面給出二分查找算法遞歸與非遞歸的兩種形式:
private int binarySearch(int[] nums, int left, int right, int target) {
if (left > right) {
return -1;
}
int mid = left + (right - left) / 2;//防止數值溢出
if (nums[mid] > target) {
return binarySearch(nums, left, mid - 1, target);
} else if (nums[mid] < target) {
return binarySearch(nums,mid + 1,right,target);
}
return mid;
}
private int binarySearch(int[] nums, int left, int right, 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 mid;
}
}
return -1;
}
也許,這並不足以證明二分查找的威力,下面擴展一下原題目。
假設按照升序排序的數組在預先未知的某個點上進行了旋轉
( 例如,數組 [0,1,2,4,5,6,7] 可能變爲 [4,5,6,7,0,1,2] )。
搜索一個給定的目標值,如果數組中存在這個目標值,則返回它的索引,否則返回 -1 。你可以假設數組中不存在重複的元素。
這個題我給出兩種解題思路,而這兩種方法都用到了二分查找。
第一種方法:二分查找加分治算法,第一次取得原數組的中間元素進行比較(中間元素恰好是目標值的情況單獨拿出來即可),不難發現總有一側的元素是有序,另外一側的元素是無序的,有序的一側直接複用二分查找即可,而無序的一側仿照剛纔思路再來一次,就這樣通過二分來實現遞歸分治來判斷目標值是否存在。
具體代碼如下:
public int search1(int[] nums, int target) {
return find(nums, 0, nums.length - 1, target);
}
private int find(int[] nums, int left, int right, int target) {
//遞歸的終止條件
if (left > right) {
return -1;
}
//判斷邊界條件
int mid = left + (right - left) / 2;
if (target == nums[left]) {
return left;
}
if (target == nums[right]) {
return right;
}
if (target == nums[mid]) {
return mid;
}
if (target > nums[right] && target < nums[left]) {
return -1;
}
//判斷的核心代碼
if (nums[mid] < nums[right]) {
if (target > nums[mid] && target < nums[right]) {
return binarySearch(nums, mid + 1, right - 1, target);
} else {
return find(nums, left, mid - 1, target);
}
} else {
if (target > nums[left] && target < nums[mid]) {
return binarySearch(nums, left + 1, mid - 1, target);
} else {
return find(nums, mid + 1, right, target);
}
}
}
private int binarySearch(int[] nums, int left, int right, 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 mid;
}
}
return -1;
}
第二種方法:二分查找加二分查找,先通過整體的二分查找得到數組的旋轉點,從而不難得出以旋轉點爲中心的左右兩側都是有序數組,進行再分別對其兩側進行二分查找來判斷目標值是否存在。
具體代碼如下:
public int search2(int[] nums, int target) {
if (nums == null || nums.length == 0) {
return -1;
}
int partition = findPartition(nums);
if (partition != -1) {
if (target == nums[partition]) {
return partition;
} else if (target > nums[nums.length - 1]) {
return binarySearch(nums, 0, partition - 1, target);
} else {
return binarySearch(nums, partition + 1, nums.length - 1, target);
}
}
return binarySearch(nums, 0, nums.length - 1, target);
}
private int findPartition(int[] nums) {
int left = 0;
int right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
//此處是通過旋轉點特殊的性質來得到旋轉點
if (mid != right && nums[mid] > nums[mid + 1]) {
return mid;
}
if (mid != left && nums[mid] < nums[mid - 1]) {
return mid - 1;
}
if (nums[mid] < nums[left]) {
right = mid - 1;
} else {
left = mid + 1;
}
}
return -1;
}
通過上面的題目就足以見得二分查找的威力不小。
其實就上面的題目還能進一步拓展,就是把上述數組中不存在重複的元素的條件去掉。
此時題目難度又增加了一些,因爲有重複的元素必然會影響到二分查找的性能和查找的正確性,從而一個很直接的想法就是去重,即在上述代碼的基礎上加上一段可以去掉數組重複元素的代碼即可。
具體代碼如下:
public boolean search1(int[] nums, int target) {
return find(nums, 0, nums.length - 1, target);
}
private boolean find(int[] nums, int left, int right, int target) {
if (left > right) {
return false;
}
while (left < right && nums[left] == nums[left + 1]) {
left++;
}
while (left < right && nums[right] == nums[right - 1]) {
right--;
}
int mid = left + (right - left) / 2;
if (target == nums[left]) {
return true;
}
if (target == nums[right]) {
return true;
}
if (target == nums[mid]) {
return true;
}
if (target > nums[right] && target < nums[left]) {
return false;
}
if (nums[mid] < nums[right]) {
if (target > nums[mid] && target < nums[right]) {
return binarySearch(nums, mid + 1, right - 1, target);
} else {
return find(nums, left, mid - 1, target);
}
} else {
if (target > nums[left] && target < nums[mid]) {
return binarySearch(nums, left + 1, mid - 1, target);
} else {
return find(nums, mid + 1, right, target);
}
}
}
private boolean binarySearch(int[] nums, int left, int right, 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;
}
以上就是我對這幾個題目以及二分查找的理解,如若讀者有更好的想法或者方法願意分享,感謝留言。