Woodstock Blog

a tech blog for general algorithmic interview questions

[Design] Cache and Page Replacement Algorithms

Cache Algorithms

This post is originally written for Cache Algo only, before I found out this 2 topics are very similar, so I changed the title to “Cache and Page Replacement Algorithms”.

Equation

of memory reference time is:

T = m * T(m) + T(h) + E

m: miss ratio = 1 - hit ratio

T(m): time for main memory access

T(h): latency (when there’s a hit)

E: secondary effects liek queuing effects etc.

hit ratio

how often a searched-for item is actually found in the cache

latency

how long after requesting a desired item the cache can return that item (when there is a hit).

Replacement policy

Bélády’s Algorithm (Optimal Algorithm)

The optimal algorithm, not implementable in practise.

LFU

Least Frequently Used, slow and not very adaptive.

LRU

Fast and adaptive, but hard to implement.

It can be implemented with either a counter or a stack/doubleLinkedList.

Web browser use this.

LRU2 and 2Qs

LRU2 - Only add entries to the cache the second time they are accessed.

Two Queues - Add entries to an normal LRU cache. If accessed again, move it to second, larger, LRU cache.

MRU (most recent used)

Some claim that MRU cache algorithms have more hits than LRU due to their tendency to retain older data.

FIFO

Low-overhead, fast but not adaptive.

Second-chance

Modified version of FIFO. Relatively better than FIFO at little cost.

Initially, a reference bit is set. Instead of removing old entries, it clears reference bit first, and insert that entry at the back of the queue. An entry is only cleared if the reference bit is not set. This is like a circular queue.

If all the pages have been referenced, second chance degenerates into pure FIFO. Why? Let’s say all entries reference are set, the pointer will go around the entire list and clear all references, and in the end come back to the starting point. Then, it’s like a FIFO. For more, see the link.

Clock

Modified version of second-hand. Better than second hand. Instead of pushing to the back, it keep a “hand” pointer in the circular list. link

When cache miss occurs and no empty place exists, then I consult the R (referenced) bit at the hand’s location to know what I should do. If R is 0, then I will place the new entry at the “hand” position, otherwise I will clear the R bit. Then, I will increment the hand (iterator) and repeat the process until an entry is replaced.

Simple time-based

Fast, but not adaptive. Entries remain in cache for a specific amount of time.

Extended time-based expiration

Only clear cache at certain points in time (say every 5 minutes).

Conclusion

Each replacement strategy is a compromise between hit rate and latency.

One more thing

What is Distributed cache?

Distributed cache is an extension of the traditional concept of cache used in a single locale.

A distributed cache may span multiple servers so that it can grow in size and in transactional capacity. It is mainly used to store application data residing in database and web session data.

The idea of distributed caching has become feasible now because main memory has become very cheap and network cards have become very fast (speed of 1 Gbit is now standard, and 10 Gbit is gaining traction).

Also, a distributed cache works well on lower cost machines usually employed for web servers as opposed to database servers which require expensive hardware.

[Brain Teaser] 2 Eggs 100 Floors Puzzle

Question

link

You are given 2 eggs.

You have access to a 100-storey building.

Eggs can be very hard or very fragile means it may break if dropped from the first floor or may not even break if dropped from 100 th floor.Both eggs are identical.

You need to figure out the highest floor of a 100-storey building an egg can be dropped without breaking.

Now the question is how many drops you need to make. You are allowed to break 2 eggs in the process

Analysis

Most obvious solutoin is drop in 10th, 20th, 30th … floor. But this solution would result in 19 drops in worst case. We should try to reduce the worst case scenario by making all possible scenarios take the same number of drops!

The best solution is:

What if we tried to reduce the number of drops that would be required with the linear search (with the 2nd egg) after we get to one of the higher floors? This way we counteract the fact that getting to the higher floor took so many drops, and if we use less drops for the linear search we are balancing out the worst case.

According to the solution, we form a series:

x + (x-1) + (x-2) + (x-3) + … + 1 = 100

x(x+1)/2 = 100

x = 14

Final Result

We would drop in this way:

 Drop  Floor 
#114
#227
#339
#450
#560
#669
#777
#884
#990
#1095
#1199
#12100

[Question] Number Sum Sequence

Question

link

定义一个数字有以下的特征, 它可以被分成几个部分,而前面的部分相加起来的和是后面的部分.举例来说

1235813, 1+2=3; 2+3=5;3+5=8; 5+8=13;

112112224, 112+112=224;

1981100, 19+81=100;

122436, 12+24=36;

1299111210, 12+99=111,99+111=210;

要求写出一个函数,输入一个数字,判断这个数字是不是要求的这个数。

Analysis

This is a difficult DFS search question. Basic idea from this blog:

取前i位为a1,第i+1到j位为a2,检测n是否由a1和a2生成的序列组成。

时间复杂度为O(n3)(或O(n2)).

Code

The code is surprisingly very short.

I have yet to prove whether this code works.

def isLegal(n, i, j):
    """取前i位为a1,第i+1到j位为a2,检测n是否由a1和a2生成的序列组成。"""
    nextDigit = j
    a1 = int(n[:i])
    a2 = int(n[i:j])
    while nextDigit<len(n): # n还有数字可用
        a2, a1 = a1+a2, a2
        s2 = str(a2)
        if n.startswith(s2, nextDigit): #从n的第nextDigit位开始,与s2比较
            nextDigit += len(s2)
        else:
            return False
    return True

def test(n):
    for i in range(1, len(n)-1):
        for j in range(i+1, len(n)-1):
            if isLegal(n, i, j):
                return True
    return False

[NineChap 4.2] Linked List Additional

Question list

  1. Union and Intersection of two Linked Lists

  2. Insertion Sort List - difficult

  3. Flatten Binary Tree to Linked List

  4. Convert Sorted List to Binary Search Tree

  5. Rotate List

  6. Remove Nth Node From End of List

  7. LRU Cache

  8. Reverse Nodes in k-Groups

  9. Swap Nodes in Pairs

Code

Union and Intersection of two Linked Lists

Think about the idea only.

Insertion Sort List

public ListNode insertionSortList(ListNode head) {
    if (head == null) {
        return null;
    }
    ListNode dummy = new ListNode(0);
    ListNode cur = head;
    while (cur != null) {
        // insert cur into correct pos
        ListNode pos = dummy;
        while (pos.next != null && pos.next.val < cur.val) {
            pos = pos.next;
        }
        ListNode temp = cur.next;
        cur.next = pos.next;
        pos.next = cur;
        cur = temp;
    }
    return dummy.next;
}

Flatten Binary Tree to Linked List

I forgot to set “root.left = null” again, which result in long-time debugging. This is a very common and very silly mistake that I really should avoid.

public void flatten(TreeNode root) {
    root = helper(root);
}

private TreeNode helper(TreeNode node) {
    if (node == null) {
        return null;
    } else if (node.left == null && node.right == null) {
        return node;
    } else if (node.left == null) {
        return helper(node.right);
    } else if (node.right == null) {
        node.right = node.left;
        node.left = null;
        return helper(node.right);
    } else {
        TreeNode tempRight = node.right;
        node.right = node.left;
        node.left = null;
        TreeNode leftTail = helper(node.right);
        leftTail.right = tempRight;
        return helper(tempRight);
    }
}

Convert Sorted List to Binary Search Tree

This is the Mock Interview question. My solution is:

public TreeNode sortedListToBST(ListNode listHead) {
    if (listHead == null) {
        return null;
    }
    if (listHead.next == null) {
        return new TreeNode(listHead.val);
    }
    ListNode listFirstHalf = listHead;
    ListNode listPreMid = findMiddle(listHead);
    ListNode listSecondHalf = null;
    if (listPreMid.next != null) {
        listSecondHalf = listPreMid.next.next;
    }
    TreeNode head = new TreeNode(listPreMid.next.val);
    listPreMid.next = null;
    head.left = sortedListToBST(listFirstHalf);
    head.right = sortedListToBST(listSecondHalf);
    return head;
}

private ListNode findMiddle(ListNode listHead) {
    if (listHead == null) {
        return null;
    }
    ListNode slow = listHead;
    ListNode fast = listHead.next;
    while (fast != null && fast.next != null&& fast.next.next != null) {
        slow = slow.next;
        fast = fast.next.next;
    }
    return slow;
}

This is not a good answer, cuz I have to findMid in each recursion.

The best solution is, use a global variable and 2 numbers to simplify this process. Code:

ListNode cur = null;

public TreeNode sortedListToBST(ListNode head) {
    if (head == null) {
        return null;
    }
    cur = head;
    int k = 0;
    ListNode p = head;
    while (p != null) {
        k++;
        p = p.next;
    }
    return build(0, k - 1);
}

private TreeNode build(int start, int end) {
    if (start > end) {
        return null;
    }
    int mid = start + (end - start) / 2;
    TreeNode leftBranch = build(start, mid - 1);
    TreeNode head = new TreeNode(cur.val);
    cur = cur.next;
    head.left = leftBranch;
    head.right = build(mid + 1, end);
    return head;
}

Rotate List

Naive solution:

public ListNode rotateRight(ListNode head, int n) {
    if (head == null) {
        return null;
    }
    ListNode p = head;
    for (int i = 0; i < n; i++) {
        if (p.next == null) {
            p = head;
        } else {
            p = p.next;
        }
    }
    ListNode q = head;
    while (p.next != null) {
        p = p.next;
        q = q.next;
    }
    p.next = head;
    ListNode newHead = q.next;
    q.next = null;
    return newHead;
}

Make a circular list:

public ListNode rotateRight(ListNode head, int n) {
    if (head == null) {
        return null;
    }
    ListNode p = head;
    int k = 1;
    while (p.next != null) {
        k++;
        p = p.next;
    }
    p.next = head;
    int steps = k - (n % k);
    for (int i = 0; i < steps; i++) {
        p = p.next;
    }
    head = p.next;
    p.next = null;
    return head;
}

Remove Nth Node From End of List

public ListNode removeNthFromEnd(ListNode head, int n) {
    if (head == null || n == 0) {
        return null;
    }
    ListNode dummy = new ListNode(0);
    dummy.next = head;
    ListNode right = dummy;
    for (int i = 0; i < n; i++) {
        right = right.next;
    }
    ListNode left = dummy;
    while (right.next != null) {
        left = left.next;
        right = right.next;
    }
    left.next = left.next.next;
    return dummy.next;
}

LRU Cache

I solved it in the original post.

Reverse Nodes in k-Groups

public ListNode reverseKGroup(ListNode head, int k) {
    ListNode p = head;
    int count = 0;
    while (p != null) {
        p = p.next;
        count++;
    }
    return helper(head, k, count);
}

public ListNode helper(ListNode head, int k, int count) {
    if (head == null || k < 1 || count < k) {
        return head;
    }
    ListNode result = null;
    ListNode cur = head;
    for (int i = 0; i < k; i++) {
        if (cur == null) break;
        ListNode temp = cur.next;
        cur.next = result;
        result = cur;
        cur = temp;
    }
    head.next = helper(cur, k, count - k);
    return result;
}

Swap Nodes in Pairs

public ListNode swapPairs(ListNode head) {
    if (head == null || head.next == null) {
        return head;
    }
    ListNode result = head.next;
    ListNode temp = head.next.next;
    result.next = head;
    head.next = swapPairs(temp);
    return result;
}

[Question] Union and Intersection of Two Linked Lists

Question

link

Given two Linked Lists, create union and intersection lists that contain union and intersection of the elements present in the given lists. Order of elments in output lists doesn’t matter.

Example:

Input: “10->15->4->20” and “8->4->2->10”

Intersection: 4->10

Union: 2->8->20->4->15->10

Analysis

There are 2 solutions.

First solution is to do mergesort, then do a linear search. Time complexity is O(mlgm + nlgn).

Second solution is using hashing. On time complexity:

Time complexity of this method depends on the hashing technique used and the distribution of elements in input lists. In practical, this approach may turn out to be better than above method.

[Design] Time Complexity Calculation (Master Theorem)

Master theorem

In the analysis of algorithms, the master theorem provides a cookbook solution in asymptotic terms (using Big O notation) for recurrence relations that occur in many divide and conquer algorithms. It was introduced and popularized by Introduction to Algorithms.

Examples with common algorithms

Algorithm Recurrence Big-Oh Solution
Binary Search T(n) = T(n/2) + O(1) O(log n)
tree traversal T(n) = 2 T(n/2) + O(1) O(n)
Mergesort T(n) = 2 T(n/2) + O(n) O(n log n)

source

[LeetCode Plus] Reverse Linked List Iteratively and Recursively

Question

link

Implement the reversal of a singly linked list iteratively and recursively.

Iteratively

First, the iterative solution is very common, and is listed as one of the “5 fundamental operations of linked list” in the NineChap4 post. I will quote below.

First variant: Reverse from a particular node to the end

public ListNode reverse(ListNode start) {
    ListNode result = null;
    ListNode cur = start;
    while (cur != null) {
        ListNode temp = cur.next;
        cur.next = result;
        result = cur;
        cur = temp;
    }
    return result;
}

Second variant: Reverse from a node until another node

public ListNode reverseRange(ListNode start, int len) {
    ListNode result = null;
    ListNode cur = start;
    while (len > 0) {
        ListNode temp = cur.next;
        cur.next = result;
        result = cur;
        cur = temp;
        len--;
    }
    start.next = cur;
    return result;
}

Recursively

A good solution suggest by this answer:

public ListNode reverseRecursively(ListNode head) {
    if (head == null || head.next == null) {
        return head;
    }
    ListNode temp = head.next;
    // temp is not NULL
    ListNode newHead = reverseRecursively(temp);
    temp.next = head;
    head.next = null;
    return newHead;
}

[NineChap 4.1] Linked List

First Word

LinkedList aims to test one of the most important concepts in C++, pointers.

Unlike array, linked list does not have ‘in-place’ operations. This is very important to understand.

Type 1: Dummy Node

When the head is not determined, use DummyHead.

Note that when using DummyHead to solve problems, the pointer starts from DummyHead. By doing this, we assuming that DummyHead must be valid, and we only check pointer.next (instead of checking pointer itself). See ‘Remove Duplicates from Sorted List II’ for details.

Type 2: Five Basic Operations in Linked List

  1. Insert in Sorted List
  2. Remove in Sorted List
  3. Reverse a list
  4. Merge 2 Sorted List
  5. Find middle

1.Insert in Sorted List

public ListNode insert(ListNode head, ListNode node) {
    // first, initialize
    ListNode dummy = new ListNode(Integer.MIN_VALUE);
    dummy.next = head;

    // second, assume p is less than node, and check p.next
    ListNode p = dummy;
    while (p.next != null && p.next.val < node.val) {
        p = p.next;
    }

    // insert node after 'p'
    node.next = p.next;
    p.next = node;
    return dummy.next;
}

2.Remove in Sorted List

(written by me)

public ListNode remove(ListNode head, int value) {
    ListNode dummy = new ListNode(0);
    dummy.next = head;
    ListNode p = dummy;
    while (p.next != null && p.next.val != value) {
        p = p.next;
    }
    if (p.next != null && p.next.val == value) {
        while (p.next != null && p.next.val == value)
            p.next = p.next.next;
    }
    return dummy.next;
}

3.Reverse a list

First variant: Reverse from a particular node to the end.

四句话 statement.

public ListNode reverse(ListNode start) {
    ListNode result = null;
    ListNode cur = start;
    while (cur != null) {
        ListNode temp = cur.next;
        cur.next = result;
        result = cur;
        cur = temp;
    }
    return result;
}

Second variant: Reverse from a node until another node

// Given 1->2->3->4->5->NULL, start = 2 and len = 3,
// return 1->4->3->2->5->NULL. 
public ListNode reverseRange(ListNode start, int len) {
    ListNode result = null;
    ListNode cur = start;
    while (len > 0) {
        ListNode temp = cur.next;
        cur.next = result;
        result = cur;
        cur = temp;
        len--;
    }
    start.next = cur;
    return result;
}

The comparison:

More: there is a way to reverse list recursively. This can be another good interview question. Reverse linkedlist recursively

4.Merge 2 Sorted List

public ListNode merge(ListNode head1, ListNode head2) {
    ListNode dummy = new ListNode(0);
    ListNode p = dummy;
    while (head1 != null && head2 != null) {
        if (head1.val < head2.val) {
            p.next = head1;
            head1 = head1.next;
            p = p.next;
        } else {
            p.next = head2;
            head2 = head2.next;
            p = p.next;
        }
    }
    if (head1 == null) {
        p.next = head2;
    } else {
        p.next = head1;
    }
    return dummy.next;
}

5.Find middle

There are 2 ways to do this: calculate the total length, or fast/slow pointer. But fast/slow pointer is better because in engineering world, a lot of problems only allows information to flow once (数据流概念). Sometimes it’s not a good idea to read list for a second (or 1.5) time.

public ListNode findMiddle(ListNode head) {
    if (head == null) {
        return null;
    }
    ListNode slow = head, fast = head.next;
    while (fast != null && fast.next != null) {
        fast = fast.next.next;
        slow = slow.next;
    }
    return slow;
}

Dummy head questions

  1. Remove Duplicates from Sorted List

  2. Remove Duplicates from Sorted List II

  3. Partition List

  4. Merge Two Sorted Lists

5 basic operations questions

  1. Reverse Linked List II - difficult

  2. Sort List

    2 operations used

  3. Reorder List

    3 operations used

  4. Linked List Cycle

  5. Linked List Cycle II

  6. Merge k Sorted Lists

    For this question, it’s important to write a comparator by yourself, to show your understanding of a PriorityQueue.

    nklgk time, why? 1:14:30 recording about heap 1:15:00 recording

  7. Copy List with Random Pointer

Code

Remove Duplicates from Sorted List

Easy, no dummy head needed.

Remove Duplicates from Sorted List II

public ListNode deleteDuplicates(ListNode head) {
    ListNode dummy = new ListNode(0);
    dummy.next = head;
    ListNode p = dummy;
    while (p.next != null && p.next.next != null) {
        if (p.next.val == p.next.next.val) {
            int dupVal = p.next.val;
            while (p.next != null && p.next.val == dupVal) {
                p.next = p.next.next;
            }
        } else {
            p = p.next;
        }
    }
    return dummy.next;
}

Partition List - spend a lot of time on a list cycle in the result

public ListNode partition(ListNode head, int x) {
    if (head == null) {
        return null;
    }
    ListNode head1 = new ListNode(0);
    ListNode head2 = new ListNode(0);

    ListNode p1 = head1;
    ListNode p2 = head2;
    ListNode cur = head;

    while (cur != null) {
        if (cur.val < x) {
            p1.next = cur;
            p1 = cur;
        } else {
            p2.next = cur;
            p2 = cur;
        }
        cur = cur.next;
    }

    p1.next = head2.next;
    // VERY IMPORTANT THIS LINE !!!
    p2.next = null;
    // VERY IMPORTANT THIS LINE !!!
    return head1.next;
}

Merge Two Sorted Lists

public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
    ListNode dummy = new ListNode(0);
    ListNode p = dummy;
    while (l1 != null && l2 != null) {
        if (l1.val < l2.val) {
            p.next = l1;
            l1 = l1.next;
            p = p.next;
        } else {
            p.next = l2;
            l2 = l2.next;
            p = p.next;
        } 
    }
    if (l1 == null) {
        p.next = l2;
    } else {
        p.next = l1;
    }
    return dummy.next;
}

5 basic operations

Reverse Linked List II

public ListNode reverseBetween(ListNode head, int m, int n) {
    ListNode dummy = new ListNode(0);
    dummy.next = head;
    ListNode start = dummy;
    int count = 1;
    while (head != null && count < m) {
        start = start.next;
        count++;
    }
    start.next = reverseRange(start.next, n - m + 1);
    return dummy.next;
}

private ListNode reverseRange(ListNode start, int len) {
    ListNode result = null;
    ListNode cur = start;
    while (len > 0) {
        ListNode temp = cur.next;
        cur.next = result;
        result = cur;
        cur = temp;
        len--;
    }
    start.next = cur;
    return result;
}

Sort List

Time complexity analysis: T(n) = 2 T(n/2) + O(n). By applying Master theorem, time = O(nlgn).

public ListNode sortList(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        }
        ListNode mid = findMid(head);
        ListNode secondHalf = mid.next;
        mid.next = null;
        head = sortList(head);
        secondHalf = sortList(secondHalf);
        return merge(head, secondHalf);
}

private ListNode findMid(ListNode head) {
    ListNode slow = head;
    ListNode fast = head.next;
    while (fast != null && fast.next != null) {
        slow = slow.next;
        fast = fast.next.next;
    }
    return slow;
}

private ListNode merge(ListNode h1, ListNode h2) {
    ListNode dummy = new ListNode(0);
    ListNode p = dummy;
    while (h1 != null && h2 != null) {
        if (h1.val < h2.val) {
            p.next = h1;
            h1 = h1.next;
        } else {
            p.next = h2;
            h2 = h2.next;
        }
        p = p.next;
    }
    if (h1 == null) {
        p.next = h2;
    } else if (h2 == null) {
        p.next = h1;
    }
    return dummy.next;
}

Reorder List

public void reorderList(ListNode head) {
    if (head == null || head.next == null) {
        return;
    }
    ListNode mid = findMid(head);
    ListNode secondHalf = mid.next;
    mid.next = null;
    secondHalf = reverse(secondHalf);
    head = mergeInterlace(head, secondHalf);
}

private ListNode findMid(ListNode head) {
    if (head == null) {
        return null;
    }
    ListNode slow = head;
    ListNode fast = head.next;
    while (fast != null && fast.next != null) {
        slow = slow.next;
        fast = fast.next.next;
    }
    return slow;
}

private ListNode reverse(ListNode head) {
    ListNode result = null;
    ListNode cur = head;
    while (cur != null) {
        ListNode temp = cur.next;
        cur.next = result;
        result = cur;
        cur = temp;
    }
    return result;
}

private ListNode mergeInterlace(ListNode h1, ListNode h2) {
    ListNode result = h1;
    h1 = h1.next;
    ListNode p = result;
    while (h1 != null && h2 != null) {
        p.next = h2;
        h2 = h2.next;
        p.next.next = h1;
        h1 = h1.next;
        p = p.next.next;
    }
    if (h1 == null) {
        p.next = h2;
    } else {
        p.next = h1;
    }
    return result;
}

Linked List Cycle

public boolean hasCycle(ListNode head) {
    if (head == null || head.next == null) 
        return false;
    ListNode slow = head;
    ListNode fast = head.next;
    while (fast != null && fast.next != null) {
        fast = fast.next.next;
        slow = slow.next;
        if (fast == slow) {
            return true;
        }
    }
    return false;
}

Linked List Cycle II

public ListNode detectCycle(ListNode head) {
    if (head == null || head.next == null) 
        return null;
    ListNode slow = head;
    ListNode fast = head;
    while (fast != null && fast.next != null) {
        fast = fast.next.next;
        slow = slow.next;
        if (fast == slow) {
            slow = head;
            while (slow != fast) {
                slow = slow.next;
                fast = fast.next;
            }
            return slow;
        }
    }
    return null;
}

Merge k Sorted Lists

I write the code in the original post.

Copy List with Random Pointer

public RandomListNode copyRandomList(RandomListNode head)  {
    if (head == null)  {
        return null;
    }
    // 1, make a new copy of each node
    RandomListNode p = head;
    while (p != null) {
        RandomListNode copy = new RandomListNode(p.label);
        copy.next = p.next;
        p.next = copy;
        p = copy.next;
    }
    // 2. link the random pointer of copied nodes
    p = head;
    while (p != null) {
        if (p.random != null) {
            p.next.random = p.random.next;
        }
        p = p.next.next;
    }
    // 3. break the copied nodes from original nodes
    RandomListNode result = head.next;
    p = head;
    RandomListNode p2 = head.next;
    while (p != null) {
        p.next = p2.next;
        if (p2.next != null) {
            p2.next = p2.next.next;
        }
        p = p.next;
        p2 = p2.next;
    }
    return result;
}

[LeetCode Plus] Binary Tree Serialize and Deserialize

Question

link1, link2.

Variant 1: Given a Binary Search Tree, serialize and deserialize it.

Variant 2: Given a Binary Tree, serialize and deserialize it.

Variant 1 - Binary search tree

We must only use pre-order.

Think about why, or read link1. So, serialization is simple - preorder traversal.

The desecialization would make use of the first element, and range validation method. This is very similar to another question ‘validate BST’. Rmb the key is:

Each time we add a number, we also pass the valid range within which the number can lie between.

Code

The code is concise, but may not be easy to write:

void readBSTHelper(int min, int max, int &insertVal,
                   BinaryTree *&p, ifstream &fin) {
  if (insertVal > min && insertVal < max) {
    int val = insertVal;
    p = new BinaryTree(val);
    if (fin >> insertVal) {
      readBSTHelper(min, val, insertVal, p->left, fin);
      readBSTHelper(val, max, insertVal, p->right, fin);
    }
  }
}

void readBST(BinaryTree *&root, ifstream &fin) {
  int val;
  fin >> val;
  readBSTHelper(INT_MIN, INT_MAX, val, root, fin);
}

Variant 2 - Binary tree

For binary tree, we could not use above solution any more. We must use some NULL pointers to fill in empty slots. For this variant, pre-order and level-order both would work.

Then which of these 2 is a better choice?

   1
  / \
 2   3

Given the tree above:

The pre-order serialization is: {1, 2, #, #, 3, #, #}

The level-order serialization is: {1, 2, 3}

We can see that level-order is a better idea, because last level null pointers need not be handled.

Code (preorder)

The serializaion is a simple traversal.

void writeBinaryTree(BinaryTree *p, ostream &out) {
  if (!p) {
    out << "# ";
  } else {
    out << p->data << " ";
    writeBinaryTree(p->left, out);
    writeBinaryTree(p->right, out);
  }
}

The deserialization is a little bit like “convert linked list to balanced tree” (where we use first element of the list as root of the tree).

void readBinaryTree(BinaryTree *&p, ifstream &fin) {
  int token;
  bool isNumber;
  if (!readNextToken(token, fin, isNumber)) 
    return;
  if (isNumber) {
    p = new BinaryTree(token);
    readBinaryTree(p->left, fin);
    readBinaryTree(p->right, fin);
  }
}

I did not find any code for level-order, but it’s similar to ‘level-order traversal’.

One more thing

A Binary Search Tree (BST) is useful for storing phone book records in a memory limited device, such as a cell phone. The records are always maintained in sorted order, inserting and deleting a record takes O(lg n) time (slower than linked list, but much better than array).

One more one-more-thing

This post we use # as a sentinel. There is also another idea of doing both Inorder and Preorder traversal to searialize the tree data, and use the solution to “Construct Binary Tree from Preorder and Inorder” to deserialize it.

[NineChap 3.4] Binary Tree Additional

These are some additional questions that are not covered in previous NineChap posts. Some questions are non-standard and difficult to solve, and some are not found in OJ websites. But these are real questions that has been asked during interviews.

Question list

  1. Binary Search Tree find upper/lower bound

  2. Implement iterator of Binary Search Tree

  3. Binary Tree Serialize and Deserialize

  4. Populating Next Right Pointers in Each Node

  5. Populating Next Right Pointers in Each Node II - difficult

  6. Symmetric Tree

  7. Same Tree

Code

Binary Search Tree find upper/lower bound

Find the new post.

Implement iterator of Binary Search Tree

Find the new post.

Binary Tree Serialize and Deserialize

Find the new post.

Populating Next Right Pointers in Each Node

public void connect(TreeLinkNode root) {
    TreeLinkNode dummy = new TreeLinkNode(0);
    dummy.left = root;
    helper(dummy, root);
}

private void helper(TreeLinkNode parent, TreeLinkNode child) {
    if (child == null) {
        return;
    }
    if (child == parent.left) {
        child.next = parent.right;
    } else if (child == parent.right) {
        if (parent.next != null) {
            child.next = parent.next.left;
        }
    }
    helper(child, child.left);
    helper(child, child.right);
}

Populating Next Right Pointers in Each Node II

This is a very tricky variant of DFS where the left sub-tree is making use of right sub-tree. I did not solve it even at second time.

public void connect(TreeLinkNode root) {
    if (root == null) return;
    if (root.left == null && root.right == null) return;
    TreeLinkNode levelNext = root.next;
    TreeLinkNode lowerNext = null;
    while (levelNext != null && lowerNext == null) {
        if (levelNext.left != null) {
            lowerNext = levelNext.left;
            break;
        } else if (levelNext.right != null) {
            lowerNext = levelNext.right;
            break;
        } else {
            // if there is no child node of levelNext
            levelNext = levelNext.next;
        }
    }
    if (root.left == null) {
        root.right.next = lowerNext;
    } else if (root.right == null) {
        root.left.next = lowerNext;
    } else {
        root.left.next = root.right;
        root.right.next = lowerNext;
    }
    connect(root.right);
    connect(root.left);
}

Symmetric Tree

public boolean isSymmetric(TreeNode root) {
    if (root == null) {
        return true;
    }
    return mirror(root.left, root.right);
}

private boolean mirror(TreeNode left, TreeNode right) {
    if (left == null && right == null) {
        return true;
    }
    if (left == null || right == null) {
        return false;
    }
    return (left.val == right.val) 
        & mirror(left.left, right.right)
        & mirror(left.right, right.left);
}

Same Tree

public boolean isSameTree(TreeNode left, TreeNode right) {
    if (left == null && right == null) {
        return true;
    }
    if (left == null || right == null) {
        return false;
    }
    return (left.val == right.val) 
        & isSameTree(left.left, right.left)
        & isSameTree(left.right, right.right);
}