1472. Design Browser History - Explanation
Description
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 thehomepageof the browser.void visit(string url)Visitsurlfrom the current page. It clears up all the forward history.string back(int steps)Movestepsback in history. If you can only returnxsteps in the history andsteps > x, you will return onlyxsteps. Return the currenturlafter moving back in history at moststeps.string forward(int steps)Movestepsforward in history. If you can only forwardxsteps in the history andsteps > x, you will forward onlyxsteps. Return the currenturlafter forwarding in history at moststeps.
Example 1:
Input:
["BrowserHistory","visit","visit","visit","back","back","forward","visit","forward","back","back"]
[["neetcode.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","neetcode.com"]
Explanation:
BrowserHistory browserHistory = new BrowserHistory("neetcode.com");
browserHistory.visit("google.com"); // You are in "neetcode.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 "neetcode.com". return "neetcode.com"Constraints:
1 <= homepage.length <= 201 <= url.length <= 201 <= steps <= 100homepageandurlconsist of'.'or lower case English letters.- At most
5000calls will be made tovisit,back, andforward.
Topics
Prerequisites
Before attempting this problem, you should be comfortable with:
- Stacks - LIFO data structure for managing back and forward navigation history
- Dynamic Arrays - Using arrays with a cursor pointer to track current position
- Doubly Linked Lists - Nodes with prev/next pointers for bidirectional traversal
1. Two Stacks
Intuition
A browser's back and forward functionality naturally maps to two stacks. Think of browsing as a timeline where the back stack holds pages you've visited (including the current one), and the forward stack holds pages you can go forward to. When you visit a new URL, you push it onto the back stack and clear the forward stack since you've branched off onto a new path. Going back means moving pages from the back stack to the forward stack, and going forward is the reverse.
Algorithm
- Initialize two stacks:
backHistorywith the homepage, andfrontHistoryas empty. - For
visit(url): Push the new URL ontobackHistoryand clearfrontHistory(visiting a new page invalidates forward history). - For
back(steps): While steps remain andbackHistoryhas more than one element, pop frombackHistoryand push ontofrontHistory. Return the top ofbackHistory. - For
forward(steps): While steps remain andfrontHistoryis not empty, pop fromfrontHistoryand push ontobackHistory. Return the top ofbackHistory.
class BrowserHistory:
def __init__(self, homepage: str):
self.back_history = [homepage]
self.front_history = []
def visit(self, url: str) -> None:
self.back_history.append(url)
self.front_history = []
def back(self, steps: int) -> str:
while steps and len(self.back_history) > 1:
self.front_history.append(self.back_history.pop())
steps -= 1
return self.back_history[-1]
def forward(self, steps: int) -> str:
while steps and self.front_history:
self.back_history.append(self.front_history.pop())
steps -= 1
return self.back_history[-1]Time & Space Complexity
- Time complexity:
- time for initialization.
- time for each function call.
- time for each and function calls.
- Space complexity:
Where is the number of visited urls, is the average length of each url, and is the number of steps we go forward or back.
2. Dynamic Array
Intuition
Instead of two stacks, we can use a single list and a cursor pointer. The list stores all visited URLs in order, and the cursor indicates which page we're currently viewing. Going back decreases the cursor, going forward increases it. The key insight is that when we visit a new URL, we truncate the list at the current position before appending the new URL, since forward history becomes invalid.
Algorithm
- Initialize an array
historywith the homepage and setcur = 0. - For
visit(url): Incrementcur, truncate the array to include only elements up to indexcur, then append the new URL. - For
back(steps): Setcur = max(0, cur - steps)to avoid going below index0. Returnhistory[cur]. - For
forward(steps): Setcur = min(len(history) - 1, cur + steps)to avoid going past the last element. Returnhistory[cur].
class BrowserHistory:
def __init__(self, homepage: str):
self.history = [homepage]
self.cur = 0
def visit(self, url: str) -> None:
self.cur += 1
self.history = self.history[:self.cur]
self.history.append(url)
def back(self, steps: int) -> str:
self.cur = max(0, self.cur - steps)
return self.history[self.cur]
def forward(self, steps: int) -> str:
self.cur = min(len(self.history) - 1, self.cur + steps)
return self.history[self.cur]Time & Space Complexity
- Time complexity:
- time for initialization.
- time for each function call.
- time for each and function calls.
- Space complexity:
Where is the number of visited urls and is the average length of each url.
3. Dynamic Array (Optimal)
Intuition
The previous approach truncates the array on each visit, which requires copying elements. We can avoid this by keeping track of the "logical" end of the array separately from its physical size. Instead of removing elements when visiting a new URL, we simply overwrite the element at the current position and update a variable that tracks how many elements are valid. This allows O(1) visit operations.
Algorithm
- Initialize
historywith the homepage,cur = 0, andn = 1(number of valid URLs). - For
visit(url): Incrementcur. Ifcurequals the array length, append the URL and incrementn. Otherwise, overwritehistory[cur]with the URL and setn = cur + 1. - For
back(steps): Setcur = max(0, cur - steps). Returnhistory[cur]. - For
forward(steps): Setcur = min(n - 1, cur + steps). Returnhistory[cur].
class BrowserHistory:
def __init__(self, homepage: str):
self.history = [homepage]
self.cur = 0
self.n = 1
def visit(self, url: str) -> None:
self.cur += 1
if self.cur == len(self.history):
self.history.append(url)
self.n += 1
else:
self.history[self.cur] = url
self.n = self.cur + 1
def back(self, steps: int) -> str:
self.cur = max(0, self.cur - steps)
return self.history[self.cur]
def forward(self, steps: int) -> str:
self.cur = min(self.n - 1, self.cur + steps)
return self.history[self.cur]Time & Space Complexity
- Time complexity:
- time for initialization.
- time for each function call.
- time for each and function calls.
- Space complexity:
Where is the number of visited urls and is the average length of each url.
4. Doubly Linked List
Intuition
A doubly linked list provides a natural representation of browser history where each node contains a URL and pointers to the previous and next pages. We maintain a pointer to the current node. Navigating back follows the prev pointer, navigating forward follows the next pointer, and visiting a new URL creates a new node linked to the current one while discarding any forward history.
Algorithm
- Initialize a single node with the homepage and set
curto point to it. - For
visit(url): Create a new node with the URL, link it as the next node ofcurwithcuras itsprevpointer, then movecurto this new node. This automatically discards forward history since the new node has nonext. - For
back(steps): While steps remain andcur.prevexists, movecurtocur.prev. Returncur.val. - For
forward(steps): While steps remain andcur.nextexists, movecurtocur.next. Returncur.val.
class ListNode:
def __init__(self, val, prev=None, next=None):
self.val = val
self.prev = prev
self.next = next
class BrowserHistory:
def __init__(self, homepage: str):
self.cur = ListNode(homepage)
def visit(self, url: str) -> None:
self.cur.next = ListNode(url, self.cur)
self.cur = self.cur.next
def back(self, steps: int) -> str:
while self.cur.prev and steps > 0:
self.cur = self.cur.prev
steps -= 1
return self.cur.val
def forward(self, steps: int) -> str:
while self.cur.next and steps > 0:
self.cur = self.cur.next
steps -= 1
return self.cur.valTime & Space Complexity
- Time complexity:
- time for initialization.
- time for each function call.
- time for each and function calls.
- Space complexity:
Where is the number of visited urls, is the average length of each url, and is the number of steps we go forward or back.
Common Pitfalls
Not Clearing Forward History on Visit
When visiting a new URL, the forward history must be invalidated. A common mistake is forgetting to clear the forward stack or reset the logical end pointer.
# Wrong - forward history persists after new visit
def visit(self, url: str) -> None:
self.back_history.append(url)
# Missing: self.front_history = []
# Correct
def visit(self, url: str) -> None:
self.back_history.append(url)
self.front_history = [] # Clear forward historyAllowing Back to Go Before Homepage
The homepage is always the first page and cannot be navigated before. A common mistake is not preserving at least one element in the back history.
# Wrong - can empty the back history
def back(self, steps: int) -> str:
while steps and self.back_history:
self.front_history.append(self.back_history.pop())
steps -= 1
return self.back_history[-1] # IndexError when empty!
# Correct - keep at least one element (homepage)
def back(self, steps: int) -> str:
while steps and len(self.back_history) > 1:
self.front_history.append(self.back_history.pop())
steps -= 1
return self.back_history[-1]Confusing Array Index with Logical Size
In the optimal dynamic array approach, using len(history) instead of n (logical size) for forward navigation causes accessing stale elements that should be considered deleted.
# Wrong - uses physical array length
def forward(self, steps: int) -> str:
self.cur = min(len(self.history) - 1, self.cur + steps)
return self.history[self.cur]
# Correct - uses logical size
def forward(self, steps: int) -> str:
self.cur = min(self.n - 1, self.cur + steps)
return self.history[self.cur]
Sign in to join the discussion