Taro Logo

Design Browser History

Medium
Roblox logo
Roblox
0 views
Topics:
ArraysTwo PointersSliding Windows

You have a browser of one tab where you start on the homepage and you can visit another url, get back in the history number of steps or move forward in the history number of steps.

Implement the BrowserHistory class:

  • BrowserHistory(string homepage) Initializes the object with the homepage of the browser.
  • void visit(string url) Visits url from the current page. It clears up all the forward history.
  • string back(int steps) Move steps back in history. If you can only return x steps in the history and steps > x, you will return only x steps. Return the current url after moving back in history at most steps.
  • string forward(int steps) Move steps forward in history. If you can only forward x steps in the history and steps > x, you will forward only x steps. Return the current url after forwarding in history at most steps.

Example:

Input:
["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]]
Output:
[null,null,null,null,"facebook.com","google.com","facebook.com",null,"linkedin.com","google.com","leetcode.com"]

Explanation:
BrowserHistory browserHistory = new BrowserHistory("leetcode.com");
browserHistory.visit("google.com");       // You are in "leetcode.com". Visit "google.com"
browserHistory.visit("facebook.com");     // You are in "google.com". Visit "facebook.com"
browserHistory.visit("youtube.com");      // You are in "facebook.com". Visit "youtube.com"
browserHistory.back(1);                   // You are in "youtube.com", move back to "facebook.com" return "facebook.com"
browserHistory.back(1);                   // You are in "facebook.com", move back to "google.com" return "google.com"
browserHistory.forward(1);                // You are in "google.com", move forward to "facebook.com" return "facebook.com"
browserHistory.visit("linkedin.com");     // You are in "facebook.com". Visit "linkedin.com"
browserHistory.forward(2);                // You are in "linkedin.com", you cannot move forward any steps.
browserHistory.back(2);                   // You are in "linkedin.com", move back two steps to "facebook.com" then to "google.com". return "google.com"
browserHistory.back(7);                   // You are in "google.com", you can move back only one step to "leetcode.com". return "leetcode.com"

Constraints:

  • 1 <= homepage.length <= 20
  • 1 <= url.length <= 20
  • 1 <= steps <= 100
  • homepage and url consist of  '.' or lower case English letters.
  • At most 5000 calls will be made to visit, back, and forward.

Solution


Clarifying Questions

When you get asked this question in a real-life environment, it will often be ambiguous (especially at FAANG). Make sure to ask these questions in that case:

  1. What is the maximum length of the URLs that will be visited?
  2. If I try to go back further than the beginning of the history, what should be returned? Should I return null, an empty string, or the furthest URL back I can reach?
  3. Similarly, if I try to go forward further than the most recent page, what should happen?
  4. Is the number of steps to go back or forward always a non-negative integer? Can it be zero?
  5. How frequently will each of the operations (visit, back, forward, and get current URL) be called relative to each other? This will influence my choice of data structure.

Brute Force Solution

Approach

We need to simulate how a browser's history works when you visit websites, go back, and go forward. A brute force approach means we'll keep a simple record of all the visited websites and directly manipulate it as needed.

Here's how the algorithm would work step-by-step:

  1. When the user visits a new website, we simply add it to the end of our list of visited websites. Anything we had stored in 'future' websites is now gone.
  2. When the user goes back, we move our 'current' position in the list one step backwards. We just need to make sure we don't go back too far beyond the beginning of the list.
  3. When the user goes forward, we move our 'current' position in the list one step forward. Again, we have to be careful not to go forward beyond the end of the list.

Code Implementation

class BrowserHistory:

    def __init__(self, homepage: str):
        self.history = [homepage]
        self.current_index = 0

    def visit(self, url: str) -> None:
        # When visiting, clear future history
        self.history = self.history[:self.current_index + 1]
        self.history.append(url)
        self.current_index += 1

    def back(self, steps: int) -> str:
        # Move back, but not past the beginning
        self.current_index = max(0, self.current_index - steps)

        return self.history[self.current_index]

    def forward(self, steps: int) -> str:
        # Move forward, but not past the end.
        self.current_index = min(len(self.history) - 1, self.current_index + steps)

        return self.history[self.current_index]

Big(O) Analysis

Time Complexity
O(1)The 'visit' operation appends to the end of a list, which takes constant time. The 'back' and 'forward' operations simply decrement or increment the current position index respectively, with a check to ensure the position stays within bounds. Since all operations perform a fixed amount of work regardless of the number of websites visited (n), the time complexity of each operation is O(1).
Space Complexity
O(N)The described solution involves maintaining a list of visited websites. In the worst-case scenario, the user visits N distinct websites before going back or forward, resulting in a list that stores all N visited URLs. Therefore, the auxiliary space required grows linearly with the number of visited websites. Thus, the space complexity is O(N), where N is the number of visited websites stored in the browser history.

Optimal Solution

Approach

We can efficiently simulate browser history navigation using two separate stacks. One stack stores the past URLs, and another stores the future URLs. This allows us to easily move back and forth through the history.

Here's how the algorithm would work step-by-step:

  1. Use one stack to represent the history you can go back to.
  2. Use another stack to represent the history you can go forward to.
  3. When you visit a new webpage, put the current page on the back stack, and empty the forward stack because you're starting a new branch.
  4. When you go back, move the current webpage from the back stack to the forward stack.
  5. When you go forward, move the current webpage from the forward stack to the back stack.
  6. If you try to go back or forward but the corresponding stack is empty, just stay where you are.

Code Implementation

class BrowserHistory:

    def __init__(self, homepage: str):
        self.back_stack = []
        self.forward_stack = []
        self.current_page = homepage

    def visit(self, url: str) -> None:
        # When visiting a new page, clear future history.
        self.back_stack.append(self.current_page)
        self.forward_stack = []
        self.current_page = url

    def back(self, steps: int) -> str:
        # Move back in history, or stay if no history exists.
        while steps > 0 and self.back_stack:
            self.forward_stack.append(self.current_page)
            self.current_page = self.back_stack.pop()
            steps -= 1
        return self.current_page

    def forward(self, steps: int) -> str:
        # Move forward in history, or stay if no history exists.
        while steps > 0 and self.forward_stack:
            self.back_stack.append(self.current_page)
            self.current_page = self.forward_stack.pop()
            steps -= 1

        return self.current_page

# Your BrowserHistory object will be instantiated and called as such:
# browser_history = BrowserHistory(homepage)
# browser_history.visit(url)
# browser_history.back(steps)
# browser_history.forward(steps)

Big(O) Analysis

Time Complexity
O(1)The BrowserHistory class uses stacks. The visit, back, and forward operations all involve pushing or popping elements from the stacks. Each of these stack operations takes constant time, O(1), regardless of the number of visited URLs. Therefore, the time complexity for each method (visit, back, forward) is O(1). The space complexity would be O(n) where n is the number of visited URLs as we might store all URLs in the stacks.
Space Complexity
O(N)The solution uses two stacks, 'back stack' and 'forward stack', to store URLs. In the worst-case scenario, all visited URLs could be pushed onto the 'back stack' before any 'back' or 'forward' operations are performed. Therefore, the auxiliary space used is proportional to the number of URLs visited, denoted as N. This results in a space complexity of O(N).

Edge Cases

CaseHow to Handle
Initial URL is null or empty stringTreat null/empty string as a valid, albeit potentially nonsensical, starting point, defaulting to an empty history state.
visit() called with null or empty stringHandle null/empty URL in visit() as a valid operation, perhaps resulting in an empty URL entry in the history.
back() or forward() called with 0 stepsIf steps is 0 in back() or forward(), return the current URL without modifying the history.
back() called with steps larger than the current history sizeCap the back movement to the maximum possible distance, going to the oldest entry available.
forward() called with steps larger than the available forward historyCap the forward movement to the maximum possible distance, going to the newest entry available.
Repeated calls to visit() creating a long historyEnsure the data structure used for the history (e.g., array, linked list) can efficiently handle a large number of visited URLs without excessive memory usage or slowdown.
Concurrent access to BrowserHistory object (multithreading)If multithreading is a concern, use appropriate locking mechanisms to protect the internal state of the BrowserHistory object and prevent race conditions.
Very long URLsThe URL strings might be very long; confirm that your chosen data structure (e.g. String or StringBuilder) can handle them without performance issues.