二分查找专题(二)
- 此文内容来自左程云算法,里面题目均为经典以及面试常考题,自己给分析并实现了一遍。
(一).局部最小值位置练习题
【题目】定义局部最小的概念。arr长度为1时,arr[0]是局部最小。arr的长度为N(N>1)时,如果arr[0]< arr[1],那么arr[0]是局部最小;如果arr[N-1]< arr[N-2],那么arr[N-1]是局部最小;如果0< i< N-1,既有arr[i]< arr[i-1]又有arr[i]< arr[i+1],那么arr[i]是局部最小。 给定无序数组arr,已知arr中任意两个相邻的数都不相等,写一个函数,只需返回arr中任意一个局部最小出现的位置即可。
- 此题告诉我们二分搜索不一定必须在查找表为顺序排列时适用,只要可以每次判断丢弃一半就可以适用二分搜索。
- 此题关键是mid下标元素的判断。
public class Solution {
public int getLessIndex(int[] arr) {
int n=arr.length;
//先进行边界条件判断
if(arr == null ||n==0)
return -1;
if(n==1 || arr[0]<arr[1])
return 0;
if(arr[n-1]<arr[n-2])
return n-1;
int left = 1;
int right=n-2;
int mid=0;
//局部最小值非数值的左右俩头
while(left <right){
mid = left+(right-left)/2;
if(arr[mid]>arr[mid-1]){
right = mid-1;
}else if(arr[mid]>arr[mid+1]){
left = mid+1;
}else{
//mid左右俩边都较大,所以选择mid
return mid;
}
}
return left;
}
}
(二).元素最左出现练习题
【题目】对于一个有序数组arr,再给定一个整数num,请在arr中找到num这个数出现的最左边的位置。
给定一个数组arr及它的大小n,同时给定num。请返回所求位置。若该元素在数组中未出现,请返回-1。
用temp来记录等于num的下标,但是不停止查找,继续查找等于num的值,并且记录下标,直到left> right。
import java.util.*;
public class LeftMostAppearance {
public int findPos(int[] arr, int n, int num) {
//边界条件判断
if(arr==null || n==0){
return -1;
}
int left=0;
int right=n-1;
int mid =-1;
int temp=-1;
while(left<=right){
mid=left+(right-left)/2;
if(arr[mid]<num){
left = mid+1;
}else if(arr[mid]>num){
right=mid -1;
}else{
//用temp记录当前相等的元素,但不一定是最终返回的元素
temp =mid;
right = mid-1;
}
}
return temp;
}
}
(三).循环有序数组最小值练习题
【题目】对于一个有序循环数组arr,返回arr中的最小值。有序循环数组是指,有序数组左边任意长度的部分放到右边去,右边的部分拿到左边来。比如数组[1,2,3,3,4],是有序循环数组,[4,1,2,3,3]也是。
给定数组arr及它的大小n,请返回最小值。
这道题比较难,需要考虑多种情况,各种情况分析在代码中详细注释
import java.util.*;
public class MinValue {
public int getMin(int[] arr, int n) {
//边界条件
if(arr==null||arr.length == 0){
return -1;
}
int left=0;
int right=n-1;
int mid;
while(left<right){
//当左右下标靠近时就推出
if(left == right-1){
break;
}
mid = left+(right-left)/2;
//有序排列,且非循环数组
if(arr[left]<arr[right]){
return arr[left];
}
//循环第一个元素肯定在左半边,即最小值在左半边
if(arr[left]>arr[mid]){
right =mid;
continue;
}
//循环第一个元素肯定在左半边,即最小值在左半边
if(arr[mid]>arr[right]){
left=mid;
continue;
}
/**
arr[l]>=arr[r]且arr[l]<=arr[m]且arr[m]<=arr[r] ===> arr[l]==arr[m]==arr[r]
比如2 2 2 2 1 2这种情况
这时候不能用二分方法,通过左半部分遍历。如果没有成功则最小值在右半部分
**/
while(left<mid){
if(arr[left]==arr[mid]){
left++;
}else if(arr[left]<arr[mid]){
return arr[left];
}else{
right = mid;
break;
}
}
}
return Math.min(arr[left],arr[right]);
}
}
(四).最左原位
【题目】有一个有序数组arr,其中不含有重复元素,请找到满足arr[i]==i条件的最左的位置。如果所有位置上的数都不满足条件,返回-1。
给定有序数组arr及它的大小n,请返回所求值。
import java.util.*;
public class Find {
public int findPos(int[] arr, int n) {
/**有序数组arr,且不含重复元素说明数组元素递增,这里注意下标递增1,数组元素递增>=1
arr[M]>M时,右半部分不可能出现arr[i]==i;
arr[M]<M时,左半部分不可能出现arr[i]==i;
**/
if(arr == null || n==0){
return -1;
}
int left=0;
int right=n-1;
int temp=-1;
int mid=0;
while(left<=right){
mid = left+(right-left)/2;
if(arr[mid]>mid){
right = mid-1;
}else if(arr[mid]<mid){
left=mid+1;
}else if(arr[mid]==mid){
temp = mid;
right =mid-1;
}
}
return temp;
}
}
(五).完全二叉树计数练习题
【题目】给定一棵完全二叉树的根节点root,返回这棵树的节点个数。如果完全二叉树的节点数为N,请实现时间复杂度低于O(N)的解法。
给定树的根结点root,请返回树的大小。
这种方法用后序遍历,每遍历一个结点,N加一,显然时间复杂度为O(N)。不符合要求。
import java.util.*;
/*
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}*/
public class CountNodes {
int N = 0;
public int count(TreeNode root) {
if(root == null){
return 0;
}
//后序遍历,左 右 根
count(root.left);
count(root.right);
N++;
return N;
}
}
此方法为标准答案,按照完全二叉树的特性以及满二叉树N=2^k - 1的性质。时间复杂度为:O(log H^2).
import java.util.*;
/*
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}*/
public class CountNodes {
public int count(TreeNode root) {
//1.先找到完全二叉树左子树最左边节点的层数。然后再找出右子树的左节点能到达的层数
//2.看左右子树的左节点能到的层数相等,则左子树必定是一个满二叉树,那使用满二叉树的性质求出左子树的节点数,右子树的节点数使用递归方式求出
//3.如果左右子树的左节点能到的层数不相等,则右子树必定是一颗少一层的满二叉树。然后左子树使用递归方法求出节点数
return bsCount(root,1,mostLeftLevel(root,1));
}
//start表示从头结点开始的层数,height表示左子树最左结点的层数
public int bsCount(TreeNode root,int start,int height){
//表示只有一个结点
if(start == height){
return 1;
}
//如果右子树的最左结点层数==height,说明root的左子树是满二叉树,N=2^k -1,直接算出左子树结点,右子树用递归
if(mostLeftLevel(root.right,start+1)==height){
return (1<<(height-start))+bsCount(root.right,start+1,height);
}else{
//如果右子树的最左结点层数!=height,说明root的右子树是满二叉树
return (1<<(height-start-1))+bsCount(root.left,start+1,height);
}
}
//得到左结点的层数
public int mostLeftLevel(TreeNode node,int level){
while(node!=null){
level++;
node = node.left;
}
return level-1;
}
}