algorithm-base/animation-simulation/二分查找及其变种/leetcode33不完全有序查找目标元素(不包含重复值).md
2021-07-27 00:16:35 +08:00

168 lines
8.0 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

> 如果阅读时发现错误或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ,备注 github + 题目 + 问题 向我反馈
>
> 感谢支持该仓库会一直维护希望对各位有一丢丢帮助
>
> 另外希望手机阅读的同学可以来我的 <u>[**公众号袁厨的算法小屋**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步,想要和题友一起刷题,互相监督的同学,可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。
# **不完全有序**
## **查找目标元素不含重复元素**
之前我们说二分查找需要在完全有序的数组里使用那么不完全有序时可以用吗
![](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/案例1.2wan88b4sdk0.png)
上面的新数组虽然不是完全有序但是也可以看成是由一个完全有序的数组翻折得到的或者可以理解成两个有序数组且第二个数组的最大值小于第一的最小值我们将其拼接拼接成了一个不完全有序的数组在这个数组中我们需要找到 target 找到后返回其索引如果没有找到则返回 -1
我们第一次看到这种题目时可能会想到我们只需要挨个遍历就好啦发现后返回索引即可这样做当然是可以滴那么我们可不可以使用二分查找呢
下面我们看一下解决该题的具体思路
首先我们设想一下 mid 值会落到哪里我们一起来想一下
是不是只有两种情况 left 在一个数组同时落在 数组 1 或同时在 数组 2或者不在一个数组 left 在数组 1mid 在数组 2想到这里咱们这个题目已经完成一半了
![mid值情况](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/mid值情况.3879bq8s3xk0.png)
那么我们先来思考一下我们可以根据 nums[mid] nums[left] 判断是因为我们的 mid 一定是会落在 left right 之间那如果 nums[mid] >= nums[left] 说明他俩落在一个数组里了如果 nums[mid] < nums[left] 说明他俩落在了不同的数组此时 left 在数组 1 mid 在数组 2.
left mid 落在同一数组时不能是 left 数组 2 mid 数组 1 因为咱们的 mid 是通过 left right 的下标求得所以应该在 left right 中间
如果我们的 mid left 在同一个数组内时咱们的 target 会有几种情况呢我们通过都落在 数组 1 举例
![left左](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/left左.6kl90uroee40.png)
无非也是两种情况用我们上面的例子来说
1.**落在 mid 的左边**当前例子中 情况是落在 [4,7区间内 4 <= target < 7 也就是 target >= nums[left] && target < nums[mid]此时我们让 right = mid -1 left right 都落到数组 1 下次查找我们就是在数组 1 中进行了完全有序
2.**落在 mid 的右边**此时例子中 target 不落在 [4,7区间内那就 target = 8 0 <= target <= 2 此时我们的 target 均小于 nums[left] 两种情况也就是 target > nums[mid] || target < nums[left] 此时我们让 left = mid + 1 即可也是为了慢慢将 left right 指针赶到一个有序数组内
那我们在来思考一下当 mid 值落在 **数组 2** 中时target 会有几种情况呢其实和上面的例子思路一致情况相反而已
![right右](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/right右.3yvrwxloi3c0.png)
1. target <= nums[right] && target > nums[mid]
> 这里和上面的对应此时的情况就是整个落在右半部分我们下次就可以在数组 2 内进行查找
2. target > nums[right] || target < nums[mid]
> 这里就是和上面的第二种情况对应落在 mid 的左半部分我们尽量将两个指针赶到一起
希望我的表达能够让大家对这个变种理解透彻如果没能让各位理解或者有表达不当的地方欢迎各位批评指导然后我们一起来做一下 leetcode 33 题吧
#### [33. 搜索旋转排序数组](https://leetcode-cn.com/problems/search-in-rotated-sorted-array/)
#### 题目描述
给你一个整数数组 nums 和一个整数 target
该整数数组原本是按升序排列但输入时在预先未知的某个点上进行了旋转例如数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2]
请你在数组中搜索 target 如果数组中存在这个目标值则返回它的索引否则返回 -1
示例 1
> 输入nums = [4,5,6,7,0,1,2], target = 0
> 输出4
示例 2
> 输入nums = [4,5,6,7,0,1,2], target = 3
> 输出-1
示例 3
> 输入nums = [1], target = 0
> 输出-1
#### 题目解析
这个题目的解答方法咱们在上面已经有所描述下面我们来看一下下面这个例子的代码执行过程吧.
> 输入 nums = [4,5,6,7,8,0,1,2] target = 8
下面我们看题目代码吧如果还没有完全理解的同学可以仔细阅读 if else if 里面的语句还有注释一定可以整透的
#### 题目代码
Java Code:
```java
class Solution {
public int search(int[] nums, int target) {
//左右指针
int left = 0;
int right = nums.length - 1;
while (left <= right) {
int mid = left+((right-left)>>1);
if (nums[mid] == target) {
return mid;
}
//落在同一数组的情况同时落在数组1 或 数组2
if (nums[mid] >= nums[left]) {
//target 落在 left 和 mid 之间则移动我们的right完全有序的一个区间内查找
if (nums[mid] > target && target >= nums[left]) {
right = mid - 1;
// target 落在right和 mid 之间有可能在数组1 也有可能在数组2
} else if (target > nums[mid] || target < nums[left]) {
left = mid + 1;
}
//不落在同一数组的情况left 在数组1 mid 落在 数组2
}else if (nums[mid] < nums[left]) {
//有序的一段区间target 在 mid 和 right 之间
if (nums[mid] < target && target <= nums[right]) {
left = mid + 1;
// 两种情况target 在left 和 mid 之间
} else if (target < nums[mid] || target > nums[right]) {
right = mid - 1;
}
}
}
//没有查找到
return -1;
}
}
```
Go Code:
```go
func search(nums []int, target int) int {
// 左右指针
left := 0
right := len(nums) - 1
for (left <= right) {
mid := left + ((right - left) >> 1)
rightNum, leftNum, midNum := nums[right], nums[left], nums[mid]
if midNum == target {
return mid
}
//落在同一数组的情况同时落在数组1 或 数组2
if midNum >= leftNum {
//target 落在 left 和 mid 之间则移动我们的right完全有序的一个区间内查找
if midNum > target && target >= leftNum {
right = mid - 1
} else {
// target落在另一个无序区间 但和原数组的性质相符, 所以可以继续循环
left = mid + 1
}
} else {
//target 落在 mid 和 right 之间则移动我们的left完全有序的一个区间内查找
if midNum < target && target <= rightNum {
left = mid + 1
} else {
// target落在另一个无序区间 但和原数组的性质相符, 所以可以继续循环
right = mid - 1
}
}
}
return -1
}
```