Skip to content

Commit 5b050ad

Browse files
committed
add 链表
1 parent 577c785 commit 5b050ad

File tree

4 files changed

+794
-5
lines changed

4 files changed

+794
-5
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
明年就是找工作了,又要开始刷题了,把之前做过的题目再梳理整理一遍,但愿明年不要那么拉跨,祈祷明年能找到工作,千万不能毕业就失业。
22

3-
这个仓库的v1 tag是我第一次学习算法时,使用python刷题分类整理的笔记: https://github.com/lxztju/leetcode-algorithm/tree/v1
43

54

5+
分类别解析leetcode上的一些相关的例题路,代码采用**C++**实现。
66

7-
分类别解析leetcode上的一些相关的例题路,代码采用**C++与python**实现。
7+
使用**python**刷题分类整理的笔记,请参考这个仓库的v1 tag: [https://github.com/lxztju/leetcode-algorithm/tree/v1](https://github.com/lxztju/leetcode-algorithm/tree/v1)
88

99

1010

leetcode算法之二分查找.md

Lines changed: 295 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
1+
今天来盘一盘 ** 二分查找 ** 这类题目
2+
3+
4+
代码采用**C++**实现
5+
6+
使用**python**刷题分类整理的笔记,请参考: [https://github.com/lxztju/leetcode-algorithm/tree/v1](https://github.com/lxztju/leetcode-algorithm/tree/v1)
7+
8+
9+
10+
## 二分查找
11+
二分查找是针对一个排序列表,每次利用中间元素折半去掉部分元素,减少重复的查找遍历.
12+
13+
* 对于一个排序数组nums,查找指定的一个数字target,采用二分查找的解题思路
14+
* 利用target与nums数组的中间元素相比较,
15+
1. 如果target> nums[mid],说明target在数组的后半部分,
16+
2. 如果target < nums[mid], 说明target在数组的前半部分
17+
3. 如果target == nums[mid], 找到target.
18+
19+
二分查找的典型解题思路模板代码:
20+
21+
```c++
22+
int binary_search(vector<int>& nums, int target){
23+
int l = 0, r = nums.size() - 1;
24+
while (l <= r){
25+
int mid = l + (r - l) / 2; // 直接采用(r+l)/2. 容易出现整形溢出
26+
// 找到对应的元素,返回索引
27+
if (nums[mid] == target) return mid;
28+
// target比中间值大,说明存在数组后半部分
29+
else if (nums[mid] < target)
30+
l = mid + 1;
31+
// target小, 说明存在数组的前半部分.
32+
else
33+
r = mid - 1;
34+
}
35+
return -1;
36+
}
37+
```
38+
39+
**两个非常困扰而且易错的细节点:**
40+
* while循环的判断条件是`l<r`还是`l<=r`
41+
* 当target != nums[mid]时, 使用`mid`还是`mid (+或者-) 1`
42+
43+
解决这两个问题的只需要考虑清楚,**查找区间的封闭情况**, 例如对于上边写的代码,采用的方式为(**左右均为闭区间**)[l, r], 因此决定了循环判断条件为`l<=r`. 同时 `l = mid + 1`与`r = mid - 1`, 在过程中**始终保持全闭区间的情况不变**
44+
45+
当然代码也可以采用左开右闭或者左闭右开的区间进行查找,然后判断需要如何更改这两个问题.
46+
47+
* 69 x 的平方根 (Easy)
48+
* 744 寻找比目标字母大的最小字母 (Easy)
49+
* 278 第一个错误的版本 (Easy)
50+
* 540 有序数组中的单一元素 (Medium)
51+
* 153 寻找旋转排序数组中的最小值 (Medium)
52+
* 34 在排序数组中查找元素的第一个和最后一个位置(medium)
53+
54+
55+
56+
#### 69 x 的平方根 (Easy)
57+
* 题解: 二分查找
58+
* 全闭区间进行搜索
59+
* 注意在计算过程中,直接平方会导致int溢出,需要转换为double,或者long long
60+
61+
```c++
62+
class Solution {
63+
public:
64+
int mySqrt(int x) {
65+
// 采用二分查找
66+
// 查找区间为[l, r]
67+
int l=0, r=x/2;
68+
while (l <= r){
69+
int mid = l + (r - l) / 2;
70+
double pow1 = static_cast<double>(mid) * mid;
71+
double pow2 = static_cast<double>(mid + 1) * (mid + 1);
72+
if (pow2 == x) return mid + 1;
73+
else if (pow1 == x || (pow1 < x && pow2 > x)) return mid;
74+
else if (pow1 > x)
75+
r = mid - 1;
76+
else
77+
l = mid + 1;
78+
}
79+
return l;
80+
}
81+
};
82+
```
83+
84+
85+
#### 744 寻找比目标字母大的最小字母 (Easy)
86+
87+
```c++
88+
class Solution {
89+
public:
90+
char nextGreatestLetter(vector<char>& letters, char target) {
91+
if (target >= *(letters.end() - 1)) return *(letters.begin());
92+
// 全闭区间
93+
int l = 0, r = letters.size() - 1;
94+
while (l <= r){
95+
int mid = l + (r - l) / 2;
96+
if (target < letters[mid])
97+
r = mid - 1;
98+
else if (target >= letters[mid])
99+
l = mid + 1;
100+
}
101+
return letters[l];
102+
}
103+
};
104+
```
105+
#### 278 第一个错误的版本 (Easy)
106+
```c++
107+
// The API isBadVersion is defined for you.
108+
// bool isBadVersion(int version);
109+
110+
class Solution {
111+
public:
112+
int firstBadVersion(int n) {
113+
// 直接二分即可,找到第一个为false的版本
114+
// 全闭区间
115+
int l = 1, r = n;
116+
while (l <= r){
117+
int mid = l + (r - l) /2 ;
118+
if (isBadVersion(mid))
119+
r = mid - 1;
120+
else
121+
l = mid + 1;
122+
}
123+
return l;
124+
}
125+
};
126+
```
127+
128+
#### 540 有序数组中的单一元素 (Medium)
129+
130+
* 题解: 二分查找
131+
* 这道题直接判断中间元素左右两侧的子数组哪一部分是奇数,说明单一元素就存在对应的部分
132+
* 如果mid后半部分的数组是偶数:
133+
* 1. 那么如果mid与mid+1位置的元素相等, 那么去掉这俩元素后,后半部分就是奇数,说明单一元素存在右半部分, l = mid + 2
134+
* 2. 如果mid与mid - 1位置相等,那么单一元素存在左半部分, r = mid - 2
135+
* 同理分析mid的后半部分是奇数.
136+
* 如果两侧均为偶数,那么mid即为待查找的单一元素.
137+
138+
139+
```c++
140+
class Solution {
141+
public:
142+
int singleNonDuplicate(vector<int>& nums) {
143+
// 二分查找
144+
// mid为数组中间的元素索引
145+
// 如果mid与后边或者前边的元素相等, 那么判断哪一侧是奇数就说明在哪一侧
146+
// 如果mid刚好就是单独的元素,那么直接返回即可
147+
int l = 0, r = nums.size() - 1;
148+
while (l < r){
149+
int mid = l + (r - l) / 2;
150+
// 后半部分是偶数
151+
bool second_even = ((r - mid) % 2 == 0);
152+
153+
if (nums[mid + 1] == nums[mid]){
154+
if (second_even)
155+
l = mid + 2;
156+
else
157+
r = mid - 1;
158+
}
159+
else if (nums[mid] == nums[mid - 1]){
160+
if (second_even)
161+
r = mid - 2;
162+
else
163+
l = mid + 1;
164+
}
165+
else
166+
return nums[mid];
167+
}
168+
return nums[l];
169+
}
170+
};
171+
```
172+
173+
174+
#### 153 寻找旋转排序数组中的最小值 (Medium)
175+
* 题解1: 简单顺序查找
176+
* 遍历数组, 如果nums[i] > nums[i+1] return num[i+1]
177+
* 如果遍历完全没有找到,就说明nums[0]是最小的元素.
178+
179+
180+
```c++
181+
class Solution {
182+
public:
183+
int findMin(vector<int>& nums) {
184+
// 顺序查找, 如果前边元素,大于后边,那么就找到了对应的元素
185+
for (int i = 0; i < nums.size() - 1; i++){
186+
if (nums[i] > nums[i+1])
187+
return nums[i+1];
188+
}
189+
return nums[0];
190+
}
191+
};
192+
```
193+
194+
* 题解2: 二分查找
195+
* 采用中间元素与首尾元素进行对比的方式
196+
* 如果中间元素大于数组尾元素,说明反转点在mid的右边
197+
* 如果中间元素小于数组的尾部元素. 说明反转点在mid或者在mid的左边
198+
199+
```c++
200+
class Solution {
201+
public:
202+
int findMin(vector<int>& nums) {
203+
int left = 0;
204+
int right = nums.size() - 1;
205+
while (left < right) {
206+
int mid = left + (right - left) / 2;
207+
if (nums[mid] > nums[right]) {
208+
left = mid + 1;
209+
} else {
210+
right = mid;
211+
}
212+
}
213+
return nums[left];
214+
}
215+
};
216+
```
217+
218+
219+
#### 34 在排序数组中查找元素的第一个和最后一个位置(medium)
220+
221+
* 题解: 顺序查找
222+
223+
```c++
224+
class Solution {
225+
public:
226+
vector<int> searchRange(vector<int>& nums, int target) {
227+
// 直接顺序查找
228+
int l = 0, r = nums.size() - 1;
229+
vector<int> res;
230+
while (l <nums.size() && nums[l] != target)
231+
l++;
232+
while (r > 0 && nums[r] != target )
233+
r--;
234+
if (l != nums.size())
235+
return {l, r};
236+
else
237+
return {-1, -1};
238+
}
239+
};
240+
```
241+
242+
* 题解2: 二分查找
243+
* 首先利用二分查找,找到对应的target
244+
* 然后顺序往左右扩展找到对应的边界
245+
246+
```c++
247+
class Solution {
248+
public:
249+
vector<int> searchRange(vector<int>& nums, int target) {
250+
int l = 0, r = nums.size() - 1;
251+
// 使用二分查找,找到对应元素, 然后左右遍历得到两个坐标
252+
int index = -1;
253+
while (l <= r){
254+
int mid = l + ( r - l ) / 2;
255+
//找到对应的元素索引
256+
if (nums[mid] == target){
257+
index = mid;
258+
break;
259+
}
260+
else if(nums[mid] < target)
261+
l = mid + 1;
262+
else
263+
r = mid - 1;
264+
}
265+
// 没有找到对应的索引
266+
if (index == -1)
267+
return {-1, -1};
268+
269+
int left = index, right = index;
270+
// 向左扩展,找到元素左边界
271+
while (left >= 0 && nums[left] == target )
272+
left--;
273+
// 向右扩展找到有边界
274+
while (right < nums.size() && nums[right] == target)
275+
right++;
276+
return {left + 1, right - 1};
277+
}
278+
};
279+
```
280+
281+
282+
283+
284+
285+
## 更多分类刷题资料
286+
287+
* 微信公众号: 小哲AI
288+
289+
![wechat_QRcode](https://img-blog.csdnimg.cn/20210104185413204.jpg)
290+
291+
* GitHub地址: [https://github.com/lxztju/leetcode-algorithm](https://github.com/lxztju/leetcode-algorithm)
292+
* csdn博客: [https://blog.csdn.net/lxztju](https://blog.csdn.net/lxztju)
293+
* 知乎专栏: [小哲AI](https://www.zhihu.com/column/c_1101089619118026752)
294+
* AI研习社专栏:[小哲AI](https://www.yanxishe.com/column/109)
295+

0 commit comments

Comments
 (0)