algorithm-base/gif-algorithm/二分查找及其变种/leetcode33不完全有序查找目标元素(不包含重复值).md
2021-03-18 13:33:53 +08:00

127 lines
6.0 KiB
Markdown
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.

# **不完全有序**
## **查找目标元素(不含重复元素)**
之前我们说二分查找需要在完全有序的数组里使用,那么不完全有序时可以用吗?
例:
![](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 题吧
### leetcode33搜索旋转排序数组
#### 题目描述
给你一个整数数组 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
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;
}
}
```
##