1.二分查找的应用
1.1.在排序数组中查找元素的第一个和最后一个位置
思路:
- 两次二分查找,一次寻找左边界,一次寻找右边界。
- 技巧:寻找左边界用high,寻找右边界用low
- 注意点:最后得到的左右边界与实际边界差一
- 有三种情况:情况一:target 在数组范围的右边或者左边,例如数组{3, 4, 5},target为2或者数组{3, 4, 5},target为6,此时应该返回{-1, -1}
情况二:target 在数组范围中,且数组中不存在target,例如数组{3,6,7},target为5,此时应该返回{-1, -1}
情况三:target 在数组范围中,且数组中存在target,例如数组{3,6,7},target为6,此时应该返回{1, 1}
class Solution {
public int[] searchRange(int[] nums, int target) {
int leftBorder = getLeftBorder(nums,target);
int rightBorder = getRightBorder(nums,target);
if(leftBorder == -2 || rightBorder == -2) return new int[]{-1,-1};
if(rightBorder - leftBorder > 1) return new int[]{leftBorder+1,rightBorder-1};
else return new int[]{-1,-1};
}
public int getRightBorder(int[] nums,int target){
int low = 0;
int high = nums.length - 1;
// target不在数组中时的默认值
int rightBorder = -2;
while(low <= high){
int mid = low + ((high - low) >> 1);
if(nums[mid] > target) high = mid - 1;
else {
low = mid + 1;
rightBorder = low;
}
}
return rightBorder;
}
public int getLeftBorder(int[] nums,int target){
int low = 0;
int high = nums.length - 1;
// target不在数组中时的默认值
int leftBorder = -2;
while(low <= high){
int mid = low + ((high - low) >> 1);
if(nums[mid] >= target){
high = mid - 1;
leftBorder = high;
}
else {
low = mid + 1;
}
}
return leftBorder;
}
}
2.基于二分查找的拓展问题
2.1.山脉数组的峰顶索引
二分查找的变形,峰顶前升序,峰顶后降序。
- 在上升阶段,low = mid + 1;
- 在下降阶段,high= mid - 1;
- 在峰顶,return;
class Solution {
public int peakIndexInMountainArray(int[] arr) {
if(arr.length == 3){
return 1;
}
int low = 1;
int high = arr.length - 2;
while(low <= high){
int mid = low + ((high - low) >> 1);
// 在上升阶段
if(arr[mid] > arr[mid-1] && arr[mid] < arr[mid+1]) low = mid + 1;
// 在峰顶
else if(arr[mid] > arr[mid-1] && arr[mid] > arr[mid+1]) return mid;
// 在下降阶段
else high = mid - 1;
}
return low;
}
}
2.2.旋转数字的最小数组
思路:
数组经过旋转后会分成两段递增的数组,如下图:
用nums[high]来作比较
如果 中间值小于nums[high],最小值在左侧,舍弃右侧;
如果中间值大于nums[high],最小值在右侧,舍弃左侧
跟普通二分相比,只是将中间值和目标值比较换成了中间值和high处的元素比较.
class Solution {
public int findMin(int[] nums) {
int low = 0;
int high = nums.length - 1;
while(low <= high){
int mid = low + ((high - low) >> 1);
// 中间值小于nums[high],说明mid在上升阶段,最小值在左侧,舍弃右侧
if(nums[mid] < nums[high]) high = mid;
else{
low = mid + 1;
}
}
// 因为循环条件为<=,所以在找到中间值后会再执行一次mid+1
return nums[low - 1];
}
}
2.3.0-n-1缺失的数字
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IE81mn5v-1693230285841)(C:\Users\李建伟llx\AppData\Roaming\Typora\typora-user-images\image-20230826213000301.png)]
思路:
- 有序数组加查找,首先想到二分查找;
- 规律:如果没有缺失数字,那么在这个递增数字中有下标等于对应的元素(nums[i] == i);
- 如果有缺失数字,那么下标不等于对应的元素(nums[i] != i)
class Solution {
public int missingNumber(int[] nums) {
int low = 0;
int high = nums.length - 1;
while(low <= high){
int mid = low + ((high - low) >> 1);
// mid之前连续
if(nums[mid] == mid){
low = mid + 1;
}
else {
high = mid - 1;
}
}
return low;
}
}
2.4.二分法求平方根
class Solution {
public int mySqrt(int x) {
int low = 0;
int high = x;
int res = 0;
while(low <= high){
int mid = low + ((high - low) >> 1);
if((long) mid * mid <= x){
res = mid;
low = mid + 1;
}
else {
high = mid - 1;
}
}
return res;
}
}
2.中序与搜索树原理
二叉搜索树:左子树的值均小于根节点的值;右子树的值都大于根节点的值。
二叉搜索树的中序序列是一个递增序列。
2.1.二叉搜索树中的搜索
class Solution {
public TreeNode searchBST(TreeNode root, int val) {
if(root == null || root.val == val) return root;
return val < root.val ? searchBST(root.left,val) : searchBST(root.right,val);
}
}
2.2.验证二叉搜索树
类似中序遍历
二叉搜索树的中序序列是一个递增序列。所以只需要判断当前节点的值是否大于前一个遍历到的节点即可
class Solution {
// 中序遍历,二叉搜索树为升序
long pre = Long.MIN_VALUE;
public boolean isValidBST(TreeNode root) {
if(root == null) return true;
boolean left = isValidBST(root.left);
if(!left) return false;
if(pre >= root.val) return false;
pre = root.val;
boolean right = isValidBST(root.right);
return right;
}
}
2.3.二叉搜索树的最小绝对差
用一个pre保存中序遍历的前一个结果,
class Solution {
int pre;
int ans;
public int getMinimumDifference(TreeNode root) {
ans = Integer.MAX_VALUE;
pre = -1;
dfs(root);
return ans;
}
public void dfs(TreeNode root){
if(root == null) return;
dfs(root.left);
// 赋初值
if(pre == -1){
pre = root.val;
}else{
ans = Math.min(ans,root.val - pre);
// 更新前驱节点的值
pre = root.val;
}
dfs(root.right);
}
}