From 8cf18afebddb3f1db0201f6397d005ef73efe058 Mon Sep 17 00:00:00 2001 From: chefyuan Date: Thu, 18 Mar 2021 12:47:47 +0800 Subject: [PATCH] test --- .../leetcode35搜索插入位置.md | 64 +++++++ .../二分查找及其变种/二分查找详解.md | 23 +-- .../查找第一个位置和最后一个位置.md | 173 ++++++++++++++++++ 3 files changed, 249 insertions(+), 11 deletions(-) create mode 100644 gif-algorithm/二分查找及其变种/leetcode35搜索插入位置.md create mode 100644 gif-algorithm/二分查找及其变种/查找第一个位置和最后一个位置.md diff --git a/gif-algorithm/二分查找及其变种/leetcode35搜索插入位置.md b/gif-algorithm/二分查找及其变种/leetcode35搜索插入位置.md new file mode 100644 index 0000000..50848a7 --- /dev/null +++ b/gif-algorithm/二分查找及其变种/leetcode35搜索插入位置.md @@ -0,0 +1,64 @@ +### 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 + +#### 题目解析 + +这个题目完全就和咱们的二分查找一样,只不过有了一点改写,那就是将咱们的返回值改成了 left,具体实现过程见下图 + +![搜索插入位置](https://img-blog.csdnimg.cn/img_convert/d806cb5199c4baeebc62bebe29d7eded.gif) + +#### 题目代码 + +```java +class Solution { + public int searchInsert(int[] nums, int target) { + + int left = 0, right = nums.length-1; + //注意循环条件 + while (left <= right) { + //求mid + int mid = left + ((right - left ) >> 1); + //查询成功 + if (target == nums[mid]) { + return mid; + //右区间 + } else if (nums[mid] < target) { + left = mid + 1; + //左区间 + } else if (nums[mid] > target) { + right = mid - 1; + } + } + //返回插入位置 + return left; + } +} +``` + + + diff --git a/gif-algorithm/二分查找及其变种/二分查找详解.md b/gif-algorithm/二分查找及其变种/二分查找详解.md index bd02198..19c0f06 100644 --- a/gif-algorithm/二分查找及其变种/二分查找详解.md +++ b/gif-algorithm/二分查找及其变种/二分查找详解.md @@ -1,4 +1,4 @@ -### 什么是二分? +### 什么是二分? 废话不多说,让导演帮我们把镜头切到袁记菜馆吧! @@ -46,7 +46,7 @@ int[ ] nums = {1,3,4,5,6,8,12,14,16}; target = 8 -![二分查找1](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/二分查找1.72m12umcouc0.png) +![二分查找1](https://img-blog.csdnimg.cn/img_convert/b58d55a34b32a342f652792297a1e4d7.png) @@ -58,7 +58,7 @@ int[ ] nums = {1,3,4,5,6,8,12,14,16}; target = 8 -![二分查找2](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/二分查找2.5i4ne6b2w1w0.png) +![二分查找2](https://img-blog.csdnimg.cn/img_convert/5354d4c9ea5e5bd28a77a202b4dd3445.png) @@ -66,7 +66,7 @@ int[ ] nums = {1,3,4,5,6,8,12,14,16}; target = 8 -![](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/二分查找3.55jtarz6ahs0.png) +![](https://img-blog.csdnimg.cn/img_convert/97c584c48d6c1c06dffb94c6481f82c6.png) @@ -74,7 +74,7 @@ int[ ] nums = {1,3,4,5,6,8,12,14,16}; target = 8 -![二分查找3](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/二分查找3.5p4wxbtr8cw0.png) +![二分查找3](https://img-blog.csdnimg.cn/img_convert/471b4093db0233e41d4875fc6b2e4359.png) @@ -82,7 +82,7 @@ int[ ] nums = {1,3,4,5,6,8,12,14,16}; target = 8 -![二分查找4](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/二分查找4.1j32wt421e74.png) +![二分查找4](https://img-blog.csdnimg.cn/img_convert/2145730bf3a6373f1cf60da628bf85e6.png) @@ -90,7 +90,7 @@ int[ ] nums = {1,3,4,5,6,8,12,14,16}; target = 8 -![二分查找6](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/二分查找6.5hs1o6fe4g80.png) +![二分查找6](https://img-blog.csdnimg.cn/img_convert/0aba81887cfbc25ce9a859ba8137cd4a.png) @@ -108,7 +108,7 @@ target < nums[mid] 则在左半区间继续进行搜索,即 right = mid -1; **动图解析** -![二分查找2](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/二分查找2.4ycnmywmcl40.gif) +![二分查找2](https://img-blog.csdnimg.cn/img_convert/eb648f86b4ada5b32afc7a52e78d9953.gif) 下面我们来看一下二分查找的代码,可以认真思考一下 if 语句的条件,每个都没有简写。 @@ -141,9 +141,9 @@ target < nums[mid] 则在左半区间继续进行搜索,即 right = mid -1; 2.while (left < = right) { } 注意括号内为 left <= right ,而不是 left < right ,我们继续回顾刚才的例子,如果我们设置条件为 left < right 则当我们执行到最后一步时,则我们的 left 和 right 重叠时,则会跳出循环,返回 -1,区间内不存在该元素,但是不是这样的,我们的 left 和 right 此时指向的就是我们的目标元素 ,但是此时 left = right 跳出循环 -3.left = mid + 1,right = mid - 1 而不是 left = mid 和 right = mid。我们思考一下这种情况,见下图,当我们的target 元素为 16 时,然后我们此时 left = 7 ,right = 8,mid = left + (right - left) = 7 + (8-7) = 7,那如果设置 left = mid 的话,则会进入死循环,mid 值一直为7 。 +3. left = mid + 1,right = mid - 1 而不是 left = mid 和 right = mid。我们思考一下这种情况,见下图,当我们的target 元素为 16 时,然后我们此时 left = 7 ,right = 8,mid = left + (right - left) = 7 + (8-7) = 7,那如果设置 left = mid 的话,则会进入死循环,mid 值一直为7 。 -![二分查找出差](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/二分查找出差.8ny6xh9s3z4.png) +![二分查找出差](https://img-blog.csdnimg.cn/img_convert/d7ff6aba9a1e9d673ae24667823d5770.png) @@ -170,4 +170,5 @@ public static int binarySearch(int[] nums,int target,int left, int right) { } ``` -### \ No newline at end of file + + diff --git a/gif-algorithm/二分查找及其变种/查找第一个位置和最后一个位置.md b/gif-algorithm/二分查找及其变种/查找第一个位置和最后一个位置.md new file mode 100644 index 0000000..403b77f --- /dev/null +++ b/gif-algorithm/二分查找及其变种/查找第一个位置和最后一个位置.md @@ -0,0 +1,173 @@ +## 查找元素第一个位置和最后一个位置 + +上面我们说了如何使用二分查找在数组或区间里查出特定值的索引位置。但是我们刚才数组里面都没有重复值,查到返回即可,那么我们思考一下下面这种情况 + +![二分查找变种一](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/二分查找变种一.3axfe8rq0ei0.png) + + + +此时我们数组里含有多个 5 ,我们查询是否含有 5 可以很容易查到,但是我们想获取第一个 5 和 最后一个 5 的位置应该怎么实现呢?哦!我们可以使用遍历,当查询到第一个 5 时,我们设立一个指针进行定位,然后到达最后一个 5 时返回,这样我们就能求的第一个和最后一个五了?因为我们这个文章的主题就是二分查找,我们可不可以用二分查找来实现呢?当然是可以的。 + +### leetcode 34在排序数组中查找元素的第一个和最后一个位置 + +#### 题目描述 + +> 给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。 +> +> 如果数组中不存在目标值 target,返回 [-1, -1]。 + + +示例 1: + +> 输入:nums = [5,7,7,8,8,10], target = 8 +> 输出:[3,4] + +示例 2: + +> 输入:nums = [5,7,7,8,8,10], target = 6 +> 输出:[-1,-1] + +示例 3: + +> 输入:nums = [], target = 0 +> 输出:[-1,-1] + +#### 题目解析 + +这个题目很容易理解,我们在上面说了如何使用遍历解决该题,但是这个题目的目的就是让我们使用二分查找,我们来逐个分析,先找出目标元素的下边界,那么我们如何找到目标元素的下边界呢? + +我们来重点分析一下刚才二分查找中的这段代码 + +```java + if (nums[mid] == target) { + return mid; + }else if (nums[mid] < target) { + //这里需要注意,移动左指针 + left = mid + 1; + }else if (nums[mid] > target) { + //这里需要注意,移动右指针 + right = mid - 1; + } +``` + +我们只需在这段代码中修改即可,我们再来剖析一下这块代码,nums[mid] == target 时则返回,nums[mid] < target 时则移动左指针,在右区间进行查找, nums[mid] > target时则移动右指针,在左区间内进行查找。 + +那么我们思考一下,如果此时我们的 nums[mid] = target ,但是我们不能确定 mid 是否为该目标数的左边界,所以此时我们不可以返回下标。例如下面这种情况。![二分查找下边界](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/二分查找下边界.m9m083jempc.png) + + + +此时 mid = 4 ,nums[mid] = 5,但是此时的 mid 指向的并不是第一个 5,所以我们需要继续查找 ,因为我们要找 + +的是数的下边界,所以我们需要在 mid 的值的左区间继续寻找 5 ,那我们应该怎么做呢?我们只需在 + +target <= nums[mid] 时,让 right = mid - 1即可,这样我们就可以继续在 mid 的左区间继续找 5 。是不是听着有点绕,我们通过下面这组图进行描述。 + +![左边界1](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/左边界1.5o2ihokjfg80.png) + +![左边界2](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/左边界2.5wazlfm298s0.png) + + + +其实原理很简单,就是我们将小于和等于合并在一起处理,当 target <= nums[mid] 时,我们都移动右指针,也就是 right = mid -1,还有一个需要注意的就是,我们计算下边界时最后的返回值为 left ,当上图结束循环时,left = 3,right = 2,返回 left 刚好时我们的下边界。我们来看一下求下边界的具体执行过程。 + +**动图解析** + +![二分查找下边界](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/二分查找下边界.u1cidx99yio.gif)计算下边界代码 + +```java +int lowerBound(int[] nums, int target) { + int left = 0, right = nums.length - 1; + while (left <= right) { + //这里需要注意,计算mid + int mid = left + ((right - left) >> 1); + if (target <= nums[mid]) { + //当目标值小于等于nums[mid]时,继续在左区间检索,找到第一个数 + right = mid - 1; + + }else if (target > nums[mid]) { + //目标值大于nums[mid]时,则在右区间继续检索,找到第一个等于目标值的数 + left = mid + 1; + + } + } + return left; + } +``` + + + +计算上边界时算是和计算上边界时条件相反, + +计算下边界时,当 target <= nums[mid] 时,right = mid -1;target > nums[mid] 时,left = mid + 1; + +计算上边界时,当 target < nums[mid] 时,right = mid -1; target >= nums[mid] 时 left = mid + 1;刚好和计算下边界时条件相反,返回right。 + +**计算上边界代码** + +```java +int upperBound(int[] nums, int target) { + int left = 0, right = nums.length - 1; + while (left <= right) { + //求mid + int mid = left + ((right - left) >> 1); + //移动左指针情况 + if (target >= nums[mid]) { + left = mid + 1; + //移动右指针情况 + }else if (target < nums[mid]) { + right = mid - 1; + } + + } + return left; + } +``` + +#### **题目完整代码** + +```java +class Solution { + public int[] searchRange (int[] nums, int target) { + int upper = upperBound(nums,target); + int low = lowerBound(nums,target); + //不存在情况 + if (upper < low) { + return new int[]{-1,-1}; + } + return new int[]{low,upper}; + } + //计算下边界 + int lowerBound(int[] nums, int target) { + int left = 0, right = nums.length - 1; + while (left <= right) { + //这里需要注意,计算mid + int mid = left + ((right - left) >> 1); + if (target <= nums[mid]) { + //当目标值小于等于nums[mid]时,继续在左区间检索,找到第一个数 + right = mid - 1; + + }else if (target > nums[mid]) { + //目标值大于nums[mid]时,则在右区间继续检索,找到第一个等于目标值的数 + left = mid + 1; + + } + } + return left; + } + //计算上边界 + int upperBound(int[] nums, int target) { + int left = 0, right = nums.length - 1; + while (left <= right) { + int mid = left + ((right - left) >> 1); + if (target >= nums[mid]) { + left = mid + 1; + }else if (target < nums[mid]) { + right = mid - 1; + } + } + return right; + } +} +``` + +## \ No newline at end of file