题目

标题和出处

标题:设计浏览器历史记录

出处:1472. 设计浏览器历史记录

难度

6 级

题目描述

要求

你有一个只支持单个标签页的浏览器,最开始你浏览的网页是 栈题目:设计浏览器历史记录 - 图1,你可以访问其他的网站 栈题目:设计浏览器历史记录 - 图2,也可以在浏览历史中后退 栈题目:设计浏览器历史记录 - 图3 步或前进 栈题目:设计浏览器历史记录 - 图4 步。

请你实现 栈题目:设计浏览器历史记录 - 图5 类:

  • 栈题目:设计浏览器历史记录 - 图6%7D#card=math&code=%5Ctexttt%7BBrowserHistory%28string%20homepage%29%7D&id=S5ss0) 用 栈题目:设计浏览器历史记录 - 图7 初始化浏览器类。
  • 栈题目:设计浏览器历史记录 - 图8%7D#card=math&code=%5Ctexttt%7Bvoid%20visit%28string%20url%29%7D&id=XHOvv) 从当前页跳转访问 栈题目:设计浏览器历史记录 - 图9 对应的页面。执行此操作会把浏览历史前进的记录全部删除。
  • 栈题目:设计浏览器历史记录 - 图10%7D#card=math&code=%5Ctexttt%7Bstring%20back%28int%20steps%29%7D&id=A2CBd) 在浏览历史中后退 栈题目:设计浏览器历史记录 - 图11 步。如果你只能在浏览历史中后退至多 栈题目:设计浏览器历史记录 - 图12 步且 栈题目:设计浏览器历史记录 - 图13,那么你只后退 栈题目:设计浏览器历史记录 - 图14 步。请返回后退至多 栈题目:设计浏览器历史记录 - 图15 步以后的 栈题目:设计浏览器历史记录 - 图16
  • 栈题目:设计浏览器历史记录 - 图17%7D#card=math&code=%5Ctexttt%7Bstring%20forward%28int%20steps%29%7D&id=ET2yX) 在浏览历史中前进 栈题目:设计浏览器历史记录 - 图18 步。如果你只能在浏览历史中前进至多 栈题目:设计浏览器历史记录 - 图19 步且 栈题目:设计浏览器历史记录 - 图20,那么你只前进 栈题目:设计浏览器历史记录 - 图21 步。请返回前进至多 栈题目:设计浏览器历史记录 - 图22 步以后的 栈题目:设计浏览器历史记录 - 图23

示例

示例 1:

输入:
栈题目:设计浏览器历史记录 - 图24
栈题目:设计浏览器历史记录 - 图25
输出:
栈题目:设计浏览器历史记录 - 图26
解释:
栈题目:设计浏览器历史记录 - 图27%3B%7D#card=math&code=%5Ctexttt%7BBrowserHistory%20browserHistory%20%3D%20new%20BrowserHistory%28%22leetcode.com%22%29%3B%7D&id=J4iqB)
栈题目:设计浏览器历史记录 - 图28%3B%7D#card=math&code=%5Ctexttt%7BbrowserHistory.visit%28%22google.com%22%29%3B%7D&id=uNb3j) // 你原本在浏览 栈题目:设计浏览器历史记录 - 图29。访问 栈题目:设计浏览器历史记录 - 图30
栈题目:设计浏览器历史记录 - 图31%3B%7D#card=math&code=%5Ctexttt%7BbrowserHistory.visit%28%22facebook.com%22%29%3B%7D&id=tzXKK) // 你原本在浏览 栈题目:设计浏览器历史记录 - 图32。访问 栈题目:设计浏览器历史记录 - 图33
栈题目:设计浏览器历史记录 - 图34%3B%7D#card=math&code=%5Ctexttt%7BbrowserHistory.visit%28%22youtube.com%22%29%3B%7D&id=g4Xd0) // 你原本在浏览 栈题目:设计浏览器历史记录 - 图35。访问 栈题目:设计浏览器历史记录 - 图36
栈题目:设计浏览器历史记录 - 图37%3B%7D#card=math&code=%5Ctexttt%7BbrowserHistory.back%281%29%3B%7D&id=qXyow) // 你原本在浏览 栈题目:设计浏览器历史记录 - 图38,后退到 栈题目:设计浏览器历史记录 - 图39 并返回 栈题目:设计浏览器历史记录 - 图40
栈题目:设计浏览器历史记录 - 图41%3B%7D#card=math&code=%5Ctexttt%7BbrowserHistory.back%281%29%3B%7D&id=axZ8g) // 你原本在浏览 栈题目:设计浏览器历史记录 - 图42,后退到 栈题目:设计浏览器历史记录 - 图43 并返回 栈题目:设计浏览器历史记录 - 图44
栈题目:设计浏览器历史记录 - 图45%3B%7D#card=math&code=%5Ctexttt%7BbrowserHistory.forward%281%29%3B%7D&id=zSRqH) // 你原本在浏览 栈题目:设计浏览器历史记录 - 图46,前进到 栈题目:设计浏览器历史记录 - 图47 并返回 栈题目:设计浏览器历史记录 - 图48
栈题目:设计浏览器历史记录 - 图49%3B%7D#card=math&code=%5Ctexttt%7BbrowserHistory.visit%28%22linkedin.com%22%29%3B%7D&id=KwVKA) // 你原本在浏览 栈题目:设计浏览器历史记录 - 图50。访问 栈题目:设计浏览器历史记录 - 图51
栈题目:设计浏览器历史记录 - 图52%3B%7D#card=math&code=%5Ctexttt%7BbrowserHistory.forward%282%29%3B%7D&id=Eb7DW) // 你原本在浏览 栈题目:设计浏览器历史记录 - 图53,你无法前进任何步数。
栈题目:设计浏览器历史记录 - 图54%3B%7D#card=math&code=%5Ctexttt%7BbrowserHistory.back%282%29%3B%7D&id=Gg7sR) // 你原本在浏览 栈题目:设计浏览器历史记录 - 图55,后退两步依次先到 栈题目:设计浏览器历史记录 - 图56,然后到 栈题目:设计浏览器历史记录 - 图57,并返回 栈题目:设计浏览器历史记录 - 图58
栈题目:设计浏览器历史记录 - 图59%3B%7D#card=math&code=%5Ctexttt%7BbrowserHistory.back%287%29%3B%7D&id=DeEkG) // 你原本在浏览 栈题目:设计浏览器历史记录 - 图60,你只能后退一步到 栈题目:设计浏览器历史记录 - 图61,并返回 栈题目:设计浏览器历史记录 - 图62

数据范围

  • 栈题目:设计浏览器历史记录 - 图63
  • 栈题目:设计浏览器历史记录 - 图64
  • 栈题目:设计浏览器历史记录 - 图65
  • 栈题目:设计浏览器历史记录 - 图66栈题目:设计浏览器历史记录 - 图67 都只包含 栈题目:设计浏览器历史记录 - 图68 或者小写英语字母
  • 最多调用 栈题目:设计浏览器历史记录 - 图69栈题目:设计浏览器历史记录 - 图70栈题目:设计浏览器历史记录 - 图71栈题目:设计浏览器历史记录 - 图72

解法一

思路和算法

由于浏览器历史记录需要存储相邻的页面记录,因此可以使用链表实现。由于需要同时支持后退和前进操作,因此使用双向链表。

双向链表中的每个结点包含 栈题目:设计浏览器历史记录 - 图73 的信息,表示该结点对应的页面,以及更早的相邻结点 栈题目:设计浏览器历史记录 - 图74 和更新的相邻结点 栈题目:设计浏览器历史记录 - 图75

实现中需要记录当前访问的页面的结点 栈题目:设计浏览器历史记录 - 图76,所有的操作都是基于 栈题目:设计浏览器历史记录 - 图77

初始化时,用 栈题目:设计浏览器历史记录 - 图78 创建一个结点,此时双向链表中只有一个结点,栈题目:设计浏览器历史记录 - 图79 指向唯一的结点。

对于访问操作,创建一个页面为 栈题目:设计浏览器历史记录 - 图80 的结点 栈题目:设计浏览器历史记录 - 图81,然后令 栈题目:设计浏览器历史记录 - 图82 指向 栈题目:设计浏览器历史记录 - 图83,令 栈题目:设计浏览器历史记录 - 图84 指向 栈题目:设计浏览器历史记录 - 图85,最后令 栈题目:设计浏览器历史记录 - 图86 指向 栈题目:设计浏览器历史记录 - 图87,表示当前访问的页面是最新页面。

对于后退操作,将 栈题目:设计浏览器历史记录 - 图88栈题目:设计浏览器历史记录 - 图89 方向移动,直到移动次数达到 栈题目:设计浏览器历史记录 - 图90 次或者 栈题目:设计浏览器历史记录 - 图91 变为 栈题目:设计浏览器历史记录 - 图92(此时 栈题目:设计浏览器历史记录 - 图93 对应最早访问的页面),然后返回 栈题目:设计浏览器历史记录 - 图94

对于前进操作,将 栈题目:设计浏览器历史记录 - 图95栈题目:设计浏览器历史记录 - 图96 方向移动,直到移动次数达到 栈题目:设计浏览器历史记录 - 图97 次或者 栈题目:设计浏览器历史记录 - 图98 变为 栈题目:设计浏览器历史记录 - 图99(此时 栈题目:设计浏览器历史记录 - 图100 对应最新访问的页面),然后返回 栈题目:设计浏览器历史记录 - 图101

代码

  1. class BrowserHistory {
  2. Node curr;
  3. public BrowserHistory(String homepage) {
  4. Node home = new Node(homepage);
  5. curr = home;
  6. }
  7. public void visit(String url) {
  8. Node node = new Node(url);
  9. curr.next = node;
  10. node.prev = curr;
  11. curr = node;
  12. }
  13. public String back(int steps) {
  14. while (curr.prev != null && steps > 0) {
  15. curr = curr.prev;
  16. steps--;
  17. }
  18. return curr.url;
  19. }
  20. public String forward(int steps) {
  21. while (curr.next != null && steps > 0) {
  22. curr = curr.next;
  23. steps--;
  24. }
  25. return curr.url;
  26. }
  27. }
  28. class Node {
  29. String url;
  30. Node prev;
  31. Node next;
  32. public Node(String url) {
  33. this.url = url;
  34. }
  35. }

复杂度分析

  • 时间复杂度:构造方法和访问操作的时间复杂度是 栈题目:设计浏览器历史记录 - 图102#card=math&code=O%281%29&id=ylij5),后退和前进操作的时间复杂度是 栈题目:设计浏览器历史记录 - 图103#card=math&code=O%28%5Ctextit%7Bsteps%7D%29&id=EGf7C)。
  • 空间复杂度:栈题目:设计浏览器历史记录 - 图104#card=math&code=O%28n%29&id=KJofM),其中 栈题目:设计浏览器历史记录 - 图105 是浏览器历史记录中的页面个数。

解法二

思路和算法

浏览器历史记录的访问和后退操作非常适合用栈实现,只需要使用一个栈即可支持访问和后退操作。对于前进操作,需要在后退操作的同时记录访问过的更新的页面,因此需要使用两个栈。

创建两个栈,分别为后退栈和前进栈,后退栈用于支持访问和后退操作,前进栈用于支持前进操作。对于任何操作,总是保证后退栈的栈顶元素为当前访问的页面。

初始化时,将 栈题目:设计浏览器历史记录 - 图106 入后退栈。

对于访问操作,将 栈题目:设计浏览器历史记录 - 图107 入后退栈,并将前进栈清空,因为当前访问的页面后面没有更新访问的页面。

对于后退操作,将后退栈内的元素依次出栈并入前进栈,直到操作的元素个数达到 栈题目:设计浏览器历史记录 - 图108 个或者后退栈只剩下 栈题目:设计浏览器历史记录 - 图109 个元素,然后返回后退栈的栈顶元素。

对于前进操作,将前进栈内的元素依次出栈并入后退栈,直到操作的元素个数达到 栈题目:设计浏览器历史记录 - 图110 个或者前进栈变为空,然后返回后退栈的栈顶元素。

代码

  1. class BrowserHistory {
  2. Deque<String> backStack;
  3. Deque<String> forwardStack;
  4. public BrowserHistory(String homepage) {
  5. backStack = new ArrayDeque<String>();
  6. forwardStack = new ArrayDeque<String>();
  7. backStack.push(homepage);
  8. }
  9. public void visit(String url) {
  10. backStack.push(url);
  11. forwardStack.clear();
  12. }
  13. public String back(int steps) {
  14. while (backStack.size() > 1 && steps > 0) {
  15. forwardStack.push(backStack.pop());
  16. steps--;
  17. }
  18. return backStack.peek();
  19. }
  20. public String forward(int steps) {
  21. while (!forwardStack.isEmpty() && steps > 0) {
  22. backStack.push(forwardStack.pop());
  23. steps--;
  24. }
  25. return backStack.peek();
  26. }
  27. }

复杂度分析

  • 时间复杂度:构造方法和访问操作的时间复杂度是 栈题目:设计浏览器历史记录 - 图111#card=math&code=O%281%29&id=TRfqA),后退和前进操作的时间复杂度是 栈题目:设计浏览器历史记录 - 图112#card=math&code=O%28%5Ctextit%7Bsteps%7D%29&id=vFJRR)。
  • 空间复杂度:栈题目:设计浏览器历史记录 - 图113#card=math&code=O%28n%29&id=snJdh),其中 栈题目:设计浏览器历史记录 - 图114 是浏览器历史记录中的页面个数。

解法三

思路和算法

上述两种解法的后退和前进操作的时间复杂度都是 栈题目:设计浏览器历史记录 - 图115#card=math&code=O%28%5Ctextit%7Bsteps%7D%29&id=ko1sJ),因为没有下标信息,无法快速定位到目标元素。如果使用动态数组存储浏览器历史记录,则可以使各项操作的时间复杂度都达到 栈题目:设计浏览器历史记录 - 图116#card=math&code=O%281%29&id=diEm4)。

创建动态数组,同时记录当前下标 栈题目:设计浏览器历史记录 - 图117 和最大下标 栈题目:设计浏览器历史记录 - 图118,最大下标表示前进操作时的最后一个下标。

初始化时,将 栈题目:设计浏览器历史记录 - 图119 加入动态数组,将 栈题目:设计浏览器历史记录 - 图120栈题目:设计浏览器历史记录 - 图121 都初始化为 栈题目:设计浏览器历史记录 - 图122

对于访问操作,将 栈题目:设计浏览器历史记录 - 图123栈题目:设计浏览器历史记录 - 图124,然后将 栈题目:设计浏览器历史记录 - 图125 赋到动态数组的下标 栈题目:设计浏览器历史记录 - 图126 处。特别地,如果动态数组中的元素个数小于或等于更新后的 栈题目:设计浏览器历史记录 - 图127,则将 栈题目:设计浏览器历史记录 - 图128 添加到动态数组的末尾,此时动态数组的下标 栈题目:设计浏览器历史记录 - 图129 处的元素即为 栈题目:设计浏览器历史记录 - 图130。由于当前访问的页面为最新访问的页面,因此令 栈题目:设计浏览器历史记录 - 图131

对于后退操作,将 栈题目:设计浏览器历史记录 - 图132 更新为 栈题目:设计浏览器历史记录 - 图133栈题目:设计浏览器历史记录 - 图134 中的最大值,则更新后的 栈题目:设计浏览器历史记录 - 图135 为动态数组中目标页面的下标,返回动态数组的下标 栈题目:设计浏览器历史记录 - 图136 处的元素。

对于前进操作,将 栈题目:设计浏览器历史记录 - 图137 更新为 栈题目:设计浏览器历史记录 - 图138栈题目:设计浏览器历史记录 - 图139 中的最小值,则更新后的 栈题目:设计浏览器历史记录 - 图140 为动态数组中目标页面的下标,返回动态数组的下标 栈题目:设计浏览器历史记录 - 图141 处的元素。

由于动态数组中可以根据下标快速定位元素,因此对于后退和前进操作,首先计算出目标页面的下标,然后根据下标得到目标页面,时间复杂度从 栈题目:设计浏览器历史记录 - 图142#card=math&code=O%28%5Ctextit%7Bsteps%7D%29&id=VImJy) 降到 栈题目:设计浏览器历史记录 - 图143#card=math&code=O%281%29&id=y3vmo)。

代码

  1. class BrowserHistory {
  2. List<String> history;
  3. int index;
  4. int end;
  5. public BrowserHistory(String homepage) {
  6. history = new ArrayList<String>();
  7. history.add(homepage);
  8. index = 0;
  9. end = 0;
  10. }
  11. public void visit(String url) {
  12. index++;
  13. if (index < history.size()) {
  14. history.set(index, url);
  15. } else {
  16. history.add(url);
  17. }
  18. end = index;
  19. }
  20. public String back(int steps) {
  21. index = Math.max(index - steps, 0);
  22. return history.get(index);
  23. }
  24. public String forward(int steps) {
  25. index = Math.min(index + steps, end);
  26. return history.get(index);
  27. }
  28. }

复杂度分析

  • 时间复杂度:构造方法和各项操作的时间复杂度是 栈题目:设计浏览器历史记录 - 图144#card=math&code=O%281%29&id=Xcq1B)。
  • 空间复杂度:栈题目:设计浏览器历史记录 - 图145#card=math&code=O%28n%29&id=BZzx3),其中 栈题目:设计浏览器历史记录 - 图146 是浏览器历史记录中的页面个数。