LeetCode 436中等二分查找
寻找右区间 图解题解
这道题到底在问什么
区间数组 intervals,每个 intervals[i]=[start, end],所有 start 互不相同。对每个 i,找满足 start_j ≥ end_i 且 start_j 最小的区间 j,返回其下标数组;不存在记 -1(j 可以等于 i)。
- 输入
- intervals=[[3,4],[2,3],[1,2]]
- 输出
- [-1,0,1]
- 输入
- intervals=[[1,4],[2,3],[3,4]]
- 输出
- [-1,2,-1]
最优解:一步一步想明白
- 3记住三步:① 起点带原下标一起排序;② 对每个区间的终点 end,二分找第一个 ≥ end 的起点(所在区间);③ 用原下标映射回答案。下面每帧都在套它。
- 4先把四个区间的起点单独拎出来:1、3、2、5,它们的原区间编号依次是 0、1、2、3。直接二分需要有序,所以下一步排序;但排序会打乱顺序,必须把「原区间编号」绑在每个起点身上一起搬。
- 5排序完成,起点升序排好:1、2、3、5。看右边侧栏,这是全篇的关键账本:排序下标 0 到 3 分别对应原区间 0、2、1、3。二分会在这个有序数组上定位,定位后再查账本换回原区间编号。
- 6现在处理 区间 0=[1, 2]。它的右侧区间,就是「起点 ≥ 它的终点 2」里起点最小的那个。我们就在排序后的起点数组里,用左闭右开 [0, 4) 二分,找第一个 ≥ 2 的起点。
- 7搜索窗口 [0, 4) 的正中间是下标 2,起点 3。它 ≥ 终点 2,本身就是一个可行候选,记成绿色;再到它左边看有没有起点更小、也合格的。
- 8把起点 3 记成新的、更靠左的候选(绿色移到下标 2),只把它右边灰掉。接着到它左边找有没有更小、也合格的起点。
- 9搜索窗口 [0, 2) 的正中间是下标 1,起点 2。它 ≥ 终点 2,本身就是一个可行候选,记成绿色;再到它左边看有没有起点更小、也合格的。绿色那格是目前记下的候选。
- 10把起点 2 记成新的、更靠左的候选(绿色移到下标 1),只把它右边灰掉。接着到它左边找有没有更小、也合格的起点。
- 11搜索窗口 [0, 1) 的正中间是下标 0,起点 1。它比终点 2 小,接不住这个终点,它和它左边都排除掉,到右边继续找。绿色那格是目前记下的候选。
- 12起点 1 比终点 2 小,把 mid 及它左边整段灰掉,l 收到 1。窗口收空,保留的候选下标 1 就是位置。
- 13二分结束,l 停在下标 1(起点 2 ≥ 终点 2)。注意答案要的是「原区间编号」,查侧栏面板:排序下标 1 对应原区间 2,所以 区间 0=[1, 2] 的答案是 2。
- 14现在处理 区间 1=[3, 4]。它的右侧区间,就是「起点 ≥ 它的终点 4」里起点最小的那个。我们就在排序后的起点数组里,用左闭右开 [0, 4) 二分,找第一个 ≥ 4 的起点。
- 15搜索窗口 [0, 4) 的正中间是下标 2,起点 3。它比终点 4 小,接不住这个终点,它和它左边都排除掉,到右边继续找。
- 16起点 3 比终点 4 小,把 mid 及它左边整段灰掉,l 收到 3。继续往右找第一个够大的起点。
- 17搜索窗口 [3, 4) 的正中间是下标 3,起点 5。它 ≥ 终点 4,本身就是一个可行候选,记成绿色;再到它左边看有没有起点更小、也合格的。
- 18把起点 5 记成新的、更靠左的候选(绿色移到下标 3),只把它右边灰掉。此时窗口 [3, 3) 收空,保留的候选下标 3 就是要找的位置。
- 19二分结束,l 停在下标 3(起点 5 ≥ 终点 4)。注意答案要的是「原区间编号」,查侧栏面板:排序下标 3 对应原区间 3,所以 区间 1=[3, 4] 的答案是 3。
- 20现在处理 区间 2=[2, 3]。它的右侧区间,就是「起点 ≥ 它的终点 3」里起点最小的那个。我们就在排序后的起点数组里,用左闭右开 [0, 4) 二分,找第一个 ≥ 3 的起点。
- 21搜索窗口 [0, 4) 的正中间是下标 2,起点 3。它 ≥ 终点 3,本身就是一个可行候选,记成绿色;再到它左边看有没有起点更小、也合格的。
- 22把起点 3 记成新的、更靠左的候选(绿色移到下标 2),只把它右边灰掉。接着到它左边找有没有更小、也合格的起点。
- 23搜索窗口 [0, 2) 的正中间是下标 1,起点 2。它比终点 3 小,接不住这个终点,它和它左边都排除掉,到右边继续找。绿色那格是目前记下的候选。
- 24起点 2 比终点 3 小,把 mid 及它左边整段灰掉,l 收到 2。窗口收空,保留的候选下标 2 就是位置。
- 25二分结束,l 停在下标 2(起点 3 ≥ 终点 3)。注意答案要的是「原区间编号」,查侧栏面板:排序下标 2 对应原区间 1,所以 区间 2=[2, 3] 的答案是 1。
- 26现在处理 区间 3=[5, 6]。它的右侧区间,就是「起点 ≥ 它的终点 6」里起点最小的那个。我们就在排序后的起点数组里,用左闭右开 [0, 4) 二分,找第一个 ≥ 6 的起点。
- 27搜索窗口 [0, 4) 的正中间是下标 2,起点 3。它比终点 6 小,接不住这个终点,它和它左边都排除掉,到右边继续找。
- 28起点 3 比终点 6 小,把 mid 及它左边整段灰掉,l 收到 3。继续往右找第一个够大的起点。
- 29搜索窗口 [3, 4) 的正中间是下标 3,起点 5。它比终点 6 小,接不住这个终点,它和它左边都排除掉,到右边继续找。
- 30起点 5 比终点 6 小,把 mid 及它左边整段灰掉,l 收到 4。窗口收空且一直没有候选,说明没有起点 ≥ 6,答案是 -1。
- 31二分结束,l 被一路推到 4,等于数组长度,说明所有起点都比终点 6 小,谁也接不住它。区间 3=[5, 6] 没有右侧区间,答案记 -1。
⚠️ 容易写错的地方
✗ 错:排序时丢了原下标
✓ 对:把 (起点, 原下标) 一起排序
答案要的是原区间编号,排序打乱了顺序,不带原下标就映射不回去
✗ 错:二分条件用错(漏掉等于)
✓ 对:找第一个 start ≥ end,相等也算合格
题目允许 j 等于 i,start_j 可以等于 end_i
✗ 错:找不到时没处理越界
✓ 对:二分结果 j 等于 n 时返回 -1
所有起点都比终点小时,l 会停在 n,直接取 arr[n] 会越界
完整代码(Python / C++ / Java)
Python
from __future__ import annotations
from typing import *
from collections import *
from functools import *
from itertools import *
from math import *
from heapq import *
from bisect import *
class Solution:
def findRightInterval(self, intervals: List[List[int]]) -> List[int]:
n = len(intervals)
ans = [-1] * n
arr = sorted((st, i) for i, (st, _) in enumerate(intervals))
for i, (_, ed) in enumerate(intervals):
j = bisect_left(arr, (ed, -inf))
if j < n:
ans[i] = arr[j][1]
return ansC++
#include <algorithm>
#include <array>
#include <cctype>
#include <climits>
#include <cmath>
#include <deque>
#include <functional>
#include <iostream>
#include <limits>
#include <map>
#include <numeric>
#include <queue>
#include <set>
#include <sstream>
#include <stack>
#include <string>
#include <tuple>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
using namespace std;
class Solution {
public:
vector<int> findRightInterval(vector<vector<int>>& intervals) {
int n = intervals.size();
vector<pair<int, int>> arr;
for (int i = 0; i < n; ++i) {
arr.emplace_back(intervals[i][0], i);
}
sort(arr.begin(), arr.end());
vector<int> ans;
for (auto& e : intervals) {
int j = lower_bound(arr.begin(), arr.end(), make_pair(e[1], -1)) - arr.begin();
ans.push_back(j == n ? -1 : arr[j].second);
}
return ans;
}
};Java
import java.util.*;
class Solution {
public int[] findRightInterval(int[][] intervals) {
int n = intervals.length;
int[][] arr = new int[n][0];
for (int i = 0; i < n; ++i) {
arr[i] = new int[] {intervals[i][0], i};
}
Arrays.sort(arr, (a, b) -> a[0] - b[0]);
int[] ans = new int[n];
for (int i = 0; i < n; ++i) {
int j = search(arr, intervals[i][1]);
ans[i] = j < n ? arr[j][1] : -1;
}
return ans;
}
private int search(int[][] arr, int x) {
int l = 0, r = arr.length;
while (l < r) {
int mid = (l + r) >> 1;
if (arr[mid][0] >= x) {
r = mid;
} else {
l = mid + 1;
}
}
return l;
}
}复杂度
时间
O(n log n)
排序 O(n log n) + n 次二分合计 O(n log n)
空间
O(n)
存放排序后的 (起点, 原下标) 数组
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 寻找右区间 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
为什么能用二分?这题哪里有序?+
右侧区间只跟「起点」有关,跟终点、区间宽度都无关。把所有起点排成升序后,「第一个 ≥ end 的起点」就是一个标准的 lower_bound 二分。识别出「找第一个满足条件的位置」这个母题,就能套左闭右开二分模板。
排序破坏了原顺序,怎么还原答案下标?+
排序时不是只排起点,而是排 (起点, 原下标) 这个二元组,原下标跟着起点一起走。二分在排序数组里定位到位置 j 后,取 arr[j] 里存的原下标,就是答案要的区间编号。这一步是本题最容易错的地方。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄声线把 寻找右区间 一步步讲透(全站 0 节语音精讲,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。