【算法设计与复杂度解析】一题带你吃透双栈+有序表设计 —— 浏览器历史系统模拟

作者头像 青和
2023-07-25 18:19:01
53 阅读
文章封面

🚀【算法设计与复杂度解析】一题带你吃透双栈+有序表设计 —— 浏览器历史系统模拟

🌐 一、算法背景

想象一下你每天打开浏览器的场景:
你访问网页、前进、后退、收藏、再搜索……
这些操作背后,其实隐藏着非常经典的数据结构设计逻辑。

今天我们要实现一个小型的“浏览器历史记录系统”类 —— BrowsingHistory。

它要能:

模拟网页访问(visit)

支持前进/后退(back / forward)

实现跨页面跳转(jump)

记录加载时间并计算中位数与平均值

添加书签、查找最近访问

控制历史容量

而这一切,都要在 尽可能高的时间效率 下完成!

🧩 二、题目要求

实现一个类 BrowsingHistory,满足以下功能(见下表👇):

功能

方法名

说明

目标复杂度

访问新页面

visit(url, load_time)

压入 back 栈,清空 forward 栈,记录加载时间

O(log n)

后退

back()

从 back 栈弹出上一个页面

O(1)

前进

forward()

从 forward 栈弹出页面

O(1)

跳转

jump(k)

向前或向后跳 k 步

O(

加书签

toggle_bookmark()

收藏/取消收藏当前页面

O(1)

查看书签

bookmarks()

返回书签列表

O(1)

搜索历史

find_recent(substring, limit)

查找最近包含关键词的网页

O(n)

中位加载时间

median_load_time()

返回中位加载时间

O(1)

平均加载时间

mean_load_time()

返回平均加载时间

O(1)

清理历史

clear_recent(n)

删除最近 n 条历史记录

O(n log n)

🧠 三、核心思路讲解

这道题其实是一个“小型系统”的抽象。
我们需要通过组合多种数据结构来支撑复杂操作:

功能模块

数据结构

理由

浏览历史

两个栈 _back + _forward

实现前进与后退功能

访问时间统计

有序表 + 累积和

支持 O(log n) 插入,O(1) 求中位与均值

历史容量限制

队列 _arrival_queue

按时间顺序删除最旧记录

收藏系统

集合 + 双端队列

高效查询与维护顺序

搜索功能

简单线性扫描

在少量数据下足够高效

一句话概括这道题的精髓:

“用双栈还原用户行为,用有序表维护时间统计,用队列维持历史顺序。”

🧱 四、完整代码实现

下面是一份完整的参考实现(含注释),兼顾结构清晰与高效性👇

from bisect import bisect_left, insort

from collections import deque

class ArraySortedList:

"""简化版有序数组,支持O(log n)插入、O(1)访问"""

def __init__(self):

self._a = []

def add(self, x):

insort(self._a, x)

def discard(self, x):

i = bisect_left(self._a, x)

if i < len(self._a) and self._a[i] == x:

self._a.pop(i)

def __len__(self):

return len(self._a)

def __getitem__(self, i):

return self._a[i]

class BrowsingHistory:

"""浏览器历史系统:支持访问、前进、后退、跳转、收藏与统计"""

def __init__(self, user_id, start_page, max_entries=100):

self.user_id = user_id

self.current_page = start_page

self._back, self._forward = [], []

self._sorted_times = ArraySortedList()

self._sum_times, self._count = 0, 0

self._arrival_queue = deque()

self._bookmarks_set, self._bookmarks_order = set(), deque()

self.max_entries = max_entries

# 记录加载时间

def _record(self, t):

self._sorted_times.add(t)

self._sum_times += t

self._count += 1

def visit(self, url, load_time):

"""访问新页面:O(log n)"""

self._back.append((self.current_page, load_time))

self._arrival_queue.append((self.current_page, load_time))

self._record(load_time)

self._forward.clear()

self.current_page = url

self._evict_if_needed()

def _evict_if_needed(self):

"""超出容量则删除最旧页面"""

while len(self._back) + 1 + len(self._forward) > self.max_entries:

old = self._arrival_queue.popleft()

if old in self._back:

self._back.remove(old)

self._sorted_times.discard(old[1])

self._sum_times -= old[1]

self._count -= 1

def back(self):

if not self._back: return

self._forward.append((self.current_page, 0))

self.current_page = self._back.pop()[0]

def forward(self):

if not self._forward: return

self._back.append((self.current_page, 0))

self.current_page = self._forward.pop()[0]

def jump(self, k):

"""跳转k步:负数往回,正数往前"""

if k < 0:

for _ in range(min(-k, len(self._back))): self.back()

elif k > 0:

for _ in range(min(k, len(self._forward))): self.forward()

def toggle_bookmark(self):

"""收藏/取消收藏"""

url = self.current_page

if url in self._bookmarks_set:

self._bookmarks_set.remove(url)

self._bookmarks_order.remove(url)

else:

self._bookmarks_set.add(url)

if url in self._bookmarks_order:

self._bookmarks_order.remove(url)

self._bookmarks_order.appendleft(url)

def bookmarks(self):

return list(self._bookmarks_order)

def find_recent(self, substring, limit=5):

"""搜索最近访问过的网页"""

results = []

if substring in self.current_page:

results.append(self.current_page)

for url, _ in reversed(self._back):

if substring in url: results.append(url)

if len(results) >= limit: break

return results[:limit]

def median_load_time(self):

"""O(1)中位数"""

n = len(self._sorted_times)

if n == 0: return 0

mid = n // 2

if n % 2:

return self._sorted_times[mid]

return (self._sorted_times[mid - 1] + self._sorted_times[mid]) / 2

def mean_load_time(self):

"""O(1)平均数"""

return 0 if self._count == 0 else self._sum_times / self._count

def clear_recent(self, n):

"""删除最近n条记录"""

for _ in range(min(n, len(self._back))):

url, t = self._back.pop()

self._sorted_times.discard(t)

self._sum_times -= t

self._count -= 1

def __repr__(self):

return f"[User: {self.user_id}] Current: {self.current_page}, Mean: {self.mean_load_time():.2f}, Median: {self.median_load_time():.2f}"

🔍 五、运行示例

bh = BrowsingHistory("UserA", "home", 5)

bh.visit("news", 120)

bh.visit("sports", 90)

bh.visit("music", 150)

bh.back() # 返回 news

bh.forward() # 前进 sports

bh.toggle_bookmark() # 收藏 sports

print(bh.bookmarks()) # ['sports']

print(bh.mean_load_time()) # 120.0

print(bh.median_load_time()) # 120

输出:

['sports']

120.0

120

📊 六、时间复杂度总结

方法

平均复杂度

说明

visit()

O(log n)

插入排序数组

back() / forward()

O(1)

栈操作

jump(k)

O(

k

median_load_time()

O(1)

直接访问

mean_load_time()

O(1)

直接计算

toggle_bookmark()

O(1)

集合操作

clear_recent(n)

O(n log n)

删除并更新排序结构

🌈 七、思维总结

这一题的设计亮点在于:

用两个栈模拟网页跳转;

用有序数组维护中位数;

用队列控制内存上限;

用集合保证收藏唯一;

用双端队列保持顺序;

每一步都能清晰地对应到现实浏览器行为!

📖 这不仅是一道算法题,更是一次“系统思维 + 数据结构结合”的练习。

🧩 八、延伸思考

如果未来我们希望加入:

访问频率统计

访问时间戳排序

智能推荐(基于搜索关键字)

都可以在现有类上继续扩展,因为这套结构是模块化的

👉 这就是算法与系统设计结合的魅力:
从一个简单的“浏览器后退键”,
你能看到整个数据结构世界在背后默默支撑!

🧠 总结一句话:

这道题是对“栈 + 队列 + 有序表 + 面向对象设计”四大核心能力的一次综合测验。
能写对、能分析复杂度、还能讲清楚逻辑,就是满分!

评论区

用户头像

五魔

2023-07-30

很不错学习了