1019. Next Greater Node In Linked List
Problem Description
In this problem, we are provided with the head of a singly linked list, which contains n
nodes. Each node in the list contains a value, and we are tasked with finding the value of the next greater node for each node in the list. This means for each node in the list, we need to find the first node that appears after it whose value is strictly larger than the value of the current node.
The output of the problem should be an array of integers, where each entry corresponds to the value of the next greater node. Specifically, answer[i]
would contain the value of the next greater node for the i-th
node in the list (the list is 1-indexed). If a node does not have a next greater node, we should set answer[i]
to 0
.
To illustrate with an example, let's say we have a linked list 2 -> 1 -> 5
. The next greater node for the first node (2
) is 5
, since 5
is the next node with a value greater than 2
. The second node (1
) has a next greater node (5
) as well. However, the third node (5
) doesn't have a next greater node, so its corresponding output would be 0
. Hence, the answer would be [5, 5, 0]
.
Intuition
To arrive at the solution, we analyze the requirements. We need to find the next greater value for each node, and this gives us a hint that we should look at each node from a reverse perspective. If we traverse from the end of the list to the beginning, we can keep track of the greater values we have seen so far, which can help us determine if there's a greater next node.
The solution uses a "stack" data structure to maintain a collection of the greater values we've encountered as we iterate in reverse. At each node, we examine the stack:
- We remove from the stack all the elements that are less than or equal to the current node's value, because they will not be the "next greater node" for any of the following elements.
- If the stack still has elements after this operation, the top of the stack represents the next greater value for the current node.
- We record this value in the answer array.
- We then add the current node's value onto the stack to be a potential "next greater node" for the preceding nodes.
- Finally, we proceed to the next node (which is actually the previous list node, as we are going in reverse).
By performing these steps for each node, we leverage the stack to keep track of potential "next greater nodes" for each element and efficiently compute the result in a single reverse pass through the list.
Learn more about Stack, Linked List and Monotonic Stack patterns.
Solution Approach
The solution involves a technique known as Monotonic Stack. The stack is used to keep the elements in a sorted manner so that for any given element, we can quickly find the next greater element.
Let's break down the implementation step-by-step:
- First, we convert the linked list into an array
nums
to enable easy reverse traversal and to avoid dealing with list node pointers. This is done using a simple while loop that iterates through the linked list and appends each value tonums
.
nums = []
while head:
nums.append(head.val)
head = head.next
- We initialize a stack
stk
that will store values from the list in a decreasing order. This is crucial for the monotonic stack pattern to work. Additionally, we initialize the answer arrayans
with the same length asnums
and fill it with zeros.
stk = []
ans = [0] * len(nums)
- We iterate through the
nums
list in reverse order using a for loop, starting from the last element down to the first. This enables us to consider the "next" elements before the current element, which makes determining the next greater node possible.
for i in range(len(nums) - 1, -1, -1):
...
- Inside the loop, while the stack is not empty and the value at the top of the stack is less than or equal to
nums[i]
, we pop elements from the stack. This is done to maintain the monotonic decreasing order of the stack.
while stk and stk[-1] <= nums[i]: stk.pop()
- After cleaning the top of the stack, if the stack is not empty, the new top is guaranteed to be the next greater element for
nums[i]
because of the property of the monotonic stack. We record this value in theans
array at the corresponding index.
if stk: ans[i] = stk[-1]
- Finally, we push
nums[i]
onto the stack. This is becausenums[i]
could be the next greater element for the previous nodes in the original list.
stk.append(nums[i])
- After the loop completes,
ans
contains all the next greater values for each node.
The overall complexity of the solution is O(N), where N is the number of nodes in the list because each element is pushed onto and popped from the stack at most once.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's illustrate the solution approach using a small example of a linked list: 3 -> 2 -> 1 -> 5
.
-
Convert Linked List to Array:
- Initialize
nums
as an empty list. - Traverse the linked list:
nums = [3, 2, 1, 5]
.
- Initialize
-
Initializations:
- Initialize the stack
stk
as an empty list and the answer listans
as[0, 0, 0, 0]
(equal in length tonums
).
- Initialize the stack
-
Iterate in Reverse:
- Start from the end of
nums
: indexi = 3
(value =5
). - Stack
stk
is empty, so addnums[3]
tostk
:stk = [5]
.
- Start from the end of
-
Reverse iteration to
i = 2
(value =1
):- Stack
stk
is[5]
. Top (5) is greater thannums[2]
(1), soans[2] = 5
. - Push
nums[2]
onto the stack:stk = [5, 1]
.
- Stack
-
Reverse iteration to
i = 1
(value =2
):- Stack
stk
is[5, 1]
. Pop top (1) since it's not greater thannums[1]
(2). - Now
stk = [5]
. Top (5) is greater thannums[1]
(2), soans[1] = 5
. - Push
nums[1]
onto the stack:stk = [5, 2]
.
- Stack
-
Reverse iteration to
i = 0
(value =3
):- Stack
stk
is[5, 2]
. Pop top (2) since it's not greater thannums[0]
(3). - Now
stk = [5]
. Top (5) is greater thannums[0]
(3), soans[0] = 5
. - Push
nums[0]
onto the stack:stk = [5, 3]
.
- Stack
-
With the iteration complete, the
ans
list is[5, 5, 5, 0]
, which corresponds to the next greater values for each node in the list:[3, 2, 1, 5]
.
Hence, for the linked list 3 -> 2 -> 1 -> 5
, the output array indicating the next greater node values is [5, 5, 5, 0]
.
Solution Implementation
1# Class definition for singly-linked list.
2class ListNode:
3 def __init__(self, val=0, next=None):
4 self.val = val
5 self.next = next
6
7class Solution:
8 def nextLargerNodes(self, head: Optional[ListNode]) -> List[int]:
9 # Convert the linked list into an array to work with indices.
10 valuesList = []
11 while head:
12 valuesList.append(head.val) # Add node's value into the valuesList
13 head = head.next # Move to the next node
14
15 # Stack to keep track of the next larger element
16 stack = []
17
18 # Length of the linked list or the valuesList
19 n = len(valuesList)
20
21 # Initialize an answer list of size 'n' with default value of 0
22 answer = [0] * n
23
24 # Iterate over the values list in reverse
25 for i in range(n - 1, -1, -1):
26 # Pop the elements from the stack if they are smaller or equal
27 # to the current element since we are looking for the next larger element
28 while stack and stack[-1] <= valuesList[i]:
29 stack.pop()
30
31 # If stack is not empty, assign the next larger element to answer
32 if stack:
33 answer[i] = stack[-1]
34
35 # Push current value onto stack
36 stack.append(valuesList[i])
37
38 # Return the filled answer list
39 return answer
40
1class Solution {
2
3 // Method to find the next larger node values for each node in the linked list
4 public int[] nextLargerNodes(ListNode head) {
5 // Use an ArrayList to store the values of the nodes for easier access
6 List<Integer> nodeValues = new ArrayList<>();
7 // Traverse the linked list and add values to the list
8 while (head != null) {
9 nodeValues.add(head.val);
10 head = head.next;
11 }
12
13 // Use a Deque as a stack to keep track of next larger element we have seen so far
14 Deque<Integer> stack = new ArrayDeque<>();
15 // Find the size of the linked list
16 int listSize = nodeValues.size();
17 // Create an array to store the result
18 int[] result = new int[listSize];
19
20 // Traverse the list in reverse using the values ArrayList
21 for (int i = listSize - 1; i >= 0; --i) {
22 // Pop elements from the stack if they are less than or equal
23 // to the current node's value, since we are only interested
24 // in the next greater value
25 while (!stack.isEmpty() && stack.peek() <= nodeValues.get(i)) {
26 stack.pop();
27 }
28 // If the stack is not empty after popping, the current value at the top
29 // is the next greater value, so we add it to the result
30 if (!stack.isEmpty()) {
31 result[i] = stack.peek();
32 }
33 // Push the current node's value onto stack
34 stack.push(nodeValues.get(i));
35 }
36 // Return the populated result array containing next larger values
37 return result;
38 }
39}
40
1/**
2 * Definition for singly-linked list.
3 * struct ListNode {
4 * int val;
5 * ListNode *next;
6 * ListNode(int x) : val(x), next(nullptr) {}
7 * ListNode(int x, ListNode *next) : val(x), next(next) {}
8 * };
9 */
10
11class Solution {
12public:
13 // Function to find the next greater node for each element in a linked list.
14 vector<int> nextLargerNodes(ListNode* head) {
15 vector<int> values; // This vector will hold the values of the nodes in the list
16
17 // Traverse the linked list and populate the values vector.
18 while (head != nullptr) {
19 values.push_back(head->val);
20 head = head->next;
21 }
22
23 // Initialize a stack to keep track of the next larger elements.
24 stack<int> stack;
25 // Determine the size of the values vector.
26 int size = values.size();
27 // Create a vector to store the answers (next larger elements).
28 vector<int> answers(size, 0); // Initialize with zeros.
29
30 // Loop through the values vector in reverse to find next larger elements.
31 for (int i = size - 1; i >= 0; --i) {
32 // Pop elements from the stack that are smaller or equal to the current element,
33 // since we want to find the next larger one.
34 while (!stack.empty() && stack.top() <= values[i]) {
35 stack.pop();
36 }
37 // If the stack is not empty, the top element is the next larger element.
38 if (!stack.empty()) {
39 answers[i] = stack.top();
40 }
41 // Push the current element onto the stack.
42 stack.push(values[i]);
43 }
44
45 // Return the vector with the next larger elements.
46 return answers;
47 }
48};
49
1/**
2 * Definition for singly-linked list.
3 */
4interface ListNode {
5 val: number;
6 next: ListNode | null;
7}
8
9/**
10 * Function to find the next larger value for every element of a linked list.
11 * @param head - The head of the linked list.
12 * @returns An array of next larger values.
13 */
14function nextLargerNodes(head: ListNode | null): number[] {
15 // Initialize an array to hold the list node values.
16 const valuesArray: number[] = [];
17
18 // Traverse the linked list and populate the valuesArray with node values.
19 while (head !== null) {
20 valuesArray.push(head.val);
21 head = head.next;
22 }
23
24 // Initialize a stack to keep track of the larger values.
25 const stack: number[] = [];
26 const length = valuesArray.length;
27 // Initialize an array to hold the answer.
28 const nextLargerValues: number[] = new Array(length).fill(0);
29
30 // Iterate the valuesArray from the end to the beginning.
31 for (let i = length - 1; i >= 0; i--) {
32 // Pop elements from the stack that are less than or equal to the current value.
33 while (stack.length > 0 && stack[stack.length - 1] <= valuesArray[i]) {
34 stack.pop();
35 }
36
37 // If stack is not empty, the top will be the next larger value.
38 nextLargerValues[i] = stack.length > 0 ? stack[stack.length - 1] : 0;
39
40 // Push the current value onto the stack.
41 stack.push(valuesArray[i]);
42 }
43
44 // Return the array of next larger values.
45 return nextLargerValues;
46}
47
Time and Space Complexity
Time Complexity
The time complexity of the given code involves iterating over all the elements of the linked list, followed by a single reverse iteration over the list of node values while maintaining a stack to keep track of the next larger node values. Specifically:
-
Converting the linked list to an array: We iterate over the linked list once, which has
n
elements, so this portion of the algorithm isO(n)
. -
Next Larger Elements using a Stack: In the worst case, every element is pushed to and popped from the stack once. Because each element is handled twice (once for push and once for pop) in this process, and these are constant-time operations (assuming the stack's
append
andpop
operations areO(1)
), the complexity isO(2n)
, which simplifies toO(n)
.
Combined, since both operations are sequential and not nested, the overall time complexity is O(n) + O(n)
which simplifies to O(n)
.
Space Complexity
The space complexity of the algorithm arises from the space needed to store the list of values and the stack, in addition to the list used for the output:
-
Array of node values: The array to store node values has the same size as the number of nodes in the linked list, therefore the space complexity is
O(n)
. -
Stack: In the worst case, the stack might contain all
n
elements at the same time if the sequence is monotonically decreasing, which gives us a space complexity ofO(n)
for the stack. -
Output list: An output list of size
n
is used to store the answer, resulting in a space complexity ofO(n)
.
When we sum these up, we get O(n) + O(n) + O(n)
which simplifies to O(n)
overall space complexity as constants are dropped in big O notation.
Learn more about how to find time and space complexity quickly using problem constraints.
Which data structure is used to implement recursion?
Recommended Readings
Stack Intro Imagine you have a pile of books on your desk If you want to add a new book you place it on top If you want to read a book you take it from the top And if you simply want to see which book is on the top you
Linked List Cycle Given a linked list with potentially a loop determine whether the linked list from the first node contains a cycle in it For bonus points do this with constant space Parameters nodes The first node of a linked list with potentially a loop Result Whether there is a loop contained
Monotonic Stack Deque Intro If you'd prefer a video div class responsive iframe iframe src https www youtube com embed Dq_ObZwTY_Q title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div The word monotonic means a list or
Want a Structured Path to Master System Design Too? Don’t Miss This!