汽车出行路线重复检测(2026.11.04)
2026年最新华为OD机考题库(按考点分类): 2026年最新华为OD机考题库
题目描述
汽车智能导航系统需要检测用户的出行存在多少个闭环路线,以此优化导航推荐。
给定一个地点编号数组 locations,它按行驶顺序记录车辆途经的每个地点(locations[j] 是第 j 个途经地点)。
一段连续子路线 locations[i..j](0≤i<j≤n−1,两端都取到)是闭环路线,当且仅当:
- 起点与终点是同一个地点:
locations[i] == locations[j]; - 除首尾外,中间地点
locations[i+1..j-1]两两不同(含空中间段); - 中间地点都不等于首尾地点:
locations[i+1..j-1]中没有值等于locations[i]。
请返回数组中满足上述条件的连续子路线的条数。
注意下面几个由样例确定的计数口径:
- 长度只有 2 的子路线
[x, x]也算闭环(中间段为空,两条附加条件自动满足),见样例 3、样例 5; - 计数按下标区间统计,同一个地点值在不同位置形成的闭环要分别计数:例如
[1,1,1]答案是 2 而不是 1([1,1]取前两个 1、[1,1]取后两个 1),见样例 3; - 中间只要出现重复地点、或出现与首尾相同的地点,该子路线就不是闭环:例如
[1,2,2,1]中整段[1,2,2,1]不合法(中间 2 重复,且中间含 1),只有[2,2]合法,答案是 1,见样例 5。
输入描述
输入一行,形如 [1,2,1,3,4],用方括号包住整个地点编号数组,元素之间用半角逗号分隔,逗号后可能带空格;数组为空时为 []。
数据范围
- 0≤locations.length≤106(数组可以为空);
- 地点编号为非负整数,0≤locations[i]≤109。
输出描述
输出一行一个十进制整数,表示闭环路线的条数;没有时输出 0。
样例
用例输入 1
[1,2,1,3,4]
用例输出 1
1
用例解释 1
只有一个闭环 [1,2,1]:首尾都是地点 1,中间只有地点 2(不重复、也不等于 1)。其余子路线首尾都不相同,或中间不满足条件。
用例输入 2
[2,5,3,2,4,6,4]
用例输出 2
2
用例解释 2
- 闭环 1:
[2,5,3,2](下标 0…3,首尾都是 2,中间 5、3 互不相同且都不等于 2); - 闭环 2:
[4,6,4](下标 4…6,首尾都是 4,中间只有 6)。
用例输入 3
[1,1,1]
用例输出 3
2
用例解释 3
闭环 1:[1,1](前两个 1),闭环 2:[1,1](后两个 1)。整段 [1,1,1] 不合法,因为中间的那个 1 与首尾地点相同。
来源
本题来源于网络、非官方材料、仅供学习交流,侵删
🧠 解题思路
第一步:题目在问什么
统计有多少段连续子路线 locations[i…j](i < j)满足:首尾地点相同;中间的地点两两不同;中间的地点也都不等于首尾。中间可以为空,例如 [x, x] 也算一条。
第二步:最直接的做法为什么慢
枚举所有 (i, j) 再检查中间部分,子路线有约 n²/2 段,数组长度最大 10⁶,是 5×10¹¹ 段,根本数不完。
第三步:固定终点 j,起点只有一个候选
按终点 j 来数。设 v = locations[j],起点 i 必须也满足 locations[i] = v。
如果 v 在 j 前面出现过好几次,只有最近的那一次 p 可能当起点:如果选更早的一次当起点,最近的那个 p 就落在了中间,而中间的地点不能等于首尾,不合法。
所以以 j 结尾的闭环最多只有一条,就是 [p, j]。还要检查它的中间 locations[p+1…j−1] 两两不同。中间不可能有 v(p 已经是 j 之前最后一次出现),这一条自动满足。
第四步:用滑动窗口判断“中间两两不同”
维护一个窗口 [left, j−1],保证窗口里的地点两两不同,并且 left 尽量靠左。这就是“最长无重复子串”的滑动窗口:用 last_pos 记录每个地点上一次出现的位置,读到 locations[j] 时,如果它上次出现的位置 p 在窗口里(p ≥ left),就出现了重复,left 跳到 p + 1。
这个窗口刚好能回答第三步的问题:
- p ≥ left:中间部分 [p+1, j−1] 在窗口里面,两两不同,[p, j] 是一条闭环,答案加 1。然后窗口里有了两个 v,left 跳到 p + 1;
- p < left:left 之所以已经越过 p,是因为 p 和 j 之间出现过一对重复的地点,[p, j] 的中间不合法,不计数;
- v 第一次出现:没有起点,不计数。
最后都要把 last_pos[v] 更新为 j。
用样例走一遍
样例 2:locations = [2, 5, 3, 2, 4, 6, 4]
| j | 地点 | 上次出现 p | left | 处理 | 答案 |
|---|---|---|---|---|---|
| 0 | 2 | 无 | 0 | 第一次出现 | 0 |
| 1 | 5 | 无 | 0 | 第一次出现 | 0 |
| 2 | 3 | 无 | 0 | 第一次出现 | 0 |
| 3 | 2 | 0 | 0 | p ≥ left,[0, 3] 是闭环;left 改为 1 | 1 |
| 4 | 4 | 无 | 1 | 第一次出现 | 1 |
| 5 | 6 | 无 | 1 | 第一次出现 | 1 |
| 6 | 4 | 4 | 1 | p ≥ left,[4, 6] 是闭环;left 改为 5 | 2 |
答案是 2:路线 2→5→3→2 和 4→6→4。
看一个不计数的例子:[1, 2, 2, 1]。j = 2 时 2 和下标 1 构成闭环 [1, 2],left 跳到 2;j = 3 时 1 的上次出现 p = 0 < left = 2,中间是 2, 2,有重复,不计数。答案是 1。
样例 3:[1, 1, 1]。j = 1 时 [0, 1] 计数,left = 1;j = 2 时 p = 1 ≥ left,[1, 2] 计数。[0, 2] 不算,因为中间的 1 等于首尾。答案是 2。
⚠️ 易错点
- 长度为 2 的 [x, x] 也是闭环(中间为空),不能漏。
- 同一个地点出现多次时,只和上一次出现配对,更早的不行。
- 判断用 p ≥ left:p 在窗口左边时,p 和 j 之间已经有重复的地点,不能计数。
- 地点编号最大 10⁹,last_pos 要用哈希表,不能按编号开数组。
- 数组为空时返回 0。
⏱️ 复杂度
时间复杂度:O(n),每个位置只处理一次,哈希表查找、更新都是 O(1);n 不超过 10⁶。
空间复杂度:O(n),哈希表最多存 n 个不同的地点。
💻 参考代码(Java)
import java.util.*;
class Solution {
public long countLoops(int[] locations) {
int n = locations.length;
// lastPos 记录每个值上一次出现的位置
Map<Integer, Integer> lastPos = new HashMap<>();
// left 维护无重复元素窗口的左边界
int left = 0;
long ans = 0;
for (int j = 0; j < n; ++j) {
int v = locations[j];
Integer pObj = lastPos.get(v);
int p = (pObj == null) ? -1 : pObj;
// 若上一次出现位置在窗口内,则 [p, j] 是唯一以 j 结尾的合法闭环
if (p >= left) {
++ans;
// 窗口内出现重复,收缩左边界
left = p + 1;
}
lastPos.put(v, j);
}
return ans;
}
}
💻 参考代码(C++)
class Solution {
public:
long long countLoops(vector<int>& locations) {
int n = locations.size();
// last_pos 记录每个值上一次出现的位置
unordered_map<int, int> last_pos;
// left 维护无重复元素窗口的左边界
int left = 0;
long long ans = 0;
for (int j = 0; j < n; ++j) {
int v = locations[j];
auto it = last_pos.find(v);
int p = (it == last_pos.end()) ? -1 : it->second;
// 若上一次出现位置在窗口内,则 [p, j] 是唯一以 j 结尾的合法闭环
if (p >= left) {
++ans;
// 窗口内出现重复,收缩左边界
left = p + 1;
}
last_pos[v] = j;
}
return ans;
}
};
💻 参考代码(Python)
class Solution:
def countLoops(self, locations):
n = len(locations)
# last_pos 记录每个值上一次出现的位置
last_pos = {}
# left 维护无重复元素窗口的左边界
left = 0
ans = 0
for j in range(n):
v = locations[j]
p = last_pos.get(v, -1)
# 若上一次出现位置在窗口内,则 [p, j] 是唯一以 j 结尾的合法闭环
if p >= left:
ans += 1
# 窗口内出现重复,收缩左边界
left = p + 1
last_pos[v] = j
return ans
💻 参考代码(JavaScript)
class Solution {
countLoops(locations) {
const n = locations.length;
// lastPos 记录每个值上一次出现的位置
const lastPos = new Map();
// left 维护无重复元素窗口的左边界
let left = 0;
let ans = 0;
for (let j = 0; j < n; ++j) {
const v = locations[j];
const p = lastPos.has(v) ? lastPos.get(v) : -1;
// 若上一次出现位置在窗口内,则 [p, j] 是唯一以 j 结尾的合法闭环
if (p >= left) {
++ans;
// 窗口内出现重复,收缩左边界
left = p + 1;
}
lastPos.set(v, j);
}
return ans;
}
}
module.exports = Solution;
928



被折叠的 条评论
为什么被折叠?



