Skip to content

Instantly share code, notes, and snippets.

@OleksandrKucherenko
Last active October 20, 2022 13:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save OleksandrKucherenko/cc03eb23d50cea6048e929ca38fec822 to your computer and use it in GitHub Desktop.
Save OleksandrKucherenko/cc03eb23d50cea6048e929ca38fec822 to your computer and use it in GitHub Desktop.
// Here's a sample binary tree node class:
open class BinaryTreeNode constructor(var value: Int) {
var left: BinaryTreeNode = Empty
protected set
var right: BinaryTreeNode = Empty
protected set
fun insertLeft(leftValue: Int): BinaryTreeNode {
check(leftValue >= 0) { "Expected only positive values" }
this.left = BinaryTreeNode(leftValue)
this.value = selectMinOrNotFound(left.value, right.value)
return this.left
}
fun insertRight(rightValue: Int): BinaryTreeNode {
check(rightValue >= 0) { "Expected only positive values" }
this.right = BinaryTreeNode(rightValue)
this.value = selectMinOrNotFound(left.value, right.value)
return this.right
}
override fun toString(): String {
return "$value [${left.value}, ${right.value}]"
}
override fun hashCode(): Int = value.hashCode()
override fun equals(other: Any?): Boolean = (other is BinaryTreeNode) && value == other.value
fun findSecondMin(minOrAvoid: BinaryTreeNode = this): Int = when {
isAnyEmpty() -> value.takeIf { it != minOrAvoid.value } ?: NotFound
isInRange(minOrAvoid, maxNode()) -> value
else -> selectMinOrNotFound(left.findSecondMin(minOrAvoid), right.findSecondMin(minOrAvoid))
}
companion object {
const val NotFound: Int = -1;
val Empty = object : BinaryTreeNode(NotFound) {
init {
this.left = this;
this.right = this;
}
}
}
}
operator fun BinaryTreeNode.compareTo(other: BinaryTreeNode): Int = this.value.compareTo(other.value)
fun BinaryTreeNode.isAnyEmpty(): Boolean {
return (this == BinaryTreeNode.Empty || this.left === BinaryTreeNode.Empty || this.right === BinaryTreeNode.Empty)
}
fun BinaryTreeNode.isInRange(min: BinaryTreeNode, max: BinaryTreeNode): Boolean = (min < this && this < max)
fun BinaryTreeNode.maxNode(): BinaryTreeNode = if (left > right) left else right
fun selectMinOrNotFound(first: Int, second: Int): Int =
arrayOf(first, second).filter { it > BinaryTreeNode.NotFound }.sorted()
.elementAtOrElse(0) { BinaryTreeNode.NotFound }
/** ref: https://stackoverflow.com/questions/4965335/how-to-print-binary-tree-diagram-in-java */
fun BinaryTreeNode.printTree(prefix: String = "", isLeft: Boolean = false): String {
if(this == BinaryTreeNode.Empty) return "";
/* box symbols - ref: https://gist.github.com/dsample/79a97f38bf956f37a0f99ace9df367b9 */
val (nodeLine, connectorLine) = (if (isLeft) "├──" to "│ " else "└──" to " ")
val nodes = "${prefix}$nodeLine"
val branches = "${prefix}$connectorLine "
return "$nodes $this\n" + left.printTree(branches, true) + right.printTree(branches)
}
/* ------------------------------------------ */
/** Data for testing. */
object Data {
val simple1 = BinaryTreeNode(2)
val simple2 = BinaryTreeNode(2).apply { insertLeft(2); insertRight(5) };
val example1 = BinaryTreeNode(2).apply {
insertLeft(2)
insertRight(5).apply {
insertLeft(5)
insertRight(7)
}
}
val example2 = BinaryTreeNode(2).apply {
insertLeft(2)
insertRight(2)
}
val example3 = BinaryTreeNode(2).apply {
insertLeft(2).apply {
insertLeft(2)
insertRight(5)
}
insertRight(3).apply {
insertLeft(3)
insertRight(5)
}
}
val example4 = BinaryTreeNode(2).apply {
insertLeft(2).apply {
insertLeft(2).apply {
insertLeft(2)
insertRight(4)
}
insertRight(5)
}
insertRight(2).apply {
insertLeft(2).apply {
insertLeft(2)
insertRight(3)
}
insertRight(5)
}
}
}
/** Verify function. */
fun verify(function: (BinaryTreeNode) -> Int) {
check(-1 == function(Data.simple1)) { "expected -1 for ${Data.simple1.printTree()}" }
check(5 == function(Data.simple2)) { "expected 5 for ${Data.simple2.printTree()}" }
check(5 == function(Data.example1)) { "expected 5 for ${Data.example1.printTree()}" }
check(-1 == function(Data.example2)) { "expected -1 for ${Data.example2.printTree()}" }
check(3 == function(Data.example3)) { "expected 3 for ${Data.example3.printTree()}" }
check(3 == function(Data.example4)) { "expected 3 for ${Data.example4.printTree()}" }
}
fun main() {
verify { println(it.printTree()); it.findSecondMin() }
}
@OleksandrKucherenko
Copy link
Author

Instructions:

Given a non-empty special binary tree consisting of nodes with the non-negative value, where each node in this tree has exactly two or zero sub-node. If the node has two sub-nodes, then this node’s value is the smaller value among its two sub-nodes. More formally, the property root.val = min(root.left.val, root.right.val) always holds.

Given such a binary tree, you need to output the second minimum value in the set made of all the nodes’ value in the whole tree.

If no such second minimum value exists, output -1 instead.

Example 1:

Input: root = [2,2,5,null,null,5,7] Output: 5 Explanation: The smallest value is 2, the second smallest value is 5. Example 2:

Input: root = [2,2,2] Output: -1 Explanation: The smallest value is 2, but there isn’t any second smallest value.

Constraints:

The number of nodes in the tree is in the range [1, 25]. 1 <= Node.val <= 231 - 1 root.val == min(root.left.val, root.right.val) for each internal node of the tree

@OleksandrKucherenko
Copy link
Author

OleksandrKucherenko commented Oct 19, 2022

Pros/Cons:

+ introduced Empty & NotFound constants
+ introduced private constructor that protects us from NULL's
+ added debug functions for easier understanding the node values and hierarchy of the tree
+ override of `==`operator so code is easier to read now
+ override of `compareTo` operator, so expressions are easier to read
+ introduced some extension functions that helps to make code cleaner
+ added tests for verifying that function solves all as expected
+ added ability to run different versions of function and measure the performance
+ second min value never left the scope of function 
+  ^  (usual implementations have "global" variable, which is not thread safe)

Cons:

- BinaryTreeNode does not re-evalute own value on left/right side changes
- recursive code, not extra good for large binary tree's
- possible to compose wrong data structure, where left, right and value does not match each other.

You are welcome to suggest the improvements.

@OleksandrKucherenko
Copy link
Author

OleksandrKucherenko commented Oct 19, 2022

Version 1:

fun findSecondMinimumValue(root: BinaryTreeNode, parentMin: BinaryTreeNode = root): Int {
    // avoid wrong data structure (No-NULLs)
    val left = root.left!!
    val right = root.right!!
    val higher = if (left.value > right.value) left else right

    return if (root.isEmpty()) {
        root.valueOrElseOnEquals(parentMin) { BinaryTreeNode.NotFound }
    } else if (parentMin == left || parentMin == right) { // parent and child have the same value
        // (OR both sides have the same value)
        // expectations: parentMin == 2 && [4, 5], [-1, 5], [-1, -1], [4, -1]
        arrayOf(
            findSecondMinimumValue(left, parentMin), findSecondMinimumValue(right, parentMin)
        )
        .filter { it > BinaryTreeNode.NotFound }
        .sorted() // find min in array
        .elementAtOrElse(0) { BinaryTreeNode.NotFound }
    } else if (parentMin < root && root < higher) { // other values are higher found minimum
        root.value
    } else { // continue search
        findSecondMinimumValue(higher, parentMin)
    }
}

@OleksandrKucherenko
Copy link
Author

OleksandrKucherenko commented Oct 20, 2022

Version 3:

fun secondMinV3(root: BinaryTreeNode, parentMin: BinaryTreeNode = root): Int {
    val left = root.left!!
    val right = root.right!!
    val higher = if (left.value > right.value) left else right

    return when {
        root.isEmpty() -> arrayOf(root.valueOrElseOnEquals(parentMin) { BinaryTreeNode.NotFound })
        root.hasValueOf(parentMin) -> arrayOf(secondMinV3(left, parentMin), secondMinV3(right, parentMin))
        root.inRange(parentMin, higher) -> arrayOf(root.value)
        else -> arrayOf(secondMinV3(higher, parentMin))
    }.filter { it > BinaryTreeNode.NotFound }.sorted().elementAtOrElse(0) { BinaryTreeNode.NotFound }
}

removed NULLs:

fun secondMinV3(root: BinaryTreeNode, parentMin: BinaryTreeNode = root): Int {
    val higher = if (root.left.value > root.right.value) root.left else root.right

    return when {
        root.isEmpty() -> arrayOf(root.onEqualsDoElse(parentMin) { BinaryTreeNode.NotFound })
        root.hasValueOf(parentMin) -> arrayOf(secondMinV3(root.left, parentMin), secondMinV3(root.right, parentMin))
        root.inRange(parentMin, higher) -> arrayOf(root.value)
        else -> arrayOf(secondMinV3(higher, parentMin))
    }.filter { it > BinaryTreeNode.NotFound }.sorted().elementAtOrElse(0) { BinaryTreeNode.NotFound }
}

@OleksandrKucherenko
Copy link
Author

Version 5:

class BinaryTreeNode { 
  /* ... */
    fun findSecondMin(min: BinaryTreeNode = this): Int = when {
        isEmpty() -> valueOrElse(min) { NotFound }
        isInRange(min, maxNode()) -> value
        hasNodeWithValue(min) -> result(left.findSecondMin(min), right.findSecondMin(min))
        else -> throw RuntimeException("Should never happens")
    }
  /* ... */
}

@OleksandrKucherenko
Copy link
Author

OleksandrKucherenko commented Oct 20, 2022

Version 6: better naming

class BinaryTreeNode { 
  /* ... */
    fun findSecondMin(minOrAvoid: BinaryTreeNode = this): Int = when {
        isAnyEmpty() -> value.takeIf { it != minOrAvoid.value } ?: NotFound
        isInRange(minOrAvoid, maxNode()) -> value
        else -> selectMinOrNotFound(left.findSecondMin(minOrAvoid), right.findSecondMin(minOrAvoid))
    }
  /* ... */
}

@OleksandrKucherenko
Copy link
Author

Samples/Testing Data:

└── 2 [-1, -1]

└── 2 [2, 5]
    ├── 2 [-1, -1]
    └── 5 [-1, -1]

└── 2 [2, 5]
    ├── 2 [-1, -1]
    └── 5 [5, 7]
        ├── 5 [-1, -1]
        └── 7 [-1, -1]

└── 2 [2, 2]
    ├── 2 [-1, -1]
    └── 2 [-1, -1]

└── 2 [2, 3]
    ├── 2 [2, 5]
    │   ├── 2 [-1, -1]
    │   └── 5 [-1, -1]
    └── 3 [3, 5]
        ├── 3 [-1, -1]
        └── 5 [-1, -1]

└── 2 [2, 2]
    ├── 2 [2, 5]
    │   ├── 2 [2, 4]
    │   │   ├── 2 [-1, -1]
    │   │   └── 4 [-1, -1]
    │   └── 5 [-1, -1]
    └── 2 [2, 5]
        ├── 2 [2, 3]
        │   ├── 2 [-1, -1]
        │   └── 3 [-1, -1]
        └── 5 [-1, -1]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment