目录
二分法步骤回顾
现在让我们再来回顾一下二分法的基本知识:参考:3.1 二分查找习题分类 | 算法吧 (suanfa8.com)
二分法是一种减治思想
其基本步骤如下:
一.选择合适的循环终止条件
1.while(left<right)最常用,其返回一个区间,其循环结束的条件的是left==right 能够把left==right位置的数据单独拿出来进行处理,适用绝大多数情况 【本文此步骤我们采用这个while条件】
将整个数据分成了两个区间:mid符合题目条件的区间 mid不符合题目条件的区间
2.while (left+1<right) 其返回的是left 和 right的两个数据 返回的是【left,right】数据,还需要对这两个数据进行判断,来选取符合条件的。
3.while (left<=right) 其返回空区间,在循环体当中就对中间数据进行了处理
将整个数据分成了三个区间:mid的左边区间 mid mid的右边区间
二.编写 if else语句 关键
一个妙招:if()当中是mid不符合题解的条件,让符合条件的mid处于else当中
三.修改取中间数行为
一般我们采用是 int mid=left+((right-left)>>1); 【向下取整】
如果我们在语句当中缩小区间的时候采用了 left=mid的语句 则进行【向上取整】
即:int mid=left+((right-left+1)>>1)
四.退出循环后看是否还需要对目标元素进行额外判断
public int Binarysearch(int[] nums,int target) {
int len=nums.length;
int left=0;
int right=len-1;
while(left<right) {
//向下取整
int mid=left+((right-left)>>1);
if(nums[mid]<target) {//mid不符合条件,一定不是题解
left=mid+1;
}else {//mid在符合条件的区间之中,可能是题解
right=mid;
}
// //向下取整
// int mid=left+((right-left+1)>>1);
//
// if(nums[mid]>target) {//mid不符合条件,一定不是题解
// right=mid-1;
// }else {//mid在符合条件的区间之中,可能是题解
// left=mid;
// }
}
//退出循环————>left==right
if(nums[left]==target) {
return left;
}
return -1;
}
题型一:二分求下标(在数组中查找符合条件的元素的下标)
第一题: 在排序数组当中查找元素的第一个和最后一个位置
我们此题可以采用二分法来进行处理,对于元素第一次出现的位置和最后一次出现的位置分别进行处理。
本题的关键再于如何编写if else的条件
我们先来进行处理元素第一次出现的位置://要查找开始位置即查找第一个 大于等于目标元素的值!!!
我们根据上面的步骤知道,在此处的技巧是将符合条件的mid放在else区间当中,我们将其不符合的区间放入if当中。
//寻找开始位置
private int first(int[] nums,int target) {
int len=nums.length;
int left=0;
int right=len-1;
while(left<right) {
int mid=left+((right-left)>>1);
//nums[mid]<target,mid及其左边一定不存在target的开始位置
if(nums[mid]<target) {
left=mid+1;
}else {
right=mid;
}
}
/*
因为我们采用的是while(left<right)这一个循环体
其结束循环的条件是left==right
对于此位置是不是我们要找的target我们还需要进一步的判断
*/
if(nums[left]==target) {
return left;
}
return -1;
}
我们再来进行处理元素最后一次出现的位置:
我们根据上面的步骤知道,在此处的技巧是将符合条件的mid放在else区间当中,我们将其不符合的区间放入if当中。
private int last(int[] nums,int target) {
int len=nums.length;
int left=0;
int right=len-1;
while(left<right) {
int mid=left+((right-left+1)>>1);
//nums[mid]>target,mid及其右边一定不存在target的末尾位置
if(nums[mid]>target) {
right=mid-1;
}else {
left=mid;//出现left=mid;则向上取整
}
}
/*
我们前面对first的位置进行了判断,如果数组当中存着target的元素。
则一定存着其末尾下标,则不需要对当left==right的时候进行判断
*/
return left;
}
综上此题代码如下:
public int[] Binarysearch(int[] nums,int target) {
int len=nums.length;
int left=0;
int right=len-1;
int f=first(nums, target);
//当不存在最开始的位置的时候,说明不存在此数
if(f==-1) {
return new int[] {-1,-1};
}
int l=last(nums, target);
return new int[] {f,l};
}
//寻找开始位置
private int first(int[] nums,int target) {
int len=nums.length;
int left=0;
int right=len-1;
while(left<right) {
int mid=left+((right-left)>>1);
//nums[mid]<target,mid及其左边一定不存在target的开始位置
if(nums[mid]<target) {
left=mid+1;
}else {
right=mid;
}
}
/*
因为我们采用的是while(left<right)这一个循环体
其结束循环的条件是left==right
对于此位置是不是我们要找的target我们还需要进一步的判断
*/
if(nums[left]==target) {
return left;
}
return -1;
}
private int last(int[] nums,int target) {
int len=nums.length;
int left=0;
int right=len-1;
while(left<right) {
int mid=left+((right-left+1)>>1);
//nums[mid]>target,mid及其右边一定不存在target的末尾位置
if(nums[mid]>target) {
right=mid-1;
}else {
left=mid;//出现left=mid;则向上取整
}
}
/*
我们前面对first的位置进行了判断,如果数组当中存着target的元素。
则一定存着其末尾下标,则不需要对当left==right的时候进行判断
*/
return left;
}
做题感悟:
刚开始并不明白为什么能够二分法可以寻找到其第一次以及最后一次元素出现的位置,后来体会了一下二分法的思想才得以明白 我们是采用减而治之的思想来进行处理,以查找第一次出现的位置为例:我们需要先取中间数,如果中间数小于target则在mid及其左边一定不存在其第一次出现的位置,我们令left=mid+1 从而减少了一部分查找的区间,实现减治的思想,也就是电脑一定会根据我们二分法所编写的代码对数组来进行排除,相当于通过排除的形式来对数组进行筛选,最后是left和right相遇 此时只剩下一个数值,我们只需对此进行额外的判断即可。
第二题 有效三角形的个数有效三角形的个数
https://leetcode-cn.com/problems/valid-triangle-number/
组成三角形的三条边需满足最长的边小于另外两条边之和,最短的边大于另外两条边之差的绝对值。满足其中一条即可!!!
题解如下 大佬写的真好!!!
排序以后找第 1 个大于等于两边之和的下标(Java) - 有效三角形的个数 - 力扣(LeetCode) (leetcode-cn.com)
/*
给定一个包含非负整数的数组 nums ,返回其中可以组成三角形三条边的三元组个数。
对于满足三角形的三条边 满足如下条件其中之一即可:
1.最长边小于另外两边之和的绝对值
2.最短边大于另外两边之差的绝对值
*/
/*
* 思路:我们发现三角形的三条边排列在一起其实存在一定的顺序,我们可以将其排序
* 1.通过设置两边来寻找第三边 nums[i] nums[j]
* 在[j+1,len-1]区间中寻找第一个大于等于nums[i]+nums[j]的下标k
* 2.通过下标k和j+1差值来获取可以组成三角形的组合个数
*/
public class Main {
//要查找开始位置即查找第一个 大于等于目标元素的值!!!
public int triangleNumber(int[] nums) {
Arrays.sort(nums);//对数组进行排序
int ans=0;
int len=nums.length;
if(len<=2)
return 0;
for (int i = 0; i <= nums.length-3; i++) {
for (int j = i+1; j <= nums.length-2; j++) {
int left=j+1;int right=len-1;
while(left<right) {
int mid=left+((right-left)>>1);
if(nums[mid]<(nums[i]+nums[j]))
left=mid+1;
else
right=mid;
}
if(nums[left]>=(nums[i]+nums[j]))
ans+=left-j-1;
else
ans+=left-j;//[j+1,len-1]全部满足
}
}
return ans;
}
}
做题思路:
1.我们看到有序数组来进行查找 想到二分法 我们先采用二分查找到第一个大于等于x的位置
2.然后设置左 右索引来进行移动获取数组元素
3.要注意写好特定的判别条件!!
class Solution {
public List<Integer> findClosestElements(int[] arr, int k, int x) {
int len=arr.length;
ArrayList<Integer> list=new ArrayList<>();
int left=0;int right=arr.length-1;
while(left<right) {
int mid=left+((right-left)>>1);
if(arr[mid]<x)
left=mid+1;
else
right=mid;
}
int L=left-1;
int R=left;
if(arr[left]==x) {
list.add(arr[left]);
k--;R++;
}
while(k>0) {
if(L==-1) {//左边到达最外面
for (int i = 0; i < k; i++)
list.add(arr[R++]);
break;
}else if(R==len){//达到最右边
for (int i = 0; i < k; i++)
list.add(arr[L--]);
break;
}
//普通情况 |a - x| < |b - x| 或者 |a - x| == |b - x| 且 a < b
if((Math.abs(arr[L]-x) < Math.abs(arr[R]-x) ) ||
(Math.abs(arr[L]-x)==Math.abs(arr[R]-x)&&arr[L]<arr[R])) {
list.add(arr[L]);
L--;k--;
}else {
list.add(arr[R]);
R++;k--;
}
}
Collections.sort(list);
return list;
}
}