二分查找专题(二)

二分查找专题(二)


  • 此文内容来自左程云算法,里面题目均为经典以及面试常考题,自己给分析并实现了一遍。

(一).局部最小值位置练习题

【题目】定义局部最小的概念。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;
    }


}
  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值