Skip to content

Commit 478248b

Browse files
committed
‘’
1 parent def3d10 commit 478248b

File tree

15 files changed

+242
-0
lines changed

15 files changed

+242
-0
lines changed

learning/1.9.二分法系列/904.md

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
---
2+
title: 旋转排序数组中的最小值
3+
date: 2020-07-03
4+
---
5+
6+
> 今天继续为大家讲解二分查找,分享一道知乎面试题。话不多说,直接看题。
7+
8+
## 01、题目示例
9+
10+
> 这道题目有两个版本,一道简单,一道困难。我们从简单的开始讲起。
11+
12+
| 第153题:旋转排序数组最小值Ⅰ |
13+
| ------------------------------------------------------------ |
14+
| 假设按照升序排序的数组在预先未知的某个点上进行了旋转。( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。请找出其中最小的元素。你可以假设数组中不存在重复元素。 |
15+
16+
**示例 1:**
17+
18+
```
19+
输入: [3,4,5,1,2]
20+
输出: 1
21+
```
22+
23+
**示例 2:**
24+
25+
```
26+
输入: [4,5,6,7,0,1,2]
27+
输出: 0
28+
```
29+
30+
## 02、题解分析
31+
32+
> 这道题目的关键点取决于是否可以想到二分,难点:无。但是如果把题中的条件,换成数组中元素可重复,本题的难度就会大幅上升。
33+
34+
<br/>
35+
36+
当然,本题可以直接暴力搜索,但是这就就会被面试官撵出去。为了不被他撵出去,我们还是使用二分更为稳妥!******二分搜索中,我们找到区间的中间点并根据某些条件决定去区间左半部分还是右半部分搜索**。但是麻烦的是,我们的数组被旋转了,因此肯定不能直接使用二分。那我们需要先观察一下,假若我们的原始数组如下:
37+
38+
<img src="./904/1.jpg" alt="PNG" style="zoom: 80%;" />
39+
40+
无论怎么旋转,我们都可以得到一个结论,首元素 > 尾元素,像是下面这样。虽然不知道这个结论有什么用,但是我们先记下来。
41+
42+
<img src="./904/2.jpg" alt="PNG" style="zoom: 80%;" />
43+
44+
继续进行观察,上面其实是两种极端情况,那如果普通的情况旋转,大概是下面这样:
45+
46+
<img src="./904/3.jpg" alt="PNG" style="zoom: 80%;" />
47+
48+
问题似乎变得简单了,旋转将原数组一分为二,并且我们已知了首元素值总是大于尾元素,那我们只要找到将其一分为二的那个点(该点左侧的元素都大于首元素,该点右侧的元素都小于首元素),是不是就可以对应找到数组中的最小值。
49+
50+
<img src="./904/4.jpg" alt="PNG" style="zoom: 80%;" />
51+
52+
然后我们通过二分来进行查找,先找到中间节点mid,如果中间元素大于首元素,我们就把mid向右移动。
53+
54+
<img src="./904/5.jpg" alt="PNG" style="zoom: 80%;" />
55+
56+
如果中间元素小于首元素,我们就把mid向左移动。
57+
58+
<img src="./904/6.jpg" alt="PNG" style="zoom: 80%;" />
59+
60+
根据分析,完成题解:
61+
62+
java版本
63+
64+
```java
65+
//java
66+
class Solution {
67+
public int findMin(int[] nums) {
68+
int left = 0;
69+
int right = nums.length - 1;
70+
while (left < right) {
71+
int mid = left + (right - left) / 2 + 1;
72+
if (nums[left] < nums[mid]) {
73+
left = mid;
74+
} else if (nums[left] > nums[mid]) {
75+
right = mid - 1;
76+
}
77+
}
78+
return nums[(right + 1) % nums.length];
79+
}
80+
};
81+
```
82+
83+
python版本
84+
85+
```python
86+
//python
87+
class Solution:
88+
def findMin(self, nums: List[int]) -> int:
89+
left = 0
90+
right = len(nums) - 1
91+
while left < right:
92+
mid = (left + right) >> 1
93+
if nums[mid] > nums[right]:         
94+
left = mid + 1
95+
else:                               
96+
right = mid
97+
return nums[left]
98+
```
99+
100+
c版本
101+
102+
```c
103+
//c
104+
int findMin(int* nums, int numsSize){
105+
int left=0;
106+
int right=numsSize-1;
107+
while(right>left)
108+
{
109+
int mid=left+(right-left)/2;
110+
if(nums[mid]>nums[right])
111+
left=mid+1;
112+
else
113+
right=mid;
114+
}
115+
return nums[left];
116+
}
117+
```
118+
119+
执行结果:
120+
121+
<img src="./904/7.jpg" alt="PNG" style="zoom: 80%;" />
122+
123+
## 03、课后思考
124+
125+
> 本题有多种变形,是一道练习二分法的绝佳题目。比如“把元素中不可重复的条件去掉”,又或者是“编写一个函数来判断目标值是否在数组中”等等,不同的改动,都会对题目解题方式有略微的影响,但是万变不离其宗,统统都是二分法。
126+
127+
<br/>
128+
129+
其他的就不啰嗦了,明天将为大家答疑分析,解决“元素可重复”的版本。所以,今天的问题你学会了吗,评论区留下你的想法!
3.38 KB
Loading
38 KB
Loading
13.4 KB
Loading
51.9 KB
Loading
17.2 KB
Loading
17.1 KB
Loading
24 KB
Loading

learning/1.9.二分法系列/905.md

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
---
2+
title: 旋转排序数组中的最小值Ⅱ(154)
3+
date: 2020-07-03
4+
---
5+
6+
今天继续为大家讲解二分法系列篇 - 旋转排序数组最小值Ⅱ(进阶版)。话不多说,直接看题:
7+
8+
## 01、题目示例
9+
10+
> 昨天为大家讲解了元素不可重复的版本,那如果元素重复该如何处理呢?
11+
12+
| 第154题:旋转排序数组最小值Ⅱ |
13+
| ------------------------------------------------------------ |
14+
| 假设按照升序排序的数组在预先未知的某个点上进行了旋转。( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。请找出其中最小的元素。 注意数组中可能存在重复的元素。 |
15+
16+
**示例 1:**
17+
18+
```
19+
输入: [1,3,5]
20+
输出: 1
21+
```
22+
23+
**示例 2:**
24+
25+
```
26+
输入: [2,2,2,0,1]
27+
输出: 0
28+
```
29+
30+
**说明:**
31+
32+
- 这道题是 [旋转排序数组中的最小值(153)](1.9/904.md) 的延伸题目。
33+
- 允许重复会影响算法的时间复杂度吗?会如何影响,为什么?
34+
35+
## 02、题目回顾
36+
37+
> 之前我也说过,通过改变题中条件,使得题目难度上升的做法。在算法题目的设计中,是一种非常常见的手段。本题就是这样,从中等变成了困难。
38+
39+
<img src="./905/1.jpg" alt="PNG" style="zoom: 80%;" />
40+
41+
<img src="./905/2.jpg" alt="PNG" style="zoom: 80%;" />
42+
43+
在讲解本题之前,首先要对昨天的题目进行一个答疑。昨天有人问我为什么题目中讲的是与left进行比较,但是最后代码中写的时候变成了和right比较。这个确实是我讲的时候讲忘了,但是这其实是一个思维转化的问题:因为在旋转之前的原数组是一个递增序列,那一定是左边的数小,右边的数大,而我们要找的是最小值,所以比较偏向左找。那如果和left进行比较,其实也是完全ok的,那我们的思路就变成了找到偏右的最大值,进而向右再移动一位,自然也就是最小值。如果不能理解的话,可以回顾一下昨天的文章:
44+
45+
[旋转排序数组中的最小值(153)](1.9/904.md)
46+
47+
并且我这里对昨天的题目,补上一个和left对比的版本,供大家参考学习(昨天没有给Go的示例,所以今天补一个Go的)
48+
49+
```go
50+
51+
```
52+
53+
上面的代码有两处需要说明,第一:mid中最后加1的目的,是为了使得mid更加靠近right,增加容错性。当然,你写到里边也是可以的,甚至更好。我怕大家看不懂,所以写在外面了。第二:最后一行代码取模,是需要考虑最大值刚好在最右边的情况。
54+
55+
## 03、题解分析
56+
57+
> 二分查找的本质,其实就是通过收敛查找空间,找到目标值的一种方式。请大家认真阅读这句话。不管是采用不同的mid定义方式,又或者是不一样的while条件,统统都是为了这个目的。在完成这个目的的基础上,我们才去考虑如何减少冗余代码,减少循环次数等等,完成进一步的优化。
58+
59+
<br/>
60+
61+
现在再来看今天的题目。相对比昨天题目而言,其实只是多了**nums[mid] 等于 nums[right] 时的额外处理**。(当然, 如果是和left进行比较,就是nums[mid]等于nums[left]
62+
63+
<br/>
64+
65+
对比一下下面两个图:
66+
67+
<img src="./905/3.jpg" alt="PNG" style="zoom: 67%;" />
68+
69+
<center>(无重复) </b></center>
70+
71+
<img src="./905/4.jpg" alt="PNG" style="zoom: 67%;" />
72+
73+
<center>(有重复) </b></center>
74+
75+
其实直接就可以给出代码了:
76+
77+
```java
78+
//java
79+
class Solution {
80+
public int findMin(int[] nums) {
81+
int left = 0;
82+
int right = nums.length - 1;
83+
while (left < right) {
84+
int mid = left + (right - left) / 2;
85+
if (nums[mid] > nums[right]) {
86+
left = mid + 1;
87+
} else if (nums[mid] < nums[right]) {
88+
right = mid;
89+
} else {
90+
right--;
91+
}
92+
}
93+
return nums[left];
94+
}
95+
};
96+
97+
```
98+
99+
如果我们再对比一下代码的差异,就会非常的明显:
100+
101+
<img src="./905/5.jpg" alt="PNG" style="zoom: 80%;" />
102+
103+
<center>(左边是有重复,右边是无重复) </b></center>
104+
105+
可以看到在 nums[mid] 等于 nums[right] 时的情况下,我们只多了一个 right-1 的操作。这里需要额外说明的是,为什么要这样做?我看leetcode上的题解,这块很多都是长篇大论,其实没那么复杂,一句话就可以给你讲明白,看看下面这个!
106+
107+
<img src="./905/6.jpg" alt="PNG" style="zoom: 67%;" />
108+
109+
因为 mid 和 right 相等时,最小值既可能在左边,又可能在右边,所以此时自然二分思想作废,咱们就砍掉一个右边界。说白了,就是**让子弹再飞一会儿**
110+
111+
<br/>
112+
113+
所以,今天的问题你学会了吗,评论区留下你的想法!
15.1 KB
Loading
15.4 KB
Loading
11 KB
Loading
11.1 KB
Loading
107 KB
Loading
32.8 KB
Loading

0 commit comments

Comments
 (0)