Start learning free

LESSON 413 OF 572 · STACK

Design Browser History in C#: O(1) Back and Forward

Practice O(1) Read the full explanation

A browser tab starts on a homepage. From the page it is on, it can visit a new page, go back a number of pages in its history, or go forward again. Visiting a new page erases the pages you could have gone forward to, the way a real browser does.

Implement the class BrowserHistory:

  • BrowserHistory(homepage) starts the tab on the page homepage.
  • visit(url) goes from the current page to url. Every page ahead of the current page (the pages forward could reach) is erased from the history.
  • back(steps) goes back steps pages. If fewer than steps pages are behind the current one, it goes back as far as it can, to the homepage. It returns the url of the page it ends on.
  • forward(steps) goes forward steps pages, or as far as it can if fewer pages are ahead. It returns the url of the page it ends on.

The input is the homepage and a list of operations, applied in order to one new BrowserHistory. The output lists what the back and forward calls return, in order. The starter code spells these names the way your language does.

 

Example 1:

Input:
    BrowserHistory("start.com")
    visit("news.com")
    visit("shop.com")
    visit("video.com")
    back(1)
    back(1)
    forward(1)
    visit("jobs.com")
    forward(2)
    back(2)
    back(7)
Output: shop.com news.com shop.com jobs.com news.com start.com
Explanation:
The history is start.com, news.com, shop.com, video.com, and the tab is on video.com.
back(1) goes to shop.com, the next back(1) to news.com, and forward(1) to shop.com again.
visit("jobs.com") erases video.com, the page that was ahead:
the history is now start.com, news.com, shop.com, jobs.com.
forward(2) can't move, because no page is ahead: it stays on jobs.com.
back(2) goes to news.com. back(7) can only go back one more page, to start.com.

Example 2:

Input:
    BrowserHistory("home.com")
    back(1)
    visit("a.com")
    visit("b.com")
    back(5)
    forward(1)
    forward(9)
Output: home.com home.com a.com b.com
Explanation: Nothing is behind the homepage, so back(1) stays on home.com. From b.com only two pages are behind,
so back(5) goes back two pages, to home.com. forward(9) goes forward as far as it can, to b.com.

Example 3:

Input:
    BrowserHistory("home.com")
    visit("a.com")
    visit("b.com")
    visit("c.com")
    back(2)
    visit("d.com")
    forward(1)
    back(1)
    forward(2)
Output: a.com d.com a.com d.com
Explanation: After back(2) the tab is on a.com, with b.com and c.com ahead. visit("d.com") erases both:
the history is now home.com, a.com, d.com. So forward(1) stays on d.com, and from a.com forward(2) can only reach d.com.

 

Constraints:

  • 1 <= homepage.length, url.length <= 20
  • homepage and url consist of lowercase English letters and '.'.
  • 1 <= steps <= 105
  • At most 105 calls in total are made to visit, back and forward.

Note:

Each call should run in O(1) time, whatever its steps. Two stacks, one for the pages behind and one for the pages ahead, also work, but they move one page at a time, so back and forward take O(steps) time.

STEP 1 OF 12
    Ctrl + Enter to check

    C# · STACK

    Design Browser History in C#, explained

    Problem overview

    You write the class BrowserHistory. A tab starts on a homepage. Visit(url) goes to a new page and erases every page ahead of the current one. Back(steps) goes back that many pages, or as far as the homepage if fewer are behind, and returns the url it ends on. Forward(steps) does the same in the other direction and stops at the last page that is still reachable.

    Here is a tab in use. It starts on home.com, then visits news.com, shop.com and mail.com. The calls return:

    Back(2)      news.com
    Visit("maps.com")
    Forward(5)   maps.com
    Visit("chat.com")
    Back(9)      home.com
    

    The tab was on mail.com, so Back(2) went to news.com. The visit of maps.com erased shop.com and mail.com, the pages that were ahead, so the history became home.com, news.com, maps.com. Then Forward(5) had nowhere to go and stayed on maps.com. After the visit of chat.com, Back(9) could only go back as far as the homepage.

    Approach: one list and two indexes

    The natural design uses two stacks: pages behind and pages ahead. Back(steps) moves one page at a time from one stack to the other, so it costs O(steps), and steps can be as large as 100,000.

    A list with indexes does the same in O(1). Keep all the pages in one List<string> in visiting order, plus two integers:

    • current: the index of the page the tab is on.
    • last: the index of the last page Forward can reach.

    Back and Forward only change current, by any number of steps in one subtraction or addition, kept between 0 and last.

    The trick is Visit. Erasing the forward pages from a list would cost time, so it does not erase anything. It moves current one ahead, writes the new url at that index (or appends it if the list ends there), and sets last to current. Old pages may still sit in the list after last, but Forward never goes past last, and later visits overwrite them.

    The history list, current and last in the example above, step by step (this is the class's internal state, printed from a copy that exposes it):

    start            home.com, news.com, shop.com, mail.com | current=3 last=3
    after Back(2)    home.com, news.com, shop.com, mail.com | current=1 last=3
    after Visit      home.com, news.com, maps.com, mail.com | current=2 last=2
    after Forward(5) home.com, news.com, maps.com, mail.com | current=2 last=2
    after Visit      home.com, news.com, maps.com, chat.com | current=3 last=3
    after Back(9)    home.com, news.com, maps.com, chat.com | current=0 last=3
    

    After the first visit, mail.com is still in the list at index 3, but last is 2, so Forward cannot reach it. The next visit writes over it with chat.com. Going back and forward is only a change of current.

    Step by step

    1. Declare history, a List<string> set to new List<string>().
    2. Declare current and last as int fields, both 0. The tab starts on the homepage, which is also the last reachable page.
    3. The constructor adds homepage to history, at index 0.
    4. Visit moves current ahead with ++.
    5. If current < history.Count, an erased page sits at that index: overwrite it with url. Otherwise the list ends at the previous page, so add url.
    6. Set last to current. This one line erases the forward history.
    7. Back sets current to Math.Max(0, current - steps) and returns history[current].
    8. Forward sets current to Math.Min(last, current + steps) and returns history[current].

    The solution in C#

    class BrowserHistory {
        List<string> history = new List<string>();
        int current = 0;
        int last = 0;
    
        public BrowserHistory(string homepage) {
            history.Add(homepage);
        }
    
        public void Visit(string url) {
            current++;
            if (current < history.Count) {
                history[current] = url;
            } else {
                history.Add(url);
            }
            last = current;
        }
    
        public string Back(int steps) {
            current = Math.Max(0, current - steps);
            return history[current];
        }
    
        public string Forward(int steps) {
            current = Math.Min(last, current + steps);
            return history[current];
        }
    }
    

    C# details: Math.Max and Math.Min are in the System namespace. The constructor shares the class's name and has no return type; it is the only place the homepage enters the list. The list indexer history[current] = url replaces an existing element and throws if the index is past the end, which is why the else branch uses Add instead.

    Complexity

    • Time: every method is O(1), whatever steps is. The list's Add is amortized O(1).
    • Space: O(n) for the pages in the list, where n is the longest the history ever got.

    Testing

    The second example starts on home.com. The calls Back(1), then two visits (a.com, b.com), then Back(5), Forward(1) and Forward(9) return:

    home.com
    home.com
    a.com
    b.com
    

    Nothing is behind the homepage, so the first Back stays. From b.com two pages are behind, so Back(5) goes to home.com. Forward(9) goes as far as b.com.

    The third example: from home.com visit a.com, b.com and c.com, then Back(2), Visit("d.com"), Forward(1), Back(1), Forward(2). The four results are:

    a.com
    d.com
    a.com
    d.com
    

    Edge cases and common mistakes

    • Capping Forward at the end of the list. The cap is last. Pages after it were erased by a visit but may still be in the list.
    • Going below index 0. Back must not pass the homepage, so use Math.Max(0, ...); otherwise history[current] throws for a negative index.
    • Forgetting to set last in Visit. The forward history would then survive the visit.
    • Always calling Add in Visit. The new page would land after the erased pages, and the indexes would no longer match.
    • Walking page by page. It is correct but takes O(steps) per call.
    Keep going with C#500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free