Skip to content

Commit aaa5264

Browse files
committed
34
1 parent 2f74eb1 commit aaa5264

File tree

2 files changed

+226
-0
lines changed

2 files changed

+226
-0
lines changed

SUMMARY.md

+1
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,5 @@
3434
* [31. Next Permutation](leetCode-31-Next-Permutation.md)
3535
* [32. Longest Valid Parentheses](leetCode-32-Longest-Valid-Parentheses.md)
3636
* [33. Search in Rotated Sorted Array](leetCode-33-Search-in-Rotated-Sorted-Array.md)
37+
* [34. Find First and Last Position of Element in Sorted Array](leetCode-34-Find-First-and-Last-Position-of-Element-in-Sorted-Array.md)
3738
* [79. Word Search](leetCode-79-Word-Search.md)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
# 题目描述(中等难度)
2+
3+
![](https://windliang.oss-cn-beijing.aliyuncs.com/34.jpg)
4+
5+
找到目标值的第一次出现和最后一次出现的位置,同样要求 log ( n ) 下完成。
6+
7+
先分享 [leetcode](https://leetcode.com/problems/find-first-and-last-position-of-element-in-sorted-array/solution/) 提供的两个解法。
8+
9+
# 解法一 线性扫描
10+
11+
从左向右遍历,一旦出现等于 target 的值就结束,保存当前下标。如果从左到右没有找到 target,那么就直接返回 [ -1 , -1 ] 就可以了,因为从左到右没找到,那么从右到左也一定不会找到的。如果找到了,然后再从右到左遍历,一旦出现等于 target 的值就结束,保存当前下标。
12+
13+
时间复杂度是 O(n)并不满足题意,但可以了解下这个思路,从左到右,从右到左之前也遇到过。
14+
15+
```java
16+
public int[] searchRange(int[] nums, int target) {
17+
int[] targetRange = {-1, -1};
18+
19+
// 从左到右扫描
20+
for (int i = 0; i < nums.length; i++) {
21+
if (nums[i] == target) {
22+
targetRange[0] = i;
23+
break;
24+
}
25+
}
26+
27+
// 如果之前没找到,直接返回 [ -1 , -1 ]
28+
if (targetRange[0] == -1) {
29+
return targetRange;
30+
}
31+
32+
//从右到左扫描
33+
for (int j = nums.length-1; j >= 0; j--) {
34+
if (nums[j] == target) {
35+
targetRange[1] = j;
36+
break;
37+
}
38+
}
39+
40+
return targetRange;
41+
}
42+
```
43+
44+
时间复杂度:O(n)。
45+
46+
空间复杂度:O(1)。
47+
48+
# 解法二 二分查找
49+
50+
让我们先看下正常的二分查找。
51+
52+
```java
53+
int start = 0;
54+
int end = nums.length - 1;
55+
while (start <= end) {
56+
int mid = (start + end) / 2;
57+
if (target == nums[mid]) {
58+
return mid;
59+
} else if (target < nums[mid]) {
60+
end = mid - 1;
61+
} else {
62+
start = mid + 1;
63+
}
64+
}
65+
```
66+
67+
二分查找中,我们找到 target 就结束了,这里我们需要修改下。
68+
69+
我们如果找最左边等于 target 的值,找到 target 时候并不代表我们找到了我们所需要的,例如下边的情况,
70+
71+
![](https://windliang.oss-cn-beijing.aliyuncs.com/34_2.jpg)
72+
73+
此时虽然 mid 指向的值等于 target 了,但是我们要找的其实还在左边,为了达到 log 的时间复杂度,我们依旧是丢弃一半,我们需要更新 end = mid - 1,图示如下。
74+
75+
![](https://windliang.oss-cn-beijing.aliyuncs.com/34_3.jpg)
76+
77+
此时 tartget > nums [ mid ] ,更新 start = mid + 1。
78+
79+
![](https://windliang.oss-cn-beijing.aliyuncs.com/34_6.jpg)
80+
81+
此时 target == nums [ mid ] ,但由于我们改成了 end = mid - 1,所以继续更新,end 就到了 mid 的左边,此时 start > end 了,就会走出 while 循环, 我们要找的值刚好就是 start 指向的了。那么我们修改的代码如下:
82+
83+
```java
84+
while (start <= end) {
85+
int mid = (start + end) / 2;
86+
if (target == nums[mid]) {
87+
end = mid - 1;
88+
} else if (target < nums[mid]) {
89+
end = mid -1 ;
90+
} else {
91+
start = mid + 1;
92+
}
93+
}
94+
```
95+
96+
找右边的同样的分析思路,就是判断需要丢弃哪一边。
97+
98+
所以最后的代码就出来了。leetcode 中是把找左边和找右边的合并起来了,本质是一样的。
99+
100+
```java
101+
public int[] searchRange(int[] nums, int target) {
102+
int start = 0;
103+
int end = nums.length - 1;
104+
int[] ans = { -1, -1 };
105+
if (nums.length == 0) {
106+
return ans;
107+
}
108+
while (start <= end) {
109+
int mid = (start + end) / 2;
110+
if (target == nums[mid]) {
111+
end = mid - 1;
112+
} else if (target < nums[mid]) {
113+
end = mid - 1;
114+
} else {
115+
start = mid + 1;
116+
}
117+
}
118+
//考虑 tartget 是否存在,判断我们要找的值是否等于 target 并且是否越界
119+
if (start == nums.length || nums[ start ] != target) {
120+
return ans;
121+
} else {
122+
ans[0] = start;
123+
}
124+
ans[0] = start;
125+
start = 0;
126+
end = nums.length - 1;
127+
while (start <= end) {
128+
int mid = (start + end) / 2;
129+
if (target == nums[mid]) {
130+
start = mid + 1;
131+
} else if (target < nums[mid]) {
132+
end = mid - 1;
133+
} else {
134+
start = mid + 1;
135+
}
136+
}
137+
ans[1] = end;
138+
return ans;
139+
}
140+
```
141+
142+
时间复杂度:O(log(n))。
143+
144+
空间复杂度:O(1)。
145+
146+
# 解法三
147+
148+
以上是 leetcode 提供的思路,我觉得不是很好,因为它所有的情况都一定是循环 log(n)次,讲一下我最开始想到的。
149+
150+
相当于在解法二的基础上优化了一下,下边是解法二的代码。
151+
152+
```java
153+
while (start <= end) {
154+
int mid = (start + end) / 2;
155+
if (target == nums[mid]) {
156+
end = mid - 1;
157+
} else if (target < nums[mid]) {
158+
end = mid -1 ;
159+
} else {
160+
start = mid + 1;
161+
}
162+
}
163+
```
164+
165+
考虑下边的一种情况,如果我们找最左边等于 target 的,此时 mid 的位置已经是我们要找的了,而解法二更新成了 end = mid - 1,然后继续循环了,而此时我们其实完全可以终止了。只需要判断 nums[ mid - 1] 是不是小于 nums [ mid ] ,如果小于就刚好是我们要找的了。
166+
167+
![](https://windliang.oss-cn-beijing.aliyuncs.com/34_4.jpg)
168+
169+
当然,找最右边也是同样的思路,看下代码吧。
170+
171+
```java
172+
public int[] searchRange(int[] nums, int target) {
173+
int start = 0;
174+
int end = nums.length - 1;
175+
int[] ans = { -1, -1 };
176+
if (nums.length == 0) {
177+
return ans;
178+
}
179+
while (start <= end) {
180+
int mid = (start + end) / 2;
181+
if (target == nums[mid]) {
182+
//这里是为了处理 mid - 1 越界的问题,可以仔细想下。
183+
//如果 mid == 0,那么 mid 一定是我们要找的了,而此时 mid - 1 就会越界了,
184+
//为了使得下边的 target > n 一定成立,我们把 n 赋成最小值
185+
//如果 mid > 0,直接吧 nums[mid - 1] 赋给 n 就可以了。
186+
int n = mid > 0 ? nums[mid - 1] : Integer.MIN_VALUE;
187+
if (target > n) {
188+
ans[0] = mid;
189+
break;
190+
}
191+
end = mid - 1;
192+
} else if (target < nums[mid]) {
193+
end = mid - 1;
194+
} else {
195+
start = mid + 1;
196+
}
197+
}
198+
start = 0;
199+
end = nums.length - 1;
200+
while (start <= end) {
201+
int mid = (start + end) / 2;
202+
if (target == nums[mid]) {
203+
int n = mid < nums.length - 1 ? nums[mid + 1] : Integer.MAX_VALUE;
204+
if (target < n) {
205+
ans[1] = mid;
206+
break;
207+
}
208+
start = mid + 1;
209+
} else if (target < nums[mid]) {
210+
end = mid - 1;
211+
} else {
212+
start = mid + 1;
213+
}
214+
}
215+
return ans;
216+
}
217+
```
218+
219+
时间复杂度:O(log(n))。
220+
221+
空间复杂度:O(1)。
222+
223+
#
224+
225+
总体来说,这道题并不难,本质就是对二分查找的修改,以便满足我们的需求。

0 commit comments

Comments
 (0)