返回 LeetCode 刷题

Markdown File

滑动窗口和红蓝染色

滑动窗口和红蓝染色.md

1一、LeetCode 3:无重复字符的最长子串
21. 前提条件
3题目给你一个字符串:
4char* s
5要求你返回:
6不含重复字符的最长子串长度
7注意是:
8子串 substring:必须连续
9子序列 subsequence:可以不连续
10例如:
11s = "abcabcbb"
12答案是:"abc"
13长度 = 3
14也可以是 "bca" 或 "cab",只要长度是 3 即可。
15
162. 核心概念:滑动窗口
17滑动窗口适合处理这种问题:
18
19在一段连续区间里,维护某种条件。
20
21第 3 题里的条件是:窗口内不能有重复字符。
22
23我们用两个指针:
24
25int left = 0;
26int right = 0;
27
28表示当前窗口:[left, right]
29
30也就是当前正在检查的一段连续子串。
31
32例如:
33
34s = "abcabcbb"
35
36left = 0
37right = 2
38窗口 = "abc"
39
40这个窗口没有重复字符,所以可以更新答案。
41
423. 滑动窗口的基本思想
43
44滑动窗口一般分两步:(最重要的一点和计网的GBN回退N步那里用到的方法一样)
45
461. right 向右扩张,把新字符加入窗口。
472. 如果窗口不合法,就移动 left 缩小窗口,直到重新合法。
48
49第 3 题中:合法条件:窗口内没有重复字符。
50
51所以:
52
53如果 right 加进来的字符重复了,就不断 left++,把左边字符移出窗口。
544. 固定模板
55int left = 0;
56int ans = 0;
57
58for (int right = 0; right < n; right++) {
59 // 1. 把 s[right] 加入窗口
60
61 while (窗口不合法) {
62 // 2. 移出 s[left]
63 left++;
64 }
65
66 // 3. 此时窗口合法,更新答案
67 ans = max(ans, right - left + 1);
68}
69
70第 3 题中,窗口是否合法要看字符是否重复,所以可以用一个数组记录字符出现次数:
71
72int count[256] = {0};
735. C 语言代码模板
74
75int lengthOfLongestSubstring(char* s) {
76 int count[256] = {0};
77
78 int left = 0;
79 int ans = 0;
80
81 for (int right = 0; s[right] != '\0'; right++) {
82 unsigned char c = s[right];
83 count[c]++;
84//这里用c来表示这个字符,之后根据ASCII对应在数组里面的位置,不重复就加位置对应的值成1,然后right增加
85 while (count[c] > 1) {
86 unsigned char d = s[left];
87 count[d]--;
88 left++;
89 }
90//这里是发现重复了,所以left取到之后把次数清0,然后左窗口往右移动
91 int len = right - left + 1;
92 if (len > ans) {
93 ans = len;
94 }
95 }
96
97 return ans;
98}
996. 代码怎么跑?
100
101以:
102
103s = "abcabcbb"
104
105为例。
106
107一开始:
108
109left = 0
110right = 0
111窗口 = "a"
112ans = 1
113
114继续扩张:
115
116窗口 = "ab"
117ans = 2
118
119继续扩张:
120
121窗口 = "abc"
122ans = 3
123
124再加入下一个 'a':
125
126窗口 = "abca"
127
128现在 'a' 重复了,不合法。
129
130于是移动 left,把左边的 'a' 移出去:
131
132窗口 = "bca"
133
134又合法了。
135
136所以这题本质是:
137
138right 负责扩大窗口;
139left 负责在重复时缩小窗口;
140每次窗口合法后更新最大长度。
1417. 为什么 while (count[c] > 1)?
142
143因为我们每次新加入的是:
144
145s[right]
146
147只有这个新字符可能导致重复。
148
149所以只需要判断:
150
151count[s[right]] > 1
152
153如果它重复了,就移动 left,直到这个字符不重复为止。
154
155例如:
156
157s = "abba"
158
159当窗口变成:
160
161"abb"
162
163第二个 'b' 进来后,b 重复了。
164
165移动 left:
166
167去掉 'a' 后,窗口 = "bb"
168
169还是重复。
170
171继续移动:
172
173去掉第一个 'b' 后,窗口 = "b"
174
175这时合法。
176
177所以这里必须是 while,不能只用 if。
178
1798. 易错点
180易错点 1:把子串和子序列搞混
181子串:必须连续
182子序列:可以不连续
183
184第 3 题要的是连续子串。
185
186易错点 2:窗口不合法时只移动一次 left
187
188错误想法:
189
190if (count[c] > 1) {
191 left++;
192}
193
194不一定够。
195
196比如:
197
198s = "abba"
199
200遇到第二个 'b' 时,只移动一次 left 去掉 'a',窗口还是 "bb",仍然重复。
201
202所以要写:
203
204while (count[c] > 1)
205易错点 3:更新答案的位置
206
207应该在窗口合法之后更新:
208
209int len = right - left + 1;
210if (len > ans) {
211 ans = len;
212}
213
214不能在窗口还重复时更新。
215
216易错点 4:C 语言字符串可以用 '\0' 判断结束
217
218因为字符串有结束标志:
219
220s[right] != '\0'
221
222普通数组不行,普通数组必须靠 numsSize。
223
2249. 复杂度
225时间复杂度:O(n)
226空间复杂度:O(1)
227
228为什么时间是 O(n)?
229
230因为:
231
232right 每个字符只走一次;
233left 每个字符最多也只走一次。
234
235虽然里面有 while,但整体不是 O(n^2)。
236
23710. 题型总结
238
239第 3 题属于:
240
241滑动窗口 / 同向双指针
242
243规律:
244
245窗口合法:right 继续扩张,更新答案;
246窗口不合法:left 收缩窗口,直到合法。
247
248一句话:
249
250第 3 题的滑动窗口,本质是维护一个“不含重复字符”的连续区间。
251二、LeetCode 34:二分查找 / 红蓝染色法
2521. 前提条件
253
254题目给你:
255
256int* nums
257int numsSize
258int target
259
260并且数组满足:
261
262nums 是非递减排序数组
263
264也就是:
265
266允许重复元素的升序数组。
267
268要求返回:
269
270target 第一次出现的位置和最后一次出现的位置。
271
272例如:
273
274nums = [5,7,7,8,8,10]
275target = 8
276
277答案:
278
279[3,4]
280
281如果不存在:
282
283[-1,-1]
284
285题目还要求:
286
287时间复杂度必须是 O(log n)
288
289所以不能从头遍历。
290
2912. 核心概念:找边界
292
293这题不是普通二分找一个 target 就完事。
294
295因为数组里可能有多个 target:
296
297[5, 7, 7, 8, 8, 10]
298 ^ ^
299 左边界 右边界
300
301所以要找两个位置:
302
303第一个 >= target 的位置
304第一个 > target 的位置
305
306然后:
307
308左边界 = 第一个 >= target 的位置
309右边界 = 第一个 > target 的位置 - 1
310
311例如:
312
313nums = [5,7,7,8,8,10]
314target = 8
315
316第一个 >= 8 的位置是:
317
318index = 3
319
320第一个 > 8 的位置是:
321
322index = 5
323
324所以右边界是:
325
3265 - 1 = 4
327
328答案就是:
329
330[3,4]
331三、红蓝染色法
3321. 红蓝染色法是什么?
333
334红蓝染色法就是把数组位置分成两类:
335
336蓝色:不满足条件
337红色:满足条件
338
339二分的目标是:
340
341找到第一个红色位置。
342
343这个模板非常适合找:
344
345第一个 >= target
346第一个 > target
347第一个满足某条件的位置
3482. 找第一个 >= target
349
350条件是:
351
352nums[i] >= target
353
354对于排序数组:
355
356nums = [5,7,7,8,8,10]
357target = 8
358
359染色:
360
361索引: 0 1 2 3 4 5
362值: 5 7 7 8 8 10
363颜色: 蓝 蓝 蓝 红 红 红
364
365因为:
366
3675,7,7 都 < 8,不满足 nums[i] >= 8,所以是蓝色;
3688,8,10 都 >= 8,满足条件,所以是红色。
369
370我们要找:
371
372第一个红色
373
374也就是 index = 3。
375
3763. 红蓝染色固定模板
377
378这个模板建议你背下来:
379
380int lowerBoundGE(int* nums, int numsSize, int target) {
381 int blue = -1;
382 int red = numsSize;
383
384 while (blue + 1 < red) {
385 int mid = blue + (red - blue) / 2;
386
387 if (nums[mid] >= target) {
388 red = mid;
389 } else {
390 blue = mid;
391 }
392 }
393
394 return red;
395}
396
397这里:
398
399blue = -1 表示最左边虚拟蓝色位置
400red = numsSize 表示最右边虚拟红色位置
401
402也就是:
403
404[-1, numsSize]
405
406一开始我们假设:
407
408-1 一定是蓝色
409numsSize 一定是红色
410
411这两个是虚拟边界,不是真实数组元素。
412
413循环结束时:
414
415blue + 1 == red
416
417说明蓝色和红色挨在一起了。
418
419此时:
420
421red 就是第一个红色位置。
4224. 为什么 nums[mid] >= target 时 red = mid?
423
424因为我们现在要找的是:
425
426第一个满足 nums[i] >= target 的位置。
427
428如果:
429
430nums[mid] >= target
431
432说明 mid 已经是红色。
433
434但是它不一定是第一个红色,左边可能还有红色。
435
436所以要往左收缩:
437
438red = mid;
439
440如果:
441
442nums[mid] < target
443
444说明 mid 是蓝色。
445
446那么第一个红色一定在右边,所以:
447
448blue = mid;
4495. 找第一个 > target
450
451同理,条件换成:
452
453nums[i] > target
454
455代码:
456
457int lowerBoundGT(int* nums, int numsSize, int target) {
458 int blue = -1;
459 int red = numsSize;
460
461 while (blue + 1 < red) {
462 int mid = blue + (red - blue) / 2;
463
464 if (nums[mid] > target) {
465 red = mid;
466 } else {
467 blue = mid;
468 }
469 }
470
471 return red;
472}
473
474例如:
475
476nums = [5,7,7,8,8,10]
477target = 8
478
479按 nums[i] > 8 染色:
480
481索引: 0 1 2 3 4 5
482值: 5 7 7 8 8 10
483颜色: 蓝 蓝 蓝 蓝 蓝 红
484
485第一个红色位置是:
486
487index = 5
488
489所以:
490
491最后一个 8 的位置 = 5 - 1 = 4
492四、第 34 题完整 C 代码
493#include <stdlib.h>
494
495int lowerBoundGE(int* nums, int numsSize, int target) {
496 int blue = -1;
497 int red = numsSize;
498
499 while (blue + 1 < red) {
500 int mid = blue + (red - blue) / 2;
501
502 if (nums[mid] >= target) {
503 red = mid;
504 } else {
505 blue = mid;
506 }
507 }
508
509 return red;
510}
511
512int lowerBoundGT(int* nums, int numsSize, int target) {
513 int blue = -1;
514 int red = numsSize;
515
516 while (blue + 1 < red) {
517 int mid = blue + (red - blue) / 2;
518
519 if (nums[mid] > target) {
520 red = mid;
521 } else {
522 blue = mid;
523 }
524 }
525
526 return red;
527}
528
529int* searchRange(int* nums, int numsSize, int target, int* returnSize) {
530 int* ans = malloc(2 * sizeof(int));
531 *returnSize = 2;
532
533 int left = lowerBoundGE(nums, numsSize, target);
534
535 if (left == numsSize || nums[left] != target) {
536 ans[0] = -1;
537 ans[1] = -1;
538 return ans;
539 }
540
541 int right = lowerBoundGT(nums, numsSize, target) - 1;
542
543 ans[0] = left;
544 ans[1] = right;
545
546 return ans;
547}
5485. 为什么不用 target + 1?
549
550有些写法会这样:
551
552right = lowerBoundGE(nums, numsSize, target + 1) - 1;
553
554在普通整数范围内可以理解。
555
556但是更稳的写法是:
557
558right = lowerBoundGT(nums, numsSize, target) - 1;
559
560因为如果 target 已经是很大的整数,target + 1 可能溢出。
561
562所以推荐记:
563
564左边界:第一个 >= target
565右边界:第一个 > target 的位置 - 1
5666. 第 34 题易错点
567易错点 1:普通二分只能找到一个 target
568
569普通二分可能找到中间那个:
570
571[5,7,7,8,8,10]
572 或者
573
574但题目要的是:
575
576第一个 target 和最后一个 target。
577
578所以要找边界。
579
580易错点 2:left == numsSize 要先判断
581
582如果:
583
584left == numsSize
585
586说明数组里没有任何元素 >= target。
587
588此时不能访问:
589
590nums[left]
591
592因为越界了。
593
594所以必须写:
595
596if (left == numsSize || nums[left] != target)
597
598注意 C 语言的短路特性:
599
600left == numsSize 为真时,后面的 nums[left] 不会再判断。
601易错点 3:二分循环条件
602
603红蓝染色法固定写:
604
605while (blue + 1 < red)
606
607结束时:
608
609blue 和 red 相邻
610red 是第一个红色
611
612不要和其他二分模板混在一起。
613
614易错点 4:mid 推荐这样写
615int mid = blue + (red - blue) / 2;
616
617不推荐:
618
619int mid = (blue + red) / 2;
620
621因为 blue + red 理论上可能溢出。
622
6237. 第 34 题复杂度
624时间复杂度:O(log n)
625空间复杂度:O(1)
626
627虽然调用了两次二分,但还是:
628
629O(log n) + O(log n) = O(log n)
6308. 第 34 题题型总结
631第 34 题 = 二分找边界。
632
633左边界:
634 找第一个 nums[i] >= target 的位置。
635
636右边界:
637 找第一个 nums[i] > target 的位置,再减 1。
638
639红蓝染色法:
640 蓝色 = 不满足条件
641 红色 = 满足条件
642 目标 = 找第一个红色。
643
644最核心一句:
645
646找范围不要普通二分找 target,要二分找左右边界。
Rendered Preview

一、LeetCode 3:无重复字符的最长子串

  1. 前提条件
    题目给你一个字符串:
    char* s
    要求你返回:
    不含重复字符的最长子串长度
    注意是:
    子串 substring:必须连续
    子序列 subsequence:可以不连续
    例如:
    s = "abcabcbb"
    答案是:"abc"
    长度 = 3
    也可以是 "bca" 或 "cab",只要长度是 3 即可。

  2. 核心概念:滑动窗口
    滑动窗口适合处理这种问题:

在一段连续区间里,维护某种条件。

第 3 题里的条件是:窗口内不能有重复字符。

我们用两个指针:

int left = 0;
int right = 0;

表示当前窗口:[left, right]

也就是当前正在检查的一段连续子串。

例如:

s = "abcabcbb"

left = 0
right = 2
窗口 = "abc"

这个窗口没有重复字符,所以可以更新答案。

  1. 滑动窗口的基本思想

滑动窗口一般分两步:(最重要的一点和计网的GBN回退N步那里用到的方法一样)

  1. right 向右扩张,把新字符加入窗口。
  2. 如果窗口不合法,就移动 left 缩小窗口,直到重新合法。

第 3 题中:合法条件:窗口内没有重复字符。

所以:

如果 right 加进来的字符重复了,就不断 left++,把左边字符移出窗口。
4. 固定模板
int left = 0;
int ans = 0;

for (int right = 0; right < n; right++) {
// 1. 把 s[right] 加入窗口

while (窗口不合法) {
    // 2. 移出 s[left]
    left++;
}

// 3. 此时窗口合法,更新答案
ans = max(ans, right - left + 1);

}

第 3 题中,窗口是否合法要看字符是否重复,所以可以用一个数组记录字符出现次数:

int count[256] = {0};
5. C 语言代码模板

int lengthOfLongestSubstring(char* s) {
int count[256] = {0};

int left = 0;
int ans = 0;

for (int right = 0; s[right] != '\0'; right++) {
    unsigned char c = s[right];
    count[c]++;

//这里用c来表示这个字符,之后根据ASCII对应在数组里面的位置,不重复就加位置对应的值成1,然后right增加
while (count[c] > 1) {
unsigned char d = s[left];
count[d]--;
left++;
}
//这里是发现重复了,所以left取到之后把次数清0,然后左窗口往右移动
int len = right - left + 1;
if (len > ans) {
ans = len;
}
}

return ans;

}
6. 代码怎么跑?

以:

s = "abcabcbb"

为例。

一开始:

left = 0
right = 0
窗口 = "a"
ans = 1

继续扩张:

窗口 = "ab"
ans = 2

继续扩张:

窗口 = "abc"
ans = 3

再加入下一个 'a':

窗口 = "abca"

现在 'a' 重复了,不合法。

于是移动 left,把左边的 'a' 移出去:

窗口 = "bca"

又合法了。

所以这题本质是:

right 负责扩大窗口;
left 负责在重复时缩小窗口;
每次窗口合法后更新最大长度。
7. 为什么 while (count[c] > 1)?

因为我们每次新加入的是:

s[right]

只有这个新字符可能导致重复。

所以只需要判断:

count[s[right]] > 1

如果它重复了,就移动 left,直到这个字符不重复为止。

例如:

s = "abba"

当窗口变成:

"abb"

第二个 'b' 进来后,b 重复了。

移动 left:

去掉 'a' 后,窗口 = "bb"

还是重复。

继续移动:

去掉第一个 'b' 后,窗口 = "b"

这时合法。

所以这里必须是 while,不能只用 if。

  1. 易错点
    易错点 1:把子串和子序列搞混
    子串:必须连续
    子序列:可以不连续

第 3 题要的是连续子串。

易错点 2:窗口不合法时只移动一次 left

错误想法:

if (count[c] > 1) {
left++;
}

不一定够。

比如:

s = "abba"

遇到第二个 'b' 时,只移动一次 left 去掉 'a',窗口还是 "bb",仍然重复。

所以要写:

while (count[c] > 1)
易错点 3:更新答案的位置

应该在窗口合法之后更新:

int len = right - left + 1;
if (len > ans) {
ans = len;
}

不能在窗口还重复时更新。

易错点 4:C 语言字符串可以用 '\0' 判断结束

因为字符串有结束标志:

s[right] != '\0'

普通数组不行,普通数组必须靠 numsSize。

  1. 复杂度
    时间复杂度:O(n)
    空间复杂度:O(1)

为什么时间是 O(n)?

因为:

right 每个字符只走一次;
left 每个字符最多也只走一次。

虽然里面有 while,但整体不是 O(n^2)。

  1. 题型总结

第 3 题属于:

滑动窗口 / 同向双指针

规律:

窗口合法:right 继续扩张,更新答案;
窗口不合法:left 收缩窗口,直到合法。

一句话:

第 3 题的滑动窗口,本质是维护一个“不含重复字符”的连续区间。
二、LeetCode 34:二分查找 / 红蓝染色法

  1. 前提条件

题目给你:

int* nums
int numsSize
int target

并且数组满足:

nums 是非递减排序数组

也就是:

允许重复元素的升序数组。

要求返回:

target 第一次出现的位置和最后一次出现的位置。

例如:

nums = [5,7,7,8,8,10]
target = 8

答案:

[3,4]

如果不存在:

[-1,-1]

题目还要求:

时间复杂度必须是 O(log n)

所以不能从头遍历。

  1. 核心概念:找边界

这题不是普通二分找一个 target 就完事。

因为数组里可能有多个 target:

[5, 7, 7, 8, 8, 10]
^ ^
左边界 右边界

所以要找两个位置:

第一个 >= target 的位置
第一个 > target 的位置

然后:

左边界 = 第一个 >= target 的位置
右边界 = 第一个 > target 的位置 - 1

例如:

nums = [5,7,7,8,8,10]
target = 8

第一个 >= 8 的位置是:

index = 3

第一个 > 8 的位置是:

index = 5

所以右边界是:

5 - 1 = 4

答案就是:

[3,4]
三、红蓝染色法

  1. 红蓝染色法是什么?

红蓝染色法就是把数组位置分成两类:

蓝色:不满足条件
红色:满足条件

二分的目标是:

找到第一个红色位置。

这个模板非常适合找:

第一个 >= target
第一个 > target
第一个满足某条件的位置
2. 找第一个 >= target

条件是:

nums[i] >= target

对于排序数组:

nums = [5,7,7,8,8,10]
target = 8

染色:

索引: 0 1 2 3 4 5
值: 5 7 7 8 8 10
颜色: 蓝 蓝 蓝 红 红 红

因为:

5,7,7 都 < 8,不满足 nums[i] >= 8,所以是蓝色;
8,8,10 都 >= 8,满足条件,所以是红色。

我们要找:

第一个红色

也就是 index = 3。

  1. 红蓝染色固定模板

这个模板建议你背下来:

int lowerBoundGE(int* nums, int numsSize, int target) {
int blue = -1;
int red = numsSize;

while (blue + 1 < red) {
    int mid = blue + (red - blue) / 2;

    if (nums[mid] >= target) {
        red = mid;
    } else {
        blue = mid;
    }
}

return red;

}

这里:

blue = -1 表示最左边虚拟蓝色位置
red = numsSize 表示最右边虚拟红色位置

也就是:

[-1, numsSize]

一开始我们假设:

-1 一定是蓝色
numsSize 一定是红色

这两个是虚拟边界,不是真实数组元素。

循环结束时:

blue + 1 == red

说明蓝色和红色挨在一起了。

此时:

red 就是第一个红色位置。
4. 为什么 nums[mid] >= target 时 red = mid?

因为我们现在要找的是:

第一个满足 nums[i] >= target 的位置。

如果:

nums[mid] >= target

说明 mid 已经是红色。

但是它不一定是第一个红色,左边可能还有红色。

所以要往左收缩:

red = mid;

如果:

nums[mid] < target

说明 mid 是蓝色。

那么第一个红色一定在右边,所以:

blue = mid;
5. 找第一个 > target

同理,条件换成:

nums[i] > target

代码:

int lowerBoundGT(int* nums, int numsSize, int target) {
int blue = -1;
int red = numsSize;

while (blue + 1 < red) {
    int mid = blue + (red - blue) / 2;

    if (nums[mid] > target) {
        red = mid;
    } else {
        blue = mid;
    }
}

return red;

}

例如:

nums = [5,7,7,8,8,10]
target = 8

按 nums[i] > 8 染色:

索引: 0 1 2 3 4 5
值: 5 7 7 8 8 10
颜色: 蓝 蓝 蓝 蓝 蓝 红

第一个红色位置是:

index = 5

所以:

最后一个 8 的位置 = 5 - 1 = 4
四、第 34 题完整 C 代码
#include <stdlib.h>

int lowerBoundGE(int* nums, int numsSize, int target) {
int blue = -1;
int red = numsSize;

while (blue + 1 < red) {
    int mid = blue + (red - blue) / 2;

    if (nums[mid] >= target) {
        red = mid;
    } else {
        blue = mid;
    }
}

return red;

}

int lowerBoundGT(int* nums, int numsSize, int target) {
int blue = -1;
int red = numsSize;

while (blue + 1 < red) {
    int mid = blue + (red - blue) / 2;

    if (nums[mid] > target) {
        red = mid;
    } else {
        blue = mid;
    }
}

return red;

}

int* searchRange(int* nums, int numsSize, int target, int* returnSize) {
int* ans = malloc(2 * sizeof(int));
*returnSize = 2;

int left = lowerBoundGE(nums, numsSize, target);

if (left == numsSize || nums[left] != target) {
    ans[0] = -1;
    ans[1] = -1;
    return ans;
}

int right = lowerBoundGT(nums, numsSize, target) - 1;

ans[0] = left;
ans[1] = right;

return ans;

}
5. 为什么不用 target + 1?

有些写法会这样:

right = lowerBoundGE(nums, numsSize, target + 1) - 1;

在普通整数范围内可以理解。

但是更稳的写法是:

right = lowerBoundGT(nums, numsSize, target) - 1;

因为如果 target 已经是很大的整数,target + 1 可能溢出。

所以推荐记:

左边界:第一个 >= target
右边界:第一个 > target 的位置 - 1
6. 第 34 题易错点
易错点 1:普通二分只能找到一个 target

普通二分可能找到中间那个:

[5,7,7,8,8,10]
或者

但题目要的是:

第一个 target 和最后一个 target。

所以要找边界。

易错点 2:left == numsSize 要先判断

如果:

left == numsSize

说明数组里没有任何元素 >= target。

此时不能访问:

nums[left]

因为越界了。

所以必须写:

if (left == numsSize || nums[left] != target)

注意 C 语言的短路特性:

left == numsSize 为真时,后面的 nums[left] 不会再判断。
易错点 3:二分循环条件

红蓝染色法固定写:

while (blue + 1 < red)

结束时:

blue 和 red 相邻
red 是第一个红色

不要和其他二分模板混在一起。

易错点 4:mid 推荐这样写
int mid = blue + (red - blue) / 2;

不推荐:

int mid = (blue + red) / 2;

因为 blue + red 理论上可能溢出。

  1. 第 34 题复杂度
    时间复杂度:O(log n)
    空间复杂度:O(1)

虽然调用了两次二分,但还是:

O(log n) + O(log n) = O(log n)
8. 第 34 题题型总结
第 34 题 = 二分找边界。

左边界:
找第一个 nums[i] >= target 的位置。

右边界:
找第一个 nums[i] > target 的位置,再减 1。

红蓝染色法:
蓝色 = 不满足条件
红色 = 满足条件
目标 = 找第一个红色。

最核心一句:

找范围不要普通二分找 target,要二分找左右边界。