Start learning free

LESSON 410 OF 569 · STACK

Design Browser History in Python: O(1) Back and Forward with Indexes

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 11
    Ctrl + Enter to check

    PYTHON · STACK

    Design Browser History in Python, explained

    Problem overview

    A browser tab starts on a homepage. From any page it can visit a new url, go back some number of pages, or go forward again. Visiting a new page erases the pages ahead of the current one, as in a real browser. The class BrowserHistory has these methods:

    • BrowserHistory(homepage) starts the tab on homepage.
    • visit(url) moves to url and erases every page that forward could have reached.
    • back(steps) goes back steps pages, or as far as the homepage if fewer are behind. It returns the url it ends on.
    • forward(steps) goes forward steps pages, or as far as it can. It returns the url it ends on.

    Example: a tab starts on home.com, then visits a.com, b.com and c.com. The call back(2) lands on a.com. Visiting d.com now erases b.com and c.com, so the history is home.com, a.com, d.com and forward(1) stays on d.com.

    Approach: one list and two indexes

    The natural first design uses two stacks: pages behind the current one and pages ahead. back(steps) pops from one stack and pushes onto the other, one page at a time, so it costs O(steps), and steps can be as large as 100,000. The task asks for O(1) per call, whatever steps is.

    The faster design keeps all the pages in one list in visiting order, and moves around it with numbers. Going back or forward is then arithmetic on an index, however far it goes. Two indexes are enough:

    • self.current: the position of the page the tab is on.
    • self.last: the position of the last page forward may reach.

    The list can be longer than self.last + 1. Pages after self.last are leftovers that a visit erased. Instead of deleting them, which takes time, the code simply never goes past self.last, and later visits write over the leftovers. A single assignment, self.last = self.current, erases the whole forward history.

    Step by step

    1. In __init__, set self.history to a list holding only homepage, at index 0.
    2. Set self.current to 0: the tab starts on the homepage.
    3. Set self.last to 0. Nothing is ahead yet, so the homepage is also the last reachable page.
    4. In visit, add 1 to self.current. The new page goes right after the current one.
    5. If self.current < len(self.history), an old, erased page sits at that position. Overwrite it with url, which takes O(1).
    6. Otherwise the list ends at the old current page, so append url.
    7. Set self.last to self.current. The new page is now the last one that forward can reach, and everything after it counts as erased.
    8. In back, set self.current to max(0, self.current - steps). Index 0 is the homepage, the furthest back you can go.
    9. Return self.history[self.current].
    10. In forward, set self.current to min(self.last, self.current + steps). The cap is self.last, not the end of the list, because the list may hold erased leftovers.
    11. Return self.history[self.current] again.

    The solution in Python

    class BrowserHistory:
        def __init__(self, homepage: str):
            self.history = [homepage]
            self.current = 0
            self.last = 0
    
        def visit(self, url: str) -> None:
            self.current += 1
            if self.current < len(self.history):
                self.history[self.current] = url
            else:
                self.history.append(url)
            self.last = self.current
    
        def back(self, steps: int) -> str:
            self.current = max(0, self.current - steps)
            return self.history[self.current]
    
        def forward(self, steps: int) -> str:
            self.current = min(self.last, self.current + steps)
            return self.history[self.current]
    

    The class in use, printing the list and both indexes so the leftovers are visible:

    b = BrowserHistory("home.com")
    print(b.back(1))
    b.visit("a.com")
    b.visit("b.com")
    b.visit("c.com")
    print(b.history, b.current, b.last)
    print(b.back(2), b.current)
    b.visit("d.com")
    print(b.history, b.current, b.last)
    print(b.forward(1), b.back(1), b.forward(2))
    b.visit("e.com")
    print(b.history, b.current, b.last)
    print(b.back(9), b.forward(9))
    
    home.com
    ['home.com', 'a.com', 'b.com', 'c.com'] 3 3
    a.com 1
    ['home.com', 'a.com', 'd.com', 'c.com'] 2 2
    d.com a.com d.com
    ['home.com', 'a.com', 'd.com', 'e.com'] 3 3
    home.com e.com
    

    Reading it line by line:

    • back(1) on a new tab stays on home.com, because nothing is behind it.
    • After back(2) the tab is on a.com (index 1), with b.com and c.com still in the list and last still 3.
    • visit("d.com") overwrites b.com at index 2 and sets last to 2. The list still contains c.com at index 3, but forward can't reach it any more.
    • forward(1) from d.com stays put, back(1) returns to a.com, and forward(2) stops at d.com because last is 2.
    • The next visit overwrites c.com, so the leftover is gone. back(9) goes as far as home.com, and forward(9) as far as e.com.

    Python details worth knowing:

    • max and min clamp an index without any if: max(0, ...) stops it below 0, and min(self.last, ...) stops it above self.last.
    • A list can be assigned by position (self.history[i] = url) only for positions that exist, which is why the code checks len(self.history) first and appends otherwise.
    • Python integers don't overflow, so a large steps such as 100,000 added to an index is safe.

    Complexity

    Time: every method is a few index operations, so visit, back and forward are O(1) (an append is O(1) amortized). Jumping 100,000 pages costs the same as jumping one.

    Space: O(n), where n is the longest the history ever got. The list never shrinks, but visits reuse the leftover slots, so it doesn't grow without need.

    Edge cases and common mistakes

    • Going back past the homepage. Without max(0, ...) the index would turn negative and, in Python, a negative index silently reads from the end of the list, returning the wrong page instead of an error.
    • Going forward past the end. The cap must be self.last. Capping at len(self.history) - 1 would let forward reach erased pages such as c.com above.
    • Forgetting to update self.last in visit. Then the forward history survives a visit.
    • Using append in every visit. After going back, the new page would land at the end of the list, after the pages that should be gone.
    • A visit with nothing ahead. The index equals the list length, so the page is appended: the list grows only when the history reaches further than it ever did.

    Testing

    Another small case, straight from the rules: from a new tab, go back, visit twice, then go back too far and forward too far.

    b = BrowserHistory("home.com")
    print(b.back(1))
    b.visit("a.com")
    b.visit("b.com")
    print(b.back(5), b.forward(1), b.forward(9))
    
    home.com
    home.com a.com b.com
    
    Keep going with Python500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free