二分查找相关题目

一、注意:

(1)有序数组,时间复杂度O(logn)  while(low<=high) mid=(left+right)/2

(2)有序循环数组(有序数组左边任意长度移到右边,比如1 2 3 4 5的变形3 4 5 1 2就是有序循环数组。)

(3)Mid=(left+right)/2是经典写法,但当left或right特别大时可能溢出,所以安全写法是mid=left+(right-left)/2;

(4)二分查找不一定是有序数组的,只要在查找过程中能够留下一半抛弃一半,就符合这种方法。


二、练习题:

(1)局部最小:二分查找 时间复杂度O(logn)

a长度为1时,a[0]是局部最小。a的长度为N(N>1)时,如果a[0]<a[1],那么a[0]是局部最小;如果a[N-1]<a[N-2],那么a[N-1]是局部最小;如果0<i<N-1,既有a[i]<a[i-1]又有a[i]<a[i+1],那么a[i]是局部最小。 给定无序数组a,已知a任意两个相邻的数都不相等,写一个函数,只需返回a中任意一个局部最小出现的位置即可

思路:首先,判断数组的两边是否为局部最小即a[left]和a[right]。

然后,判断mid=(left+right)/2是否局部最小。有以下情况:

Left 和right是局部最小时,那么从left向右看数组是向下的,从right向左看数组也是向下的。此时:

1)如果mid比m-1,m+1都大,那么mid向左向右看都是向下的,所以左右两侧必然都存在局部最小。

2)如果mid比m-1,m+1都小,那么mid是局部最小,返回mid即可

3)如果mid比m-1大,m+1小,那么从mid向左看数组是向下的,则左半部分必定存在局部最小。<m-1,>m+1也是一样。

public class Solution {
    public int getLessIndex(int[] arr) {
		int len = arr.length;
        if (len == 0) return -1;
        if (len == 1) return 0;
        if (arr[0] < arr[1]) return 0;
        if (arr[len-1] < arr[len-2]) return len-1;
        int result = getLess(arr, 1, len-2);
        return result;
    }

    private int getLess(int[] arr, int left, int right) {
        while (left <= right) {
            int mid = left+(right-left)/2;
            if (arr[mid+1] > arr[mid] && arr[mid-1] > arr[mid]) {
                return mid;
            }else if (arr[mid] > arr[mid-1]) {
                right = mid-1;
            }else if (arr[mid] > arr[mid+1]) {
                left = mid+1;
            }
        }
        return -1;
    }
}

-------------------------------------------------------------------------------------------------------------

2有序数组a[n],再给定一个整数num,请在a中找到num这个数出现的最左边的位置。

给定一个数组a及它的大小n,同时给定num。请返回所求位置。若该元素在数组中未出现,请返回-1。 如:数组[1,2,3,3,3,4,4,4,4,4,4,4] num=3,则返回2

Static int res=-1记录3最后一次出现的位置。

二分查找mid=4,则抛弃右边;继续查找 mid=3,记录此时3的位置res=3;继续二分mid=2抛弃左半部分,mid=3 更新res=2.

public class LeftMostAppearance {
    static int res=-1;
    public int findPos(int[] a, int n, int num) {
        // write code here
        binSearch(a,0,n-1,num);
        return res;
    }
    
    public void binSearch(int[]a ,int left,int right,int num){
        if(left<=right){
            int mid=left+(right-left)/2;
            if(a[mid]==num) {
                res=mid;
                binSearch(a,left,mid-1,num);
            }else if(a[mid]>num){
                binSearch(a,left,mid-1,num);
            }else{
                binSearch(a,mid+1,right,num);
            }
        }
    }
}


-------------------------------------------------------------------------------------------------------------


(3)有序循环数组最小值:

有序循环数组:有序数组,左边x个数移到右边,如1 2 3 4 5的变形 4 5 1 2 3就是有序循环数组,利用二分查找找到最小值,时间复杂度O(logn),最坏情况O(n) 最好别用递归方法,小李啊

1)如果a[left]<a[right] 说明[left,right]之间是有序的不包括循环部分,此时该区间最小值为a[left]。

2)如果a[left]>=a[right]说明[left,right]之间包括循环部分,如[4 5 1 2],则进行二分查找,mid=(left+right)/2:(即这种情况就是最小值在循环部分)

如果a[right]<a[mid]则说明右半边[mid,right]是包含循环部分的,最小值该在右半边。如 4 5 6 7 8 9 1 2 3,mid=8,right=3

如果a[left]>a[mid]说明左半边是包含循环的,最小值在[left,mid]

如果以上两个条件都不满足,说明a[left]=a[mid]=a[right],例如{2 2 2 1 2 2 2 2} 不符合二分查找,用遍历方式查找left~right部分。


public class MinValue {
  public int getMin(int[] a,int n) {
      if(a==null||n==0) return -1;
      if(n==1) return a[0];
      int left=0;
      int right=n-1;
      while(left<right){
          if(left==right-1) break;//将最小值锁定在 a[i]和a[i+1]上
          if(a[left]<a[right]) return a[left];//left到right是顺序的
          
          int mid=left+(right-left)/2;
          if(a[left]>a[mid]){//最小值是循环的开头,在左半边,
              right=mid;//a[mid]可能是最小值
              continue;
          }
          if(a[mid]>a[right]){//循环在右半边
              left=mid;
              continue;
          }
          //不符合上述两种情况,//a[left]=a[mid]=a[right]情况 遍历法
          while(left<right){
              if(a[left]==a[mid]){
                  left++;
              }else if(a[left]<a[mid]){
                  return a[left];
              }else{
                  right=mid;
                  break;
              }
          }
      }
      return Math.min(a[left],a[right]);
      
  }
}


-------------------------------------------------------------------------------------------------------------


(4)完全二叉树的节点计数时间小于O(N)

1)完全二叉树左子树的深度ldepth.(找最左节点)

2)二叉树右子树的深度rdepth.(找root.right的最左节点)

3)如果ldepth==rdepth,说明左子树是一棵满完全二叉树。总节点数=左子树个数+head+递归求右子树的节点

4)如果ldepth>rdepth,head的右子树是满的,总结点数=右子树个数+head+递归求左子树节点。

 

使用遍历的方式,时间复杂度为O(n),使用二分查找方式时间为O(h^2)=O((logn)^2)


public class CountNodes {
    public int count(TreeNode root) {
        if(root==null) return 0;
        int nodeCount=0;
        int lDepth=0;
        TreeNode cur=root.left;
        while(cur!=null){//记录左子树的深度
            lDepth++;
            cur=cur.left;
        }
        
        int rDepth=0;
        cur=root.right;
        while(cur!=null){//记录右子树右节点的深度
            rDepth++;
            cur=cur.left;
        }
        
        if(lDepth==rDepth){//左子树是一棵满二叉树
            nodeCount=(int)Math.pow(2,lDepth)+count(root.right);
        }else{//ldepth>rdepth 右子树是一棵满二叉树
            nodeCount=(int) Math.pow(2,rDepth)+count(root.left);
        }
        return nodeCount;
    }
    
    
}
   

-------------------------------------------------------------------------------------------------------------


(5)快速求k^n:时间复杂度O(logN)

题目:

求一个整数k的n次方。两个整数相乘并得到结果的时间复杂度为O(1),得到整数k的N次方的过程请实现时间复杂度为O(logN)的方法。

给定k和n,请返回k的n次方,为了防止溢出,请返回结果Mod 1000000007的值。

 

思路:首先将指数n->二进制,以10^75为例

75的二进制数是1001011,  分别对应10^64 ,10^32,10^16,10^8 ,10^4 ,10^2 ,10^1(注意这里从10^1开始,不是10^0),其中10^n是不是10^75的分式,与75的二进制相同,1则是0则不是。

所以10^75= 10^64 * 10^8 * 10^2 *10^1

k^n是n的二进制每次右移一位,k每次自乘,但是最终结果是否乘以k自乘的结果看n是否为1,为1结果乘以k自乘的结果,为0则不乘


import java.util.*;
import java.math.BigInteger;
public class QuickPower {
public int getPower(int a, int n) {
		BigInteger res = BigInteger.valueOf(1); //BigInteger是不可变的任意精度的整数。所有操作中,都以二进制补码形式表示 BigInteger
		BigInteger tmp = BigInteger.valueOf(a);
		for (; n != 0; n >>= 1) {//n每次右移1位 相当于除2
			if ((n & 1) != 0) {//n的二进制树为1对应的位置要乘以当前k值
				res = res.multiply(tmp);
			}
			tmp = tmp.multiply(tmp);//tmp就对应 a^1,a^2,a^3...
			res = res.mod(BigInteger.valueOf(1000000007));//防止溢出
			tmp = tmp.mod(BigInteger.valueOf(1000000007));
		}
		return res.mod(BigInteger.valueOf(1000000007)).intValue();
	}
}




  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值