本文是【常用算法思路分析系列】的第五篇,总结二分搜索相关的高频题目和解题思路。本文分析如下几个问题:1、求数组局部最小值问题;2、元素最左出现的位置;3、循环有序数组求最小值;4、最左原位;5、完全二叉树计算结点数;6、快速N次方。
本系列前四篇导航:
【常用算法思路分析系列】排序高频题集
【常用算法思路分析系列】字符串高频题集
【常用算法思路分析系列】栈和队列高频题集(修改版)
【常用算法思路分析系列】链表相关高频题集
二分搜索的重要提醒:
public int getLessIndex(int[] arr) { if(arr == null || arr.length == 0){ return -1; } if(arr.length == 1 || arr[0] < arr[1]){ return 0; } if (arr[arr.length - 1] < arr[arr.length - 2]) { return arr.length - 1; } int left = 1; int right = arr.length - 2; int mid = 0; while(left < right){ mid = (left + right) / 2; if(arr[mid] > arr[mid - 1]){ right = mid - 1; }else if(arr[mid] > arr[mid + 1]){ left = mid + 1; }else{ return mid; } } return left; }
public int getLessIndex(int[] arr) { if(arr == null || arr.length == 0){ return -1; } if(arr.length == 1){ return 0; } return getLess(arr, 0, arr.length - 1); } private int getLess(int[] arr, int left, int right) { if(left == right){ return left; } if(arr[left] < arr[left + 1]){ return left; } if(arr[right] < arr[right - 1]){ return right; } int mid = (left + right) / 2; if(arr[mid] < arr[mid - 1] && arr[mid] < arr[mid + 1]){ return mid; } if(arr[mid] > arr[mid - 1]){ return getLess(arr, left + 1, mid - 1); }else if(arr[mid] > arr[mid + 1]){ return getLess(arr, mid + 1, right - 1); } return left; }
对于一个有序数组arr,再给定一个整数num,请在arr中找到num这个数出现的最左边的位置。
给定一个数组arr及它的大小n,同时给定num。请返回所求位置。若该元素在数组中未出现,请返回-1。
public int findPos(int[] arr, int n, int num) { if(arr == null || arr.length == 1){ return -1; } int res = -1; int left = 0; int right = arr.length - 1; int mid = 0; while(left <= right){ mid = (left + right) / 2; if(arr[mid] > num){ right = mid - 1; }else if(arr[mid] < num){ left = mid + 1; }else {//arr[mid] == num res = mid; //因为需要寻找最左边的,而数组也是有序的,因此,还需要往mid左边寻找最左的一个num值位置 right = mid -1; } } return res; }
给定数组arr及它的大小n,请返回最小值。
[4,1,2,3,3],5
返回:1
思路:
(1)首先设置好下标,left=0,right=arr.length-1,如果arr[left] < arr[right],由于数组有序,则arr[left]即为最小值,此时数组没有循环;
(2)当arr[left] >= arr[right]时,即数组为循环状态,开始二分搜索,找到中间位置mid = (left + right) / 2;如果arr[left] > arr[mid],说明最小值一定在mid左半部分;
(3)如果arr[mid] > arr[right],说明最小值一定出现在mid右半部分,否则,arr[left] <=arr[mid],arr[mid] <= arr[right],又arr[left] >= arr[right],由三个条件==> arr[left] == arr[right] == arr[mid]。
此时,需要用遍历的方式在left-->right的区间寻找最小值。(或者说,因为arr[left] == arr[right] == arr[mid],说明在[left,mid]区间内,必定有最小值,再在这个区间遍历搜索)
public static int getMin(int[] arr, int n) { if(arr == null || arr.length == 0){ return -1; } if(arr.length == 1){ return arr[0]; } int left = 0; int right = arr.length - 1; int mid = 0; //下面是arr[left] >= arr[right]的情形,即数组为循环状态 while(left < right){ if (left == right - 1) { break; } if(arr[left] < arr[right]){ return arr[left]; } mid = (left + right) / 2; if(arr[left] > arr[mid]){//最小值一定在mid左半部分 right = mid; continue; }else if(arr[mid] > arr[right]){//最小值一定出现在mid右半部分 left = mid; continue; }else{ //这种情况下,arr[left] <=arr[mid],arr[mid] <= arr[right],又arr[left] >= arr[right],由三个条件==> arr[left] == arr[right] == arr[mid] //此时,需要用遍历的方式在left-->right的区间寻找最小值 // int min = arr[left]; // for(int i = left; i <= right; i++){ // if(arr[i] < min){ // min = arr[i]; // } // } // return min; 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,请返回所求值。
[-1,0,2,3],4
返回:2
public int findPos(int[] arr, int n, int num) { if(arr == null || n == 0){ return -1; } int left = 0; int right = arr.length - 1; if(arr[left] > n - 1 || arr[right] < 0){ return -1; } int mid = 0; int res = -1; while(left <= right){ if (arr[left] > left || arr[right] < right) { break; } mid = (left + right) / 2; if(arr[mid] > mid){ right = mid - 1; }else if(arr[mid] < mid){ left = mid + 1; }else{ res = mid; right = mid - 1; } } return res; }
给定一棵完全二叉树的根节点root,返回这棵树的节点个数。如果完全二叉树的节点数为N,请实现时间复杂度低于O(N)的解法。
给定树的根结点root,请返回树的大小。
思路:public int count(TreeNode root) { if(root == null){ return 0; } int high = 0; TreeNode node = root; while(node != null){ high++; node = node.left; } int rightHigh = 0; node = root; while(node != null){ rightHigh++; node = node.left; } if(high == rightHigh){//表示根节点的左子树为满二叉树,高度为high - 1 return (int) (Math.pow(2, high - 1)) + count(root.right); }else{ return (int) (Math.pow(2, rightHigh - 1)) + count(root.left); } }
如何更快的求一个整数k的n次方。如果两个整数相乘并得到结果的时间复杂度为O(1),得到整数k的N次方的过程请实现时间复杂度为O(logN)的方法。
给定k和n,请返回k的n次方,为了防止溢出,请返回结果Mod 1000000007的值。
2,3
返回:8
public int getPower(int k, int N) { if(k == 0){ return 0; } if(k == 1 || N == 0){ return 1; } long modNum = 1000000007; long res = 1; long temp = k; for(; N > 0; N >>= 1){ if((N & 1) != 0){ res *= temp; } temp = (temp * temp) % modNum; res = res % modNum; } return (int) res; }