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 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 =newBrowserHistory("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 <= 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.
Please upgrade to NeetCode Pro to view company tags.
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: backHistory with the homepage, and frontHistory as empty.
For visit(url): Push the new URL onto backHistory and clear frontHistory (visiting a new page invalidates forward history).
For back(steps): While steps remain and backHistory has more than one element, pop from backHistory and push onto frontHistory. Return the top of backHistory.
For forward(steps): While steps remain and frontHistory is not empty, pop from frontHistory and push onto backHistory. Return the top of backHistory.
O(min(n,steps)) time for each back() and forward() function calls.
Space complexity: O(m∗n)
Where n is the number of visited urls, m is the average length of each url, and steps 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 history with the homepage and set cur = 0.
For visit(url): Increment cur, truncate the array to include only elements up to index cur, then append the new URL.
For back(steps): Set cur = max(0, cur - steps) to avoid going below index 0. Return history[cur].
For forward(steps): Set cur = min(len(history) - 1, cur + steps) to avoid going past the last element. Return history[cur].
publicclassBrowserHistory{privateList<String> history;privateint cur;publicBrowserHistory(String homepage){
history =newArrayList<>();
history.add(homepage);
cur =0;}publicvoidvisit(String url){
cur++;
history = history.subList(0, cur);
history.add(url);}publicStringback(int steps){
cur =Math.max(0, cur - steps);return history.get(cur);}publicStringforward(int steps){
cur =Math.min(history.size()-1, cur + steps);return history.get(cur);}}
classBrowserHistory{private:
vector<string> history;int cur;public:BrowserHistory(string homepage){
history.push_back(homepage);
cur =0;}voidvisit(string url){
cur++;
history.resize(cur);
history.push_back(url);}
string back(int steps){
cur =max(0, cur - steps);return history[cur];}
string forward(int steps){
cur =min((int)history.size()-1, cur + steps);return history[cur];}};
publicclassBrowserHistory{privateList<string> history;privateint cur;publicBrowserHistory(string homepage){
history =newList<string>{ homepage };
cur =0;}publicvoidVisit(string url){
cur++;
history = history.GetRange(0, cur);
history.Add(url);}publicstringBack(int steps){
cur = Math.Max(0, cur - steps);return history[cur];}publicstringForward(int steps){
cur = Math.Min(history.Count -1, cur + steps);return history[cur];}}
type BrowserHistory struct{
history []string
cur int}funcConstructor(homepage string) BrowserHistory {return BrowserHistory{
history:[]string{homepage},
cur:0,}}func(this *BrowserHistory)Visit(url string){
this.cur++
this.history = this.history[:this.cur]
this.history =append(this.history, url)}func(this *BrowserHistory)Back(steps int)string{
this.cur =max(0, this.cur-steps)return this.history[this.cur]}func(this *BrowserHistory)Forward(steps int)string{
this.cur =min(len(this.history)-1, this.cur+steps)return this.history[this.cur]}funcmax(a, b int)int{if a > b {return a
}return b
}funcmin(a, b int)int{if a < b {return a
}return b
}
classBrowserHistory(homepage: String){privatevar history =mutableListOf(homepage)privatevar cur =0funvisit(url: String){
cur++
history = history.subList(0, cur).toMutableList()
history.add(url)}funback(steps: Int): String {
cur =maxOf(0, cur - steps)return history[cur]}funforward(steps: Int): String {
cur =minOf(history.size -1, cur + steps)return history[cur]}}
classBrowserHistory{privatevar history:[String]privatevar cur:Intinit(_ homepage:String){
history =[homepage]
cur =0}funcvisit(_ url:String){
cur +=1
history =Array(history.prefix(cur))
history.append(url)}funcback(_ steps:Int)->String{
cur =max(0, cur - steps)return history[cur]}funcforward(_ steps:Int)->String{
cur =min(history.count -1, cur + steps)return history[cur]}}
O(1) time for each back() and forward() function calls.
Space complexity: O(m∗n)
Where n is the number of visited urls and m 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 history with the homepage, cur = 0, and n = 1 (number of valid URLs).
For visit(url): Increment cur. If cur equals the array length, append the URL and increment n. Otherwise, overwrite history[cur] with the URL and set n = cur + 1.
For back(steps): Set cur = max(0, cur - steps). Return history[cur].
For forward(steps): Set cur = min(n - 1, cur + steps). Return history[cur].
publicclassBrowserHistory{privateList<String> history;privateint cur;privateint n;publicBrowserHistory(String homepage){
history =newArrayList<>();
history.add(homepage);
cur =0;
n =1;}publicvoidvisit(String url){
cur++;if(cur == history.size()){
history.add(url);
n++;}else{
history.set(cur, url);
n = cur +1;}}publicStringback(int steps){
cur =Math.max(0, cur - steps);return history.get(cur);}publicStringforward(int steps){
cur =Math.min(n -1, cur + steps);return history.get(cur);}}
classBrowserHistory{private:
vector<string> history;int cur, n;public:BrowserHistory(string homepage){
history.push_back(homepage);
cur =0;
n =1;}voidvisit(string url){
cur++;if(cur == history.size()){
history.push_back(url);
n++;}else{
history[cur]= url;
n = cur +1;}}
string back(int steps){
cur =max(0, cur - steps);return history[cur];}
string forward(int steps){
cur =min(n -1, cur + steps);return history[cur];}};
publicclassBrowserHistory{privateList<string> history;privateint cur;privateint n;publicBrowserHistory(string homepage){
history =newList<string>{ homepage };
cur =0;
n =1;}publicvoidVisit(string url){
cur++;if(cur == history.Count){
history.Add(url);
n++;}else{
history[cur]= url;
n = cur +1;}}publicstringBack(int steps){
cur = Math.Max(0, cur - steps);return history[cur];}publicstringForward(int steps){
cur = Math.Min(n -1, cur + steps);return history[cur];}}
type BrowserHistory struct{
history []string
cur int
n int}funcConstructor(homepage string) BrowserHistory {return BrowserHistory{
history:[]string{homepage},
cur:0,
n:1,}}func(this *BrowserHistory)Visit(url string){
this.cur++if this.cur ==len(this.history){
this.history =append(this.history, url)
this.n++}else{
this.history[this.cur]= url
this.n = this.cur +1}}func(this *BrowserHistory)Back(steps int)string{
this.cur =max(0, this.cur-steps)return this.history[this.cur]}func(this *BrowserHistory)Forward(steps int)string{
this.cur =min(this.n-1, this.cur+steps)return this.history[this.cur]}funcmax(a, b int)int{if a > b {return a
}return b
}funcmin(a, b int)int{if a < b {return a
}return b
}
classBrowserHistory(homepage: String){privateval history =mutableListOf(homepage)privatevar cur =0privatevar n =1funvisit(url: String){
cur++if(cur == history.size){
history.add(url)
n++}else{
history[cur]= url
n = cur +1}}funback(steps: Int): String {
cur =maxOf(0, cur - steps)return history[cur]}funforward(steps: Int): String {
cur =minOf(n -1, cur + steps)return history[cur]}}
classBrowserHistory{privatevar history:[String]privatevar cur:Intprivatevar n:Intinit(_ homepage:String){
history =[homepage]
cur =0
n =1}funcvisit(_ url:String){
cur +=1if cur == history.count {
history.append(url)
n +=1}else{
history[cur]= url
n = cur +1}}funcback(_ steps:Int)->String{
cur =max(0, cur - steps)return history[cur]}funcforward(_ steps:Int)->String{
cur =min(n -1, cur + steps)return history[cur]}}
O(1) time for each back() and forward() function calls.
Space complexity: O(m∗n)
Where n is the number of visited urls and m 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 cur to point to it.
For visit(url): Create a new node with the URL, link it as the next node of cur with cur as its prev pointer, then move cur to this new node. This automatically discards forward history since the new node has no next.
For back(steps): While steps remain and cur.prev exists, move cur to cur.prev. Return cur.val.
For forward(steps): While steps remain and cur.next exists, move cur to cur.next. Return cur.val.
O(min(n,steps)) time for each back() and forward() function calls.
Space complexity: O(m∗n)
Where n is the number of visited urls, m is the average length of each url, and steps 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 visitdefvisit(self, url:str)->None:
self.back_history.append(url)# Missing: self.front_history = []# Correctdefvisit(self, url:str)->None:
self.back_history.append(url)
self.front_history =[]# Clear forward history
Allowing 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 historydefback(self, steps:int)->str:while steps and self.back_history:
self.front_history.append(self.back_history.pop())
steps -=1return self.back_history[-1]# IndexError when empty!# Correct - keep at least one element (homepage)defback(self, steps:int)->str:while steps andlen(self.back_history)>1:
self.front_history.append(self.back_history.pop())
steps -=1return 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.
Sign in to join the discussion