栈题目:设计浏览器历史记录

题目

标题和出处

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

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

难度

6 级

题目描述

要求

你有一个只支持单个标签页的浏览器,最开始你浏览的网页是 homepage \texttt{homepage} homepage,你可以访问其他的网站 url \texttt{url} url,也可以在浏览历史中后退 steps \texttt{steps} steps 步或前进 steps \texttt{steps} steps 步。

请你实现 BrowserHistory \texttt{BrowserHistory} BrowserHistory 类:

  • BrowserHistory(string   homepage) \texttt{BrowserHistory(string homepage)} BrowserHistory(string homepage) homepage \texttt{homepage} homepage 初始化浏览器类。
  • void   visit(string   url) \texttt{void visit(string url)} void visit(string url) 从当前页跳转访问 url \texttt{url} url 对应的页面。执行此操作会把浏览历史前进的记录全部删除。
  • string   back(int   steps) \texttt{string back(int steps)} string back(int steps) 在浏览历史中后退 steps \texttt{steps} steps 步。如果你只能在浏览历史中后退至多 x \texttt{x} x 步且 steps   >   x \texttt{steps > x} steps > x,那么你只后退 x \texttt{x} x 步。请返回后退至多 steps \texttt{steps} steps 步以后的 url \texttt{url} url
  • string   forward(int   steps) \texttt{string forward(int steps)} string forward(int steps) 在浏览历史中前进 steps \texttt{steps} steps 步。如果你只能在浏览历史中前进至多 x \texttt{x} x 步且 steps   >   x \texttt{steps > x} steps > x,那么你只前进 x \texttt{x} x 步。请返回前进至多 steps \texttt{steps} steps 步以后的 url \texttt{url} url

示例

示例 1:

输入:
["BrowserHistory","visit","visit","visit","back","back","forward","visit","forward","back","back"] \texttt{["BrowserHistory","visit","visit","visit","back","back","forward","visit","forward","back","back"]} ["BrowserHistory","visit","visit","visit","back","back","forward","visit","forward","back","back"]
[["leetcode.com"],["google.com"],["facebook.com"],["youtube.com"],[1],[1],[1],["linkedin.com"],[2],[2],[7]] \texttt{[["leetcode.com"],["google.com"],["facebook.com"],["youtube.com"],[1],[1],[1],["linkedin.com"],[2],[2],[7]]} [["leetcode.com"],["google.com"],["facebook.com"],["youtube.com"],[1],[1],[1],["linkedin.com"],[2],[2],[7]]
输出:
[null,null,null,null,"facebook.com","google.com","facebook.com",null,"linkedin.com","google.com","leetcode.com"] \texttt{[null,null,null,null,"facebook.com","google.com","facebook.com",null,"linkedin.com","google.com","leetcode.com"]} [null,null,null,null,"facebook.com","google.com","facebook.com",null,"linkedin.com","google.com","leetcode.com"]
解释:
BrowserHistory   browserHistory   =   new   BrowserHistory("leetcode.com"); \texttt{BrowserHistory browserHistory = new BrowserHistory("leetcode.com");} BrowserHistory browserHistory = new BrowserHistory("leetcode.com");
browserHistory.visit("google.com"); \texttt{browserHistory.visit("google.com");} browserHistory.visit("google.com"); // 你原本在浏览 "leetcode.com" \texttt{"leetcode.com"} "leetcode.com"。访问 "google.com" \texttt{"google.com"} "google.com"
browserHistory.visit("facebook.com"); \texttt{browserHistory.visit("facebook.com");} browserHistory.visit("facebook.com"); // 你原本在浏览 "google.com" \texttt{"google.com"} "google.com"。访问 "facebook.com" \texttt{"facebook.com"} "facebook.com"
browserHistory.visit("youtube.com"); \texttt{browserHistory.visit("youtube.com");} browserHistory.visit("youtube.com"); // 你原本在浏览 "facebook.com" \texttt{"facebook.com"} "facebook.com"。访问 "youtube.com" \texttt{"youtube.com"} "youtube.com"
browserHistory.back(1); \texttt{browserHistory.back(1);} browserHistory.back(1); // 你原本在浏览 "youtube.com" \texttt{"youtube.com"} "youtube.com",后退到 "facebook.com" \texttt{"facebook.com"} "facebook.com" 并返回 "facebook.com" \texttt{"facebook.com"} "facebook.com"
browserHistory.back(1); \texttt{browserHistory.back(1);} browserHistory.back(1); // 你原本在浏览 "facebook.com" \texttt{"facebook.com"} "facebook.com",后退到 "google.com" \texttt{"google.com"} "google.com" 并返回 "google.com" \texttt{"google.com"} "google.com"
browserHistory.forward(1); \texttt{browserHistory.forward(1);} browserHistory.forward(1); // 你原本在浏览 "google.com" \texttt{"google.com"} "google.com",前进到 "facebook.com" \texttt{"facebook.com"} "facebook.com" 并返回 "facebook.com" \texttt{"facebook.com"} "facebook.com"
browserHistory.visit("linkedin.com"); \texttt{browserHistory.visit("linkedin.com");} browserHistory.visit("linkedin.com"); // 你原本在浏览 "facebook.com" \texttt{"facebook.com"} "facebook.com"。访问 "linkedin.com" \texttt{"linkedin.com"} "linkedin.com"
browserHistory.forward(2); \texttt{browserHistory.forward(2);} browserHistory.forward(2); // 你原本在浏览 "linkedin.com" \texttt{"linkedin.com"} "linkedin.com",你无法前进任何步数。
browserHistory.back(2); \texttt{browserHistory.back(2);} browserHistory.back(2); // 你原本在浏览 "linkedin.com" \texttt{"linkedin.com"} "linkedin.com",后退两步依次先到 "facebook.com" \texttt{"facebook.com"} "facebook.com",然后到 "google.com" \texttt{"google.com"} "google.com",并返回 "google.com" \texttt{"google.com"} "google.com"
browserHistory.back(7); \texttt{browserHistory.back(7);} browserHistory.back(7); // 你原本在浏览 "google.com" \texttt{"google.com"} "google.com",你只能后退一步到 "leetcode.com" \texttt{"leetcode.com"} "leetcode.com",并返回 "leetcode.com" \texttt{"leetcode.com"} "leetcode.com"

数据范围

  • 1 ≤ homepage.length ≤ 20 \texttt{1} \le \texttt{homepage.length} \le \texttt{20} 1homepage.length20
  • 1 ≤ url.length ≤ 20 \texttt{1} \le \texttt{url.length} \le \texttt{20} 1url.length20
  • 1 ≤ steps ≤ 100 \texttt{1} \le \texttt{steps} \le \texttt{100} 1steps100
  • homepage \texttt{homepage} homepage url \texttt{url} url 都只包含 ‘.’ \texttt{`.'} ‘.’ 或者小写英语字母
  • 最多调用 5000 \texttt{5000} 5000 visit \texttt{visit} visit back \texttt{back} back forward \texttt{forward} forward

解法一

思路和算法

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

双向链表中的每个结点包含 url \textit{url} url 的信息,表示该结点对应的页面,以及更早的相邻结点 prev \textit{prev} prev 和更新的相邻结点 next \textit{next} next

实现中需要记录当前访问的页面的结点 curr \textit{curr} curr,所有的操作都是基于 curr \textit{curr} curr

初始化时,用 homepage \textit{homepage} homepage 创建一个结点,此时双向链表中只有一个结点, curr \textit{curr} curr 指向唯一的结点。

对于访问操作,创建一个页面为 url \textit{url} url 的结点 node \textit{node} node,然后令 curr . next \textit{curr}.\textit{next} curr.next 指向 node \textit{node} node,令 node . prev \textit{node}.\textit{prev} node.prev 指向 curr \textit{curr} curr,最后令 curr \textit{curr} curr 指向 node \textit{node} node,表示当前访问的页面是最新页面。

对于后退操作,将 curr \textit{curr} curr prev \textit{prev} prev 方向移动,直到移动次数达到 steps \textit{steps} steps 次或者 curr . prev \textit{curr}.\textit{prev} curr.prev 变为 null \text{null} null(此时 curr \textit{curr} curr 对应最早访问的页面),然后返回 curr . url \textit{curr}.\textit{url} curr.url

对于前进操作,将 curr \textit{curr} curr next \textit{next} next 方向移动,直到移动次数达到 steps \textit{steps} steps 次或者 curr . next \textit{curr}.\textit{next} curr.next 变为 null \text{null} null(此时 curr \textit{curr} curr 对应最新访问的页面),然后返回 curr . url \textit{curr}.\textit{url} curr.url

代码

class BrowserHistory {
    Node curr;

    public BrowserHistory(String homepage) {
        Node home = new Node(homepage);
        curr = home;
    }
    
    public void visit(String url) {
        Node node = new Node(url);
        curr.next = node;
        node.prev = curr;
        curr = node;
    }
    
    public String back(int steps) {
        while (curr.prev != null && steps > 0) {
            curr = curr.prev;
            steps--;
        }
        return curr.url;
    }
    
    public String forward(int steps) {
        while (curr.next != null && steps > 0) {
            curr = curr.next;
            steps--;
        }
        return curr.url;
    }
}

class Node {
    String url;
    Node prev;
    Node next;

    public Node(String url) {
        this.url = url;
    }
}

复杂度分析

  • 时间复杂度:构造方法和访问操作的时间复杂度是 O ( 1 ) O(1) O(1),后退和前进操作的时间复杂度是 O ( steps ) O(\textit{steps}) O(steps)

  • 空间复杂度: O ( n ) O(n) O(n),其中 n n n 是浏览器历史记录中的页面个数。

解法二

思路和算法

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

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

初始化时,将 homepage \textit{homepage} homepage 入后退栈。

对于访问操作,将 url \textit{url} url 入后退栈,并将前进栈清空,因为当前访问的页面后面没有更新访问的页面。

对于后退操作,将后退栈内的元素依次出栈并入前进栈,直到操作的元素个数达到 steps \textit{steps} steps 个或者后退栈只剩下 1 1 1 个元素,然后返回后退栈的栈顶元素。

对于前进操作,将前进栈内的元素依次出栈并入后退栈,直到操作的元素个数达到 steps \textit{steps} steps 个或者前进栈变为空,然后返回后退栈的栈顶元素。

代码

class BrowserHistory {
    Deque<String> backStack;
    Deque<String> forwardStack;

    public BrowserHistory(String homepage) {
        backStack = new ArrayDeque<String>();
        forwardStack = new ArrayDeque<String>();
        backStack.push(homepage);
    }
    
    public void visit(String url) {
        backStack.push(url);
        forwardStack.clear();
    }
    
    public String back(int steps) {
        while (backStack.size() > 1 && steps > 0) {
            forwardStack.push(backStack.pop());
            steps--;
        }
        return backStack.peek();
    }
    
    public String forward(int steps) {
        while (!forwardStack.isEmpty() && steps > 0) {
            backStack.push(forwardStack.pop());
            steps--;
        }
        return backStack.peek();
    }
}

复杂度分析

  • 时间复杂度:构造方法和访问操作的时间复杂度是 O ( 1 ) O(1) O(1),后退和前进操作的时间复杂度是 O ( steps ) O(\textit{steps}) O(steps)

  • 空间复杂度: O ( n ) O(n) O(n),其中 n n n 是浏览器历史记录中的页面个数。

解法三

思路和算法

上述两种解法的后退和前进操作的时间复杂度都是 O ( steps ) O(\textit{steps}) O(steps),因为没有下标信息,无法快速定位到目标元素。如果使用动态数组存储浏览器历史记录,则可以使各项操作的时间复杂度都达到 O ( 1 ) O(1) O(1)

创建动态数组,同时记录当前下标 index \textit{index} index 和最大下标 end \textit{end} end,最大下标表示前进操作时的最后一个下标。

初始化时,将 homepage \textit{homepage} homepage 加入动态数组,将 index \textit{index} index end \textit{end} end 都初始化为 0 0 0

对于访问操作,将 index \textit{index} index 1 1 1,然后将 url \textit{url} url 赋到动态数组的下标 index \textit{index} index 处。特别地,如果动态数组中的元素个数小于或等于更新后的 index \textit{index} index,则将 url \textit{url} url 添加到动态数组的末尾,此时动态数组的下标 index \textit{index} index 处的元素即为 url \textit{url} url。由于当前访问的页面为最新访问的页面,因此令 end = index \textit{end} = \textit{index} end=index

对于后退操作,将 index \textit{index} index 更新为 index − steps \textit{index} - \textit{steps} indexsteps 0 0 0 中的最大值,则更新后的 index \textit{index} index 为动态数组中目标页面的下标,返回动态数组的下标 index \textit{index} index 处的元素。

对于前进操作,将 index \textit{index} index 更新为 index + steps \textit{index} + \textit{steps} index+steps end \textit{end} end 中的最小值,则更新后的 index \textit{index} index 为动态数组中目标页面的下标,返回动态数组的下标 index \textit{index} index 处的元素。

由于动态数组中可以根据下标快速定位元素,因此对于后退和前进操作,首先计算出目标页面的下标,然后根据下标得到目标页面,时间复杂度从 O ( steps ) O(\textit{steps}) O(steps) 降到 O ( 1 ) O(1) O(1)

代码

class BrowserHistory {
    List<String> history;
    int index;
    int end;

    public BrowserHistory(String homepage) {
        history = new ArrayList<String>();
        history.add(homepage);
        index = 0;
        end = 0;
    }

    public void visit(String url) {
        index++;
        if (index < history.size()) {
            history.set(index, url);
        } else {
            history.add(url);
        }
        end = index;
    }

    public String back(int steps) {
        index = Math.max(index - steps, 0);
        return history.get(index);
    }

    public String forward(int steps) {
        index = Math.min(index + steps, end);
        return history.get(index);
    }
}

复杂度分析

  • 时间复杂度:构造方法和各项操作的时间复杂度是 O ( 1 ) O(1) O(1)

  • 空间复杂度: O ( n ) O(n) O(n),其中 n n n 是浏览器历史记录中的页面个数。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

伟大的车尔尼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值