We can get the level-maximum by a level order traversal / Breadth-first search. The idea is that we have a list/queue of nodes on one level. For all nodes in this list the algorithm does two things:
- It calculates the maximum value on this level.
- It iterates over all nodes of the list/queue, gets all children of those nodes and put them in a new list/queue, which it can then process in the next iteration.
The algorithm starts with a list/queue holding the root of the (sub)-tree and ends when the list/queue is empty.
This can be expressed nicely with Stream
operations:
public static List<Integer> getMaxValuePerLevel(Node node) {
final ArrayList<Integer> maxPerLevel = new ArrayList();
maxPerLevel.add(node.getValue());
List<Node> children = node.getChildren();
while (!children.isEmpty()) {
maxPerLevel.add(children.stream()
.mapToInt(Node::getValue)
.max()
.getAsInt());
children = children.stream()
.map(Node::getChildren)
.flatMap(List::stream)
.collect(Collectors.toList());
}
return maxPerLevel;
}
Ideone demo
This implementation has two nice properties:
- It is iterative, not recursive, i.e. the algorithm is not subject to a
StackOverflowError
- It has linear time- and memory complexity
With a little bit of effort, we are even able to make the algorithm work with generic Node<T extends Comparable<T>>
:
public static <T extends Comparable<T>> List<T> getMaxValuePerLevel(Node<T> node) {
final ArrayList<T> maxPerLevel = new ArrayList<>();
maxPerLevel.add(node.getValue());
List<Node<T>> children = node.getChildren();
while (!children.isEmpty()) {
final Node<T> defaultNode = children.get(0);
maxPerLevel.add(children.stream()
.map(Node::getValue)
.max(Comparator.naturalOrder())
.orElseGet(defaultNode::getValue));
children = children.stream()
.map(Node::getChildren)
.flatMap(List::stream)
.collect(Collectors.toList());
}
return maxPerLevel;
}
Ideone demo