LeetCode 1019 - Next Greater Node In Linked List
The problem asks us to process a singly-linked list and, for each node, determine the value of the next node that has a strictly larger value.
Difficulty: 🟡 Medium
Topics: Array, Linked List, Stack, Monotonic Stack
Solution
Problem Understanding
The problem asks us to process a singly-linked list and, for each node, determine the value of the next node that has a strictly larger value. Concretely, if the list is [2,1,5], for the first node 2, the next larger node is 5; for the second node 1, the next larger node is also 5; for the last node 5, there is no larger node after it, so the answer is 0. The output is an array of integers where each element corresponds to a node in the original linked list, in the same order as the nodes appear.
The input head is the starting node of a linked list with n nodes, where 1 <= n <= 10^4 and each node's value lies between 1 and 10^9. The constraints imply that the solution must be efficient in both time and space; a naive nested loop approach that checks every node against every subsequent node would be too slow for the upper bound of 10,000 nodes.
Key edge cases include a list with only one node, a list where all nodes are strictly decreasing, or a list where all nodes have the same value. The problem guarantees at least one node in the list, so we do not need to handle an empty list.
Approaches
The brute-force approach iterates over each node in the linked list and, for each node, scans all subsequent nodes to find the first node with a larger value. This approach is correct because it explicitly checks every node that could satisfy the "next greater node" condition. However, it requires O(n^2) time, which is prohibitive for large inputs (up to 10,000 nodes).
The optimal approach leverages the concept of a monotonic stack, which is a stack that keeps track of nodes in decreasing order of value. As we iterate over the linked list, we can maintain a stack of indices for which the next greater node has not been found yet. When we encounter a node with a value larger than the value at the top of the stack, we know we have found the next greater node for that index, and we pop it from the stack and update the answer array. This ensures that each node is pushed and popped at most once, achieving O(n) time complexity.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n^2) | O(n) | Iterates over each node and checks all subsequent nodes |
| Optimal | O(n) | O(n) | Uses monotonic stack to efficiently track nodes without next greater value |
Algorithm Walkthrough
- Initialize an empty list
valuesand traverse the linked list fromhead, appending each node's value tovalues. This transforms the linked list into an array to facilitate index-based access. - Create an array
answerof the same length asvalues, initialized with zeros. This will hold the final result. - Initialize an empty stack
stackto keep indices ofvaluesfor which the next greater node has not yet been found. - Iterate over
valueswith indexiand valueval. For eachval, compare it with the values corresponding to indices on the top ofstack. - While the stack is not empty and
valis greater thanvalues[stack[-1]], pop the index from the stack and setanswer[index] = val. This captures the fact thatvalis the next greater node for the popped index. - After processing the stack, push the current index
iontostack. - Once the iteration completes, any indices left in
stackhave no greater node following them, and their correspondinganswervalues remain0. - Return
answer.
Why it works: The stack invariant is that indices in the stack always correspond to values that have not yet found their next greater node, and their corresponding values are in decreasing order. Each value is processed in constant time with respect to stack operations, guaranteeing that the solution is linear in time.
Python Solution
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
from typing import List, Optional
class Solution:
def nextLargerNodes(self, head: Optional[ListNode]) -> List[int]:
values = []
current = head
while current:
values.append(current.val)
current = current.next
answer = [0] * len(values)
stack = []
for i, val in enumerate(values):
while stack and val > values[stack[-1]]:
index = stack.pop()
answer[index] = val
stack.append(i)
return answer
The Python implementation first converts the linked list to a simple array for easier index manipulation. The stack stores indices of values that haven't yet found a next greater node. For each value, we pop indices from the stack when the current value is greater, ensuring we assign the next greater value correctly. Remaining indices in the stack correspond to nodes with no next greater node, which is why answer is initialized to zero.
Go Solution
/**
* Definition for singly-linked list.
* type ListNode struct {
* Val int
* Next *ListNode
* }
*/
func nextLargerNodes(head *ListNode) []int {
var values []int
for current := head; current != nil; current = current.Next {
values = append(values, current.Val)
}
answer := make([]int, len(values))
var stack []int // store indices
for i, val := range values {
for len(stack) > 0 && val > values[stack[len(stack)-1]] {
index := stack[len(stack)-1]
stack = stack[:len(stack)-1]
answer[index] = val
}
stack = append(stack, i)
}
return answer
}
The Go implementation mirrors the Python version. Since Go does not have dynamic arrays in the same way, we use slices for values and stack. Nil checks are not necessary for the loop because the linked list is guaranteed to have at least one node. Integer overflow is not a concern due to the constraints, and slice manipulation handles the stack operations efficiently.
Worked Examples
Example 1: head = [2,1,5]
| i | val | stack | answer |
|---|---|---|---|
| 0 | 2 | [0] | [0,0,0] |
| 1 | 1 | [0,1] | [0,0,0] |
| 2 | 5 | pop 1 → answer[1]=5, pop 0 → answer[0]=5 | answer=[5,5,0], stack=[2] |
Example 2: head = [2,7,4,3,5]
| i | val | stack | answer |
|---|---|---|---|
| 0 | 2 | [0] | [0,0,0,0,0] |
| 1 | 7 | pop 0 → answer[0]=7 | answer=[7,0,0,0,0], stack=[1] |
| 2 | 4 | [1,2] | [7,0,0,0,0] |
| 3 | 3 | [1,2,3] | [7,0,0,0,0] |
| 4 | 5 | pop 3 → answer[3]=5, pop 2 → answer[2]=5 | answer=[7,0,5,5,0], stack=[4] |
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n) | Each node is pushed and popped at most once on the stack; traversing the linked list is O(n) |
| Space | O(n) | Storing node values in an array and stack of indices, each up to n elements |
The algorithm is linear because each node is processed exactly twice in the worst case (once when pushed, once when popped).
Test Cases
# Provided examples
assert Solution().nextLargerNodes(ListNode(2, ListNode(1, ListNode(5)))) == [5,5,0] # simple increasing
assert Solution().nextLargerNodes(ListNode(2, ListNode(7, ListNode(4, ListNode(3, ListNode(5)))))) == [7,0,5,5,0] # mixed pattern
# Edge cases
assert Solution().nextLargerNodes(ListNode(1)) == [0] # single node
assert Solution().nextLargerNodes(ListNode(5, ListNode(4, ListNode(3, ListNode(2, ListNode(1)))))) == [0,0,0,0,0] # strictly decreasing
assert Solution().nextLargerNodes(ListNode(1, ListNode(1, ListNode(1, ListNode(1))))) == [0,0,0,0] # all same values
# Larger test case
assert Solution().nextLargerNodes(ListNode(1, ListNode(3, ListNode(2, ListNode(5, ListNode(4)))))) == [3,5,5,0,0] # mixed increases and decreases
| Test | Why |
|