1.典型的二分查找的两种方式(注意二分查找的适用条件是递增的数组)
1).递归方式
class BinaryTree
{
public static void main(String[] args)
{
int[] a = {1,3,4,6,8,9,10,11,23};
System.out.println(search(a,12,0,a.length-1));
}
public static int search(int[] a,int data,int low,int high)
{
if(low<=high)
int pivot = low+(high-low)/2;
if(data==a[pivot])
return pivot;
else if(data>a[pivot])
{
return search(a,data,pivot+1,high); //通过递归方式查找
}
else
{
return search(a,data,low,pivot-1);
}
}
else
{
return -1; //通过返回-1,表示找不到
}
}
}
2).非递归方式(while循环方式)
class BinaryTree
{
public static void main(String[] args)
{
int[] a = {1,3,4,6,8,9,10,11,23};
System.out.println(search(a,11));
}
public static int search(int[] a,int data)
{
int low = 0;
int high = a.length - 1;
while(low<=high) //通过while循环来替代递归
{
int pivot = low+(high-low)/2;
if(data==a[pivot])
return pivot;
else if(data>a[pivot])
{
low = pivot + 1;
}
else
{
high = pivot -1;
}
}
return -1;
}
}
说明:int pivot = low + (high-low)/2;其实就是int pivot = (low+high)/2,使用low+(high-low)是为了防止溢出,例如int类型数值的最大值是2^32 - 1,如果数的个数很大,此时low+high可能会溢出。
2.使用二分查找的思想来解题:
1.题目描述 (剑指offer 面试题11 旋转数组)
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。 输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。 例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。 NOTE:给出的所有元素都大于0,若数组大小为0,请返回0
分析:本题给出的数组一定程度上是排序的,因此可以试者使用二分查找法寻找最小值。
1)使用low和high分别指向数组的第一个元素和最后一个元素。如果没有旋转第一个元素是小于等于最后一个元素,旋转后第一个元素应该是大于等于最后一个元素(等于是有重复元素的情况)
2)中间元素大于第一个元素,此时说明中间元素位于前面的递增子数组中,此时最小元素位于中间元素后面的。我们可以让low指向中间元素。
3)中间元素小于第一个元素,此时说明中间元素位于后面的递增子数组中,此时最小元素位于中间元素的前面,我们可以让high指针指向中间元素。
4)通过2)3)可以缩小寻找的范围,重复2)3)直到low+1=high时,high指向的元素就是最小元素
5)考虑几种特殊情况,如果把排序数组的前面0个元素搬到最后面,即排序数组本身,此时第一个元素就是最小的。
6){1,0,1,1,1}和{1,1,1,0,1}都可看成是递增排序数组{0,1,1,1,1}的旋转,此时mid,low,high指向的数都相同,第一种情况下,中间元素位于后面的子数组,第二种情况,中间数字位于前面的子数组,此时只能采用顺序寻找的方式。
代码实现:
public class Solution {
public int minNumberInRotateArray(int [] array) {
if(array.length==0){
return 0;
}
if(array[0]<array[array.length-1])
return array[0];
return binarySearch(array,0,array.length-1);
}
public int binarySearch(int[] a,int low,int high){
while(a[low]>=a[high]){ //这边为什么要等于号
if(low+1==high){
return a[high];
}
//如果下标low,high和mid相等,这时候就需要顺序查找
int mid = (low + high)/2;
if(a[low]==a[high]&&a[low]==a[mid]){
return MininOrder(a,low,high);
}
if(a[mid]>=a[low]){ //这边的等于号不能丢
low = mid;
}else if(a[mid]<=a[low]){
high = mid;
}
}
return a[0];
}
public int MininOrder(int[] a,int low,int high){
int min = a[low];
for(int i=low+1;i<=high;i++){
if(a[i]<min){
min = a[i];
}
}
return min;
}
}
2.题目描述(剑指offer在排序数组中查找数字)
统计一个数字在排序数组中出现的次数。例如排序数组{1,2,3,3,3,4,5}和数字3,由于3在这个数组中出现了4次,因此输出4。
思路:该题的数组是一个排序数组,思路找到第一次出现3的位置,和最后出现3位置,两者相减就得到3的个数。可以考虑使用二分查找法,如上面的数字所示,典型的二分查找在找到数字3后就停止了,该题中可以在找到3后(第一次找到的是中间的3),判断3前面的数字是不是3(前面有数字的情况下,如果前面没有数字,那么这个3肯定就是第一出现的3),如果前面的数字不是3,那么找到的3就是一次出现的3,而如果前面的数字也是3,那么就在数组的前半段继续查找3,直到找到第一次出现3的位置。同样的方法找到最后一个出现3的位置,将两个位置相减就得到出现的个数。
代码实现:
class Solution {
public int GetNumberOfK(int [] array , int k) {
int N = array.length - 1;
int i = getFirstK(array,k,0,N);
System.out.println("i: "+i);
int j = getLastK(array,k,0,N);
System.out.println("j: "+j);
if(i>-1&&j>-1)
return j-i+1;
return 0;
}
public int getFirstK(int[] array,int k,int low,int high){
while(low<=high){
int mid = (low+high)/2;
if(array[mid]<k)
low = mid + 1;
else if(array[mid]>k)
high = mid -1;
else{ //找了数字k,还需继续判断这个数字是不是第一次出现
if(mid==0) //如果前面没有数字了,这个数字就是第一次出现了
return mid;
else if(mid-1>=0&&array[mid-1]!=k){ //如果前面的数字和这个数字不等,那么也是第一个出现了
return mid;
}
else{
high = mid - 1;
}
}
}
return -1; //如果没有找到该数字,返回-1
}
public int getLastK(int[] array,int k,int low,int high){
if(low<=high){
int mid = (low+high)/2;
if(array[mid]>k){
return getLastK(array,k,low,mid-1);
}
else if(array[mid]<k){
return getLastK(array,k,mid+1,high);
}
else{
if(mid==array.length-1){
return mid;
}
if(array[mid+1]<=array.length-1&&array[mid+1]==k){
return getLastK(array,k,mid+1,high);
}
else{
return mid;
}
}
}
return -1;
}
}
3.搜索旋转排序数组
整数数组 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,5,6,7] 在下标 3 处经旋转后可能变为 [4,5,6,7,0,1,2] 。
给你 旋转后 的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的下标,否则返回 -1 。
你必须设计一个时间复杂度为 O(log n) 的算法解决此问题。
解题思路:
题目要求 O(logN)O(logN) 的时间复杂度,基本可以断定本题是需要使用二分查找,怎么分是关键。
由于题目说数字了无重复,举个例子:
1 2 3 4 5 6 7 可以大致分为两类,
第一类 2 3 4 5 6 7 1 这种,也就是 nums[start] <= nums[mid]。此例子中就是 2 <= 5。
这种情况下,前半部分有序。因此如果 nums[start] <=target<nums[mid],则在前半部分找,否则去后半部分找。
第二类 6 7 1 2 3 4 5 这种,也就是 nums[start] > nums[mid]。此例子中就是 6 > 2。
这种情况下,后半部分有序。因此如果 nums[mid] <target<=nums[end],则在后半部分找,否则去前半部分找。
代码实现:
public int search(int[] nums, int target) {
if (nums == null || nums.length == 0) {
return -1;
}
int start = 0;
int end = nums.length - 1;
int mid;
while (start <= end) {
mid = start + (end - start) / 2;
if (nums[mid] == target) {
return mid;
}
//前半部分有序,注意此处用小于等于
if (nums[start] <= nums[mid]) {
//target在前半部分
if (target >= nums[start] && target < nums[mid]) {
end = mid - 1;
} else {
start = mid + 1;
}
} else {
if (target <= nums[end] && target > nums[mid]) {
start = mid + 1;
} else {
end = mid - 1;
}
}
}
return -1;
}
作者:reedfan
链接:https://leetcode.cn/problems/search-in-rotated-sorted-array/solution/ji-bai-liao-9983de-javayong-hu-by-reedfan/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
参考: