跳到主要内容

HJ178. 【模板】双指针

题目描述​

给定长度为 n 的数组,找出最长的区间,满足区间内元素两两不同。若有多个这样的区间,依次输出它们。

输入格式​

  • 第 1 行:n
  • 第 2 行:n 个整数

输出格式​

  • 第 1 行:满足条件的区间数量
  • 接下来每行:区间的左右端点 L、R(1 基,L 递增)

示例​

示例 1​

输入:

6
1 1 4 5 1 4

输出:

3
2 4
3 5
4 6

说明: 区间 [2,4]=[1,4,5]、[3,5]=[4,5,1]、[4,6]=[5,1,4] 长度为 3,均无重复元素,是最长的三个区间。


解题思路​

第一步:理解问题本质​

找到所有最长的"无重复元素"连续子数组。用双指针维护一个不含重复元素的滑动窗口。

第二步:暴力解法(枚举所有区间)​

枚举所有左端点和右端点,检查区间内是否无重复,记录最长的。

问题:区间数为 O(n²),每个区间检查去重 O(n),总复杂度 O(n³),无法接受。

第三步:优化解法(双指针滑动窗口)​

维护窗口 [left, right],保证窗口内元素两两不同:

  • 右指针 right 不断扩展
  • 若新元素 arr[right] 在窗口内已出现,则左指针 left 移到它上一次出现位置的下一个,保证无重复

用 last_pos 哈希表记录每个元素最近一次出现的位置:

last_pos = {}
left = 0
for right in range(n):
v = arr[right]
if v in last_pos and last_pos[v] >= left:
left = last_pos[v] + 1
last_pos[v] = right
cur_len = right - left + 1
# 更新最长长度并记录区间

第四步:最优解法​

记录并更新最长长度;长度相同时记录所有区间(转 1 基输出)。每个元素进出窗口一次,整体 O(n),是最优解法。


完整代码实现​

算法可视化 · 逐帧回放这道题的代码68 帧 · 录制输入
import sys


def solve() -> None:
data = sys.stdin.buffer.read().split()
n = int(data[0])
arr = list(map(int, data[1:1 + n]))

last_pos = {} # 元素 -> 最近一次出现的位置
left = 0 # 窗口左端点
best = 0
intervals = []

for right in range(n):
v = arr[right]
# 若 v 在窗口内已出现,则左指针移到其上一次出现位置的下一个
if v in last_pos and last_pos[v] >= left:
left = last_pos[v] + 1
last_pos[v] = right

cur_len = right - left + 1
if cur_len > best:
best = cur_len
intervals = [(left + 1, right + 1)] # 转 1 基
elif cur_len == best:
intervals.append((left + 1, right + 1))

print(len(intervals))
for l, r in intervals:
print(l, r)


if __name__ == "__main__":
solve()

示例推演​

以示例 1 为例,n=6,数组 [1, 1, 4, 5, 1, 4](下标 0~5)。

用 last_pos 记录每个元素最近位置,left 为窗口左端点:

  • right=0 值 1:未出现,last_pos[1]=0,窗口 [0,0] 长 1。best=1,区间 [1,1]
  • right=1 值 1:last_pos[1]=0 >= left=0,left=1,last_pos[1]=1,窗口 [1,1] 长 1
  • right=2 值 4:未出现,last_pos[4]=2,窗口 [1,2] 长 2。best=2,区间 [2,3]
  • right=3 值 5:未出现,last_pos[5]=3,窗口 [1,3] 长 3。best=3,区间 [2,4]
  • right=4 值 1:last_pos[1]=1 >= left=1,left=2,last_pos[1]=4,窗口 [2,4] 长 3。等于 best,追加区间 [3,5]
  • right=5 值 4:last_pos[4]=2 >= left=2,left=3,last_pos[4]=5,窗口 [3,5] 长 3。追加区间 [4,6]

最终 best=3,三个最长区间(1 基):[2,4]、[3,5]、[4,6]。

输出:

3
2 4
3 5
4 6

复杂度分析​

解法时间复杂度空间复杂度说明
暴力枚举O(n³)O(n)区间数与检查均开销大
双指针O(n)O(n)每元素进出窗口各一次

n 为数组长度


易错点总结​

1. 左指针移动位置错误​

当 arr[right] 已在窗口内时,left 要跳到 last_pos[v] + 1,不是简单 left += 1。只有 last_pos[v] >= left(即在当前窗口内)时才需要移动。

2. 忘记更新 last_pos​

每次处理完都要 last_pos[v] = right 更新最近位置,否则后续重复判断会出错。

3. 1 基 / 0 基混淆​

内部用 0 基下标,输出区间端点时要转成 1 基(+1),否则输出错误。


扩展思考​

  • 双指针滑动窗口是解决"最长无重复子数组/子串"问题的标准模板,可用于求最短覆盖、连续子数组和等变体。
  • 与 HJ176 单调队列都属于"滑动窗口"家族,但应用场景不同:前者求最值,后者求无重复最长区间。

相关题目​

自测​

加载评论中...