今天仔细学了二分查找,跟双指针算法。通过这篇文章可以让你更快认识到二分查找的魅力,以及双指针算法的思想。
先上模板
int left = 0; //定义左边界
int right = nums.length - 1; //定义右边界
int mid = 0;
while(left <= right){ //设置循环,直到left > right
mid = left + ((right - left) / 2); //防止溢出,等同于(left + right) / 2
if(nums[mid] == target) {
return mid; //数组中找到目标值,直接返回下标
} else if (target < nums[mid]) { //目标值在左边界到中间的位置
right = mid - 1; //所以现在数组范围为[left, mid - 1]
} else { //target在中间到右边界的位置
left = mid + 1;
}
}
return left; //重点!!! 先记住一个点 因为求mid时是向下取整,所以到最后都是left = mid。
leetcode704.二分查找
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
示例1:
输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4
示例2:
输入: nums = [-1,0,3,5,9,12], target = 2
输出: -1
解释: 2 不存在 nums 中因此返回 -1
提示:
你可以假设 nums 中的所有元素是不重复的。
n 将在 [1, 10000]之间。
nums 的每个元素都将在 [-9999, 9999]之间。
(大家可以想一下,二分查找首先是个什么东西,有一个目标值要查找,然后定位到是在左区域还是右区域,直至到区域缩小至1~2个元素)
答案:::
public class test01 {
//定义输入输出
public static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
public static BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));
//二分查找
private static int search(int[] nums, Integer target) {
//设置两个变量 分别表示左右两个边界
int left = 0;
int right = nums.length-1;
//循环
while(left <= right) {
//设置中间值,用来定位target属于左边的一半还是右边的一半
int mid = (left + right) / 2;
//如果属于左边的一半 则右边界right移动到刚刚的中间值mid
if (target <= nums[mid]) {
right = mid;
//如果属于右边的一半,则把左边界前移到中间值+1(因为上边是<=,所以nums[mid]属于左边界)
//则左边界变成mid+1
} else {
left = mid + 1;
}
}
//如果到最后没有找到target,则返回-1
if (nums[left] != target)
return -1;
return left;
}
public static void main(String[] args) throws IOException {
//读入一行,并按照空格拆分
String s = in.readLine();
String[] str = s.split(" ");
//获取数组的长度
int n = str.length;
//读入一个数字,默认读入String 需要转为int
Integer target = Integer.parseInt(in.readLine());
//转为int数组
int[] nums = new int[n];
for (int i = 0; i < n; i ++) {
nums[i] = Integer.parseInt(str[i]);
}
//输出答案并冲刷管道,关闭管道
int count = search(nums, target);
out.write(count);
out.flush();
out.close();
}
}
leetcode35.搜索插入位置
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。你可以假设数组中无重复元素。
示例 1:
输入: [1,3,5,6], 5
输出: 2
示例 2:
输入: [1,3,5,6], 2
输出: 1
示例 3:
输入: [1,3,5,6], 7
输出: 4
示例 4:
输入: [1,3,5,6], 0
输出: 0
思路:其实这道题不是很难,但是要你进一步的理解二分查找 (偷偷说一句,直接用模板就可以ac了,都不需要改代码。。。。) 大家可以试试
答案:
public class test03 {
public static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
public static BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));
public static void main(String[] args) throws IOException {
String[] arr = in.readLine().split(",");
Integer target = Integer.parseInt(in.readLine());
int n = arr.length;
Integer[] nums = new Integer[n];
for (int i = 0; i < n; i ++) {
nums[i] = Integer.parseInt(arr[i]);
}
int count = searchInsert(nums, target);
out.write(count);
out.flush();
out.close();
}
private static int searchInsert(Integer[] nums, Integer target) {
int left = 0;
int right = nums.length - 1;
int mid = 0;
while(left <= right) {
mid = (left + right) / 2;
if (target == nums[mid])
return mid;
if (target < nums[mid]) {
right = mid - 1;
} else {
left = mid + 1;
}
}
return left;
}
}
这里需要大家理解的就是为什么直接用模板就能ac?为什么目标值找不到插的位置也跟找到目标值的做法一样, 不需要额外添加判定条件? 为什么返回值是left?
先聊一下没有找到target时,while循环到最后是什么样子的。
很容易想象,经过每次while循环,都会使得[left,right]的左闭右闭区间中的元素减少。
那就有一个问题,减少到最后会是什么情况?
那就是进入最后一次while循环前,[left,right]的左闭右闭区间中只有一个或者两个元素,即
left和right的位置有且仅有2种情况
给大家一个数组[1, 3]。然后target分别等于0跟2。大家拿纸去推导一下就懂了,一定要自己去手动推导!!!
以上就解释清楚了,在进入最后一次while循环前,数组会变为一个或两个元素。
然后当while到尽头的时候 往往只剩下一个left(由于向下取整所以mid=left),那为什么找不到target的值时,总是能插入到正确的位置呢?
主要就是nums[mid]与target的位置关系了
target在nums[mid]的哪一边 如果是左边的话就占着mid当前的下标(0 因为到最后都可以看成数组里只有一个元素mid,且mid永远等于left),如果是在右边的话就用left+1来表示target的位置
看图展示!!!!
leetcode34.在排序数组中查找元素的第一个和最后一个位置
给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。 (时间复杂度为 O ( log n ) O(\log n) O(logn) !!!)
如果数组中不存在目标值 target,返回 [-1, -1]。
示例1:
示例2:
示例3:
思路:
我们要想一下有什么情况:(开始位置设为左边界leftBorder, 结束位置设为右边界rightBorder)
答案:
public class test04 {
public static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
public static BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));
public static void main(String[] args) throws IOException {
String[] arr = in.readLine().split(",");
Integer target = Integer.parseInt(in.readLine());
int n = arr.length;
Integer[] nums = new Integer[n];
for (int i = 0; i < n; i ++) {
nums[i] = Integer.parseInt(arr[i]);
}
int[] result = searchRange(nums, target);
out.write(result.toString());
out.flush();
out.close();
}
private static int[] searchRange(Integer[] nums, Integer target) {
//给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。
//判定条件:(1)leftBorder rightBorder值有一个为-2 [1,3] target = 4
//(2)leftBorder rightBorder值都不为-2
//(3)target在数组范围内,但是数组中没有target
// (这个与条件1不同的是因为在范围内所以leftBorder或者rightBorder会被赋值) 如[1,3] target = 2
int leftBorder = findLeftBorder(nums, target);
int rightBorder = findRightBorder(nums, target);
//条件(1)
if (leftBorder == -2 || rightBorder == -2)
return new int[]{-1, -1};
//条件(2) [1, 2] target = 2 =======> rightBorder = 2, leftBorder = 0
//关于left/rightBorder的取值,大家可以联想一下上一道题leetcode35找不到target的情况
//寻找左边界是找到target的左边一个数,也就是上一题中最后一个循环 target < nums[mid]的时候
//同理右边界就是找到target的右边的一个数。
if (rightBorder - leftBorder > 1)
return new int[]{leftBorder + 1, rightBorder - 1};
//条件三
return new int[]{-1, -1};
}
//找右边界
private static int findRightBorder (Integer[] nums, Integer target) {
int left = 0;
int right = nums.length - 1;
int mid = 0;
int rightBorder = -2;
//找出target的右边界(不取target)
while(left <= right) {
mid = left + ((right - left) / 2);
if (target >= nums[mid]) {
left = mid + 1;
rightBorder = left;
} else {
right = mid -1;
}
}
return rightBorder;
}
private static int findLeftBorder (Integer[] nums, Integer target) {
int left = 0;
int right = nums.length - 1;
int mid = 0;
int leftBorder = -2;
//找出target的左边界(不取target)
while(left <= right) {
mid = left + ((right - left) / 2);
if (target <= nums[mid]) {
right = mid - 1;
leftBorder = right;
} else {
left = mid + 1;
}
}
return leftBorder;
}
}
leetcode27.移动元素
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并原地修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
示例1:
示例2:
思路:
我们只需要有一个标记,当第i位的值不等于val时,直接从标记位开始把第i位的值赋值给标记位所在数组位置。
答案:
public class test02 {
public static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
public static BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));
public static void main(String[] args) throws IOException {
String str = in.readLine();
Integer val = Integer.parseInt(in.readLine());
String[] s = str.split(",");
int n = s.length;
Integer[] nums = new Integer[n];
for (int i = 0; i < n; i ++) {
nums[i] = Integer.parseInt(s[i]);
}
int len = removeElement(nums, val);
out.write(len);
out.flush();
out.close();
}
private static int removeElement(Integer[] nums, Integer val) {
//设置一个标记
int flag = 0;
for (int i = 0; i < nums.length; i ++) {
//当第i位的值不等于val时,直接从标记位开始把第i位的值赋值给标记位所在数组位置
if (nums[i] != val)
nums[flag ++] = nums[i];
}
//输出标记位
return flag;
}
}
学会了二分查找的基本应用和双指针算法的基本思路。
还是有点不熟悉,打算再做几道二分!!!
兄弟萌冲啊!
大概就是这样,大家懂了吗 有什么不懂的评论区评论或者私信我吧!!