Daily Coding Problems

Enviroment Setup


Python 2.7 Playground: https://repl.it/languages/python

Python 3 Playground: https://repl.it/languages/python3

Java Playground: https://repl.it/languages/java

Oct 31, 2019 LC 274 [Medium] H-Index


Question: The h-index is a metric that attempts to measure the productivity and citation impact of the publication of a scholar. The definition of the h-index is if a scholar has at least h of their papers cited h times.

Given a list of publications of the number of citations a scholar has, find their h-index.

Example:

Input: [3, 5, 0, 1, 3]
Output: 3
Explanation:
There are 3 publications with 3 or more citations, hence the h-index is 3.

My thoughts: The question requires finding a special cut-off number h so that we can find at least h number publications with citation number >= h. The solution is quite simple, just sort the citation in reverse order and go over the list from high to low to find the cut-off citation number less than number of publication we iterate through so far.

The best case running time requires at least O(n logn) time due to sorting. We can improve that by leveraging max-heap to achieve O(n) as heapify an array with size n can be done within O(n) time. Altough both implementations have O(n logn) worst case running time, still using priority queue can improve the best case running time and average case running time.

Solution with Priority Queue: https://repl.it/@trsong/H-Index

import unittest
from Queue import PriorityQueue

def calculate_h_index(citations):
    max_heap = PriorityQueue()
    for num in citations:
        if num > 0:
            max_heap.put(-num)
    
    count = 0
    while not max_heap.empty():
        num = -max_heap.get()
        if count >= num:
            return count
        count += 1

    # count is at least number of publications with non-zero citation 
    return count


class CalculateHIndexSpec(unittest.TestCase):
    def test_example(self):
        self.assertEqual(3, calculate_h_index([3, 5, 0, 1, 3]))

    def test_another_citation_array(self):
        self.assertEqual(3, calculate_h_index([3, 0, 6, 1, 5]))

    def test_empty_citations(self):
        self.assertEqual(0, calculate_h_index([]))

    def test_only_one_publications(self):
        self.assertEqual(1, calculate_h_index([42]))

    def test_balanced_citation_counts(self):
        self.assertEqual(5, calculate_h_index([9, 8, 7, 6, 5, 4, 3, 2, 1]))

    def test_duplicated_citations(self):
        self.assertEqual(3, calculate_h_index([3, 3, 3, 2, 2, 2, 2, 2]))
    
    def test_zero_citations_not_count(self):
        self.assertEqual(2, calculate_h_index([10, 0, 0, 0, 0, 10]))
    
    def test_citations_number_greater_than_publications(self):
        self.assertEqual(4, calculate_h_index([9, 8, 7, 6]))

    def test_citations_number_greater_than_publications2(self):
        self.assertEqual(3, calculate_h_index([1, 7, 9, 4]))   

if __name__ == '__main__':
    unittest.main(exit=False)

Oct 30, 2019 LC 93 [Medium] All Possible Valid IP Address Combinations


Question: Given a string of digits, generate all possible valid IP address combinations.

IP addresses must follow the format A.B.C.D, where A, B, C, and D are numbers between 0 and 255. Zero-prefixed numbers, such as 01 and 065, are not allowed, except for 0 itself.

For example, given "2542540123", you should return ['254.25.40.123', '254.254.0.123']

My thoughts: This problem can be solved with backtracking. We starts from empty string and choose substring with length from 1 to 3. Note that a valid solution should have 4 parts and for each part ranges from 0 to 255. Pay attention to the following constraints:

Each substring is one of the following:

  • [0-9]
  • [1-9][0-9]
  • [1-2][0-9][0-9] and int(substring) <= 255

If any of above constaint is violated, we prune this branch and move to next recursion.

Solution with Backtrack: https://repl.it/@trsong/All-Possible-Valid-IP-Address-Combinations

import unittest

AVAILABLE_LENGTH = [1, 2, 3]
NUM_SUB_PARTS = 4

def backtrack(raw_str, res, accu, pos):
    if pos == len(raw_str) and len(accu) == NUM_SUB_PARTS:
        ip_str = '.'.join(accu)
        res.append(ip_str)
    elif pos < len(raw_str):
        for length in AVAILABLE_LENGTH:
            next_pos = pos + length
            remaining_parts = NUM_SUB_PARTS - len(accu) - 1
            if next_pos > len(raw_str) or remaining_parts < 0:
                continue
            elif raw_str[pos] == '0' and length > 1:
                continue
            elif length == 3 and int(raw_str[pos: next_pos]) > 255:
                continue
            else:
                accu.append(raw_str[pos: next_pos])
                backtrack(raw_str, res, accu, next_pos)
                accu.pop()


def all_ip_combinations(raw_str):
    res = []
    backtrack(raw_str, res, [], 0)
    return res


class AllIpCombinationSpec(unittest.TestCase):
    def assert_result(self, expected, result):
        self.assertEqual(sorted(expected), sorted(result))

    def test_example(self):
        raw_str = '2542540123'
        expected = ['254.25.40.123', '254.254.0.123']
        self.assert_result(expected, all_ip_combinations(raw_str))

    def test_empty_string(self):
        self.assert_result([], all_ip_combinations(''))

    def test_no_valid_ips(self):
        raw_str = '25505011535'
        expected = []
        self.assert_result(expected, all_ip_combinations(raw_str))

    def test_multiple_outcomes(self):
        raw_str = '25525511135'
        expected = ['255.255.11.135', '255.255.111.35']
        self.assert_result(expected, all_ip_combinations(raw_str))

    def test_multiple_outcomes2(self):
        raw_str = '25011255255'
        expected = ['250.112.55.255', '250.11.255.255']
        self.assert_result(expected, all_ip_combinations(raw_str))

    def test_multiple_outcomes3(self):
        raw_str = '10101010'
        expected = ['10.10.10.10', '10.10.101.0', '10.101.0.10', '101.0.10.10', '101.0.101.0']
        self.assert_result(expected, all_ip_combinations(raw_str))
    
    def test_multiple_outcomes4(self):
        raw_str = '01010101'
        expected = ['0.10.10.101', '0.101.0.101']
        self.assert_result(expected, all_ip_combinations(raw_str))

    def test_unique_outcome(self):
        raw_str = '111111111111'
        expected = ['111.111.111.111']
        self.assert_result(expected, all_ip_combinations(raw_str))

    def test_unique_outcome2(self):
        raw_str = '0000'
        expected = ['0.0.0.0']
        self.assert_result(expected, all_ip_combinations(raw_str))


if __name__ == '__main__':
    unittest.main(exit=False)

Oct 29, 2019 [Easy] Max and Min with Limited Comparisons


Question: Given a list of numbers of size n, where n is greater than 3, find the maximum and minimum of the list using less than 2 * (n - 1) comparisons.

My thoughts: The idea is to use Tournament Method. Think about each number as a team in the tournament. One team, zero matches. Two team, one match. N team, let’s break them into half and use two matches to get best of best and worest of worest:

T(n) = 2 * T(n/2) + 2
T(1) = 0
T(2) = 1

=>
T(n) = 3n/2 - 2 

Solution with Recursion: https://repl.it/@trsong/Max-and-Min-with-Limited-Comparisons

import unittest


class MinMaxPair(object):
    def __init__(self, min_val, max_val):
        self.min_val = min_val
        self.max_val = max_val


def get_min_max_recur(nums, lo, hi):
    if lo > hi:
        return None
    elif lo == hi:
        return MinMaxPair(nums[lo], nums[lo])
    elif lo == hi - 1:
        if nums[lo] < nums[hi]:
            return MinMaxPair(nums[lo], nums[hi])
        else:
            return MinMaxPair(nums[hi], nums[lo])

    mid = lo + (hi - lo) // 2
    left_res = get_min_max_recur(nums, lo, mid)
    right_res = get_min_max_recur(nums, mid + 1, hi)
    if left_res is not None and right_res is not None:
        min_val = left_res.min_val
        max_val = left_res.max_val
        if min_val > right_res.min_val:
            min_val = right_res.min_val
        if max_val < right_res.max_val:
            max_val = right_res.max_val
        return MinMaxPair(min_val, max_val)

    elif left_res is None:
        return right_res
    else:
        return left_res


def get_min_max(nums):
    return get_min_max_recur(nums, 0, len(nums) - 1)


#######################################
# Testing Utilities
#######################################
class Number(int):
    def __new__(self, value):
        self.num_comparison = 0
        return int.__new__(self, value)

    def __cmp__(self, other):
        self.num_comparison += 1
        return int.__cmp__(self, other)

    def count_comparison(self):
        return self.num_comparison


class GetMinMaxSpec(unittest.TestCase):
    def assert_get_min_max(self, nums):
        min_val = min(nums)
        max_val = max(nums)
        n = len(nums)
        mapped_nums = list(map(Number, nums))
        res = get_min_max(mapped_nums)
        self.assertEqual(min_val, res.min_val)
        self.assertEqual(max_val, res.max_val)
        total_num_comparisons = sum(map(lambda num: num.count_comparison(), mapped_nums))
        if total_num_comparisons > 0:
            self.assertLess(total_num_comparisons, 2 * (n - 1))

    def test_empty_list(self):
        self.assertIsNone(get_min_max([]))

    def test_list_with_one_element(self):
        self.assert_get_min_max([-1])

    def test_list_with_two_elements(self):
        self.assert_get_min_max([1, 2])

    def test_increasing_list(self):
        self.assert_get_min_max([1, 2, 3, 4])

    def test_list_with_duplicated_element(self):
        self.assert_get_min_max([-1, 1, -1, 1])

    def test_long_list(self):
        self.assert_get_min_max([1, 2, 3, 4, 5, 6, 7, 7, 6, 5, 4, 3, 2, 1])


if __name__ == '__main__':
    unittest.main(exit=False)

Oct 28, 2019 [Medium] Symmetric K-ary Tree


Question: Given a k-ary tree, figure out if the tree is symmetrical.

A k-ary tree is a tree with k-children, and a tree is symmetrical if the data of the left side of the tree is the same as the right side of the tree.

Here’s an example of a symmetrical k-ary tree.

        4
     /     \
    3        3
  / | \    / | \
9   4  1  1  4  9

My thoughts: If you are thinking about comparing in-order or pre-order traversal, then you are on the wrong track. Only one traversal cannot guarantee same tree structure. Thus, we still need to use recursion. The idea is that if two trees are symmetric to each other, then one tree’s mirror should be equal to the other. So we make a mirror copy of the input tree and check if it is equal to the input tree which will tell if a tree itself is symmetric or not.

Solution: https://repl.it/@trsong/Symmetric-K-ary-Tree

import unittest

class TreeNode(object):
    def __init__(self, val, children=None):
        self.val = val
        self.children = children


def is_mirror_between(t1, t2):
    if t1 is None and t2 is None:
        return True

    if t1 and t2 and t1.val == t2.val:
        if t1.children is None and t2.children is None:
            return True
        if t1.children is None or t2.children is None:
            return False

        for t1_child, t2_child in zip(t1.children, reversed(t2.children)):
            if not is_mirror_between(t1_child, t2_child):
                return False
        return True
    else:
        return False
    

def is_symmetric(tree):
    return is_mirror_between(tree, tree)


class IsSymmetricSpec(unittest.TestCase):
    def test_example(self):
        """
                4
             /     \
            3        3
          / | \    / | \
        9   4  1  1  4  9
        """
        left_tree = TreeNode(3, [TreeNode(9), TreeNode(4), TreeNode(1)])
        right_tree = TreeNode(3, [TreeNode(1), TreeNode(4), TreeNode(9)])
        root = TreeNode(4, [left_tree, right_tree])
        self.assertTrue(is_symmetric(root))

    def test_empty_tree(self):
        self.assertTrue(is_symmetric(None))

    def test_node_with_odd_number_of_children(self):
        """
                8
            /   |   \
          4     5     4
         / \   / \   / \
        1   2 3   3 2   1
        """
        left_tree = TreeNode(4, [TreeNode(1), TreeNode(2)])
        mid_tree = TreeNode(5, [TreeNode(3), TreeNode(3)])
        right_tree= TreeNode(4, [TreeNode(2), TreeNode(1)])
        root = TreeNode(8, [left_tree, mid_tree, right_tree])
        self.assertTrue(is_symmetric(root))

    def test_binary_tree(self):
        """
             6
           /   \
          4     4 
         / \   / \
        1   2 2   1
         \       / 
          3     3 
        """
        left_tree = TreeNode(4, [TreeNode(1, [TreeNode(3)]), TreeNode(2)])
        right_tree = TreeNode(4, [TreeNode(2), TreeNode(1, [TreeNode(3)])])
        root = TreeNode(6, [left_tree, right_tree])
        self.assertTrue(is_symmetric(root))

    def test_unsymmetric_tree(self):
        """
             6
           / | \
          4  5  4 
         /  /  / \
        1  2  2   1
        """
        left_tree = TreeNode(4, [TreeNode(1)])
        mid_tree = TreeNode(5, [TreeNode(2)])
        right_tree = TreeNode(4, [TreeNode(2), TreeNode(1)])
        root = TreeNode(6, [left_tree, mid_tree, right_tree])
        self.assertFalse(is_symmetric(root))

    def test_unsymmetric_tree2(self):
        """
             6
           / | \
          4  5  4 
           / | \
          2  2  1
        """
        left_tree = TreeNode(4)
        mid_tree = TreeNode(5, [TreeNode(2), TreeNode(2), TreeNode(1)])
        right_tree = TreeNode(4)
        root = TreeNode(6, [left_tree, mid_tree, right_tree])
        self.assertFalse(is_symmetric(root))

    def test_unsymmetric_tree3(self):
        """
              6
           / | | \
          4  5 5  4 
          |  | |  |
          2  2 2  3
        """
        left_tree = TreeNode(4, [TreeNode(2)])
        mid_left_tree = TreeNode(5, [TreeNode(2)])
        mid_right_tree = TreeNode(5, [TreeNode(2)])
        right_tree = TreeNode(4, [TreeNode(3)])
        root = TreeNode(6, [left_tree, mid_left_tree, mid_right_tree, right_tree])
        self.assertFalse(is_symmetric(root))

    def test_unsymmetric_tree4(self):
        """
              1
            /    \
           2      2
          / \   / | \
         4   5 6  5  4
             |
             6
        """
        left_tree = TreeNode(2, [TreeNode(4), TreeNode(5, [TreeNode(6)])])
        right_tree = TreeNode(2, [TreeNode(6), TreeNode(5), TreeNode(4)])
        root = TreeNode(1, [left_tree, right_tree])
        self.assertFalse(is_symmetric(root))


if __name__ == '__main__':
    unittest.main(exit=False)

Oct 27, 2019 [Medium] Group Words that are Anagrams


Question: Given a list of words, group the words that are anagrams of each other. (An anagram are words made up of the same letters).

Example:

Input: ['abc', 'bcd', 'cba', 'cbd', 'efg']
Output: [['abc', 'cba'], ['bcd', 'cbd'], ['efg']]

My thoughts: Notice that two words are anagrams of each other if both of them be equal after sort and should have same prefix. eg. 'bcda' => 'abcd' and 'cadb' => 'abcd'. We can sort each word in the list and then insert those sorted words into a trie. Finally, we can perform a tree traversal to get all words with same prefix and those words will be words that are anagrams of each other.

Solution with Trie: https://repl.it/@trsong/Group-Words-that-are-Anagrams

import unittest

class Trie(object):
    def __init__(self):
        self.anagram_indices = None
        self.children = None

    def _insert(self, word, word_index):
        t = self
        for char in word:
            if t.children is None:
                t.children = {}
            if char not in t.children:
                t.children[char] = Trie()
            t = t.children[char]
        if t.anagram_indices is None:
            t.anagram_indices = []
        t.anagram_indices.append(word_index)
    
    def insert_words(self, words):
        for i, word in enumerate(words):
            sorted_word = sorted(word)
            self._insert(sorted_word, i)
    
    def query_words_groupby_anagram(self, words):
        res = []
        stack = [self]
        while stack:
            cur = stack.pop()
            if cur.anagram_indices is not None:
                res.append(map(lambda i: words[i], cur.anagram_indices))

            if cur.children is not None:
                stack.extend(cur.children.values())
        return res


def group_by_anagram(words):
    t = Trie()
    t.insert_words(words)
    return t.query_words_groupby_anagram(words)


class GroupyByAnagramSpec(unittest.TestCase):
    def assert_result(self, expected, result):
        for l in result:
            l.sort()
        result.sort()
        for l in expected:
            l.sort()
        expected.sort()
        self.assertEqual(expected, result)

    def test_example(self):
        input = ['abc', 'bcd', 'cba', 'cbd', 'efg']
        output = [['abc', 'cba'], ['bcd', 'cbd'], ['efg']]
        self.assert_result(output, group_by_anagram(input))

    def test_empty_word_list(self):
        self.assert_result([], group_by_anagram([]))

    def test_contains_duplicated_words(self):
        input = ['a', 'aa', 'aaa', 'a', 'aaa', 'aa', 'aaa']
        output = [['a', 'a'], ['aa', 'aa'], ['aaa', 'aaa', 'aaa']]
        self.assert_result(output, group_by_anagram(input))

    def test_contains_duplicated_words2(self):
        input = ['abc', 'acb', 'abcd', 'dcba', 'abc', 'abcd', 'a']
        output = [['a'], ['abc', 'acb', 'abc'], ['abcd', 'dcba', 'abcd']]
        self.assert_result(output, group_by_anagram(input))

    def test_contains_empty_word(self):
        input = ['', 'a', 'b', 'c', '', 'bc', 'ca', '', 'ab']
        output = [['', '', ''], ['a'], ['b'], ['c'], ['ab'], ['ca'], ['bc']]
        self.assert_result(output, group_by_anagram(input))

    def test_word_with_duplicated_letters(self):
        input = ['aabcde', 'abbcde', 'abccde', 'abcdde', 'abcdee', 'abcdea']
        output = [['aabcde', 'abcdea'], ['abbcde'], ['abccde'], ['abcdde'], ['abcdee']]
        self.assert_result(output, group_by_anagram(input))


if __name__ == '__main__':
    unittest.main(exit=False)

Oct 26, 2019 [Hard] Decode String


Question: Given a string with a certain rule: k[string] should be expanded to string k times. So for example, 3[abc] should be expanded to abcabcabc. Nested expansions can happen, so 2[a2[b]c] should be expanded to abbcabbc.

My thoughts: This question can be easy and hard depend on if "string" in "k[string]" contains special character '[' and ']' or not. Let’s just assume the worst case, it does contain then here comes another issue: what if there is no corresponding close parenthsis eg. 32[ab4[a]? We have to assume "32[" by design and the correct outcome is "32[abaaaa".

Solution with Recursion: https://repl.it/@trsong/Decode-String

import unittest

def decode_string(encoded_string):
    res = []
    decode_string_recur(encoded_string, res, 0, len(encoded_string)-1, 1)
    return ''.join(res)

def decode_string_recur(encoded_string, res, start, end, repeat):
    if repeat <= 0 or start > end:
        return
    
    counter = 0
    i = start
    while i <= end:
        if not encoded_string[i].isdigit():
            res.append(encoded_string[i])
            i += 1
        else:
            # In order to handle k[str] 
            # Step 1: Figure out the multiplier k
            multiplier = 0
            j = i
            while j <= end and encoded_string[j].isdigit():
                multiplier = 10 * multiplier + int(encoded_string[j])
                j += 1
            
            if j > end or encoded_string[j] != '[':
                # number is purely str not multiplier. eg. 123ab
                res.append(str(multiplier))
                i = j
                continue

            # Step 2: Figure out the range of '[' and ']'
            counter = 1
            k = j + 1
            while k <= end and counter > 0:
                if encoded_string[k] == '[':
                    counter += 1
                elif encoded_string[k] == ']':
                    counter -= 1
                k += 1

            if counter == 0:
                # Parenthesis closes. eg. 32[ab4[a]]
                # Recursion on inner part based on multiplier. eg. 32 * f(ab4[a])   
                decode_string_recur(encoded_string, res, j+1, k-2, multiplier)
            else:
                # Parenthesis never close. eg. 32[ab4[a]
                # Append first part.(eg. 32[). And then process the inner part. eg. 1 * f(ab4[a]). The final outcome is 32[ +  f(ab4[a])
                res.append(encoded_string[i:j+1])
                decode_string_recur(encoded_string, res, j+1, k-1, 1)
            i = k
    decode_string_recur(encoded_string, res, start, end, repeat-1)
        

class DecodeStringSpec(unittest.TestCase):
    def test_example1(self):
        input = "3[abc]"
        expected = 3 * "abc"
        self.assertEqual(expected, decode_string(input))

    def test_example2(self):
        input = "2[a2[b]c]"
        expected = 2 * ('a' + 2 * 'b' + 'c')
        self.assertEqual(expected, decode_string(input)) 

    def test_empty_string(self):
        self.assertEqual("", decode_string(""))
        self.assertEqual("", decode_string("42[]"))

    def test_duplicate_zero_times(self):
        self.assertEqual("", decode_string("0[magic string]"))
        self.assertEqual("", decode_string("0[0[magic string]]"))

    def test_not_decode_negative_number_of_strings(self):
        input = "-3[abc]"
        expected = "-abcabcabc"
        self.assertEqual(expected, decode_string(input))

    def test_duplicate_more_than_10_times(self):
        input = "233[ab]"
        expected =  233 * "ab"
        self.assertEqual(expected, decode_string(input))

    def test_2Level_nested_encoded_string(self):
        input = "2[3[a]3[bc]2[d]]4[2[e]]"
        expected = 2 * (3*"a" + 3*"bc" + 2*"d") + 4*2*"e"
        self.assertEqual(expected, decode_string(input))

    def test_3Level_nested_encoded_string(self):
        input = "2[a2[b3[c]d4[ef]g]h]"
        expected = 2*('a' + 2*('b' + 3*'c' + 'd' + 4 * 'ef' + 'g' ) + 'h')
        self.assertEqual(expected, decode_string(input))

    ####################################################################
    # If we allow special characters, the follow will be the test case.
    # Comment out if you program cannot handle special charactors.
    ####################################################################

    def test_encoded_string_contains_special_charactor(self):
        input = "1[3][abc]"
        expected = "3[abc]"
        self.assertEqual(expected, decode_string(input))

    def test_encoded_string_contains_special_charactor2(self):
        input = "1[2][a1[3][b]c]"
        expected = "2[a3[b]c]"
        self.assertEqual(expected, decode_string(input))
    
    def test_encoded_string_contains_special_charactor3(self):
        input = "]1[2[a]2["
        expected = "]1[aa2["
        self.assertEqual(expected, decode_string(input))
    
    def test_encoded_string_contains_special_charactor4(self):
        input = "3[[[2[a]]]"
        expected = "3[[[" + 2*'a' + "]]"
        self.assertEqual(expected, decode_string(input))

    def test_encoded_string_contains_special_charactor5(self):
        input = "[]1]"
        expected = "[]1]"
        self.assertEqual(expected, decode_string(input))


if __name__ == "__main__":
    unittest.main(exit=False)

Oct 25, 2019 [Medium] Jump to the End


Question: Starting at index 0, for an element n at index i, you are allowed to jump at most n indexes ahead. Given a list of numbers, find the minimum number of jumps to reach the end of the list.

Example:

Input: [3, 2, 5, 1, 1, 9, 3, 4]
Output: 2
Explanation:
The minimum number of jumps to get to the end of the list is 2:
3 -> 5 -> 4

Solution with DP: https://repl.it/@trsong/Jump-to-the-End

import unittest
import sys


def min_jump(nums):
    if not nums or len(nums) <= 1:
        return 0

    n = len(nums)
    # dp[i] represents min jump to reach index i
    # dp[i] = min(dp[j]) + 1 for all j < i
    dp = [sys.maxint] * n
    dp[0] = 0
    for i in xrange(1, n):
        for j in xrange(i):
            if nums[j] > 0 and j + nums[j] >= i:
                dp[i] = min(dp[i], dp[j] + 1)

    return dp[n-1] if dp[n-1] != sys.maxint else -1


class MinJumpSpec(unittest.TestCase):
    def test_example(self):
        self.assertEqual(2, min_jump([3, 2, 5, 1, 1, 9, 3, 4]))  # 3 -> 5 -> 4

    def test_empty_array(self):
        self.assertEqual(0, min_jump([]))

    def test_one_elem_array(self):
        self.assertEqual(0, min_jump([1]))
        self.assertEqual(0, min_jump([-1])) # no need to jump, already reach end

    def test_end_is_unreachable(self):
        self.assertEqual(-1, min_jump([1, 1, 1, 0, 2]))

    def test_end_is_unreachable2(self): 
        self.assertEqual(-1, min_jump([2, 3, -1, -2, -3, 0]))

    def test_multiple_routes(self):
        self.assertEqual(3, min_jump([1, 3, 5, 8, 9, 2, 6, 7, 6, 8, 9]))  # 1-> 3 -> 8 -> 9

    def test_multiple_routes2(self):
        self.assertEqual(4, min_jump([2, 2, 1, 2, 3, 4, 1, 1, 3, -1]))  # 2 -> 1 -> 2 -> 4 -> -1
    

if __name__ == '__main__':
    unittest.main(exit=False)

Oct 24, 2019 [Medium] Circle of Chained Words


Question: Two words can be ‘chained’ if the last character of the first word is the same as the first character of the second word.

Given a list of words, determine if there is a way to ‘chain’ all the words in a circle.

Example:

Input: ['eggs', 'karat', 'apple', 'snack', 'tuna']
Output: True
Explanation:
The words in the order of ['apple', 'eggs', 'snack', 'karat', 'tuna'] creates a circle of chained words.

My thoughts: Treat each non-empty word as an edge in a directed graph with vertices being the first and last letter of the word. Now, pick up any letter as a starting point. Perform DFS and remove any edge we visited from the graph. Check if all edges are used. And make sure the vertex we stop at is indeed the starting point. If all above statisfied, then there exists a cycle that chains all words.

Solution with DFS: https://repl.it/@trsong/Circle-of-Chained-Words

import unittest

def exists_cycle(words):
    if not words:
        return False

    neighbor = {}
    for word in words:
        if not word:
            continue
        u, v = word[0], word[-1]
        if u not in neighbor:
            neighbor[u] = {}
        if v not in neighbor[u]:
            neighbor[u][v] = 0
        neighbor[u][v] += 1

    if len(neighbor) == 0:
        return True
    
    begin = neighbor.keys()[0];
    stack = [begin]
    while stack and len(neighbor) > 0:
        cur = stack.pop()
        if cur not in neighbor:
            return False
        if len(neighbor[cur]) == 0:
            continue
        
        next = neighbor[cur].keys()[0]
        neighbor[cur][next] -= 1
        if neighbor[cur][next] <= 0:
            del neighbor[cur][next]
            if len(neighbor[cur]) == 0:
                del neighbor[cur]
        stack.append(next)
    
    return len(neighbor) == 0 and len(stack) == 1 and stack[0] == begin
        

class ExistsCycleSpec(unittest.TestCase):
    def test_example(self):
        words = ['eggs', 'karat', 'apple', 'snack', 'tuna']
        self.assertTrue(exists_cycle(words)) # ['apple', 'eggs', 'snack', 'karat', 'tuna']

    def test_empty_words(self):
        words = []
        self.assertFalse(exists_cycle(words))
    
    def test_not_contains_cycle(self):
        words = ['ab']
        self.assertFalse(exists_cycle(words))

    def test_not_exist_cycle(self):
        words = ['ab', 'c', 'c', 'def', 'gh']
        self.assertFalse(exists_cycle(words))

    def test_exist_cycle_but_not_chaining_all_words(self):
        words = ['ab', 'be', 'bf', 'bc', 'ca']
        self.assertFalse(exists_cycle(words))
    
    def test_exist_cycle_but_not_chaining_all_words2(self):
        words = ['ab', 'ba', 'bc', 'ca']
        self.assertFalse(exists_cycle(words))

    def test_duplicate_words_with_cycle(self):
        words = ['ab', 'bc', 'ca', 'ab', 'bd', 'da' ]
        self.assertTrue(exists_cycle(words))

    def test_contains_mutiple_cycles(self):
        words = ['ab', 'ba', 'ac', 'ca']
        self.assertTrue(exists_cycle(words))

    def test_disconnect_graph(self):
        words = ['ab', 'ba', 'cd', 'de', 'ec']
        self.assertFalse(exists_cycle(words))

    def test_conains_empty_string(self):
        words = ['']
        self.assertTrue(exists_cycle(words))
        words2 = ['', 'a', '', '', 'a']
        self.assertTrue(exists_cycle(words2))


if __name__ == '__main__':
    unittest.main(exit=False)

Oct 23, 2019 LC 227 [Medium] Basic Calculator II


Question: Implement a basic calculator to evaluate a simple expression string.

The expression string contains only non-negative integers, +, -, *, / operators and empty spaces. The integer division should truncate toward zero.

Example 1:

Input: "3+2*2"
Output: 7

Example 2:

Input: " 3/2 "
Output: 1

Example 3:

Input: " 3+5 / 2 "
Output: 5

My thoughts: A complicated expression can be broken into multiple normal terms. Expr = term1 + term2 - term3 .... Between each consecutive term we only allow + and -. Whereas within each term we only allow * and /. So we will have the following definition of an expression. e.g. 1 + 2 - 1*2*1 - 3/4*4 + 5*6 - 7*8 + 9/10 = (1) + (2) - (1*2*1) - (3/4*4) + (5*6) - (7*8) + (9/10)

Expression is one of the following:
- Empty or 0
- Term - Expression
- Term + Expression

Term is one of the following:
- 1
- A number * Term
- A number / Term

Thus, we can comupte each term value and sum them together.

Solution: https://repl.it/@trsong/Basic-Calculator-II

import unittest

def calculate(s):
    if not s:
        return 0
        
    total_sum = 0
    term_sum = 0
    num = 0
    op = '+'
    op_set = {'+', '-', '*', '/'}
     
    for index, char in enumerate(s):
        if char.isspace() and index < len(s) - 1:
            continue
        elif char.isdigit():
            num = 10 * num + int(char)
            
        if char in op_set or index == len(s) - 1:
            if op == '+':
                total_sum += term_sum
                term_sum = num
            elif op == '-':
                total_sum += term_sum
                term_sum = -num
            elif op == '*':
                term_sum *= num
            elif op == '/':
                sign = 1 if term_sum > 0 else -1
                term_sum = abs(term_sum) / num * sign
            op = char
            num = 0
            
    total_sum += term_sum
    return total_sum


class CalculateSpec(unittest.TestCase):
    def test_empty_string(self):
        self.assertEqual(0, calculate(""))

    def test_example1(self):
        self.assertEqual(7, calculate("3+2*2"))

    def test_example2(self):
        self.assertEqual(1, calculate(" 3/2 "))

    def test_example3(self):
        self.assertEqual(5, calculate(" 3+5 / 2 "))

    def test_negative1(self):
        self.assertEqual(-1, calculate("-1"))

    def test_negative2(self):
        self.assertEqual(0, calculate(" -1/2 "))

    def test_negative3(self):
        self.assertEqual(-1, calculate(" -7 / 4 "))

    def test_minus(self):
        self.assertEqual(-5, calculate("-2-3"))
    
    def test_positive1(self):
        self.assertEqual(10, calculate("100/ 10"))
    
    def test_positive2(self):
        self.assertEqual(4, calculate("9 /2"))

    def test_complicated_operations(self):
        self.assertEqual(-24, calculate("1*2-3/4+5*6-7*8+9/10"))

    def test_complicated_operations2(self):
        self.assertEqual(10000, calculate("10000-1000/10+100*1"))

    def test_complicated_operations3(self):
        self.assertEqual(13, calculate("14-3/2"))


if __name__ == '__main__':
    unittest.main(exit=False)

Oct 22, 2019 [Medium] Max Number of Edges Added to Tree to Stay Bipartite


Question: Maximum number of edges to be added to a tree so that it stays a Bipartite graph

A tree is always a Bipartite Graph as we can always break into two disjoint sets with alternate levels. In other words we always color it with two colors such that alternate levels have same color. The task is to compute the maximum no. of edges that can be added to the tree so that it remains Bipartite Graph.

Example 1:

Input : Tree edges as vertex pairs 
        1 2
        1 3
Output : 0
Explanation :
The only edge we can add is from node 2 to 3.
But edge 2, 3 will result in odd cycle, hence 
violation of Bipartite Graph property.

Example 2:

Input : Tree edges as vertex pairs 
        1 2
        1 3
        2 4
        3 5
Output : 2
Explanation : On colouring the graph, {1, 4, 5} 
and {2, 3} form two different sets. Since, 1 is 
connected from both 2 and 3, we are left with 
edges 4 and 5. Since, 4 is already connected to
2 and 5 to 3, only options remain {4, 3} and 
{5, 2}.

My thoughts: The maximum number of edges between set Black and set White equals size(Black) * size(White). And we know the total number of edges in a tree. Thus, we can run BFS to color each node and then the remaining edges is just size(Black) * size(White) - #TreeEdges.

Solution with BFS: https://repl.it/@trsong/Max-Number-of-Edges-Added-to-Tree-to-Stay-Bipartite

import unittest
from Queue import Queue

def max_edges_to_add(edges):
    if not edges:
        return 0

    neighbor = {}
    for u, v in edges:
        if u not in neighbor:
            neighbor[u] = []
        neighbor[u].append(v)

        if v not in neighbor:
            neighbor[v] = []
        neighbor[v].append(u)
    
    num_color = [0, 0]
    color = 0
    queue = Queue()
    queue.put(edges[0][0])
    visited = set()

    while not queue.empty():
        for _ in xrange(queue.qsize()):
            u = queue.get()
            if u in visited:
                continue
            visited.add(u)
            for v in neighbor[u]:
                queue.put(v)
            num_color[color] += 1
        color = 1 - color
    
    return num_color[0] * num_color[1] - len(edges)


class MaxEdgesToAddSpec(unittest.TestCase):
    def test_example(self):
        """
          1
         / \
        2   3
        """
        edges = [(1, 2), (1, 3)]
        self.assertEqual(0, max_edges_to_add(edges))

    def test_example2(self):
        """
            1
           / \
          2   3
         /     \
        4       5
        """
        edges = [(1, 2), (1, 3), (2, 4), (3, 5)]
        self.assertEqual(2, max_edges_to_add(edges)) # (3, 4), (2, 5)

    def test_empty_tree(self):
        self.assertEqual(0, max_edges_to_add([]))

    def test_right_heavy_tree(self):
        """
         1
          \ 
           2
            \
             3
              \
               4
                \
                 5
        """
        edges = [(1, 2), (4, 5), (2, 3), (3, 4)]
        # White=[1, 3, 5]. Black=[2, 4]. #TreeEdge = 4. Max = #W * #B - #T = 3 * 2 - 4 = 2
        self.assertEqual(2, max_edges_to_add(edges))  # (1, 4), (2, 5)

    def test_general_tree(self):
        """
             1
           / | \ 
          2  3  4
         / \   /|\
        5   6 7 8 9 
         \     /   \
         10   11    12
        """
        edges = [(1, 2), (1, 3), (1, 4), (2, 5), (2, 6), (4, 7), (4, 8), (4, 9), (5, 10), (8, 11), (9, 12)]
        # White=[1, 5, 6, 7, 8, 9]. Black=[2, 3, 4, 10, 11, 12]. #TreeEdge=11. Max = #W * #B - #T = 6 * 6 - 11 = 25
        self.assertEqual(25, max_edges_to_add(edges))


if __name__ == '__main__':
    unittest.main(exit=False)

Oct 21, 2019 [Medium] Is Bipartite


Question: Given an undirected graph G, check whether it is bipartite. Recall that a graph is bipartite if its vertices can be divided into two independent sets, U and V, such that no edge connects vertices of the same set.

Example:

is_bipartite(vertices=3, edges=[(0, 1), (1, 2), (2, 0)])  # returns False 
is_bipartite(vertices=2, edges=[(0, 1), (1, 0)])  # returns True. U = {0}. V = {1}. 

My thoughts: A graph is a bipartite if we can just use 2 colors to cover all nodes and each neighbor has different color. This can be implemented use BFS and we change color between layers. However, BFS from one single node cannot search entire graph if such graph is disconnected. Therefore we need to run BFS on all unvisited nodes and assign or check colors for nodes on each layer.

Solution with BFS: https://repl.it/@trsong/Is-Bipartite

from Queue import Queue
import unittest

class NodeState:
    UNVISITED = 0
    BLACK = 1
    WHITE = 2


def is_bipartite(vertices, edges):
    if vertices <= 1:
        return True

    node_states = [NodeState.UNVISITED] * vertices
    neighbor = {}
    for u, v in edges:
        if u not in neighbor:
            neighbor[u] = []
        neighbor[u].append(v)

    current_color = NodeState.BLACK
    for u in xrange(vertices):
        # We need to BFS on each nodes to avoid disconnected graph
        if node_states[u] != NodeState.UNVISITED:
            continue
        queue = Queue()
        queue.put(u)
        while not queue.empty():
            for _ in xrange(queue.qsize()):
                cur = queue.get()
                # For nodes on each layer during BFS search, their colors should be different from previous layer
                if node_states[cur] != NodeState.UNVISITED and node_states[cur] != current_color:
                    return False
                elif node_states[cur] != NodeState.UNVISITED:
                    continue
                node_states[cur] = current_color
                for v in neighbor.get(cur, []):
                    queue.put(v)
            current_color = 3 - current_color  # Flip color
    return True
                    
                    
class IsBipartiteSpec(unittest.TestCase):
    def test_example1(self):
        self.assertFalse(is_bipartite(vertices=3, edges=[(0, 1), (1, 2), (2, 0)]))

    def test_example2(self):
        self.assertTrue(is_bipartite(vertices=2, edges=[(0, 1), (1, 0)]))

    def test_empty_graph(self):
        self.assertTrue(is_bipartite(vertices=0, edges=[]))

    def test_one_node_graph(self):
        self.assertTrue(is_bipartite(vertices=1, edges=[]))
    
    def test_disconnect_graph1(self):
        self.assertTrue(is_bipartite(vertices=10, edges=[(0, 1), (1, 0)]))

    def test_disconnect_graph2(self):
        self.assertTrue(is_bipartite(vertices=10, edges=[(0, 1), (1, 0), (2, 3), (3, 4), (4, 5), (5, 2)]))

    def test_disconnect_graph3(self):
        self.assertFalse(is_bipartite(vertices=10, edges=[(0, 1), (1, 0), (2, 3), (3, 4), (4, 2)])) 

    def test_square(self):
        self.assertTrue(is_bipartite(vertices=4, edges=[(0, 1), (1, 2), (2, 3), (3, 0)]))

    def test_k5(self):
        vertices = 5
        edges = [
            (0, 1), (0, 2), (0, 3), (0, 4),
            (1, 2), (1, 3), (1, 4),
            (2, 3), (2, 4), 
            (3, 4)
        ]
        self.assertFalse(is_bipartite(vertices, edges))


if __name__ == '__main__':
    unittest.main(exit=False)

Oct 20, 2019 LC 86 [Medium] Partitioning Linked List


Question: Given a linked list and a value x, partition it such that all nodes less than x come before nodes greater than or equal to x.

You should preserve the original relative order of the nodes in each of the two partitions.

Example:

Input: head = 1->4->3->2->5->2, x = 3
Output: 1->2->2->4->3->5

Solution with Two Pointers: https://repl.it/@trsong/Partitioning-Linked-List

import unittest

def partition(lst, target_x):
    if not lst:
        return None

    p1 = dummy1 = Node(-1)
    p2 = dummy2 = Node(-1)
    while lst:
        if lst.val < target_x:
            p1.next = lst
            p1 = p1.next
        else:
            p2.next = lst
            p2 = p2.next
        lst = lst.next
    p2.next = None
    p1.next = dummy2.next
    return dummy1.next


##############################
# Below are testing utilities
##############################
class Node(object):
    def __init__(self, val, next=None):
        self.val = val
        self.next = next

    @staticmethod
    def flatten(lst):
        res = []
        while lst:
            res.append(lst.val)
            lst = lst.next
        return res

    @staticmethod
    def create(*vals):
        t = dummy = Node(-1)
        for v in vals:
            t.next = Node(v)
            t = t.next
        return dummy.next

class PartitionSpec(unittest.TestCase):
    def assert_result(self, expected_list, result_list, target_x):
        expected_arr = Node.flatten(expected_list)
        result_arr = Node.flatten(result_list)
        
        self.assertEqual(len(expected_arr), len(result_arr))
        split_index = 0
        for i in xrange(len(expected_arr)):
            if expected_arr[i] >= target_x:
                split_index = i
                break
        e1, e2 = set(expected_arr[:split_index]), set(expected_arr[split_index:]) 
        r1, r2 = set(result_arr[:split_index]), set(result_arr[split_index:]) 
    
        self.assertEqual(e1, r1)
        self.assertEqual(e2, r2)

    def test_example(self):
        original  = Node.create(1, 4, 3, 2, 5, 2)
        expected = Node.create(1, 2, 2, 4, 3, 5)
        target_x = 3
        self.assert_result(expected, partition(original, target_x), target_x)

    def test_empty_list(self):
        self.assertIsNone(partition(None, 42))

    def test_one_element_list(self):
        original  = Node.create(1)
        expected = Node.create(1)
        target_x = 0
        self.assert_result(expected, partition(original, target_x), target_x)
        target_x = 1
        self.assert_result(expected, partition(original, target_x), target_x)

    def test_list_with_duplicated_elements(self):
        original  = Node.create(1, 1, 0, 1, 1)
        expected = Node.create(0, 1, 1, 1, 1)
        target_x = 1
        self.assert_result(expected, partition(original, target_x), target_x)

    def test_list_with_duplicated_elements2(self):
        original  = Node.create(0, 2, 0, 2, 0)
        expected = Node.create(0, 0, 0, 2, 2)
        target_x = 1
        self.assert_result(expected, partition(original, target_x), target_x)

    def test_list_with_duplicated_elements3(self):
        original  = Node.create(1, 1, 1, 1)
        expected = Node.create(1, 1, 1, 1)
        target_x = 2
        self.assert_result(expected, partition(original, target_x), target_x)

    def test_unsorted_array(self):
        original  = Node.create(10, 4, 20, 10, 3)
        expected = Node.create(3, 10, 4, 20, 10)
        target_x = 3
        self.assert_result(expected, partition(original, target_x), target_x)

    def test_unsorted_array2(self):
        original  = Node.create(1, 4, 3, 2, 5, 2, 3)
        expected = Node.create(1, 2, 2, 3, 3, 4, 5)
        target_x = 3
        self.assert_result(expected, partition(original, target_x), target_x)


if __name__ == '__main__':
    unittest.main(exit=False)

Oct 19, 2019 [Medium] Min Range Needed to Sort


Question: Given a list of integers, return the bounds of the minimum range that must be sorted so that the whole list would be sorted.

Example 1:

Input: [1, 7, 9, 5, 7, 8, 10]
Output: (1, 5)
Explanation:
The numbers between index 1 and 5 are out of order and need to be sorted.

Example 2:

Input: [10, 12, 20, 30, 25, 40, 32, 31, 35, 50, 60]
Output: (3, 8)

Example 3:

Input: [0, 1, 15, 25, 6, 7, 30, 40, 50]
Output: (2, 5)

My thoughts: A sorted array has no min range to sort. So we want first identity the range (i, j) that goes wrong, that is, we want to identify first i and last j that makes array not sorted. ie. smallest i such that nums[i] > nums[i+1], largest j such that nums[j] < nums[j-1].

Secondly, range (i, j) inclusive is where we should start. And there could be number smaller than nums[i+1] and bigger than nums[j-1], therefore we need to figure out how we can release the boundary of (i, j) to get (i', j') where i' <= i and j' <= j so that i', j' covers those smallest and largest number within (i, j).

After doing that, we will get smallest range to make original array sorted, the range is i' through j' inclusive.

Solution with Two Pointers: https://repl.it/@trsong/Min-Range-Needed-to-Sort

import unittest

def min_range_to_sort(nums):
    if len(nums) <= 1:
        return None

    n = len(nums)
    i = 0
    while i < n-1 and nums[i] <= nums[i+1]:
        i += 1
    
    if i == n-1:
        # array is sorted
        return None
    
    j = n - 1
    while j > i and nums[j-1] <= nums[j]:
        j -= 1

    sub_nums = nums[i:j+1]
    min_val = min(sub_nums)
    max_val = max(sub_nums)

    while i > 0 and nums[i-1] > min_val:
        i -= 1

    while j < n - 1 and nums[j+1] < max_val:
        j += 1

    return i, j


class MinRangeToSortSpec(unittest.TestCase):
    def test_example1(self):
        self.assertEqual((1, 5), min_range_to_sort([1, 7, 9, 5, 7, 8, 10]))
        
    def test_example2(self):
        self.assertEqual((3, 8), min_range_to_sort([10, 12, 20, 30, 25, 40, 32, 31, 35, 50, 60]))

    def test_example3(self):
        self.assertEqual((2, 5), min_range_to_sort([0, 1, 15, 25, 6, 7, 30, 40, 50]))

    def test_empty_array(self):
        self.assertIsNone(min_range_to_sort([]))

    def test_already_sorted_array(self):
        self.assertIsNone(min_range_to_sort([1, 2, 3, 4]))

    def test_array_contains_one_elem(self):
        self.assertIsNone(min_range_to_sort([42]))

    def test_reverse_sorted_array(self):
        self.assertEqual((0, 3), min_range_to_sort([4, 3, 2, 1]))

    def test_table_shape_array(self):
        self.assertEqual((2, 5), min_range_to_sort([1, 2, 3, 3, 3, 2]))

    def test_increase_decrease_then_increase(self):
        self.assertEqual((2, 6), min_range_to_sort([1, 2, 3, 4, 3, 2, 3, 4, 5, 6]))

    def test_increase_decrease_then_increase2(self):
        self.assertEqual((0, 4), min_range_to_sort([0, 1, 2, -1, 1, 2]))

    def test_increase_decrease_then_increase3(self):
        self.assertEqual((0, 6), min_range_to_sort([0, 1, 2, 99, -99, 1, 2]))
        self.assertEqual((0, 6), min_range_to_sort([0, 1, 2, -99, 99, 1, 2]))
    
    def test_array_contains_duplicated_numbers(self):
        self.assertEqual((0, 5), min_range_to_sort([1, 1, 1, 0, -1, -1, 1, 1, 1]))

    def test_array_contains_one_outlier(self):
        self.assertEqual((3, 6), min_range_to_sort([0, 0, 0, 1, 0, 0, 0]))
        self.assertEqual((0, 3), min_range_to_sort([0, 0, 0, -1, 0, 0, 0]))


if __name__ == '__main__':
    unittest.main(exit=False)

Oct 18, 2019 LC 47 [Medium] All Distinct Permutations


Question: Print all distinct permutations of a given string with duplicates Given a string that may contain duplicates, write a function to return all permutations of given string such that no permutation is repeated in output.

Example 1:

Input: "112"
Output: ["112", "121", "211"]

Example 2:

Input: "AB"
Output: ["AB", "BA"]

Example 3:

Input: "ABC"
Output: ["ABC", "ACB", "BAC", "BCA", "CBA", "CAB"]

Example 4:

Input: "ABA"
Output: ["ABA", "AAB", "BAA"]

Solution with Backtrack: https://repl.it/@trsong/All-Distinct-Permutation

import unittest

def distinct_permutation(s):
    def backtrack(res, path, arr):
        if not arr:
            res.append(''.join(path))
        else:
            # Each level of recursion, take one char out
            for i in xrange(len(arr)):
                if i > 0 and arr[i] == arr[i-1]:
                    # skip duplicated chars
                    continue
                backtrack(res, path + [arr[i]], arr[:i] + arr[i+1:])
    
    res = []
    arr = sorted(list(s))
    backtrack(res, [], arr)
    return res


class DistinctPermutationSpec(unittest.TestCase):
    def assert_result(self, los1, los2):
        self.assertEqual(sorted(los1), sorted(los2))

    def test_example1(self):
        input = "112"
        output = ["112", "121", "211"]
        self.assert_result(output, distinct_permutation(input))

    def test_example2(self):
        input = "AB"
        output = ["AB", "BA"]
        self.assert_result(output, distinct_permutation(input))

    def test_example3(self):
        input = "ABC"
        output =  ["ABC", "ACB", "BAC", "BCA", "CBA", "CAB"]
        self.assert_result(output, distinct_permutation(input))

    def test_example4(self):
        input = "ABA"
        output = ["ABA", "AAB", "BAA"]
        self.assert_result(output, distinct_permutation(input))

    def test_empty_input(self):
        input = ""
        output = [""]
        self.assert_result(output, distinct_permutation(input))

    def test_input_with_unique_char(self):
        input = "FFF"
        output = ["FFF"]
        self.assert_result(output, distinct_permutation(input))

    def test_unsorted_string(self):
        input = "1212"
        output = ["1122", "2112", "2211", "1212", "2121", "1221"]
        self.assert_result(output, distinct_permutation(input))


if __name__ == '__main__':
    unittest.main(exit=False)

Oct 17, 2019 LC 722 [Medium] Remove Comments


Question: Given a C/C++ program, remove comments from it.

Example 1:

Input: 
source = ["/*Test program */", "int main()", "{ ", "  // variable declaration ", "int a, b, c;", "/* This is a test", "   multiline  ", "   comment for ", "   testing */", "a = b + c;", "}"]

Output: ["int main()","{ ","  ","int a, b, c;","a = b + c;","}"]

Example 2:

Input: 
source = ["a/*comment", "line", "more_comment*/b"]

Output: ["ab"]

Solution: https://repl.it/@trsong/Remove-Comments

import unittest

def remove_comments(source):
    is_in_comment = False
    res = []
    sub_lines = []
    for line in source:
        i = j = 0
        n = len(line)
        if not is_in_comment:
            sub_lines = []
        while j < n:
            if is_in_comment and line[j] == "*" and j + 1 < n and line[j+1] == "/":
                is_in_comment = False
                j += 2
                i = j
            elif not is_in_comment and line[j] == "/" and j + 1 < n and line[j+1] == "*":
                sub_lines.append(line[i:j])   
                j += 2
                is_in_comment = True          
            elif not is_in_comment and line[j:j+2] == "//":
                sub_lines.append(line[i:j])
                break
            else:
                j += 1
                
        if not is_in_comment and j == n:
            sub_lines.append(line[i:j])
                
        filtered_sub_lines = filter(lambda line: len(line) > 0, sub_lines)
        if not is_in_comment and len(filtered_sub_lines) > 0:
            res.append(''.join(filtered_sub_lines))
            
    return res


class RemoveCommentSpec(unittest.TestCase):
    def test_example1(self):
        source = ["/*Test program */", "int main()", "{ ", "  // variable declaration ", "int a, b, c;", "/* This is a test", "   multiline  ", "   comment for ", "   testing */", "a = b + c;", "}"]
        output = ["int main()","{ ","  ","int a, b, c;","a = b + c;","}"]
        self.assertEqual(output, remove_comments(source))

    def test_example2(self):
        source = ["a/*comment", "line", "more_comment*/b"]
        output = ["ab"]
        self.assertEqual(output, remove_comments(source))

    def test_empty_source(self):
        self.assertEqual([], remove_comments([]))
        self.assertEqual([], remove_comments(["//"]))
        self.assertEqual([], remove_comments(["/*","","*/"]))

    def test_block_comments_has_line_comment(self):
        source = ["return 1;", "/*", "function // ", "*/"]
        output = ["return 1;"]
        self.assertEqual(output, remove_comments(source))

    def test_multiple_block_comment_on_same_line(self):
        source = ["return 1 /*don't*/+ 2/*don't*//*don't*/ - 1; "]
        output = ["return 1 + 2 - 1; "]
        self.assertEqual(output, remove_comments(source))


if __name__ == '__main__':
    unittest.main(exit=False)

Oct 16, 2019 [Medium] Count Arithmetic Subsequences


Question: Given an array of n positive integers. The task is to count the number of Arithmetic Subsequence in the array. Note: Empty sequence or single element sequence is also Arithmetic Sequence.

Example 1:

Input : arr[] = [1, 2, 3]
Output : 8
Arithmetic Subsequence from the given array are:
[], [1], [2], [3], [1, 2], [2, 3], [1, 3], [1, 2, 3].

Example 2:

Input : arr[] = [10, 20, 30, 45]
Output : 12

Example 3:

Input : arr[] = [1, 2, 3, 4, 5]
Output : 23

Similar Question: LC 446 Arithmetic Slices II - Subsequence

My thoughts: this problem can be solved with DP: defined as dp[i][d] represents number of arithemtic subsequence end at index i with common difference d. dp[i][d] = dp[j][d] + 1 where d = nums[i] - nums[j] for all j < i. Thus the total number of arithemtic subsequence = sum of dp[i][d] for all i, d.

Solution with DP: https://repl.it/@trsong/Count-Arithmetic-Subsequences

import unittest

def count_arithmetic_subsequence(nums):
    if not nums:
        return 1
    
    n = len(nums)
    # let dp[i][d] represents number of arithemtic subsequence end at index i with common difference d
    #     dp[i][d] = dp[j][d] + 1 where d = nums[i] - nums[j]
    # The total number of arithemtic subsequence = sum of dp[i][d] for all i, d
    dp = [{} for _ in xrange(n)]
    res = n + 1
    for i in xrange(n):
        for j in xrange(i):
            d = nums[i] - nums[j]
            dp[i][d] = dp[i].get(d, 0) + dp[j].get(d, 0) + 1
        res += sum(dp[i].values())
    return res


class CountArithmeticSubsequenceSpec(unittest.TestCase):
    def test_example1(self):
        # All arithemtic subsequence: [], [1], [2], [3], [1, 2], [2, 3], [1, 3], [1, 2, 3].
        self.assertEqual(8, count_arithmetic_subsequence([1, 2, 3]))

    def test_example2(self):
        self.assertEqual(12, count_arithmetic_subsequence([10, 20, 30, 45]))

    def test_example3(self):
        self.assertEqual(23, count_arithmetic_subsequence([1, 2, 3, 4, 5]))

    def test_empty_array(self):
        self.assertEqual(1, count_arithmetic_subsequence([]))

    def test_array_with_one_element(self):
        self.assertEqual(2, count_arithmetic_subsequence([1]))

    def test_array_with_two_element(self):
        # All arithemtic subsequence: [], [1], [2], [1, 2]
        self.assertEqual(4, count_arithmetic_subsequence([1, 2]))

    def test_array_with_unique_number(self):
        self.assertEqual(8, count_arithmetic_subsequence([1, 1, 1]))

    def test_contains_duplicate_number(self):
        self.assertEqual(12, count_arithmetic_subsequence([2, 1, 1, 1]))


if __name__ == '__main__':
    unittest.main(exit=False)

Oct 15, 2019 [Medium] Insert into Sorted Circular Linked List


Question: Insert a new value into a sorted circular linked list (last element points to head). Return the node with smallest value.

My thoughts: This question isn’t hard. It’s just it has so many edge case we need to cover:

  • original list is empty
  • original list contains duplicate numbers
  • the first element of original list is not the smallest
  • the insert elem is the smallest
  • the insert elem is the largest
  • etc.

Solution: https://repl.it/@trsong/Insert-into-Sorted-Circular-Linked-List

import unittest

def insert(lst, val):
    target_node = Node(val)
    if lst is None:
        target_node.next = target_node
        return target_node
    
    root = lst
    while root.next != lst and root.val <= root.next.val:
        root = root.next

    # root is max, root.next is min
    if val < root.next.val:
        # the inserted node is the new min
        target_node.next = root.next
        root.next = target_node
        return target_node
    elif val > root.val:
        # the inserted node is the new max
        target_node.next = root.next
        root.next = target_node
        return target_node.next

    last = root
    # proceed max, now root is the min
    root = root.next
    lst = root

    while lst != last and lst.next.val < val:
        lst = lst.next
  
    target_node.next = lst.next
    lst.next = target_node
    return root
    

##############################
# Below are testing utilities
##############################
class Node(object):
    def __init__(self, val, next=None):
        self.val = val
        self.next = next

 
    def __eq__(self, other):
        l1 = Node.flatten(self)
        l2 = Node.flatten(other)
        if l1 != l2:
            print str(l1), '!=', str(l2)
        return l1 == l2

    def __str__(self):
        return str(Node.flatten(self))

    @staticmethod
    def flatten(lst):
        if not lst:
            return []
        root = lst
        res = [root.val]
        lst = lst.next
        while lst != root:
            res.append(lst.val)
            lst = lst.next
        return res

    @staticmethod
    def create(*vals):
        dummy = Node(-1)
        t = dummy
        for v in vals:
            t.next = Node(v)
            t = t.next
        t.next = dummy.next
        return dummy.next

class InsertSpec(unittest.TestCase):
    def test_empty_list(self):
        self.assertEqual(Node.create(1), insert(None, 1))

    def test_prepend_list(self):
        self.assertEqual(Node.create(0, 1), insert(Node.create(1), 0))

    def test_append_list(self):
        self.assertEqual(Node.create(1, 2, 3), insert(Node.create(1, 2), 3))

    def test_insert_into_correct_position(self):
        self.assertEqual(Node.create(1, 2, 3, 4, 5), insert(Node.create(1, 2, 4, 5), 3))

    def test_duplicated_elements(self):
        self.assertEqual(Node.create(0, 0, 1, 2), insert(Node.create(0, 0, 2), 1))
    
    def test_duplicated_elements2(self):
        self.assertEqual(Node.create(0, 0, 1), insert(Node.create(0, 0), 1))

    def test_duplicated_elements3(self):
        self.assertEqual(Node.create(0, 0, 0, 0), insert(Node.create(0, 0, 0), 0))

    def test_first_element_is_not_smallest(self):
        self.assertEqual(Node.create(0, 1, 2, 3), insert(Node.create(2, 3, 0), 1))

    def test_first_element_is_not_smallest2(self):
        self.assertEqual(Node.create(0, 1, 2, 3), insert(Node.create(3, 0, 2), 1))

    def test_first_element_is_not_smallest3(self):
        self.assertEqual(Node.create(0, 1, 2, 3), insert(Node.create(2, 0, 1), 3))

    def test_first_element_is_not_smallest4(self):
        self.assertEqual(Node.create(0, 1, 2, 3), insert(Node.create(2, 3, 1), 0))

    def test_first_element_is_not_smallest5(self):
        self.assertEqual(Node.create(0, 0, 1, 2, 2), insert(Node.create(2, 0, 0, 2), 1))

    def test_first_element_is_not_smallest6(self):
        self.assertEqual(Node.create(-1, 0, 0, 2, 2), insert(Node.create(2, 0, 0, 2), -1))


if __name__ == '__main__':
    unittest.main(exit=False)

Oct 14, 2019 [Easy] Word Ordering in a Different Alphabetical Order


Question: Given a list of words, and an arbitrary alphabetical order, verify that the words are in order of the alphabetical order.

Example:

Input:
words = ["abcd", "efgh"],
order="zyxwvutsrqponmlkjihgfedcba"

Output: False
Explanation: 'e' comes before 'a' so 'efgh' should come before 'abcd'

Example 2:

Input:
words = ["zyx", "zyxw", "zyxwy"],
order="zyxwvutsrqponmlkjihgfedcba"

Output: True
Explanation: The words are in increasing alphabetical order

Solution: https://repl.it/@trsong/Word-Ordering-in-a-Different-Alphabetical-Order

import unittest


def is_sorted(words, order):
    if not words or len(words) <= 1:
        return True

    score = dict(zip(order, xrange(len(order))))
    for i in xrange(1, len(words)):
        cur_word = words[i]
        prev_word = words[i-1]
        is_short_circuit = False
        for prev_char, cur_char in zip(prev_word, cur_word):
            if score[prev_char] < score[cur_char]:
                is_short_circuit = True
                break
            if score[prev_char] > score[cur_char]:
                return False
        if not is_short_circuit and len(prev_word) > len(cur_word):
            return False
    return True
    

class IsSortedSpec(unittest.TestCase):
    def test_example1(self):
        words = ["abcd", "efgh"]
        order = "zyxwvutsrqponmlkjihgfedcba"
        self.assertFalse(is_sorted(words, order))

    def test_example2(self):
        words = ["zyx", "zyxw", "zyxwy"]
        order = "zyxwvutsrqponmlkjihgfedcba"
        self.assertTrue(is_sorted(words, order))

    def test_empty_list(self):
        self.assertTrue(is_sorted([], ""))
        self.assertTrue(is_sorted([], "abc"))

    def test_one_elem_list(self):
        self.assertTrue(is_sorted(["z"], "xyz"))

    def test_empty_words(self):
        self.assertTrue(is_sorted(["", "", ""], ""))

    def test_word_of_different_length(self):
        words = ["", "1", "11", "111", "1111"]
        order = "4321"
        self.assertTrue(is_sorted(words, order))

    def test_word_of_different_length2(self):
        words = ["", "11", "", "111", "1111"]
        order = "1"
        self.assertFalse(is_sorted(words, order))

    def test_large_word_dictionary(self):
        words = ["123", "1a1b1A2ca", "ABC", "Aaa", "aaa", "bbb", "c11", "cCa"]
        order = "".join(map(chr, range(256)))
        self.assertTrue(is_sorted(words, order))
    

if __name__ == '__main__':
    unittest.main(exit=False)

Oct 13, 2019 LC 525 [Medium] Largest Subarray with Equal Number of 0s and 1s


Question: Given an array containing only 0s and 1s, find the largest subarray which contain equal number of 0s and 1s. Expected time complexity is O(n).

Example 1:

Input: arr[] = [1, 0, 1, 1, 1, 0, 0]
Output: 1 to 6 (Starting and Ending indexes of output subarray)

Example 2:

Input: arr[] = [1, 1, 1, 1]
Output: No such subarray

Example 3:

Input: arr[] = [0, 0, 1, 1, 0]
Output: 0 to 3 Or 1 to 4

Solution: https://repl.it/@trsong/Largest-Subarray-with-Equal-Number-of-0s-and-1s

import unittest

def largest_even_subarray(nums):
    if not nums:
        return None

    count_map = {0:-1}
    count = 0
    max_length = 0
    max_start_location = None
    for i, num in enumerate(nums):
        if num == 1:
            count += 1
        else:
            count -= 1

        if count not in count_map:
            count_map[count] = i
        elif i - count_map[count] > max_length:
            max_length = i - count_map[count]
            max_start_location = count_map[count]

    if max_start_location is not None:
        return max_start_location + 1, max_start_location + max_length
    else:
        None


class LargestEvenSubarraySpec(unittest.TestCase):
    def test_example1(self):
        self.assertEqual((1, 6), largest_even_subarray([1, 0, 1, 1, 1, 0, 0]))

    def test_example2(self):
        self.assertTrue(largest_even_subarray([0, 0, 1, 1, 0]) in [(0, 3), (1, 4)])

    def test_entire_array_is_even(self):
        self.assertEqual((0, 1), largest_even_subarray([0, 1]))

    def test_no_even_subarray(self):
        self.assertIsNone(largest_even_subarray([0, 0, 0, 0, 0]))
        self.assertIsNone(largest_even_subarray([1]))
        self.assertIsNone(largest_even_subarray([]))

    def test_larger_array(self):
        self.assertEqual((0, 9), largest_even_subarray([0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1]))

    def test_larger_array2(self):
        self.assertEqual((3, 8), largest_even_subarray([1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1]))

    def test_larger_array3(self):
        self.assertEqual((0, 13), largest_even_subarray([1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1])) 


if __name__ == '__main__':
    unittest.main(exit=False)

Oct 12, 2019 [Easy] Most Frequent Subtree Sum


Question: Given the root of a binary tree, find the most frequent subtree sum. The subtree sum of a node is the sum of all values under a node, including the node itself.

Example:

Given the following tree:

  5
 / \
2  -5

Return 2 as it occurs twice: once as the left leaf, and once as the sum of 2 + 5 - 5.

Solution: https://repl.it/@trsong/Most-Frequent-Subtree-Sum

import unittest

class TreeNode(object):
    def __init__(self, val, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


def calculate_tree_sum_recur(t, tree_sum_histogram):
    if t is None:
        return 0
    tree_sum = t.val + calculate_tree_sum_recur(t.left, tree_sum_histogram) + calculate_tree_sum_recur(t.right, tree_sum_histogram)
    tree_sum_histogram[tree_sum] = tree_sum_histogram.get(tree_sum, 0) + 1
    return tree_sum


def most_freq_tree_sum(tree):
    if tree is None:
        return None

    tree_sum_histogram = {}
    calculate_tree_sum_recur(tree, tree_sum_histogram)
    max_sum_count = 0
    max_sum = None
    for tree_sum, freq in tree_sum_histogram.items():
        if freq > max_sum_count:
            max_sum = tree_sum
            max_sum_count = freq

    return max_sum


class MostFreqTreeSumSpec(unittest.TestCase):
    def test_example1(self):
        """
           5
          / \
         2  -5
        """
        t = TreeNode(5, TreeNode(2), TreeNode(-5))
        self.assertEqual(2, most_freq_tree_sum(t))

    def test_empty_tree(self):
        self.assertIsNone(most_freq_tree_sum(None))

    def test_tree_with_unique_value(self):
        """
          0
         / \
        0   0
         \
          0
        """
        l = TreeNode(0, right=TreeNode(0))
        t = TreeNode(0, l, TreeNode(0))
        self.assertEqual(0, most_freq_tree_sum(t))

    def test_depth_3_tree(self):
        """
           _0_ 
          /   \
         0     -3  
        / \   /  \   
       1  -1 3   -1  
        """
        l = TreeNode(0, TreeNode(1), TreeNode(-1))
        r = TreeNode(-3, TreeNode(3), TreeNode(-1))
        t = TreeNode(0, l, r)
        self.assertEqual(-1, most_freq_tree_sum(t))
        

if __name__ == '__main__':
    unittest.main(exit=False)

Oct 11, 2019 [Hard] Largest Divisible Pairs Subset


Question: Given a set of distinct positive integers, find the largest subset such that every pair of elements in the subset (i, j) satisfies either i % j = 0 or j % i = 0.

For example, given the set [3, 5, 10, 20, 21], you should return [5, 10, 20]. Given [1, 3, 6, 24], return [1, 3, 6, 24].

My thoughts: Notice that smaller number mod large number can never be zero, but the other way around might be True. e.g. 2 % 4 == 2. 9 % 3 == 0. Thus we can eliminate the condition i % j = 0 or j % i = 0 to just j % i = 0 where j is larger. This can be done by sorting into descending order.

After that we can use dp to solve this problem. Define dp[i] to be the largest number of divisible pairs ends at i. And max(dp) will give the largest number of division pairs among all i. By backtracking from the index, we can find what are these numbers.

Solution with DP: https://repl.it/@trsong/Largest-Divisible-Pairs-Subset

import unittest

def largest_divisible_pairs_subset(nums):
    n = len(nums)
    if n < 2:
        return []
    descending_nums = sorted(nums, reverse=True)

    # Let dp[i] be the largest number of divisible pairs ends at i
    dp = [0] * n
    dp[0] = 1
    parent = [None] * n
    for i in xrange(1, n):
        max_num_pairs = 0
        cur_num = descending_nums[i]
        for j in xrange(i):
            # Check among all multiple of cur_num, find max number of pairs among them
            if descending_nums[j] % cur_num == 0 and dp[j] > max_num_pairs:
                max_num_pairs = dp[j]
                parent[i] = j
        dp[i] = max_num_pairs + 1
    
    # Backtracking from the start position of largest number of divisible pairs
    i = dp.index(max(dp))
    res = []
    while i is not None:
        res.append(descending_nums[i])
        i = parent[i]
    return res if len(res) > 1 else []

class LargestDivisiblePairsSubsetSpec(unittest.TestCase):
    def test_example1(self):
        self.assertEqual(set([5, 10, 20]), set(largest_divisible_pairs_subset([3, 5, 10, 20, 21])))

    def test_example2(self):
        self.assertEqual(set([1, 3, 6, 24]), set(largest_divisible_pairs_subset([1, 3, 6, 24])))

    def test_multiple_of_3_and_5(self):
        self.assertEqual(set([10, 5, 20]), set(largest_divisible_pairs_subset([10, 5, 3, 15, 20])))

    def test_prime_and_multiple_of_3(self):
        self.assertEqual(set([18, 1, 3, 6]), set(largest_divisible_pairs_subset([18, 1, 3, 6, 13, 17])))

    def test_decrease_array(self):
        self.assertEqual(set([8, 4, 2, 1]), set(largest_divisible_pairs_subset([8, 7, 6, 5, 4, 2, 1])))

    def test_array_with_duplicated_values(self):
        self.assertEqual(set([3, 3, 3, 1]), set(largest_divisible_pairs_subset([2, 2, 3, 3, 3, 1])))

    def test_no_divisible_pairs(self):
        self.assertEqual([], largest_divisible_pairs_subset([2, 3, 5, 7, 11, 13, 17, 19]))

    def test_no_divisible_pairs2(self):
        self.assertEqual([], largest_divisible_pairs_subset([1]))
        self.assertEqual([], largest_divisible_pairs_subset([]))


if __name__ == '__main__':
    unittest.main(exit=False)

Oct 10, 2019 LC 134 [Medium] Gas Station


Question: There are N gas stations along a circular route, where the amount of gas at station i is gas[i].

You have a car with an unlimited gas tank and it costs cost[i] of gas to travel from station i to its next station (i+1). You begin the journey with an empty tank at one of the gas stations.

Return the starting gas station’s index if you can travel around the circuit once in the clockwise direction, otherwise return -1.

Note:

  • If there exists a solution, it is guaranteed to be unique.
  • Both input arrays are non-empty and have the same length.
  • Each element in the input arrays is a non-negative integer.

Example 1:

Input: 
gas  = [1,2,3,4,5]
cost = [3,4,5,1,2]

Output: 3

Explanation:
Start at station 3 (index 3) and fill up with 4 unit of gas. Your tank = 0 + 4 = 4
Travel to station 4. Your tank = 4 - 1 + 5 = 8
Travel to station 0. Your tank = 8 - 2 + 1 = 7
Travel to station 1. Your tank = 7 - 3 + 2 = 6
Travel to station 2. Your tank = 6 - 4 + 3 = 5
Travel to station 3. The cost is 5. Your gas is just enough to travel back to station 3.
Therefore, return 3 as the starting index.

Example 2:

Input: 
gas  = [2,3,4]
cost = [3,4,3]

Output: -1

Explanation:
You can't start at station 0 or 1, as there is not enough gas to travel to the next station.
Let's start at station 2 and fill up with 4 unit of gas. Your tank = 0 + 4 = 4
Travel to station 0. Your tank = 4 - 3 + 2 = 3
Travel to station 1. Your tank = 3 - 3 + 3 = 3
You cannot travel back to station 2, as it requires 4 unit of gas but you only have 3.
Therefore, you can't travel around the circuit once no matter where you start.

My thougths: If there exists such a gas station at index i then for all stop we will have gas_level >= 0 when we reach k for all k. However if starts i, i+1, …, k and at k, gas_level < 0. Then instead of starting again from i+1, we starts from k+1 as without i sum from i+1 to k can only go lower.

Solution: https://repl.it/@trsong/Gas-Station

import unittest

def valid_starting_station(gas, cost):
    gas_level = 0
    # gross_net_gas_gain is the sum of all gas gain or loss from index 0
    # actually it is also the total gas gain or less when starts from any indices = all gas gain - all gas loss. 
    gross_net_gas_gain = 0
    i = 0
    for increase, decrease, k in zip(gas, cost, xrange(len(gas))):
        net_gas_gain = increase - decrease
        gross_net_gas_gain += net_gas_gain
        gas_level += net_gas_gain
        if gas_level < 0:
            # if sum of net gas gain from last i all the way to k < 0
            # instead of start a new i form i + 1, we jump to k + 1
            # Even with gas gain at i i, sum to k < 0, then without gas gain at i, it will be even smaller. The same case happens for all index from i to k, so we jump to k + 1
            gas_level = 0
            i = (k + 1) % len(gas)
    return i if gross_net_gas_gain >= 0 else -1


class ValidStartingStationSpec(unittest.TestCase):
    def assert_valid_starting_station(self, gas, cost, start):
        gas_level = 0
        n = len(gas)
        for i in range(n):
            shifted_index = (start + i) % n
            gas_level += gas[shifted_index] - cost[shifted_index]
            self.assertTrue(gas_level >= 0)

    def test_example1(self):
        gas  = [1, 2, 3, 4, 5]
        cost = [3, 4, 5, 1, 2]
        start = valid_starting_station(gas, cost)
        self.assert_valid_starting_station(gas, cost, start)

    def test_example2(self):
        gas  = [2, 3, 4]
        cost = [3, 4, 3]
        self.assertEqual(-1, valid_starting_station(gas, cost))

    def test_decreasing_gas_level(self):
        gas = [1, 1, 1]
        cost = [2, 2, 2]
        self.assertEqual(-1, valid_starting_station(gas, cost))

    def test_increasing_gas_level(self):
        gas = [2, 1, 0]
        cost = [1, 1, 0]
        start = valid_starting_station(gas, cost)
        self.assert_valid_starting_station(gas, cost, start)

    def test_decreasing_increasing_decreasing_increasing(self):
        gas = [0, 1, 0, 13]
        cost = [3, 0, 10, 1]
        start = valid_starting_station(gas, cost)
        print start
        self.assert_valid_starting_station(gas, cost, start)

    def test_total_gas_level_decrease(self):
        gas = [3, 2, 1, 0, 0]
        cost = [2, 3, 1, 1, 0]
        self.assertEqual(-1, valid_starting_station(gas, cost))

    def test_total_gas_level_increase(self):
        gas = [1, 1, 0, 2]
        cost = [0, 0, -3, 0]
        start = valid_starting_station(gas, cost)
        self.assert_valid_starting_station(gas, cost, start)


if __name__ == '__main__':
    unittest.main(exit=False)

Oct 9, 2019 [Hard] Number of Ways to Divide an Array into K Equal Sum Sub-arrays


Question: Given an integer K and an array arr[] of N integers, the task is to find the number of ways to split the array into K equal sum sub-arrays of non-zero lengths.

Example 1:

Input: arr[] = [0, 0, 0, 0], K = 3
Output: 3
All possible ways are:
[[0], [0], [0, 0]]
[[0], [0, 0], [0]]
[[0, 0], [0], [0]]

Example 2:

Input: arr[] = [1, -1, 1, -1], K = 2
Output: 1

My thoughts: This problem is so hard to think and even harder to deal with bugs related to index. I ended up w/ wasting lots of time figuring out the correct index.

The idea is to first figure out what is the target subarray sum. This can be achieved by sum of all element divide by K. But what if K is zero? So we need special handling for that.

After we figure out the target subarray sum, it’s still hard to come up w/ dp solution because one of the coordinate acctually represents spot to insert splitter.

Imagining the following: in order to break an array in to K parts, we can have K - 1 splitters fit into spots between each element. Like 0 | 0 0 | 0. And we can pre-compute the prefix_sum at index i. Therefore we can quickly tell if the i-th spot can put a split or not. e.g. 0 | 1 0 1 cannot put split here as the prefix_sum does not satisfy the target subarray sum.

One dp solution is to let dp[i][k] represents problem size i and k remaining spliters. We know in the end dp[n][k+1] represents array size n and k+1 spliter will be the goal. So we compute backward and figure out dp[0][1] as the prefix_sum for index 0 is 0 and always qualify target subarray sum so we put 1 splitter there.

The dp recursive relation is like the following: dp[i][k] = dp[i+1][k] when we cannot split at i due to the prefix_sum[i] not qualified. Or dp[i][k] = dp[i-1][k] + dp[i+1][k+1] if prefix_sum[i] qualified.

Qualified means if we split at i, then the prefix_sum[i] can be broken into remaining k subarrays and each of them has target subarray sum. e.g. 0 1 0 1 | 0 1 If K == 3 and we know that 0 1 0 1 sum to 2 and is (k-1) * target_sub_array_sum = 2 which means it can be break into k-1 subarray with suitable subarray sum. And we can do that again for 0 1 | 0 1.

Solution with DP: https://repl.it/@trsong/Number-of-Ways-to-Divide-an-Array-into-K-Equal-Sum-Sub-array

import unittest

def num_k_subarray(nums, K):
    if K <= 0 or not nums or K > len(nums):
        return 0
    nums_sum = sum(nums)
    if nums_sum % K > 0:
        return 0
    n = len(nums)
    subarray_sum = nums_sum // K
    prefix_sum = nums[:]
    for i in xrange(1, n):
        prefix_sum[i] += prefix_sum[i-1]
    
    # Let dp[i][k] represents number of qualified subarray for remaining array size i and remaining splitter k
    # Remember in order to break into K parts, we will need K+1 splitters
    # dp[i][k] = dp[i+1][k]                if we can cannot split at i as prefix_sum not qualified 
    #          = dp[i+1][k] + dp[i+1][k+1] if we can split as the prefix_sum[i] is qualified
    dp = [[0 for _ in xrange(K+2)] for _ in xrange(n+1)]
    dp[n][K+1] = 1
    for i in xrange(n-1, -1, -1):
        for k in xrange(K, 0, -1):
            dp[i][k] = dp[i+1][k]
            if subarray_sum == 0 and prefix_sum[i] == 0 or subarray_sum != 0 and prefix_sum[i] == subarray_sum * k:
                dp[i][k] += dp[i+1][k+1]
    
    # If remaining array has size 0 and there is one splitter
    return dp[0][1]


class NumKSubarraySpec(unittest.TestCase):
    def test_example1(self):
        """
        0 0|0|0
        0|0 0|0
        0|0|0 0
        """
        self.assertEqual(3, num_k_subarray([0, 0, 0, 0], 3))

    def test_exampl2(self):
        """
        1 -1|1 -1
        """
        self.assertEqual(1, num_k_subarray([1, -1, 1, -1], 2))

    def test_contains_negative_numbers(self):
        """
        1 1|-2 2 1 1
        1 1 -2 2|1 1
        """
        self.assertEqual(2, num_k_subarray([1, 1, -2, 2, 1, 1], 2))

    def test_array_with_unique_number(self):
        """
        1|1|1|1|1|1
        """
        self.assertEqual(1, num_k_subarray([1, 1, 1, 1, 1, 1], 6))

    def test_K_equals_4(self):
        """
        0 0 1|0 1 0|0 0 1|0 1 0
        0 0 1 0|1 0|0 0 1|0 1 0
        0 0 1 0|1|0 0 0 1|0 1 0
        0 0 1 0|1 0 0|0 1|0 1 0
        0 0 1 0|1 0 0 0|1|0 1 0
        ...
        0 0 1|0 1 0|0 0 1|0 1 0
        Subarray Sum: 4 / 4 = 1
        Number of ways to place Splitters: 2 * 4 * 2 = 16
        """
        self.assertEqual(16, num_k_subarray([0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0], 4))

    def test_K_equals_0(self):
        self.assertEqual(0, num_k_subarray([1, 2], 0))
        self.assertEqual(0, num_k_subarray([], 0))

    def test_empty_array(self):
        self.assertEqual(0, num_k_subarray([], 3))

    def test_K_too_big(self):
        self.assertEqual(0, num_k_subarray([1, 1, 1], 4))

    def test_K_equals_1(self):
        self.assertEqual(0, num_k_subarray([], 1)) # subarray must be non-zero length
        self.assertEqual(1, num_k_subarray([1, 2, 3], 1))

    def test_sum_not_multiple_or_K(self):
        self.assertEqual(0, num_k_subarray([1, 1, 1], 2))

        
if __name__ == '__main__':
    unittest.main(exit=False)

Oct 8, 2019 [Easy] Count Number of Unival Subtrees


Question: A unival tree is a tree where all the nodes have the same value. Given a binary tree, return the number of unival subtrees in the tree.

Example 1:

The following tree should return 5:

   0
  / \
 1   0
    / \
   1   0
  / \
 1   1

The 5 trees are:
- The three single '1' leaf nodes. (+3)
- The single '0' leaf node. (+1)
- The [1, 1, 1] tree at the bottom. (+1)

Example 2:

Input: root of below tree
              5
             / \
            1   5
           / \   \
          5   5   5
Output: 4
There are 4 subtrees with single values.

Example 3:

Input: root of below tree
              5
             / \
            4   5
           / \   \
          4   4   5                
Output: 5
There are five subtrees with single values.

Solution: https://repl.it/@trsong/Count-Number-of-Unival-Subtrees

import unittest

class TreeNode(object):
    def __init__(self, val, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right

def count_subtree_helper(tree):
    if not tree:
        return 0, True
    left_count, is_left_unival = count_subtree_helper(tree.left)
    right_count, is_right_unival = count_subtree_helper(tree.right)
    is_current_unival = is_left_unival and is_right_unival
    if is_current_unival and tree.left and tree.left.val != tree.val:
        is_current_unival = False
    if is_current_unival and tree.right and tree.right.val != tree.val:
        is_current_unival = False
    count = left_count + right_count + (1 if is_current_unival else 0)
    return count, is_current_unival


def count_unival_subtrees(tree):
    return count_subtree_helper(tree)[0]


class CountUnivalSubTreeSpec(unittest.TestCase):
    def test_example1(self):
        """
           0
          / \
         1   0
            / \
           1   0
          / \
         1   1
        """
        rl = TreeNode(1, TreeNode(1), TreeNode(1))
        r = TreeNode(0, rl, TreeNode(0))
        root = TreeNode(0, TreeNode(1), r)
        self.assertEqual(5, count_unival_subtrees(root))

    def test_example2(self):
        """
              5
             / \
            1   5
           / \   \
          5   5   5
        """
        l = TreeNode(1, TreeNode(5), TreeNode(5))
        r = TreeNode(5, right=TreeNode(5))
        root = TreeNode(5, l, r)
        self.assertEqual(4, count_unival_subtrees(root))

    def test_example3(self):
        """
              5
             / \
            4   5
           / \   \
          4   4   5  
        """
        l = TreeNode(4, TreeNode(4), TreeNode(4))
        r = TreeNode(5, right=TreeNode(5))
        root = TreeNode(5, l, r)
        self.assertEqual(5, count_unival_subtrees(root))

    def test_empty_tree(self):
        self.assertEqual(0, count_unival_subtrees(None))

    def test_left_heavy_tree(self):
        """
            1
           /
          1
         / \ 
        1   0
        """
        root = TreeNode(1, TreeNode(1, TreeNode(1), TreeNode(0)))
        self.assertEqual(2, count_unival_subtrees(root))

    def test_right_heavy_tree(self):
        """
          0
         / \
        1   0
             \
              0
               \
                0
        """
        rr = TreeNode(0, right=TreeNode(0))
        r = TreeNode(0, right=rr)
        root = TreeNode(0, TreeNode(1), r)
        self.assertEqual(4, count_unival_subtrees(root))

    def test_unival_tree(self):
        """
            0
           / \
          0   0
         /   /
        0   0          
        """
        l = TreeNode(0, TreeNode(0))
        r = TreeNode(0, TreeNode(0))
        root = TreeNode(0, l, r)
        self.assertEqual(5, count_unival_subtrees(root))

    def test_distinct_value_trees(self):
        """
               _0_
              /   \
             1     2
            / \   / \
           3   4 5   6
          /
         7
        """
        n1 = TreeNode(1, TreeNode(3, TreeNode(7)), TreeNode(4))
        n2 = TreeNode(2, TreeNode(5), TreeNode(6))
        n0 = TreeNode(0, n1, n2)
        self.assertEqual(4, count_unival_subtrees(n0))

if __name__ == '__main__':
    unittest.main(exit=False)

Oct 7, 2019 [Easy] One-to-one Character Mapping


Question: Determine whether there exists a one-to-one character mapping from one string s1 to another s2.

For example, given s1 = ‘abc’ and s2 = ‘bcd’, return True since we can map a to b, b to c, and c to d.

Given s1 = ‘foo’ and s2 = ‘bar’, return False since the o cannot map to two characters.

Solution: https://repl.it/@trsong/One-to-one-Character-Mapping

import unittest

CHAR_SPACE_SIZE = 256

def is_one_to_one_mapping(s1, s2):
    n1, n2 = len(s1), len(s2)

    if n1 != n2:
        return False

    char_mapping = [None] * CHAR_SPACE_SIZE
    for c1, c2 in zip(s1, s2):
        ord_c1, ord_c2 = ord(c1), ord(c2)
        if char_mapping[ord_c1] is None:
            char_mapping[ord_c1] = ord_c2
        elif char_mapping[ord_c1] != ord_c2:
            return False

    return True


class IsOneToOneMappingSpec(unittest.TestCase):
    def test_example1(self):
        self.assertTrue(is_one_to_one_mapping('abc', 'bcd'))

    def test_example2(self):
        self.assertFalse(is_one_to_one_mapping('foo', 'bar'))

    def test_empty_mapping(self):
        self.assertTrue(is_one_to_one_mapping('', ''))
        self.assertFalse(is_one_to_one_mapping('', ' '))
        self.assertFalse(is_one_to_one_mapping(' ', ''))

    def test_map_strings_with_different_lengths(self):
        self.assertFalse(is_one_to_one_mapping('abc', 'abcd'))
        self.assertFalse(is_one_to_one_mapping('abcd', 'abc'))
    
    def test_duplicated_chars(self):
        self.assertTrue(is_one_to_one_mapping('aabbcc', '112233'))
        self.assertTrue(is_one_to_one_mapping('abccba', '123321'))

    def test_same_domain(self):
        self.assertTrue(is_one_to_one_mapping('abca', 'bcab'))
        self.assertFalse(is_one_to_one_mapping('abca', 'bcaa'))


if __name__ == '__main__':
    unittest.main(exit=False)

Oct 6, 2019 [Medium] Number of Smaller Elements to the Right


Question: Given an array of integers, return a new array where each element in the new array is the number of smaller elements to the right of that element in the original input array.

For example, given the array [3, 4, 9, 6, 1], return [1, 1, 2, 1, 0], since:

  • There is 1 smaller element to the right of 3
  • There is 1 smaller element to the right of 4
  • There are 2 smaller elements to the right of 9
  • There is 1 smaller element to the right of 6
  • There are no smaller elements to the right of 1

My thoughts: Binary-indexed Tree a.k.a BIT or Fenwick Tree is used for dynamic range query. Basically, it allows efficiently query for sum of a continous interval of values.

BIT Usage:

bit = BIT(4)
bit.query(3) # => 0
bit.update(index=0, delta=1) # => [1, 0, 0, 0]
bit.update(index=0, delta=1) # => [2, 0, 0, 0]
bit.update(index=2, delta=3) # => [2, 0, 3, 0]
bit.query(2) # => 2 + 0 + 3 = 5
bit.update(index=1, delta=-1) # => [2, -1, 3, 0]
bit.query(1) # => 2 + -1 = 1

So the way we can solve this problem is to treat each number in array as an index. By BIT querying for sum, we can quickly count how many numbers are less than current number.

Solution with BIT: https://repl.it/@trsong/Number-of-Smaller-Elements-to-the-Right

import unittest

class BIT(object):
    @staticmethod
    def LSB(num):
        return num & -num

    def __init__(self, n):
        self.arr = [0] * (n+1)

    def query(self, k):
        k = k + 1
        sum = 0 
        while k > 0:
            sum += self.arr[k]
            k -= BIT.LSB(k)
        return sum

    def increment(self, i):
        i = i + 1
        while i < len(self.arr):
            self.arr[i] += 1
            i += BIT.LSB(i)


def count_right_smaller_numbers(nums):
    if not nums:
        return []
    max_val = max(nums)
    min_val = min(nums)
    bit = BIT(max_val - min_val + 1)
    n = len(nums)
    nums_count = [0] * n
    for i in xrange(n-1, -1, -1):
        shifted_value = nums[i] - min_val
        # query for number of elements smaller than nums[i]
        nums_count[i] = bit.query(shifted_value - 1)
        bit.increment(shifted_value)
    return nums_count 
 

class CountRightSmallerNuberSpec(unittest.TestCase):
    def test_example(self):
        nums = [3, 4, 9, 6, 1]
        expected_result = [1, 1, 2, 1, 0]
        self.assertEqual(expected_result, count_right_smaller_numbers(nums))

    def test_empty_array(self):
        self.assertEqual([], count_right_smaller_numbers([]))

    def test_ascending_array(self):
        nums = [0, 1, 2, 3, 4]
        expected_result = [0, 0, 0, 0, 0]
        self.assertEqual(expected_result, count_right_smaller_numbers(nums))

    def test_array_with_unique_value(self):
        nums = [1, 1, 1]
        expected_result = [0, 0, 0]
        self.assertEqual(expected_result, count_right_smaller_numbers(nums))

    def test_descending_array(self):
        nums = [4, 3, 2, 1]
        expected_result = [3, 2, 1, 0]
        self.assertEqual(expected_result, count_right_smaller_numbers(nums))

    def test_increase_decrease_increase(self):
        nums = [1, 4, 2, 5, 0, 4]
        expected_result = [1, 2, 1, 2, 0, 0]
        self.assertEqual(expected_result, count_right_smaller_numbers(nums))

    def test_decrease_increase_decrease(self):
        nums = [3, 1, 2, 0]
        expected_result = [3, 1, 1, 0]
        self.assertEqual(expected_result, count_right_smaller_numbers(nums))

    def test_decrease_increase_decrease2(self):
        nums = [12, 1, 2, 3, 0, 11, 4]
        expected_result = [6, 1, 1, 1, 0, 1, 0]
        self.assertEqual(expected_result, count_right_smaller_numbers(nums))

    def test_negative_values(self):
        nums = [8, -2, -1, -2, -1, 3]
        expected_result = [5, 0, 1, 0, 0, 0]
        self.assertEqual(expected_result, count_right_smaller_numbers(nums))


if __name__ == '__main__':
    unittest.main(exit=False)

Oct 5, 2019 LC 375 [Medium] Guess Number Higher or Lower II


Question: We are playing the Guess Game. The game is as follows:

  • I pick a number from 1 to n. You have to guess which number I picked.
  • Every time you guess wrong, I’ll tell you whether the number I picked is higher or lower.
  • However, when you guess a particular number x, and you guess wrong, you pay $x. You win the game when you guess the number I picked.

Given a particular n ≥ 1, find out how much money you need to have to guarantee a win.

Example:

n = 10, I pick 8.

First round:  You guess 5, I tell you that it's higher. You pay $5.
Second round: You guess 7, I tell you that it's higher. You pay $7.
Third round:  You guess 9, I tell you that it's lower. You pay $9.

Game over. 8 is the number I picked.

You end up paying $5 + $7 + $9 = $21.

My thoughts: The guarantee amount is the maximum amount of money you have to pay no matter how unlucky you are. i.e. the strategy is to take the best move given the worest luck.

Suppose n = 4. The best garentee to lose minimum strategy is to first guess 1, if not work, then guess 3. If you are just unlucky, the target number is 2 or 4, then you only need to pay at most $1 + $3 = $4 in worest case scenario whereas other strategies like choosing 1 through 4 one by one will yield $1 + $2 + $3 = $6 in worest case when the target is $4.

The game play strategy is called Minimax, which is basically choose the maximum among the minimum gain.

Solution with Minimax Algorithm: https://repl.it/@trsong/Guess-Number-Game-2

import unittest

def guess_number_between(lo, hi, cache):
    if lo >= hi:
        return 0
    elif cache[lo][hi] is not None:
        return cache[lo][hi]
    
    res = float('-inf')
    for i in xrange(lo, hi+1):
        # Assuming we are just so unlucky.
        # The guarantee amount should always be the best among those worest choices
        res = max(res, -i + min(guess_number_between(lo, i-1, cache), guess_number_between(i+1, hi, cache)))
    cache[lo][hi] = res
    return res
    

def guess_number(n):
    cache = [[None for _ in range(n+1)] for _ in range(n+1)]
    return -guess_number_between(1, n, cache)


class GuessNumberSpec(unittest.TestCase):
    def test_n_equals_3(self):
        # Worest case target=3
        # choose 2, target is higher, pay $2
        # total = $2
        self.assertEqual(2, guess_number(3)) # choose 2, pay $2

    def test_n_equals_4(self):
        # Worest case target=4
        # choose 1, target is higher, pay $1
        # choose 3, target is higher, pay $3
        # total = $4
        self.assertEqual(4, guess_number(4)) 

    def test_n_equals_5(self):
        # Worest case target=5
        # choose 2, target is higher, pay $2
        # choose 4, target is higher, pay $4
        # total = $6
        self.assertEqual(6, guess_number(5))

    def test_n_equals_10(self):
        # Worest case target=10
        # choose 7, target is higher, pay $7
        # choose 9, target is higher, pay $9
        # total = $16
        self.assertEqual(16, guess_number(10))


if __name__ == '__main__':
    unittest.main(exit=False)

Note: Minimax can be further optimized through Alpha-Beta Pruning: a technique to eliminate choices that cannot make current situation better.

Solution with Minimax Algorithm with Alpha-Beta Pruning: https://repl.it/@trsong/Guess-Number-Game-2-with-Alpha-Beta-Pruning

def best_max_move(lo, hi, cache, alpha, beta):
    if lo >= hi:
        return 0
    elif cache[lo][hi] is not None:
        return cache[lo][hi]

    res = float('-inf')
    for i in xrange(lo, hi+1):
        res = max(res, best_min_move(lo, hi, i, cache, alpha, beta))
        if beta <= res:
            break
        alpha = max(alpha, res)    
    cache[lo][hi] = res
    return res

def best_min_move(lo, hi, i, cache, alpha, beta):
    res = -i + best_max_move(lo, i-1, cache, alpha, beta)
    if res <= alpha:
        return res
    res = min(res, -i + best_max_move(i+1, hi, cache, alpha, beta))
    return res

def guess_number(n):
    cache = [[None for _ in range(n+1)] for _ in range(n+1)]
    return -best_max_move(1, n, cache, float('-inf'), float('inf'))

Oct 4, 2019 [Medium] Maximum Circular Subarray Sum


Question: Given a circular array, compute its maximum subarray sum in O(n) time. A subarray can be empty, and in this case the sum is 0.

Example 1:

Input: [8, -1, 3, 4]
Output: 15 
Explanation: we choose the numbers 3, 4, and 8 where the 8 is obtained from wrapping around.

Example 2:

Input: [-4, 5, 1, 0]
Output: 6 
Explanation: we choose the numbers 5 and 1.

My thoughts: The max circular subarray sum can be divied into sub-problems: max non-circular subarray sum and max circular-only subarray sum.

For max non-circular subarray sum problem, we can use dp[i] to represent max subarray sum end at index i and max(dp) will be the answer.

For max circular-only subarray sum problem, we want to find i, j where i < j such that nums[0] + nums[1] + ... + nums[i] + nums[j] + .... + nums[n-1] reaches maximum. The way we can handle it is to calculate prefix sum and suffix sum array and find max accumulated sum on the left and on the right. The max circular-only subarray sum equals the sum of those two accumulated sum.

Finally, the answer to the original problem is the larger one between answers to above two sub-problems. And one thing worth to notice is that if all elements are negative, then the answer should be 0.

Solution with DP and Prefix Sum: https://repl.it/@trsong/Maximum-Circular-Subarray-Sum

import unittest

def max_subarray_sum_helper(nums):
    n = len(nums)
    # let dp[i] represents max subarray sum ends at index i - 1
    dp = [0] * (n + 1)
    for i in xrange(1, n+1):
        dp[i] = max(dp[i-1] + nums[i-1], nums[i-1])
    return max(dp)


def max_circular_subarray_sum_helper(nums):
    n = len(nums)
    left_accu_sum = 0
    max_left_accu_sum = float('-inf')
    left_max_sum = [0] * n

    right_accu_sum = 0
    max_right_accu_sum = float('-inf')
    right_max_sum = [0] * n

    for i in xrange(n):
        left_accu_sum += nums[i]
        max_left_accu_sum = max(max_left_accu_sum, left_accu_sum)
        left_max_sum[i] = max_left_accu_sum

    for i in xrange(n-1, -1, -1):
        right_accu_sum += nums[i]
        max_right_accu_sum = max(max_right_accu_sum, right_accu_sum)
        right_max_sum[i] = max_right_accu_sum

    max_sum = 0
    for i in xrange(n):
        l_sum = left_max_sum[i-1] if i > 0 else 0
        r_sum = right_max_sum[i+1] if i < n - 1 else 0
        max_sum = max(max_sum, l_sum + r_sum)

    return max_sum


def max_circular_sum(nums):
    if not nums:
        return 0
    return max(0, max_subarray_sum_helper(nums), max_circular_subarray_sum_helper(nums))


class MaxCircularSumSpec(unittest.TestCase):
    def test_example1(self):
        self.assertEqual(15, max_circular_sum([8, -1, 3, 4]))  # 3 + 4 + 8
    
    def test_example2(self):
        self.assertEqual(6, max_circular_sum([-4, 5, 1, 0]))  # 5 + 1

    def test_empty_array(self):
        self.assertEqual(0, max_circular_sum([]))

    def test_negative_array(self):
        self.assertEqual(0, max_circular_sum([-1, -2, -3]))

    def test_circular_array1(self):
        self.assertEqual(22, max_circular_sum([8, -8, 9, -9, 10, -11, 12]))  # 12 + 8 - 8 + 9 - 9 + 10

    def test_circular_array2(self):
        self.assertEqual(23, max_circular_sum([10, -3, -4, 7, 6, 5, -4, -1]))  # 7 + 6 + 5 - 4 -1 + 10

    def test_circular_array3(self):
        self.assertEqual(52, max_circular_sum([-1, 40, -14, 7, 6, 5, -4, -1]))  # 7 + 6 + 5 - 4 - 1 - 1 + 40

    def test_all_positive_array(self):
        self.assertEqual(10, max_circular_sum([1, 2, 3, 4]))


if __name__ == '__main__':
    unittest.main(exit=False)

Oct 3, 2019 [Easy] Shift-Equivalent Strings


Question: Given two strings A and B, return whether or not A can be shifted some number of times to get B.

For example, if A is 'abcde' and B is 'cdeab', return True. If A is 'abc' and B is 'acb', return False.

My thoughts: It should be pretty easy to come up with non-linear time complexity solution. But for linear, I can only come up w/ rolling hash solution. The idea is to treat each digit as a number. For example, "1234" is really 1234, each time we move the most significant bit to right by (1234 - 1 * 10^3) * 10 + 1 = 2341. In general, we can treat 'abc' as numeric value of abc base p0 ie. a * p0^2 + b * p0^1 + c * p0^0 and in order to prevent overflow, we use a larger prime number which I personally prefer 666667 (easy to remember), 'abc' => (a * p0^2 + b * p0^1 + c * p0^0) % p1 where p0 and p1 are both prime and p0 is much smaller than p1.

Solution with Rolling Hash: https://repl.it/@trsong/Shift-Equivalent-Strings

import unittest

P0 = 23  # small prime number
P1 = 666667 # larger prime number

def hash(s):
    res = 0
    for char in s:
        ord_char = ord(char)
        res = (res * P0 % P1 + ord_char) % P1
    return res


def base_at(n):
    res = 1
    for _ in xrange(n-1):
        res = (res * P0) % P1
    return res


def is_shift_eq(source, target):
    if len(source) != len(target):
        return False    
    if source == target:
        return True

    n = len(target)
    most_significant_base = base_at(n)
    target_hash = hash(target)
    source_hash = hash(source)

    for shift_char in source:
        ord_shift_char = ord(shift_char)
        most_significant_value = (ord_shift_char * most_significant_base) % P1
        source_hash = ((source_hash - most_significant_value) * P0 + ord_shift_char) % P1
        if source_hash == target_hash:
            return True
    
    return False


class IsShiftEqSpec(unittest.TestCase):
    def test_example1(self):
        self.assertTrue(is_shift_eq('abcde', 'cdeab'))

    def test_example2(self):
        self.assertFalse(is_shift_eq('abc', 'acb'))

    def test_different_length_strings(self):
        self.assertFalse(is_shift_eq(' a ', ' a'))

    def test_empty_strings(self):
        self.assertTrue(is_shift_eq('', ''))

    def test_string_with_unique_word(self):
        self.assertTrue(is_shift_eq('aaaaa', 'aaaaa'))

    def test_string_with_multiple_spaces(self):
        self.assertFalse(is_shift_eq('aa aa aa', 'aaaa  aa'))

    def test_number_strins(self):
        self.assertTrue(is_shift_eq("567890", "890567"))

    def test_large_string_performance_test(self):
        N = 100000
        source = str(range(N))
        target = source[:N//2] + source[N//2:]
        self.assertTrue(is_shift_eq(source, target))


if __name__ == '__main__':
    unittest.main(exit=False)

Oct 2, 2019 [Medium] Numbers With Equal Digit Sum


Question: Given an array containing integers, find two integers a, b such that sum of digits of a and b is equal. Return maximum sum of a and b. Return -1 if no such numbers exist.

Example 1:

Input: [51, 71, 17, 42, 33, 44, 24, 62]
Output: 133
Explanation: Max sum can be formed by 71 + 62 which has same digit sum of 8

Example 2:

Input: [51, 71, 17, 42]
Output: 93
Explanation: Max sum can be formed by 51 + 42 which has same digit sum of 6

Example 3:

Input: [42, 33, 60]
Output: 102
Explanation: Max sum can be formed by 42 + 60 which has same digit sum of 6

Example 4:

Input: [51, 32, 43]
Output: -1
Explanation: There are no 2 numbers with same digit sum

Solution: https://repl.it/@trsong/Numbers-With-Equal-Digit-Sum

import unittest

def calc_digit_sum(num):
    res = 0
    while num > 0:
        res += num % 10
        num //= 10
    return res


def find_max_digit_sum(nums):
    nums_groupby_digit_sum = {}
    for num in nums:
        digit_sum = calc_digit_sum(num)
        if digit_sum not in nums_groupby_digit_sum:
            nums_groupby_digit_sum[digit_sum] = []
        
        same_digit_sum_nums = nums_groupby_digit_sum[digit_sum]
        if len(same_digit_sum_nums) > 1:
            max_num, min_num = same_digit_sum_nums
            same_digit_sum_nums[0] = max(max_num, num)
            same_digit_sum_nums[1] = max_num + min_num + num - same_digit_sum_nums[0] - min(min_num, num)
        else:
            same_digit_sum_nums.append(num)

    max_digit_sum = -1
    for same_digit_sum_nums in nums_groupby_digit_sum.values():
        if len(same_digit_sum_nums) < 2:
            continue
        max_digit_sum = max(max_digit_sum, sum(same_digit_sum_nums))
    
    return max_digit_sum


class FindMaxDigitSumSpec(unittest.TestCase):
    def test_example1(self):
        self.assertEqual(133, find_max_digit_sum([51, 71, 17, 42, 33, 44, 24, 62]))  # 71 + 62

    def test_example2(self):
        self.assertEqual(93, find_max_digit_sum([51, 71, 17, 42]))  # 51 + 42

    def test_example3(self):
        self.assertEqual(102, find_max_digit_sum([42, 33, 60]))  # 63 + 60

    def test_example4(self):
        self.assertEqual(-1, find_max_digit_sum([51, 32, 43]))

    def test_empty_array(self):
        self.assertEqual(-1, find_max_digit_sum([]))

    def test_same_digit_sum_yet_different_digits(self):
        self.assertEqual(11000, find_max_digit_sum([0, 1, 10, 100, 1000, 10000]))  # 10000 + 1000

    def test_special_edge_case(self):
        self.assertEqual(22, find_max_digit_sum([11, 11, 22, 33]))  # 11 + 11


if __name__ == '__main__':
    unittest.main(exit=False)

Oct 1, 2019 LC 1171 [Medium] Remove Consecutive Nodes that Sum to 0


Question: Given a linked list of integers, remove all consecutive nodes that sum up to 0.

Example 1:

Input: 10 -> 5 -> -3 -> -3 -> 1 -> 4 -> -4
Output: 10
Explanation: The consecutive nodes 5 -> -3 -> -3 -> 1 sums up to 0 so that sequence should be removed. 4 -> -4 also sums up to 0 too so that sequence should also be removed.

Example 2:

Input: 1 -> 2 -> -3 -> 3 -> 1
Output: 3 -> 1
Note: 1 -> 2 -> 1 would also be accepted.

Example 3:

Input: 1 -> 2 -> 3 -> -3 -> 4
Output: 1 -> 2 -> 4

Example 4:

Input: 1 -> 2 -> 3 -> -3 -> -2
Output: 1

My thoughts: This question is just the list version of Contiguous Sum to K. The idea is exactly the same, in previous question: sum[i:j] can be achieved use prefix[j] - prefix[i-1] where i <= j, whereas for this question, we can use map to store the “prefix” sum: the sum from the head node all the way to current node. And by checking the prefix so far, we can easily tell if there is a node we should have seen before that has “prefix” sum with same value. i.e. There are consecutive nodes that sum to 0 between these two nodes.

Solution: https://repl.it/@trsong/Remove-Consecutive-Nodes-that-Sum-to-0

import unittest

class ListNode(object):
    def __init__(self, val, next=None):
        self.val = val
        self.next = next

    def __eq__(self, other):
        res = str(self) == str(other)
        if not res:
            print str(self), '!=' , str(other)
        return res

    def __str__(self):
        current_node = self
        result = []
        while current_node:
            result.append(current_node.val)
            current_node = current_node.next
        return str(result)

    @staticmethod  
    def List(*vals):
        dummy = ListNode(-1)
        p = dummy
        for elem in vals:
            p.next = ListNode(elem)
            p = p.next
        return dummy.next  


def remove_zero_sum_sublists(head):
    if not head:
        return None
    
    dummy = ListNode(-1, head)
    node_with_prefix = {0: dummy}
    p = head
    sum_so_far = 0
    while p:
        sum_so_far += p.val
        if sum_so_far in node_with_prefix:
            sum_to_remove = sum_so_far
            t = node_with_prefix[sum_so_far].next
            while t != p:
                sum_to_remove += t.val
                del node_with_prefix[sum_to_remove]
                t = t.next
                    
            node_with_prefix[sum_so_far].next = p.next
        else:
            node_with_prefix[sum_so_far] = p
        p = p.next
    return dummy.next


class RemoveZeroSumSublistSpec(unittest.TestCase):
    def test_example1(self):
        self.assertEqual(ListNode.List(10), remove_zero_sum_sublists(ListNode.List(10, 5, -3, -3, 1, 4, -4)))

    def test_example2(self):
        self.assertEqual(ListNode.List(3, 1), remove_zero_sum_sublists(ListNode.List(1, 2, -3, 3, 1)))

    def test_example3(self):
        self.assertEqual(ListNode.List(1, 2, 4), remove_zero_sum_sublists(ListNode.List(1, 2, 3, -3, 4)))

    def test_example4(self):
        self.assertEqual(ListNode.List(1), remove_zero_sum_sublists(ListNode.List(1, 2, 3, -3, -2)))

    def test_empty_list(self):
        self.assertEqual(ListNode.List(), remove_zero_sum_sublists(ListNode.List()))

    def test_all_zero_list(self):
        self.assertEqual(ListNode.List(), remove_zero_sum_sublists(ListNode.List(0, 0, 0)))

    def test_add_up_to_zero_list(self):
        self.assertEqual(ListNode.List(), remove_zero_sum_sublists(ListNode.List(1, -1, 0, -1, 1)))

    def test_overlap_section_add_to_zero(self):
        self.assertEqual(ListNode.List(1, 5, -1, -2, 99), remove_zero_sum_sublists(ListNode.List(1, -1, 2, 3, 0, -3, -2, 1, 5, -1, -2, 99)))


if __name__ == '__main__':
    unittest.main(exit=False)

Sep 30, 2019 LC 139 [Medium] Word Break


Question: Given a non-empty string s and a dictionary wordDict containing a list of non-empty words, determine if s can be segmented into a space-separated sequence of one or more dictionary words.

Note:

  • The same word in the dictionary may be reused multiple times in the segmentation.
  • You may assume the dictionary does not contain duplicate words.

Example 1:

Input: s = "Pseudocode", wordDict = ["Pseudo", "code"]
Output: True
Explanation: Return true because "Pseudocode" can be segmented as "Pseudo code".

Example 2:

Input: s = "applepenapple", wordDict = ["apple", "pen"]
Output: True
Explanation: Return true because "applepenapple" can be segmented as "apple pen apple".
             Note that you are allowed to reuse a dictionary word.

Example 3:

Input: s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"]
Output: False

My thoughts: This question feels almost the same as LC 279 Minimum Number of Squares Sum to N. The idea is to think about the problem backwards and you may want to ask yourself: what makes s[:n] to be True? There must exist some word with length m where m < n such that s[:n-m] is True and string s[n-m:n] is in the dictionary. Therefore, the problem size shrinks from n to m and it will go all the way to empty string which definitely is True.

Solution with DP: https://repl.it/@trsong/Word-Break

import unittest

def word_break(s, word_dict):
    if not s:
        return True
    
    n = len(s)
    # dp[n] represents where s[:n] can be word break
    # dp[k] = True only if dp[i] = True and s[i:k] is in the dictionary
    dp = [False] * (n + 1)
    dp[0] = True
    for k in xrange(1, n+1):
        for word in word_dict:
            word_len = len(word)
            if word_len <= k and dp[k-word_len] and word == s[k-word_len:k]:
                # Once make sure s[:k] satisfied, just short circuit and move to next k
                dp[k] = True
                break
    return dp[n]


class WordBreakSpec(unittest.TestCase):
    def test_example1(self):
        self.assertTrue(word_break("Pseudocode", ["Pseudo", "code"]))

    def test_example2(self):
        self.assertTrue(word_break("applepenapple", ["apple", "pen"]))

    def test_example3(self):
        self.assertFalse(word_break("catsandog", ["cats", "dog", "sand", "and", "cat"]))

    def test_word_in_dict_is_a_prefix(self):
        self.assertTrue(word_break("123456", ["12", "123", "456"]))
    
    def test_word_in_dict_is_a_prefix2(self):
        self.assertTrue(word_break("123456", ["12", "123", "456"]))

    def test_word_in_dict_is_a_prefix3(self):
        self.assertFalse(word_break("123456", ["12", "12345", "456"]))

    def test_empty_word(self):
        self.assertTrue(word_break("", ['a']))
        self.assertTrue(word_break("", []))
        self.assertFalse(word_break("a", []))

    def test_use_same_word_twice(self):
        self.assertTrue(word_break("aaabaaa", ["aaa", "b"]))

    def test_impossible_word_combination(self):
        self.assertFalse(word_break("aaaaa", ["aaa", "aaaa"]))
        

if __name__ == '__main__':
    unittest.main(exit=False)

Sep 29, 2019 LC 773 [Hard] Sliding Puzzle


Question: On a 2x3 board, there are 5 tiles represented by the integers 1 through 5, and an empty square represented by 0. Given a puzzle board, return the least number of moves required so that the state of the board is solved. If it is impossible for the state of the board to be solved, return -1.

Note:

  • A move consists of choosing 0 and a 4-directionally adjacent number and swapping it.
  • The state of the board is solved if and only if the board is [[1,2,3],[4,5,0]].

Example 1:

Input: board = [
    [1, 2, 3],
    [4, 0, 5]
]
Output: 1
Explanation: Swap the 0 and the 5 in one move.

Example 2:

Input: board = [
    [1, 2, 3],
    [5, 4, 0]
]
Output: -1
Explanation: No number of moves will make the board solved.

Example 3:

Input: board = [
    [4, 1, 2],
    [5, 0, 3]
]
Output: 5
Explanation: 5 is the smallest number of moves that solves the board.
An example path:
After move 0: [
    [4, 1, 2],
    [5, 0, 3]
]
After move 1: [
    [4, 1, 2],
    [0, 5, 3]
]
After move 2: [
    [0, 1, 2],
    [4, 5, 3]
]
After move 3: [
    [1, 0, 2],
    [4, 5, 3]
]
After move 4: [
    [1, 2, 0],
    [4, 5, 3]
]
After move 5: [
    [1, 2, 3],
    [4, 5, 0]
]

Example 4:

Input: board = [
    [3, 2, 4],
    [1, 5, 0]
]
Output: 14

My thoughts: This is a typical solution searching problem. BFS/DFS/A* will work. Probably BFS easier to implement than others: just figure out how to encode intial board state, goal state and state transition function. However this reason why I choose this problem is to demonstrate A* search.

Approach 1: 2 x 3 Puzzle BFS Solution https://repl.it/@trsong/Sliding-Puzzle

import unittest
from Queue import Queue


def hash_state(board):
    res = 0
    for row in board:
        for cell in row:
            res = res * 10 + cell
    return res


def state_to_board_and_start(state, n, m):
    grid = [[0 for _ in range(m)] for _ in range(n)]
    start = None
    for r in range(n - 1, -1, -1):
        for c in range(m - 1, -1, -1):
            grid[r][c] = state % 10
            if grid[r][c] == 0:
                start = (r, c)
            state //= 10
    return grid, start


def next_move_states(state, n, m):
    grid, start = state_to_board_and_start(state, n, m)
    r, c = start
    directions = [(1, 0), (0, -1), (-1, 0), (0, 1)]
    res = []
    for dr, dc in directions:
        new_r, new_c = r + dr, c + dc
        if 0 <= new_r < n and 0 <= new_c < m:
            grid[r][c], grid[new_r][new_c] = grid[new_r][new_c], grid[r][c]
            res.append(hash_state(grid))
            grid[r][c], grid[new_r][new_c] = grid[new_r][new_c], grid[r][c]
    return res


def solve_puzzle(board):
    if not board or not board[0]:
        return -1

    n, m = len(board), len(board[0])
    goal_state = hash_state([list(range(1, n*m)) + [0]])
    queue = Queue()
    visited = set()
    initial_state = hash_state(board)
    queue.put(initial_state)
    depth = 0
    while not queue.empty():
        for _ in range(queue.qsize()):
            current_state = queue.get()
            if current_state in visited:
                continue
            elif current_state == goal_state:
                return depth

            visited.add(current_state)
            for next_state in next_move_states(current_state, n, m):
                if next_state in visited:
                    continue
                queue.put(next_state)
        depth += 1
    return -1


class SolvePuzzleSpec(unittest.TestCase):
    def test_example(self):
        self.assertEqual(1, solve_puzzle([
            [1, 2, 3],
            [4, 0, 5]
        ]))  # 0 -> 5

    def test_example2(self):
        self.assertEqual(-1, solve_puzzle([
            [1, 2, 3],
            [5, 4, 0]
        ]))

    def test_example3(self):
        self.assertEqual(5, solve_puzzle([
            [4, 1, 2],
            [5, 0, 3]
        ]))  # 0 -> 5 -> 4 -> 1 -> 2 -> 3

    def test_example4(self):
        self.assertEqual(14, solve_puzzle([
            [3, 2, 4],
            [1, 5, 0]
        ]))

    def test_back_tracking_3(self):
        self.assertEqual(3, solve_puzzle([
            [0, 1, 3],
            [4, 2, 5]
        ]))  # 0 -> 1 -> 2 -> 5

    def test_back_tacking_4(self):
        self.assertEqual(4, solve_puzzle([
            [1, 5, 2],
            [0, 4, 3]
        ]))  # 0 -> 4 -> 5 -> 2 -> 3

   def test_finish_state(self):
        self.assertEqual(0, solve_puzzle([
            [1, 2, 3],
            [4, 5, 0]
        ]))

if __name__ == '__main__':
    unittest.main(exit=False)

Approach 2: n x m Puzzle AStar Search Solution https://repl.it/@trsong/Sliding-Puzzle-AStar-Search

import unittest
from Queue import PriorityQueue

# Suppose puzzle size < 23
p = 23


def hash_state(board):
    res = 0
    for row in board:
        for cell in row:
            res = res * p + cell
    return res


def state_to_board_and_start(state, n, m):
    grid = [[0 for _ in range(m)] for _ in range(n)]
    start = None
    for r in range(n - 1, -1, -1):
        for c in range(m - 1, -1, -1):
            grid[r][c] = state % p
            if grid[r][c] == 0:
                start = (r, c)
            state //= p
    return grid, start


def heuristic_cost(board):
    # Define the heuristic cost to be the total manhattan distance of each misplaced puzzle piece
    res = 0
    n, m = len(board), len(board[0])
    for r in range(n):
        for c in range(m):
            val = board[r][c]
            if val == 0:
                continue
            target_r, target_c = val // m, val % m

            # The original co-ordinates start from 0, shift left by 1
            if target_c == 0:
                target_c = m - 1
                target_r -= 1
            else:
                target_r - 1
            distance = abs(r - target_r) + abs(c - target_c)
            res += distance
    return res


def next_move_states_and_cost(state, n, m):
    grid, start = state_to_board_and_start(state, n, m)
    r, c = start
    directions = [(1, 0), (0, -1), (-1, 0), (0, 1)]
    res = []
    edge_cost = 1
    for dr, dc in directions:
        new_r, new_c = r + dr, c + dc
        if 0 <= new_r < n and 0 <= new_c < m:
            # apply move to grid
            grid[r][c], grid[new_r][new_c] = grid[new_r][new_c], grid[r][c]

            # calculate next move
            remaining_cost_estimate = heuristic_cost(grid)
            res.append((hash_state(grid), edge_cost + remaining_cost_estimate))

            # restore previous state
            grid[r][c], grid[new_r][new_c] = grid[new_r][new_c], grid[r][c]
    return res


def solve_puzzle(board):
    if not board or not board[0]:
        return -1

    n, m = len(board), len(board[0])
    goal_state = hash_state([list(range(1, n * m)) + [0]])
    pq = PriorityQueue()
    initial_state = hash_state(board)
    pq.put((0, initial_state))
    visited = set()
    state_cost = {initial_state: 0}
    edge_cost = 1

    while not pq.empty():
        _, current_state = pq.get()
        if current_state in visited:
            continue

        visited.add(current_state)
        cost_so_far = state_cost[current_state]
        for next_state, remaining_cost in next_move_states_and_cost(current_state, n, m):
            is_visited = next_state in visited
            is_a_larger_path = next_state in state_cost and cost_so_far + edge_cost > state_cost[next_state]
            if is_visited or is_a_larger_path:
                continue
            state_cost[next_state] = cost_so_far + edge_cost
            pq.put((cost_so_far + remaining_cost, next_state))

    return state_cost[goal_state] if goal_state in state_cost else -1


class SolvePuzzleSpec(unittest.TestCase):
    def test_heuristic_function_should_not_overestimate(self):
        self.assertEqual(8, solve_puzzle([
            [0, 1, 2],
            [5, 6, 3],
            [4, 7, 8],
        ]))  # 0 -> 1 -> 2 -> 3 -> 6 -> 5 -> 4 -> 7 -> 8

    ## Time Consuming Test!
    # def test_not_solvable_puzzle(self):
    #     self.assertEqual(-1, solve_puzzle([
    #         [8, 1, 2],
    #         [0, 4, 3],
    #         [7, 6, 5],
    #     ]))


if __name__ == '__main__':
    unittest.main(exit=False)

Sep 28, 2019 LC 286 [Medium] Walls and Gates


Question: You are given a m x n 2D grid initialized with these three possible values.

  • -1 - A wall or an obstacle.
  • 0 - A gate.
  • INF - Infinity means an empty room. We use the value 2^31 - 1 = 2147483647 to represent INF as you may assume that the distance to a gate is less than 2147483647.

Fill each empty room with the distance to its nearest gate. If it is impossible to reach a gate, it should be filled with INF.

Example:

Given the 2D grid:

INF  -1  0  INF
INF INF INF  -1
INF  -1 INF  -1
  0  -1 INF INF

After running your function, the 2D grid should be:

  3  -1   0   1
  2   2   1  -1
  1  -1   2  -1
  0  -1   3   4

My thoughts: Most of time, the BFS you are familiar with has only one starting point and searching from that point onward will produce the shortest path from start to visited points. For multi-starting points, it works exactly as single starting point. All you need to do is to imagine a single vitual starting point connecting to all starting points. Moreover, the way we achieve that is to put all starting point into the queue before doing BFS.

Solution with BFS: https://repl.it/@trsong/Walls-and-Gates

import unittest
import sys
from Queue import Queue

INF = sys.maxint

def nearest_gate(grid):
    if not grid or not grid[0]:
        return
    
    n, m = len(grid), len(grid[0])
    queue = Queue()
    for r in xrange(n):
        for c in xrange(m):
            if grid[r][c] == 0:
                queue.put((r, c))

    depth = 0
    direction = [[1, 0], [0, -1], [-1, 0], [0, 1]]
    while not queue.empty():
        for _ in xrange(queue.qsize()):
            r, c = queue.get()
            if 0 < grid[r][c] < INF:
                continue
            grid[r][c] = depth
            for dr, dc in direction:
                new_r, new_c = r + dr, c + dc
                if 0 <= new_r < n and 0 <= new_c < m and grid[new_r][new_c] == INF:
                    queue.put((new_r, new_c))
        depth += 1


class NearestGateSpec(unittest.TestCase):
    def test_example(self):
        grid = [
            [INF,  -1,   0, INF],
            [INF, INF, INF,  -1],
            [INF,  -1, INF,  -1],
            [  0,  -1, INF, INF]
        ]
        expected_grid = [
            [  3,  -1,   0,   1],
            [  2,   2,   1,  -1],
            [  1,  -1,   2,  -1],
            [  0,  -1,   3,   4]
        ]
        nearest_gate(grid)
        self.assertEqual(expected_grid, grid)

    def test_unreachable_room(self):
        grid = [
            [INF, -1],
            [ -1,  0]
        ]
        expected_grid = [
            [INF, -1],
            [ -1,  0]
        ]
        nearest_gate(grid)
        self.assertEqual(expected_grid, grid)

    def test_no_gate_exists(self):
        grid = [
            [-1,   -1],
            [INF, INF]
        ]
        expected_grid = [
            [-1,   -1],
            [INF, INF]
        ]
        nearest_gate(grid)
        self.assertEqual(expected_grid, grid)

    def test_all_gates_no_room(self):
        grid = [
            [0, 0, 0],
            [0, 0, 0]
        ]
        expected_grid = [
            [0, 0, 0],
            [0, 0, 0]
        ]
        nearest_gate(grid)
        self.assertEqual(expected_grid, grid)

    def test_empty_grid(self):
        grid = []
        nearest_gate(grid)
        self.assertEqual([], grid)

    def test_1D_grid(self):
        grid = [[INF, 0, INF, INF, INF, 0, INF, 0, 0, -1, INF]]
        expected_grid = [[1, 0, 1, 2, 1, 0, 1, 0, 0, -1, INF]]
        nearest_gate(grid)
        self.assertEqual(expected_grid, grid)

    def test_multi_gates(self):
        grid = [
            [INF, INF,  -1,   0, INF],
            [INF, INF, INF, INF, INF],
            [  0, INF, INF, INF,   0],
            [INF, INF,  -1, INF, INF]
        ]
        expected_grid = [
            [  2,   3,  -1,   0,   1],
            [  1,   2,   2,   1,   1],
            [  0,   1,   2,   1,   0],
            [  1,   2,  -1,   2,   1]
        ]
        nearest_gate(grid)
        self.assertEqual(expected_grid, grid)

    def test_at_center(self):
        grid = [
            [INF, INF, INF, INF, INF],
            [INF, INF, INF, INF, INF],
            [INF, INF,   0, INF, INF],
            [INF, INF, INF, INF, INF]
        ]
        expected_grid = [
            [  4,   3,   2,   3,   4],
            [  3,   2,   1,   2,   3],
            [  2,   1,   0,   1,   2],
            [  3,   2,   1,   2,   3]
        ]
        nearest_gate(grid)
        self.assertEqual(expected_grid, grid)


if __name__ == '__main__':
    unittest.main(exit=False)

Sep 27, 2019 [Medium] Construct BST from Post-order Traversal


Question: Given the sequence of keys visited by a postorder traversal of a binary search tree, reconstruct the tree.

Example:

Given the sequence 2, 4, 3, 8, 7, 5, you should construct the following tree:

    5
   / \
  3   7
 / \   \
2   4   8

Solution: https://repl.it/@trsong/Construct-BST-from-Post-order-Traversal

import unittest
import sys

class Node(object):
    def __init__(self, val, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right

    def __eq__(self, other):
        return other and other.val == self.val and other.left == self.left and other.right == self.right


def construct_bst(post_order_traversal):
    if not post_order_traversal:
        return None
    reverse_order = post_order_traversal[::-1]
    class Context:
        index = 0

    def construct_bst_recur(min_val, max_val):
        if Context.index >= len(reverse_order):
            return None
            
        current_val = reverse_order[Context.index]
        if current_val < min_val or current_val > max_val:
            return None
        Context.index += 1
        current = Node(current_val)
        current.right = construct_bst_recur(current_val, max_val)
        current.left = construct_bst_recur(min_val, current_val)
        return current

    return construct_bst_recur(-sys.maxint, sys.maxint)


class ConstructBSTSpec(unittest.TestCase):
    def test_example(self):
        """
            5
           / \
          3   7
         / \   \
        2   4   8
        """
        post_order_traversal = [2, 4, 3, 8, 7, 5]
        n3 = Node(3, Node(2), Node(4))
        n7 = Node(7, right=Node(8))
        n5 = Node(5, n3, n7)
        self.assertEqual(n5, construct_bst(post_order_traversal))

    def test_empty_bst(self):
        self.assertIsNone(construct_bst([]))

    def test_left_heavy_bst(self):
        """
            3
           /
          2
         /
        1
        """
        self.assertEqual(Node(3, Node(2, Node(1))), construct_bst([1, 2, 3]))

    def test_right_heavy_bst(self):
        """
          1
         / \
        0   3
           / \
          2   4
               \
                5
        """
        post_order_traversal = [0, 2, 5, 4, 3, 1]
        n3 = Node(3, Node(2), Node(4, right=Node(5)))
        n1 = Node(1, Node(0), n3)
        self.assertEqual(n1, construct_bst(post_order_traversal))

    def test_complete_binary_tree(self):
        """
             3
           /   \
          1     5
         / \   / 
        0   2 4
        """
        post_order_traversal = [0, 2, 1, 4, 5, 3]
        n1 = Node(1, Node(0), Node(2))
        n5 = Node(5, Node(4))
        n3 = Node(3, n1, n5)
        self.assertEqual(n3, construct_bst(post_order_traversal))

    def test_right_left_left(self):
        """
          1
         / \
        0   4
           /
          3
         /
        2
        """
        post_order_traversal = [0, 2, 3, 4, 1]
        n4 = Node(4, Node(3, Node(2)))
        n1 = Node(1, Node(0), n4)
        self.assertEqual(n1, construct_bst(post_order_traversal))

    def test_left_right_right(self):
        """
          4
         / \
        1   5
         \
          2
           \
            3
        """
        post_order_traversal = [3, 2, 1, 5, 4]
        n1 = Node(1, right=Node(2, right=Node(3)))
        n4 = Node(4, n1, Node(5))
        self.assertEqual(n4, construct_bst(post_order_traversal))


if __name__ == '__main__':
    unittest.main(exit=False)

Sep 26, 2019 [Hard] Ordered Minimum Window Subsequence


Question: Given an array nums and a subsequence sub, find the shortest subarray of nums that contains sub.

  • If such subarray does not exist, return -1, -1.
  • Note that the subarray must contain the elements of sub in the correct order.

Example:

Input: nums = [1, 2, 3, 5, 8, 7, 6, 9, 5, 7, 3, 0, 5, 2, 3, 4, 4, 7], sub = [5, 7]
Output: start = 8, size = 2

Solution with DP: https://repl.it/@trsong/Ordered-Minimum-Window-Subsequence

import unittest
import sys

def min_window(nums, sub):
    if not nums:
        return -1, -1
    n, m = len(nums), len(sub)
    # dp[i][j] represents the largest index between [0, i) such that nums[:i] contains subsequence sub[:j]
    # then dp[i][0] = i.
    # and dp[i][j] = dp[i-1][j-1] if nums[i-1] matches sub[j-1]
    #              = dp[i-1][j]   otherwise
    # dp[i][m] will be the largest index that contains subsequence sub
    dp = [[-1 for _ in xrange(m+1)] for _ in xrange(n+1)]
    min_size = sys.maxint
    for i in xrange(n+1):
        dp[i][0] = i
    
    for i in xrange(1, n+1):
        # we cannot have j > i as we want to check if  nums[:i] contains sub[:j] or not
        for j in xrange(1, min(m, i)+1):
            if nums[i-1] == sub[j-1]:
                dp[i][j] = dp[i-1][j-1]
            else:
                dp[i][j] = dp[i-1][j]

    for i in xrange(n+1):
        if dp[i][m] != -1:
            size = i - dp[i][m]
            if size < min_size:
                start = dp[i][m]
                min_size = size

    if min_size == sys.maxint or min_size < m:
        return -1, -1
    else:
        return start, min_size


class MinWindowSpec(unittest.TestCase):
    def test_example(self):
        nums = [1, 2, 3, 5, 8, 7, 6, 9, 5, 7, 3, 0, 5, 2, 3, 4, 4, 7]
        sub = [5, 7]
        self.assertEqual((8, 2), min_window(nums, sub))

    def test_sub_not_exits(self):
        nums = [0, 1, 0, 2, 0, 0, 3, 4]
        sub = [2, 1, 3]
        self.assertEqual((-1, -1), min_window(nums, sub))

    def test_nums_is_empty(self):
        self.assertEqual((-1, -1), min_window([], [42]))
    
    def test_sub_is_empty(self):
        self.assertEqual((0, 0), min_window([1, 4, 3, 2], []))

    def test_both_nums_and_sub_are_empty(self):
        self.assertEqual((-1, -1), min_window([], []))

    def test_duplicated_numbers(self):
        nums = [1, 1, 1, 1]
        sub = [1, 1, 1, 1]
        self.assertEqual((0, 4), min_window(nums, sub))

    def test_duplicated_numbers2(self):
        nums = [1, 1, 1]
        sub = [1, 1, 1, 1]
        self.assertEqual((-1, -1), min_window(nums, sub))

    def test_duplicated_numbers3(self):
        nums = [1, 1]
        sub = [1, 0]
        self.assertEqual((-1, -1), min_window(nums, sub))

    def test_min_window(self):
        nums = [1, 0, 2, 0, 0, 1, 0, 2, 1, 1, 2]
        sub = [1, 2, 1]
        self.assertEqual((5, 4), min_window(nums, sub))
        sub2 = [1, 2, 2, 2, 1]
        self.assertEqual((-1, -1), min_window(nums, sub2))

    def test_moving_window(self):
        nums = [1, 1, 2, 1, 2, 3, 1, 2]
        sub = [1, 2, 3]
        self.assertEqual((3, 3), min_window(nums, sub))

    def test_min_window2(self):
        nums = [1, 1, 1, 0, 2, 2, 1, 1, 2, 2, 2, 2]
        sub = [1, 2]
        self.assertEqual((7, 2), min_window(nums, sub))


if __name__ == '__main__':
    unittest.main(exit=False)

Sep 25, 2019 [Easy] Flatten a Nested Dictionary


Question: Write a function to flatten a nested dictionary. Namespace the keys with a period.

Example:

Given the following dictionary:
{
    "key": 3,
    "foo": {
        "a": 5,
        "bar": {
            "baz": 8
        }
    }
}

it should become:
{
    "key": 3,
    "foo.a": 5,
    "foo.bar.baz": 8
}

You can assume keys do not contain dots in them, i.e. no clobbering will occur.

Solution with Iterator: https://repl.it/@trsong/Flatten-a-Nested-Dictionary

import unittest

def create_dictionary_iterator(dictionary):
    for k, v in dictionary.items():
        if type(v) is dict:
            for sub_k, sub_v in create_dictionary_iterator(v):
                yield '{}.{}'.format(k, sub_k), sub_v
        else:
            yield k, v


def flatten_dictionary(dictionary):
    iterator = create_dictionary_iterator(dictionary)
    return {k: v for k,v in iterator}


class FlattenDictionarySpec(unittest.TestCase):
    def test_example(self):
        self.assertEqual({
            "key": 3,
            "foo.a": 5,
            "foo.bar.baz": 8
        }, flatten_dictionary({
            "key": 3,
            "foo": {
                "a": 5,
                "bar": {
                    "baz": 8}}}))

    def test_empty_dictionary(self):
        self.assertEqual({}, flatten_dictionary({}))

    def test_simple_dictionary(self):
        d = {
            'a': 1,
            'b': 2
        }
        self.assertEqual(d, flatten_dictionary(d))
    
    def test_multi_level_dictionary(self):
        d_e = {'e': 0}
        d_d = {'d': d_e}
        d_c = {'c': d_d}
        d_b = {'b': d_c}
        d_a = {'a': d_b}
        self.assertEqual({
            'a.b.c.d.e': 0
        }, flatten_dictionary(d_a))


if __name__ == '__main__':
    unittest.main(exit=False)

Sep 24, 2019 [Medium] Interleave Stacks


Question: Given a stack of N elements, interleave the first half of the stack with the second half reversed using only one other queue. This should be done in-place.

Recall that you can only push or pop from a stack, and enqueue or dequeue from a queue.

For example, if the stack is [1, 2, 3, 4, 5], it should become [1, 5, 2, 4, 3]. If the stack is [1, 2, 3, 4], it should become [1, 4, 2, 3].

Hint: Try working backwards from the end state.

Solution: https://repl.it/@trsong/Interleave-Stacks

import unittest

def interleave_stack(stack):
    if not stack:
        return 

    # stack = [1, 2, 3, 4, 5, 6, 7], queue = []
    queue = []
    size = len(stack)
    half_size = size / 2
    for _ in xrange(half_size):
        queue.append(stack.pop())

    # stack = [1, 2, 3, 4], queue = [7, 6, 5]
    for _ in xrange(half_size):
        stack.append(queue.pop(0))

    # stack = [1, 2, 3, 4, 7, 6, 5], queue = []
    for _ in xrange(half_size):
        queue.append(stack.pop())

    # stack = [1, 2, 3, 4], queue = [5, 6, 7]
    last = None
    if len(stack) != len(queue):
        last = stack.pop()

    # stack = [1, 2, 3], queue = [5, 6, 7]
    for _ in xrange(half_size):
        queue.append(queue.pop(0))
        queue.append(stack.pop())

    # stack = [], queue = [5, 3, 6, 2, 7, 1]
    while queue:
        stack.append(queue.pop(0))

    # stack = [5, 3, 6, 2, 7, 1], queue = []
    while stack:
         queue.append(stack.pop())

    # stack = [], queue = [1, 7, 2, 6, 3, 5]
    while queue:
        stack.append(queue.pop(0))

    # stack = [1, 7, 2, 6, 3, 5], queue = []
    if last is not None:
        stack.append(last)
    # stack = [1, 7, 2, 6, 3, 5, 4], queue = []


class InterleaveStackSpec(unittest.TestCase):
    def test_example1(self):
        stack = [1, 2, 3, 4, 5]
        expected = [1, 5, 2, 4, 3]
        interleave_stack(stack)
        self.assertEqual(stack, expected)

    def test_example2(self):
        stack = [1, 2, 3, 4]
        expected = [1, 4, 2, 3]
        interleave_stack(stack)
        self.assertEqual(stack, expected)

    def test_empty_stack(self):
        stack = []
        expected = []
        interleave_stack(stack)
        self.assertEqual(stack, expected)

    def test_size_one_stack(self):
        stack = [1]
        expected = [1]
        interleave_stack(stack)
        self.assertEqual(stack, expected)

    def test_size_two_stack(self):
        stack = [1, 2]
        expected = [1, 2]
        interleave_stack(stack)
        self.assertEqual(stack, expected)

    def test_size_three_stack(self):
        stack = [1, 2, 3]
        expected = [1, 3, 2]
        interleave_stack(stack)
        self.assertEqual(stack, expected)

    def test_size_seven_stack(self):
        stack = [1, 2, 3, 4, 5, 6, 7]
        expected = [1, 7, 2, 6, 3, 5, 4]
        interleave_stack(stack)
        self.assertEqual(stack, expected)
    

if __name__ == '__main__':
    unittest.main(exit=False)

Sep 23, 2019 [Easy] BST Nodes Sum up to K


Question: Given the root of a binary search tree, and a target K, return two nodes in the tree whose sum equals K.

Example:

Given the following tree and K of 20

    10
   /   \
 5      15
       /  \
     11    15
Return the nodes 5 and 15.

My thoughts: BST in-order traversal is equivalent to sorted list. Therefore the question can be converted to 2-sum with sorted input.

Solution with In-order Traversal: https://repl.it/@trsong/BST-Nodes-Sum-up-to-K

import unittest

class Node(object):
    def __init__(self, val, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


def in_order_traversal(tree):
    if tree:
        for left_tree in in_order_traversal(tree.left):
            yield left_tree
        yield tree
        for right_tree in in_order_traversal(tree.right):
            yield right_tree


def reverse_in_order_traversal(tree):
    if tree:
        for right_tree in reverse_in_order_traversal(tree.right):
            yield right_tree
        yield tree
        for left_tree in reverse_in_order_traversal(tree.left):
            yield left_tree


def find_pair(tree, k):
    if not tree:
        return None
    
    traversal = in_order_traversal(tree)
    reverse_traversal = reverse_in_order_traversal(tree)

    left = next(traversal)
    right = next(reverse_traversal)
    while left != right:
        sum = left.val + right.val
        if sum == k:
            return [left.val, right.val]
        elif sum < k:
            left = next(traversal)
        else:
            # sum > k
            right = next(reverse_traversal)
    return None


class FindPairSpec(unittest.TestCase):
    def test_example(self):
        """
            10
           /   \
         5      15
               /  \
             11    15
        """
        n15 = Node(15, Node(11), Node(15))
        n10 = Node(10, Node(5), n15)
        self.assertEqual(find_pair(n10, 20), [5, 15])

    def test_empty_tree(self):
        self.assertIsNone(find_pair(None, 0), None)

    def test_full_tree(self):
        """
             7
           /   \
          3     13
         / \   /  \
        2   5 11   17
        """
        n3 = Node(3, Node(2), Node(5))
        n13 = Node(13, Node(11), Node(17))
        n7 = Node(7, n3, n13)
        self.assertEqual(find_pair(n7, 7), [2, 5])
        self.assertEqual(find_pair(n7, 18), [5, 13])
        self.assertEqual(find_pair(n7, 24), [7, 17])
        self.assertEqual(find_pair(n7, 28), [11, 17])
        self.assertIsNone(find_pair(n7, 4))

    def test_tree_with_same_value(self):
        """
        42
          \
           42
        """
        tree = Node(42, right=Node(42))
        self.assertEqual(find_pair(tree, 84), [42, 42])
        self.assertIsNone(find_pair(tree, 42))

    def test_sparse_tree(self):
        """
           7
         /   \
        2     17
         \   /
          5 11
         /   \
        3     13
        """
        n2 = Node(2, right=Node(5, Node(3)))
        n17 = Node(17, Node(11, right=Node(13)))
        n7 = Node(7, n2, n17)
        self.assertEqual(find_pair(n7, 7), [2, 5])
        self.assertEqual(find_pair(n7, 18), [5, 13])
        self.assertEqual(find_pair(n7, 24), [7, 17])
        self.assertEqual(find_pair(n7, 28), [11, 17])
        self.assertIsNone(find_pair(n7, 4))


if __name__ == '__main__':
   unittest.main(exit=False)

Sep 22, 2019 LC 279 [Medium] Minimum Number of Squares Sum to N


Question: Given a positive integer n, find the smallest number of squared integers which sum to n.

For example, given n = 13, return 2 since 13 = 3^2 + 2^2 = 9 + 4.

Given n = 27, return 3 since 27 = 3^2 + 3^2 + 3^2 = 9 + 9 + 9.

Solution with DP: https://repl.it/@trsong/Minimum-Number-of-Squares-Sum-to-N

import unittest
import math
import sys

def is_perfect_square(num):
    sqr_root = math.sqrt(num)
    return sqr_root - math.floor(sqr_root) == 0


def perfect_squres(N):
    if is_perfect_square(N):
        return 1
    
    # Let dp[n] represents smallest number of perfect squares that add up to n
    # Then dp[n] = 1 + min(dp[n - perfect_squares]) for all suitable perfect_squares
    dp = [sys.maxint] * (N + 1)
    dp[0] = 0

    for num in xrange(1, N + 1):
        for i in xrange(1, int(math.sqrt(num) + 1)):
            dp[num] = min(dp[num - i * i] + 1, dp[num])
    return dp[N]


class PerfectSqureSpec(unittest.TestCase):
    def test_example(self):
        self.assertEqual(perfect_squres(13), 2) # n = 13, return 2 since 13 = 3^2 + 2^2 = 9 + 4.

    def test_example2(self):
        self.assertEqual(perfect_squres(27), 3) # n = 27, return 3 since 27 = 3^2 + 3^2 + 3^2 = 9 + 9 + 9

    def test_perfect_square(self):
        self.assertEqual(perfect_squres(100), 1) # 10^2 = 100

    def test_random_number(self):
        self.assertEqual(perfect_squres(63), 4) # n = 63, return 4 since 63 = 7^2+ 3^2 + 2^2 + 1^2

    def test_random_number2(self):
        self.assertEqual(perfect_squres(12), 3) # n = 12, return 3, 12 = 4 + 4 + 4

    def test_random_number3(self):
        self.assertEqual(perfect_squres(6), 3) # 6 = 2 + 2 + 2


if __name__ == '__main__':
    unittest.main(exit=False)

Sep 21, 2019 [Medium] Subtree with Maximum Average


Question: Given an N-ary tree, find the subtree with the maximum average. Return the root of the subtree.

A subtree of a tree is the node which have at least 1 child plus all its descendants. The average value of a subtree is the sum of its values, divided by the number of nodes.

Example:

Input:
     _20_
    /    \
   12    18
 / | \   / \
11 2  3 15  8

Output: 18
Explanation:
There are 3 nodes which have children in this tree:
12 => (11 + 2 + 3 + 12) / 4 = 7
18 => (18 + 15 + 8) / 3 = 13.67
20 => (12 + 11 + 2 + 3 + 18 + 15 + 8 + 20) / 8 = 11.125

18 has the maximum average so output 18.

Solution: https://repl.it/@trsong/Subtree-with-Maximum-Average

import unittest

class Node(object):
    def __init__(self, val, *children):
        self.val = val
        self.children = children

def max_avg_subtree(tree):
    def max_avg_and_n(tree):
        if not tree.children:
            # (child_max_avg, child_node), sum, size
            return (float('-inf'), tree), tree.val, 1

        child_res = map(max_avg_and_n, tree.children)
        child_max_avg_and_nodes = map(lambda x: x[0], child_res)
        child_max_avgs = map(lambda x: x[0], child_max_avg_and_nodes)
        child_max_avg = max(child_max_avgs)
        sum_children = sum(map(lambda x: x[1], child_res))
        num_children = sum(map(lambda x: x[2], child_res))
        node_sum = sum_children + tree.val
        current_max_avg = float(node_sum) / (1 + num_children)
        res = (current_max_avg, tree)
        if child_max_avg > current_max_avg:
            index = child_max_avgs.index(child_max_avg)
            target_child = child_max_avg_and_nodes[index][1]
            res = (child_max_avg, target_child)
        return res, node_sum, num_children + 1

    return max_avg_and_n(tree)[0][1].val


class MaxAvgSubtreeSpec(unittest.TestCase):
    def test_example(self):
        """
             _20_
            /    \
           12    18
         / | \   / \
        11 2  3 15  8

        12 => (11 + 2 + 3 + 12) / 4 = 7
        18 => (18 + 15 + 8) / 3 = 13.67
        20 => (12 + 11 + 2 + 3 + 18 + 15 + 8 + 20) / 8 = 11.125
        """
        n12 = Node(12, Node(12), Node(2), Node(3))
        n18 = Node(18, Node(15), Node(8))
        n20 = Node(20, n12, n18)
        self.assertEqual(max_avg_subtree(n20), 18) 

    def test_tree_with_negative_node(self):
        """
             1
           /   \
         -5     11
         / \   /  \
        1   2 4   -2

        -5 => (-5 + 1 + 2) / 3 = -0.67
        11 => (11 + 4 - 2) / 3 = 4.333
        1 => (1 -5 + 11 + 1 + 2 + 4 - 2) / 7 = 1
        """
        n5 = Node(-5, Node(1), Node(2))
        n11 = Node(11, Node(4), Node(-2))
        n1 = Node(1, n5, n11)
        self.assertEqual(max_avg_subtree(n1), 11)

    def test_right_heavy_tree(self):
        """
          0
         / \
        10  1
          / | \
         0  4  3
         1 => (0 + 4 + 3 + 1) / 4 = 2
         0 => (10 + 1 + 4 + 3) / 6 = 3
        """
        n1 = Node(1, Node(0), Node(4), Node(3))
        n0 = Node(0, Node(10), n1)
        self.assertEqual(max_avg_subtree(n0), 0)

    def test_multiple_level_tree(self):
        """
         0
          \ 
           0
            \
             2
              \
               4

        0 => (0 + 2 + 4) / 4 = 1.5
        2 => (2 + 4) / 2 = 3       
        """
        t = Node(2, Node(0, Node(2, Node(4))))
        self.assertEqual(max_avg_subtree(t), 2)

    def test_all_negative_value(self):
        """
        -4
          \
          -2
            \
             0

        -2 => -2 / 2 = -1
        -4 => (-4 + -2) / 2 = -3
        """
        t = Node(-4, Node(-2, Node(0)))
        self.assertEqual(max_avg_subtree(t), -2)


if __name__ == '__main__':
    unittest.main(exit=False)

Sep 20, 2019 [Medium] Smallest Missing Positive Number from an Unsorted Array


Question: You are given an unsorted array with both positive and negative elements. You have to find the smallest positive number missing from the array in O(n) time using constant extra space. You can modify the original array.

Example 1:

Input: {2, 3, 7, 6, 8, -1, -10, 15}
Output: 1

Example 2:

Input: { 2, 3, -7, 6, 8, 1, -10, 15 }
Output: 4

Example 3:

Input: {1, 1, 0, -1, -2}
Output: 2 

My thoughts: The main idea is to focus on all postive numbers and use its value as index. By marking the corresponding value as negative will allow us to find which number is covered already. Then the first positive number not being covered is what we are looking for.

For example,

  • Step 1: [2, 3, 7, 6, 8, 1, -10, 15] will be partitioned into [2, 3, 7, 6, 8, 15, 1, -10] and we ignore the negative part: [2, 3, 7, 6, 8, 15, 1].
  • Step 2: ignore number that is too large, and mark corresponding value as negative [2, 3, 7, 6, 8, 15, 1]. Suitable values are [1, 2, 3, 6, 7] will map to index [0, 1, 2, 5, 6]. After that we mark corresponding val as negative: [-2, -3, -7, 6, 8, -15, -1]
  • Step 3: the first positive’s index is 3, which means 4 is missing.

Solution: https://repl.it/@trsong/Smallest-Missing-Positive-Number-from-an-Unsorted-Array

import unittest

def find_missing_positive(nums):
    if not nums:
        return 1

    # Step 1: Partition the array into positive part + non-positive part
    n = len(nums)
    i = 0 
    j = n - 1
    while i <= j:
        if nums[i] > 0:
            i += 1
        elif nums[j] <= 0:
            j -= 1
        else:
            nums[i], nums[j] = nums[j], nums[i]
            i += 1
            j -= 1

    # Step 2: Treat the first positive part as an array, and use val as index
    # val is between 0 and new_len - 1 which represents 1 to new_len, we turn corresponding number into negative
    new_len = i
    for i in xrange(new_len):
        val_as_index = abs(nums[i])
        if val_as_index - 1 < new_len and nums[val_as_index - 1] > 0:
            nums[val_as_index - 1] *= -1

    # Step 3: Find first index that is positive. Then + 1 will give the missing number
    for i in xrange(new_len):
        if nums[i] > 0:
            return i+1
    
    # if numbers are consecutive, meaning all number between 1 and new_len is covered, 
    # then new_len + 1 should be the missing number
    return new_len + 1


class FindMissingPositiveSpec(unittest.TestCase):
    def test_example1(self):
        self.assertEqual(find_missing_positive([2, 3, 7, 6, 8, -1, -10, 15]), 1)
    
    def test_example2(self):
        self.assertEqual(find_missing_positive([2, 3, -7, 6, 8, 1, -10, 15]), 4)

    def test_example3(self):
        self.assertEqual(find_missing_positive([1, 1, 0, -1, -2]), 2)

    def test_consecutive_array1(self):
        self.assertEqual(find_missing_positive([1, 2, 3]), 4)

    def test_consecutive_array2(self):
        self.assertEqual(find_missing_positive([-1, 0, 1]), 2)

    def test_non_positive_array(self):
        self.assertEqual(find_missing_positive([-5, -3, -1]), 1)

    def test_missing_multiple_positive_numbers(self):
        self.assertEqual(find_missing_positive([ 7, 8, 9, 10,-4, 1, 2, 3, 5,]), 4)

    def test_empty_array(self):
        self.assertEqual(find_missing_positive([]), 1)

    def test_negative_array(self):
        self.assertEqual(find_missing_positive([-1, -2, -3]), 1)

    def test_array_with_duplicated_number(self):
        self.assertEqual(find_missing_positive([1, 1, 2, 2, 3, 3, 5, 5, 6, -1, -1]), 4)

if __name__ == '__main__':
    unittest.main(exit=False)

Sep 19, 2019 LC 987 [Medium] Vertical Order Traversal of a Binary Tree


Question: Given a binary tree, return the vertical order traversal of its nodes’ values. (ie, from top to bottom, column by column).

If two nodes are in the same row and column, the order should be from left to right.

Example 1:

Given binary tree:

    3
   / \
  9  20
    /  \
   15   7

return its vertical order traversal as:

[
  [9],
  [3,15],
  [20],
  [7]
]

Example 2:

Given binary tree:

    _3_
   /   \
  9    20
 / \   / \
4   5 2   7

return its vertical order traversal as:

[
  [4],
  [9],
  [3,5,2],
  [20],
  [7]
]

My thoughts: Treat root node as postion 0, when move to left child positon - 1, or position + 1 for right child. Then use BFS to scan node from top to bottom to enforce vertical order of traversal.

Solution with BFS: https://repl.it/@trsong/Vertical-Order-Traversal-of-a-Binary-Tree

import unittest

class Node(object):
    def __init__(self, val, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


def vertical_traversal(tree):
    if not tree:
        return []
    lo = hi = 0
    position_map = {}
    queue = [(tree, 0)]
    while queue:
        for _ in xrange(len(queue)):
            node, pos = queue.pop(0)
            lo = min(lo, pos)
            hi = max(hi, pos)

            if pos not in position_map:
                position_map[pos] = []

            position_map[pos].append(node.val)

            if node.left:
                queue.append((node.left, pos - 1))

            if node.right:
                queue.append((node.right, pos + 1))

    res = []
    for pos in xrange(lo, hi + 1):
        res.append(position_map[pos])

    return res


class VerticalTraversalSpec(unittest.TestCase):
    def test_example1(self):
        """
         3
        / \
       9  20
         /  \
        15   7
        """
        t = Node(3, Node(9), Node(20, Node(15), Node(7)))
        self.assertEqual(vertical_traversal(t), [
            [9],
            [3,15],
            [20],
            [7]
        ])
    
    def test_example2(self):
        """
            _3_
           /   \
          9    20
         / \   / \
        4   5 2   7
        """
        t9 = Node(9, Node(4), Node(5))
        t20 = Node(20, Node(2), Node(7))
        t = Node(3, t9, t20)

        self.assertEqual(vertical_traversal(t), [
            [4],
            [9],
            [3,5,2],
            [20],
            [7]
        ])

    def test_empty_tree(self):
        self.assertEqual(vertical_traversal(None), [])

    def test_left_heavy_tree(self):
        """
            1
           / \
          2   3
         / \   \
        4   5   6
        """
        t2 = Node(2, Node(4), Node(5))
        t3 = Node(3, right=Node(6))
        t = Node(1, t2, t3)
        self.assertEqual(vertical_traversal(t), [
            [4],
            [2],
            [1,5],
            [3],
            [6]
        ])


if __name__ == '__main__':
    unittest.main(exit=False)

Sep 18, 2019 [Medium] Max Value of Coins to Collect in a Matrix


Question: You are given a 2-d matrix where each cell represents number of coins in that cell. Assuming we start at matrix[0][0], and can only move right or down, find the maximum number of coins you can collect by the bottom right corner.

Example:

Given below matrix:

0 3 1 1
2 0 0 4
1 5 3 1

The most we can collect is 0 + 2 + 1 + 5 + 3 + 1 = 12 coins.

My thoughts: This problem gives you a strong feeling that this must be a DP question. ‘coz for each step you can either move right or down, that is, max number of coins you can collect so far at current cell depends on top and left solution gives the following recurrence formula:

Let dp[i][j] be the max coin value collect when reach cell (i, j) in grid.
dp[i][j] = grid[i][j] + max(dp[i-1][j], dp[i][j-1])

You can also do it in-place using original grid. However, mutating input params in general is a bad habit as those parameters may be used in other place and might be immutable.

Solution with DP: https://repl.it/@trsong/Max-Value-of-Coins-to-Collect-in-a-Matrix

import unittest

def max_coins(grid):
    if not grid or not grid[0]:
        return 0
    n, m = len(grid), len(grid[0])

    for r in xrange(n):
        for c in xrange(m):
            left = grid[r][c-1] if c > 0 else 0
            top = grid[r-1][c] if r > 0 else 0
            grid[r][c] += max(left, top)

    return grid[n-1][m-1]


class MaxCoinSpec(unittest.TestCase):
    def test_example(self):
        self.assertEqual(max_coins([
            [0, 3, 1, 1],
            [2, 0, 0, 4],
            [1, 5, 3, 1]
        ]), 12)

    def test_empty_grid(self):
        self.assertEqual(max_coins([]), 0)
        self.assertEqual(max_coins([[]]), 0)

    def test_one_way_or_the_other(self):
        self.assertEqual(max_coins([
            [0, 3],
            [2, 0]
        ]), 3)

    def test_until_the_last_moment_knows(self):
        self.assertEqual(max_coins([
            [0, 1, 0, 1],
            [0, 0, 0, 1],
            [2, 0, 3, 0]
        ]), 5)

    def test_try_to_get_most_coins(self):
        self.assertEqual(max_coins([
            [1, 1, 1],
            [2, 3, 1],
            [1, 4, 5]
        ]), 15)


if __name__ == '__main__':
    unittest.main(exit=False)

Sep 17, 2019 LC 296 [Hard] Best Meeting Point


Question: A group of two or more people wants to meet and minimize the total travel distance. You are given a 2D grid of values 0 or 1, where each 1 marks the home of someone in the group. The distance is calculated using Manhattan Distance, where distance(p1, p2) = |p2.x - p1.x| + |p2.y - p1.y|.

Hint: Try to solve it in one dimension first. How can this solution apply to the two dimension case?

Example:

Input: 

1 - 0 - 0 - 0 - 1
|   |   |   |   |
0 - 0 - 0 - 0 - 0
|   |   |   |   |
0 - 0 - 1 - 0 - 0

Output: 6 

Explanation: Given three people living at (0,0), (0,4), and (2,2):
             The point (0,2) is an ideal meeting point, as the total travel distance 
             of 2+2+2=6 is minimal. So return 6.

My thoughts: Exactly as the hint mentioned, let’s first check the 1D case.

Example 1: If the array look like [0, 1, 0, 0, 0, 1, 0, 0] then by the definition of Manhattan Distance, any location between two 1s should be optimal. ie. all x spot in [0, 1, x, x, x, 1, 0, 0]

Example2: If the array look like [0, 1, 0, 1, 0, 1, 0, 0, 1], then we can reduce the result to [0, x, x, x, x, 1, 0, 0, 1] and [0, 0, 0, 1, x, 1, 0, 0, 0] where x is the target spots. We shrink the optimal to the x spot in [0, 0, 0, 1, x, 1, 0, 0, 0].

So if we have 2D array, then notice that the target position’s (x, y) co-ordinates are indepent of each other, ie, x and y won’t affect each other. Why? Because by definition of Manhattan Distance, distance(p1, p2) = |p2.x - p1.x| + |p2.y - p1.y|. Suppose we have an optimal positon x, y. Then total distance = all projected x-distance + all projected y-distance.

Example 3: Suppose we use the example from the question body, our projected_row is [1, 0, 1, 0, 1] which gives best meeting distance 4, and projected_col is [1, 0, 1] which gives best meeting distance 2. Therefore the total best meeting distance equals 4 + 2 = 6

Solution: https://repl.it/@trsong/Best-Meeting-Point

import unittest

def best_meeting_distance_1D(arr):
    i, j = 0, len(arr) - 1
    res = 0
    while True:
        if i >= j:
            break
        elif arr[i] > 0 and arr[j] > 0:
            res += j - i
            arr[i] -= 1
            arr[j] -= 1
        elif arr[i] == 0:
            i += 1
        elif arr[j] == 0:
            j -= 1
    return res
       

def best_meeting_distance(grid):
    if not grid or not grid[0]:
        return 0

    n, m = len(grid), len(grid[0])
    projected_row = [0] * m
    projected_col = [0] * n
    
    for r in xrange(n):
        for c in xrange(m):
            if grid[r][c]:
                projected_row[c] += 1
                projected_col[r] += 1

    return best_meeting_distance_1D(projected_row) + best_meeting_distance_1D(projected_col)


class BestMeetingPointSpec(unittest.TestCase):
    def test_example(self):
        self.assertEqual(best_meeting_distance([
            [1, 0, 0, 0, 1],
            [0, 0, 0, 0, 0],
            [0, 0, 1, 0, 0]
        ]), 6) # best position at [0, 2]
    
    def test_1D_array(self):
        self.assertEqual(best_meeting_distance([
            [1, 0, 1, 0, 0, 1]
        ]), 5) # best position at index 2
    
    def test_a_city_with_no_one(self):
        self.assertEqual(best_meeting_distance([
            [0, 0, 0],
            [0, 0, 0],
        ]), 0)

    def test_empty_grid(self):
       self.assertEqual(best_meeting_distance([
            []
        ]), 0)

    def test_even_number_of_points(self):
        self.assertEqual(best_meeting_distance([
            [1, 0, 0, 0, 1],
            [0, 0, 0, 0, 0],
            [0, 0, 1, 0, 0],
            [1, 0, 0, 0, 1],
            [0, 0, 0, 0, 0],
            [0, 0, 1, 0, 0]
        ]), 17) # best distance = x-distance + y-distance = 8 + 9 = 17. Best position at [3, 2]

    def test_odd_number_of_points(self):
        self.assertEqual(best_meeting_distance([
            [1, 0, 0, 0, 1],
            [0, 1, 0, 0, 0],
            [0, 0, 1, 0, 0],
            [1, 0, 0, 0, 1],
            [0, 1, 0, 0, 0],
            [0, 1, 1, 0, 0]
        ]), 24) # best distance = x-distance + y-distance = 10 + 14 = 24. 


if __name__ == '__main__':
    unittest.main(exit=False)

Sep 16, 2019 LC 317 [Hard] Shortest Distance from All Buildings


Question: You want to build a house on an empty land which reaches all buildings in the shortest amount of distance. You can only move up, down, left and right. You are given a 2D grid of values 0, 1 or 2, where:

  • Each 0 marks an empty land which you can pass by freely.
  • Each 1 marks a building which you cannot pass through.
  • Each 2 marks an obstacle which you cannot pass through.

Note: There will be at least one building. If it is not possible to build such house according to the above rules, return -1.

Example:

Input: [[1,0,2,0,1],[0,0,0,0,0],[0,0,1,0,0]]

1 - 0 - 2 - 0 - 1
|   |   |   |   |
0 - 0 - 0 - 0 - 0
|   |   |   |   |
0 - 0 - 1 - 0 - 0

Output: 7 

Explanation: Given three buildings at (0,0), (0,4), (2,2), and an obstacle at (0,2),
             the point (1,2) is an ideal empty land to build a house, as the total 
             travel distance of 3+3+1=7 is minimal. So return 7.

My thoughts: There is no easy way to find the shortest distance to all building. Due to the fact that obstacle is unpredictable. There could be exponentially many different situations that obstacle can affect our shortest path. And sometimes it might simply just block the way to some building which cause the problem to short-circuit and return -1.

Thus, what we can do for this problem is to run BFS on each building and calculate the accumulated/aggregated distance to current building for EACH empty land. And once done that, simple iterate through the aggregated distance array to find mimimum distance which will be the answer.

Solution with BFS: https://repl.it/@trsong/Shortest-Distance-from-All-Buildings

import unittest
import sys


def shortest_distance(grid):
    if not grid or not grid[0]:
        return -1

    n, m = len(grid), len(grid[0])
    buildings = [(r, c) for r in xrange(n) for c in xrange(m) if grid[r][c] == 1]

    if not buildings:
        return -1

    num_building = len(buildings)
    accu_distance = [[0 for _ in xrange(m)] for _ in xrange(n)]
    directions = [(0, 1), (1, 0), (0, -1), (-1, 0)]
    for building in buildings:
        reached_building = 0
        distance = 0
        queue = [building]
        visited = [[False for _ in xrange(m)] for _ in xrange(n)]
        while queue:
            for _ in xrange(len(queue)):
                r, c = queue.pop(0)
                if visited[r][c]:
                    continue

                accu_distance[r][c] += distance
                visited[r][c] = True
                if grid[r][c] == 1:
                    reached_building += 1

                for d in directions:
                    nbr_r, nbr_c = r + d[0], c + d[1]
                    if 0 <= nbr_r < n and 0 <= nbr_c < m and not visited[nbr_r][nbr_c] and grid[nbr_r][nbr_c] != 2:
                        queue.append((nbr_r, nbr_c))
            distance += 1

        if reached_building != num_building:
            # Check if all building are connected
            return -1


    min_dist = sys.maxint
    for r in xrange(n):
        for c in xrange(m):
            if grid[r][c] == 0:
                min_dist = min(min_dist, accu_distance[r][c])

    return min_dist


class ShortestDistanceSpec(unittest.TestCase):
    def test_example(self):
        self.assertEqual(shortest_distance([
            [1, 0, 2, 0, 1],
            [0, 0, 0, 0, 0],
            [0, 0, 1, 0, 0]
        ]), 7)  # target location is [1, 2] which has distance 3 + 3 + 1 = 7

    def test_inaccessible_buildings(self):
        self.assertEqual(shortest_distance([
            [1, 0, 0],
            [1, 2, 2],
            [2, 1, 1]
        ]), -1)

    def test_no_building_at_all(self):
        self.assertEqual(shortest_distance([
            [0, 2, 0],
            [0, 0, 0],
            [0, 0, 0]
        ]), -1)

    def test_empty_grid(self):
        self.assertEqual(shortest_distance([]), -1)

    def test_building_on_same_line(self):
        self.assertEqual(shortest_distance([
            [2, 1, 0, 1, 0, 0, 1]  # target is at index 2, which has distance = 1 + 1 + 4
        ]), 6)

    def test_multiple_road_same_distance(self):
        self.assertEqual(shortest_distance([
            [0, 1, 0],
            [1, 2, 0],
            [2, 1, 0]
        ]), 7)  # either top-left or top-right will give 7


if __name__ == '__main__':
    unittest.main(exit=False)

Sep 15, 2019 LC 1014 [Medium] Best Sightseeing Pair


Question: Given an array A of positive integers, A[i] represents the value of the i-th sightseeing spot, and two sightseeing spots i and j have distance j - i between them.

The score of a pair (i < j) of sightseeing spots is (A[i] + A[j] + i - j) : the sum of the values of the sightseeing spots, minus the distance between them.

Return the maximum score of a pair of sightseeing spots.

Example:

Input: [8,1,5,2,6]
Output: 11
Explanation: i = 0, j = 2, A[i] + A[j] + i - j = 8 + 5 + 0 - 2 = 11

My thoughts: You probably don’t even notice, but the problem already presents a hint in the question. So to say, A[i] + A[j] - (j - i) = A[i] + A[j] + i - j, if we re-arrange the terms even further, we can get (A[i] + i) + (A[j] - j). Remember we want to maximize (A[i] + i) + (A[j] - j). Notice that when we iterate throught the list along the way, before process A[j] we should’ve seen A[i] and i already. Thus we can store the max of (A[i] + i) so far and plus (A[j] - j) to get max along the way. This question is just a variant of selling stock problem. https://trsong.github.io/python/java/2019/05/01/DailyQuestions.html#june-4-2019-easy-sell-stock

Solution: https://repl.it/@trsong/Best-Sightseeing-Pair

import unittest

def max_score_sightseeing_pair(A):
    max_so_far = A[0] + 0
    res = 0
    for j in xrange(1, len(A)):
        res = max(res, max_so_far + A[j] - j)
        max_so_far = max(max_so_far, A[j] + j)
    return res


class MaxScoreSightseeingPairSpec(unittest.TestCase):
    def test_example(self):
        self.assertEqual(max_score_sightseeing_pair([8, 1, 5, 2, 6]), 11) # i = 0, j = 2, A[i] + A[j] + i - j = 8 + 5 + 0 - 2 = 11

    def test_two_high_value_spots(self):
        self.assertEqual(max_score_sightseeing_pair([1, 9, 1, 10]), 17) # i = 1, j = 3, 9 + 10 + 1 - 3 = 17

    def test_decreasing_value(self):
        self.assertEqual(max_score_sightseeing_pair([3, 1, 1, 1]), 3) # i = 0, j = 1, 3 + 1 + 0 - 1 = 3

    def test_increasing_value(self):
        self.assertEqual(max_score_sightseeing_pair([1, 2, 4, 8]), 11) # i = 2, j = 3, 4 + 8 + 2 - 3 = 11

    def test_tie(self):
        self.assertEqual(max_score_sightseeing_pair([2, 2, 2, 2]), 3) # i = 0, j = 1, 2 + 2 + 0 - 1 = 3

    def test_two_elements(self):
        self.assertEqual(max_score_sightseeing_pair([5, 4]), 8) # i = 0, j = 1, 5 + 4 + 0 - 1 = 8


if __name__ == '__main__':
    unittest.main(exit=False)

Sep 14, 2019 [Medium] Unique Prefix


Question: Given a list of words, return the shortest unique prefix of each word.

Example:

Given the list:
dog
cat
apple
apricot
fish

Return the list:
d
c
app
apr
f

My thoughts: Most string prefix searching problem can be solved using Trie (Prefix Tree). A trie is a N-nary tree with each edge represent a char. Each node will have two attribues: is_end and count, representing if a word is end at this node and how many words share same prefix so far separately.

The given example will generate the following trie:

The number inside each parenthsis represents how many words share the same prefix underneath.

             ""(4)
         /  |    |   \  
      d(1) c(1) a(2) f(1)
     /      |    |      \
   o(1)    a(1) p(2)   i(1)
  /         |    |  \     \
g(1)       t(1) p(1) r(1) s(1)
                 |    |    |
                l(1) i(1) h(1)
                 |    |
                e(1) c(1)
                      |
                     o(1)
                      |
                     t(1)

Our goal is to find a path for each word from root to the first node that has count equals 1, above example gives: d, c, app, apr, f

Solution with Trie: https://repl.it/@trsong/Unique-Prefix

import unittest

class Trie(object):
    TRIE_SIZE = 26
    def __init__(self):
        self.is_end = False
        self.count = 0
        self.children = None

    def has_word(self, word):
        t = self
        for char in word:
            ord_char = ord(char) - ord('a')
            if not t or not t.children:
                return False
            t = t.children[ord_char]
        return t and t.is_end
    
    def insert(self, word):
        if self.has_word(word): return
        t = self
        for char in word:
            ord_char = ord(char) - ord('a')
            if not t.children:
                t.children = [None] * Trie.TRIE_SIZE
            
            if not t.children[ord_char]:
                t.children[ord_char] = Trie()
            t.count += 1 # <-- Make sure to set the last node's count to be 1
            t = t.children[ord_char]
        t.count = 1
        t.is_end = True

    def find_prefix(self, word):
        t = self
        end = 0
        for char in word:
            ord_char = ord(char) - ord('a')
            if t.count == 1:
                break
            t = t.children[ord_char]
            end += 1
        return word[:end]


def unique_prefix(words):
    trie = Trie()
    for word in words:
        trie.insert(word)
    return map(lambda w: trie.find_prefix(w), words)


class UniquePrefixSpec(unittest.TestCase):
    """
    Assumption I made for this question:
    1. It's possible to have words being prefix of another; If that's the case, then the prefix is itself
    2. Empty word's prefix is empty word itself
    3. Duplicated words are treated as one and should have same unique prefix
    4. All letters in a word are in lower case with no whitespaces
    """
    def test_example(self):
        words = ['dog', 'cat', 'apple', 'apricot', 'fish']
        expected = ['d', 'c', 'app', 'apr', 'f']
        self.assertEqual(unique_prefix(words), expected)
    
    def test_prefix_word_of_another(self):
        words = ['greed', 'greedis', 'greedisgood', 'greeting']
        expected = ['greed', 'greedis', 'greedisg', 'greet']
        self.assertEqual(unique_prefix(words), expected)

    def test_empty_word(self):
        words = ['', 'a', 'alpha', 'aztec']
        expected = ['', 'a', 'al', 'az']
        self.assertEqual(unique_prefix(words), expected)

    def test_duplicated_words(self):
        words = ['a', 'a', 'applies', 'apt', 'apt', 'applies', 'apple', 'apple']
        expected = ['a', 'a', 'appli', 'apt', 'apt', 'appli', 'apple', 'apple']
        self.assertEqual(unique_prefix(words), expected)


if __name__ == '__main__':
    unittest.main(exit=False)

Sep 13, 2019 [Easy] Tree Isomorphism Problem


Question: Write a function to detect if two trees are isomorphic. Two trees are called isomorphic if one of them can be obtained from other by a series of flips, i.e. by swapping left and right children of a number of nodes. Any number of nodes at any level can have their children swapped. Two empty trees are isomorphic.

Example:

The following two trees are isomorphic with following sub-trees flipped: 2 and 3, NULL and 6, 7 and 8.

Tree1:
     1
   /   \
  2     3
 / \   /
4   5 6
   / \
  7   8

Tree2:
   1
 /   \
3     2
 \   / \
  6 4   5
       / \
      8   7

My thoughts: If current tree value match , only two situations would occur:

  1. T1.left match T2.left and T1.right match T2.right
  2. T1.left match T2.right and T1.right match T2.left

Solution with Recursion: https://repl.it/@trsong/Tree-Isomorphism-Problem

import unittest

class TreeNode(object):
    def __init__(self, val, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


def is_isomorphic(t1, t2):
    if not t1 and not t2: return True
    if not t1 or not t2: return False
    if t1.val != t2.val: return False
    if is_isomorphic(t1.left, t2.left) and is_isomorphic(t1.right, t2.right): return True
    if is_isomorphic(t1.left, t2.right) and is_isomorphic(t1.right, t2.left): return True
    return False

class IsIsomorphicSpec(unittest.TestCase):
    def test_example(self):
        """
        Tree1:
             1
           /   \
          2     3
         / \   /
        4   5 6
           / \
          7   8

        Tree2:
           1
         /   \
        3     2
         \   / \
          6 4   5
               / \
              8   7
        """
        p5 = TreeNode(5, TreeNode(7), TreeNode(8))
        p2 = TreeNode(2, TreeNode(4), p5)
        p3 = TreeNode(3, TreeNode(6))
        p1 = TreeNode(1, p2, p3)

        q5 = TreeNode(5, TreeNode(8), TreeNode(7))
        q2 = TreeNode(2, TreeNode(4), q5)
        q3 = TreeNode(3, right=TreeNode(6))
        q1 = TreeNode(1, q3, q2)

        self.assertTrue(is_isomorphic(p1, q1))

    def test_empty_trees(self):
        self.assertTrue(is_isomorphic(None, None))

    def test_empty_vs_nonempty_trees(self):
        self.assertFalse(is_isomorphic(None, TreeNode(1)))

    def test_same_tree_val(self):
        """
        Tree1:
        1
         \
          1
         /
        1 

        Tree2:
            1
           /
          1
           \
            1
        """
        t1 = TreeNode(1, right=TreeNode(1, TreeNode(1)))
        t2 = TreeNode(1, TreeNode(1, right=TreeNode(1)))
        self.assertTrue(is_isomorphic(t1, t2))


    def test_same_val_yet_not_isomorphic(self):
        """
        Tree1:
          1
         / \
        1   1
           / \
          1   1

        Tree2:
            1
           / \
          1   1
         /     \
        1       1
        """
        t1 = TreeNode(1, TreeNode(1, TreeNode(1), TreeNode(1)))
        t2 = TreeNode(1, TreeNode(1, TreeNode(1)), TreeNode(1, right=TreeNode(1)))
        self.assertFalse(is_isomorphic(t1, t2))       


if __name__ == '__main__':
    unittest.main(exit=False)

Sep 12, 2019 [Medium] Favorite Genres


Question: Given a map Map<String, List> userMap, where the key is a username and the value is a list of user's songs. Also given a map Map<String, List> genreMap, where the key is a genre and the value is a list of songs belonging to this genre. The task is to return a map Map<String, List>, where the key is a username and the value is a list of the user's favorite genres. Favorite genre is a genre with the most song.

Example 1:

Input:
userMap = {  
   "David": ["song1", "song2", "song3", "song4", "song8"],
   "Emma":  ["song5", "song6", "song7"]
},
genreMap = {  
   "Rock":    ["song1", "song3"],
   "Dubstep": ["song7"],
   "Techno":  ["song2", "song4"],
   "Pop":     ["song5", "song6"],
   "Jazz":    ["song8", "song9"]
}

Output: {  
   "David": ["Rock", "Techno"],
   "Emma":  ["Pop"]
}

Explanation:
David has 2 Rock, 2 Techno and 1 Jazz song. So he has 2 favorite genres.
Emma has 2 Pop and 1 Dubstep song. Pop is Emma's favorite genre.

Example 2:

Input:
userMap = {  
   "David": ["song1", "song2"],
   "Emma":  ["song3", "song4"]
},
genreMap = {}

Output: {  
   "David": [],
   "Emma":  []
}

Solution: https://repl.it/@trsong/Favorite-Genres

import unittest

def favorite_genre(user_map, genre_map):
    song_to_genre_map = {}
    for genre, songs in genre_map.items():
        for song in songs:
            if song not in song_to_genre_map:
                song_to_genre_map[song] = []
            song_to_genre_map[song].append(genre)

    res = {}
    for user, songs in user_map.items():
        genere_histogram = {}
        for song in songs:
            if song in song_to_genre_map:
                for genre in song_to_genre_map[song]:
                    genere_histogram[genre] = genere_histogram.get(genre, 0) + 1
        fav_genres = []
        max_count = 0
        for genre, count in genere_histogram.items():
            if count > max_count:
                fav_genres = [genre]
                max_count = count
            elif count == max_count:
                fav_genres.append(genre)
        res[user] = fav_genres

    return res


class FavoriteGenreSpec(unittest.TestCase):
    def assert_map(self, map1, map2):
        for _, values in map1.items():
            values.sort()
        for _, values in map2.items():
            values.sort()
        self.assertEqual(map1, map2)


    def test_example1(self):
        user_map = {
            "David": ["song1", "song2", "song3", "song4", "song8"],
            "Emma": ["song5", "song6", "song7"]
            }
        genre_map = {
            "Rock": ["song1", "song3"],
            "Dubstep": ["song7"],
            "Techno": ["song2", "song4"],
            "Pop": ["song5", "song6"],
            "Jazz": ["song8", "song9"]
        }
        expected = {
            "David": ["Rock", "Techno"],
            "Emma": ["Pop"]
        }
        self.assert_map(favorite_genre(user_map, genre_map), expected)

    def test_example2(self):
        user_map = {
            "David": ["song1", "song2"],
            "Emma": ["song3", "song4"]
        }
        genre_map = {}
        expected = {
            "David": [],
            "Emma": []
        }
        self.assert_map(favorite_genre(user_map, genre_map), expected)

    def test_same_song_with_multiple_genres(self):
        user_map = {
            "David": ["song1", "song2"],
            "Emma": ["song3", "song4"]
        }
        genre_map = {
            "Rock": ["song1", "song3"],
            "Dubstep": ["song1"],
        }
        expected = {
            "David": ["Rock", "Dubstep"],
            "Emma": ["Rock"]
        }
        self.assert_map(favorite_genre(user_map, genre_map), expected)


if __name__ == '__main__':
    unittest.main(exit=False)

Sep 11, 2019 LC 89 [Medium] Generate Gray Code


Question: Gray code is a binary code where each successive value differ in only one bit, as well as when wrapping around. Gray code is common in hardware so that we don’t see temporary spurious values during transitions.

Given a number of bits n, generate a possible gray code for it.

Example:

For n = 2, one gray code would be [00, 01, 11, 10].

My thoughts: Test Grey Code under different size. Try to find the pattern:

  • For n = 0, [0]
  • For n = 1, [00, 01]
  • For n = 2, [00, 01, 11, 10]
  • For n = 3, [000, 001, 011, 010, 110, 111, 101, 100]

Notice that 00 => 000, 001. 01 => 011, 010. 11 => 110, 111. So the pattern is if original value is of even index, append 0 and then append 1. Otherwise if it’s on odd index, append 1 then append 0. And we do that for all elements.

Solution: https://repl.it/@trsong/Generate-Gray-Code

import unittest

def gray_code(n):
    res = [0] * (2 ** n)
    for depth in xrange(n):
        for i in xrange(2**depth - 1, -1, -1):
            append_zero = res[i] << 1
            append_one = append_zero + 1
            if i % 2 == 0:
                res[2*i] = append_zero
                res[2*i + 1] = append_one
            else:
                res[2*i] = append_one
                res[2*i + 1] = append_zero
    return res


class GrayCodeSpec(unittest.TestCase):
    def validate_grey_code(self, code_arr):
        for i in xrange(1, len(code_arr)):
            cur = code_arr[i]
            prev = code_arr[i-1]
            xor_val = cur ^ prev
            if xor_val & (xor_val - 1) != 0:
                # make sure the current value is 1 bit different from the previous value
                return False
        return True

    def test_zero_bit_grey_code(self):
        self.assertTrue(self.validate_grey_code(gray_code(0)))

    def test_one_bit_grey_code(self):
        self.assertTrue(self.validate_grey_code(gray_code(1)))

    def test_two_bits_grey_code(self):
        self.assertTrue(self.validate_grey_code(gray_code(2)))

    def test_three_bits_grey_code(self):
        self.assertTrue(self.validate_grey_code(gray_code(3)))


if __name__ == '__main__':
    unittest.main(exit=False)

Sep 10, 2019 [Medium] Matrix Rotation


Question: Given an N by N matrix, rotate it by 90 degrees clockwise.

For example, given the following matrix:

[[1, 2, 3],
 [4, 5, 6],
 [7, 8, 9]]

you should return:

[[7, 4, 1],
 [8, 5, 2],
 [9, 6, 3]]

Follow-up: What if you couldn’t use any extra space?

My thoughts: There are two ways to solve this problem without using any extra space. First one is to flip matrix diagonally and vertically. Second one is to move element one by one in spiral order: left->top, bottom->left, right->bottom, top->right.

Solution with Matrix Flip: https://repl.it/@trsong/Matrix-Rotation

import unittest

def matrix_rotation(matrix):
    """
    Step1: Flip Diagonally: (r, c) -> (c, r)
    [[1, 2],
     [3, 4]]
    =>
    [[1, 3],
     [2, 4]]
     
    Step2: Flip Vertically: (r, c) -> (r, n-1-c)
    [[1, 3],
     [2, 4]]
    =>
    [[3, 1],
     [4, 2]]
    """
    n = len(matrix)
    for r in xrange(n):
        for c in xrange(r):
            matrix[r][c], matrix[c][r] = matrix[c][r], matrix[r][c]

    for row in matrix:
        for c in xrange(n/2):
            row[c], row[n-1-c] = row[n-1-c], row[c]

    return matrix

class MatrixRotationSpec(unittest.TestCase):
    def test_empty_matrix(self):
        self.assertEqual(matrix_rotation([]), [])

    def test_size_one_matrix(self):
        self.assertEqual(matrix_rotation([[1]]), [[1]])

    def test_size_two_matrix(self):
        input_matrix = [
            [1, 2],
            [3, 4]
        ]
        expected_matrix = [
            [3, 1],
            [4, 2]
        ]
        self.assertEqual(matrix_rotation(input_matrix), expected_matrix)

    def test_size_three_matrix(self):
        input_matrix = [
            [1, 2, 3],
            [4, 5, 6],
            [7, 8, 9]
        ]
        expected_matrix = [
            [7, 4, 1],
            [8, 5, 2],
            [9, 6, 3]
        ]
        self.assertEqual(matrix_rotation(input_matrix), expected_matrix)

    def test_size_four_matrix(self):
        input_matrix = [
            [1, 2, 3, 4],
            [5, 6, 7, 8],
            [9, 10, 11, 12],
            [13, 14, 15, 16]
        ]
        expected_matrix = [
            [13, 9, 5, 1],
            [14, 10, 6, 2],
            [15, 11, 7, 3],
            [16, 12, 8, 4]
        ]
        self.assertEqual(matrix_rotation(input_matrix), expected_matrix)


if __name__ == '__main__':
    unittest.main(exit=False)

Solution with Spiral Element Swap: https://repl.it/@trsong/Matrix-Rotation-Spiral

def matrix_rotation(matrix):
    n = len(matrix)
    lo = 0
    hi = n - 1
    while lo < hi:
        for i in xrange(hi - lo):
            tmp = matrix[lo][lo+i]
            # left -> top
            matrix[lo][lo+i] = matrix[hi-i][lo]
            # bottom -> left
            matrix[hi-i][lo] = matrix[hi][hi-i]
            # right -> bottom
            matrix[hi][hi-i] = matrix[lo+i][hi]
            # top -> right
            matrix[lo+i][hi] = tmp
        lo += 1
        hi -= 1
    return matrix

Sep 9, 2019 LC 403 [Hard] Frog Jump


Question: A frog is crossing a river. The river is divided into x units and at each unit there may or may not exist a stone. The frog can jump on a stone, but it must not jump into the water.

Given a list of stones’ positions (in units) in sorted ascending order, determine if the frog is able to cross the river by landing on the last stone. Initially, the frog is on the first stone and assume the first jump must be 1 unit.

If the frog’s last jump was k units, then its next jump must be either k - 1, k, or k + 1 units. Note that the frog can only jump in the forward direction.

Example 1:

[0,1,3,5,6,8,12,17]

There are a total of 8 stones.
The first stone at the 0th unit, second stone at the 1st unit,
third stone at the 3rd unit, and so on...
The last stone at the 17th unit.

Return true. The frog can jump to the last stone by jumping 
1 unit to the 2nd stone, then 2 units to the 3rd stone, then 
2 units to the 4th stone, then 3 units to the 6th stone, 
4 units to the 7th stone, and 5 units to the 8th stone.

Example 2:

[0,1,2,3,4,8,9,11]

Return false. There is no way to jump to the last stone as 
the gap between the 5th and 6th stone is too large.

My thoughts: This is a typical graph seaching problem (DAG to be specific), except that we not only need to store current stone, but the previous step as well. Besides that, so as not to exceed the time limit, we have to do tree pruning, i.e. do not visit same node with same step twice. e.g. [0, 1, 2, 3, 4] has path 0, 1, 2, 3, 4 and 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4.

Solution with BFS and Pruning: https://repl.it/@trsong/Frog-Jump

import unittest

def can_cross(stones):
    stone_set = set(stones)
    stack = [(0, 0)]
    goal = stones[-1]
    visited = set()
    while stack:
        stone, step = stack.pop()
        if stone == goal:
            return True
        visited.add((stone, step))
        
        for next_jump_delta in [-1, 0, 1]:
            next_jump = step + next_jump_delta
            next_stone = stone + next_jump
            if next_stone >= stone and next_stone in stone_set and (next_stone, next_jump) not in visited:
                # check if next step is always forward, accessible and unvisited
                stack.append((next_stone, next_jump))
    return False

class CanCrossSpec(unittest.TestCase):
    def test_example1(self):
        self.assertTrue(can_cross([0, 1, 3, 5, 6, 8, 12, 17])) # step: 1(1), 2(3), 2(5), 3(8), 4(12), 5(17)

    def test_example2(self):
        self.assertFalse(can_cross([0, 1, 2, 3, 4, 8, 9, 11]))

    def test_unreachable_last_stone(self):
        self.assertFalse(can_cross([0, 1, 3, 6, 11]))

    def test_reachable_last_stone(self):
        self.assertTrue(can_cross([0, 1, 3, 6, 10]))

    def test_fall_into_water_in_the_middle(self):
        self.assertFalse(can_cross([0, 1, 10, 1000, 1000]))
    

if __name__ == '__main__':
    unittest.main(exit=False)

Sep 8, 2019 [Medium] Evaluate Expression in Reverse Polish Notation


Question: Given an arithmetic expression in Reverse Polish Notation, write a program to evaluate it.

The expression is given as a list of numbers and operands.

Example 1:

[5, 3, '+'] should return 5 + 3 = 8.

Example 2:

 [15, 7, 1, 1, '+', '-', '/', 3, '*', 2, 1, 1, '+', '+', '-'] should return 5, 
 since it is equivalent to ((15 / (7 - (1 + 1))) * 3) - (2 + (1 + 1)) = 5.

Solution with Stack: https://repl.it/@trsong/Evaluate-Expression-in-Reverse-Polish-Notation

 import unittest

class RPNExprEvaluator(object):
    op = {
        '+': lambda a,b: a + b,
        '-': lambda a,b: a - b,
        '*': lambda a,b: a * b,
        '/': lambda a,b: a / b
    }

    @staticmethod
    def run(tokens):
        stack = []
        for token in tokens:
            if type(token) == int:
                stack.append(token)
            else:
                operand2 = stack.pop()
                operand1 = stack.pop()
                op = RPNExprEvaluator.op[token]
                stack.append(op(operand1, operand2))
        return stack[-1] if stack else 0


class RPNExprEvaluatorSpec(unittest.TestCase):
    def test_example1(self):
        self.assertEqual(RPNExprEvaluator.run([5, 3, '+']), 8) # 5 + 3 = 8

    def test_example2(self):
        tokens = [15, 7, 1, 1, '+', '-', '/', 3, '*', 2, 1, 1, '+', '+', '-']
        self.assertEqual(RPNExprEvaluator.run(tokens), 5)

    def test_empty_tokens(self):
        self.assertEqual(RPNExprEvaluator.run([]), 0)

    def test_expression_contains_just_number(self):
        self.assertEqual(RPNExprEvaluator.run([42]), 42)
    
    def test_balanced_expression_tree(self):
        tokens = [7, 2, '-', 4, 1, '+', '*'] 
        self.assertEqual(RPNExprEvaluator.run(tokens), 25)  # (7 - 2) * (4 + 1) = 25
    
    def test_left_heavy_expression_tree(self):
        tokens = [6, 4, '-', 2, '/']  
        self.assertEqual(RPNExprEvaluator.run(tokens), 1) # (6 - 4) / 2 = 1

    def test_right_heavy_expression_tree(self):
        tokens = [2, 8, 2, '/', '*']
        self.assertEqual(RPNExprEvaluator.run(tokens), 8) # 2 * (8 / 2) = 8


if __name__ == '__main__':
    unittest.main(exit=False)

Sep 7, 2019 LC 838 [Medium] Push Dominoes


Question: Given a string with the initial condition of dominoes, where:

  • . represents that the domino is standing still
  • L represents that the domino is falling to the left side
  • R represents that the domino is falling to the right side

Figure out the final position of the dominoes. If there are dominoes that get pushed on both ends, the force cancels out and that domino remains upright.

Example 1:

Input:  "..R...L..R."
Output: "..RR.LL..RR"

Example 2:

Input: "RR.L"
Output: "RR.L"
Explanation: The first domino expends no additional force on the second domino.

Example 3:

Input: ".L.R...LR..L.."
Output: "LL.RR.LLRRLL.."

My thoughts: After some observation, you will find that ‘R’ and ‘L’ will always stay the same but ‘.’ is depended on state of first non-dot on left and right:

  • .....L => LLLLLL
  • R..... => RRRRRR
  • L....L => LLLLLL
  • R....R => RRRRRR
  • R....L => RRRLLL
  • R...L => RR.LL

So, we just need to store the last non-dot domino state and compare w/ current domino will give the current state of domino.

Solution: https://repl.it/@trsong/Push-Dominoes

import unittest

def push_dominoes(dominoes):
    if len(dominoes) <= 1: return dominoes
    n = len(dominoes)
    last_falling_position = 0
    res = ['.'] * n
    for i, domino in enumerate(dominoes):
        if domino == '.':
            continue
       
        if domino == 'L' and dominoes[last_falling_position] == 'R':
            # R....L => RRRLLL
            # R...L => RR.LL
            j = last_falling_position
            k = i
            while j < k:
                res[j] = 'R'
                res[k] = 'L'
                j += 1
                k -= 1
        elif domino == dominoes[last_falling_position] or last_falling_position == 0 and domino == 'L':
            # .....L => LLLLLL
            # L....L => LLLLLL
            # R....R => RRRRRR
            for j in xrange(last_falling_position, i+1):
                res[j] = domino
        last_falling_position = i
            
    if dominoes[last_falling_position] == 'R':
        # R.... => RRRR
        for j in xrange(last_falling_position, n):
            res[j] = 'R'
    return ''.join(res)


class PushDominoeSpec(unittest.TestCase):
    def test_example1(self):
        self.assertEqual(push_dominoes("..R...L..R."), "..RR.LL..RR")

    def test_example2(self):
        self.assertEqual(push_dominoes("RR.L"), "RR.L")

    def test_example3(self):
        self.assertEqual(push_dominoes(".L.R...LR..L.."), "LL.RR.LLRRLL..")

    def test_empty_dominoes(self):
        self.assertEqual(push_dominoes(""), "")
    
    def test_one_domino(self):
        self.assertEqual(push_dominoes("."), ".")
        self.assertEqual(push_dominoes("L"), "L")
        self.assertEqual(push_dominoes("R"), "R")
    
    def test_all_fall_to_left(self):
        self.assertEqual(push_dominoes("...L"), "LLLL")
    
    def test_all_fall_to_right(self):
        self.assertEqual(push_dominoes("R..."), "RRRR")
    
    def test_left_right_and_right_left(self):
        self.assertEqual(push_dominoes(".L.RR..L."), "LL.RRRLL.")

    def test_right_left_and_left_right(self):
        self.assertEqual(push_dominoes(".R.LL..R."), ".R.LL..RR")
    
    def test_right_right_left_left(self):
        self.assertEqual(push_dominoes("R.R...LL"), "RRRR.LLL")


if __name__ == '__main__':
    unittest.main(exit=False)

Sep 6, 2019 LC 287 [Medium] Find the Duplicate Number


Question: You are given an array of length n + 1 whose elements belong to the set {1, 2, ..., n}. By the pigeonhole principle, there must be a duplicate. Find it in linear time and space.

My thoughts: Use value as the ‘next’ element index which will form a loop evently.

Why? Because the following scenarios will happen:

Scenario 1: If a[i] != i for all i, then since a[1] … a[n] contains elements 1 to n, each time when interate to next index, one of the element within range 1 to n will be removed until no element is available and/or hit a previous used element and form a loop.

Scenario 2: If a[i] == i for all i > 0, then as a[0] != 0, we will have a loop 0 -> a[0] -> a[0]

Scenario 3: If a[i] == i for some i > 0, then like scenario 2 we either we hit i evently or like scenario 1, for each iteration, we consume one element between 1 to n until all elements are used up and form a cycle.

So we can use a fast and slow pointer to find the element when loop begins.

Solution with Fast and Slow Pointers: https://repl.it/@trsong/Find-the-Duplicate-Number

import unittest

def find_duplicate(nums):
    slow = fast = 0
    while True:
        slow = nums[slow]
        fast = nums[nums[fast]]
        if slow == fast:
            break
    
    p = 0
    while p != slow:
        p = nums[p]
        slow = nums[slow]
    return p


class FindDuplicateSpec(unittest.TestCase):
    def test_all_numbers_are_same(self):
        self.assertEqual(find_duplicate([2, 2, 2, 2, 2]), 2)

    def test_number_duplicate_twice(self):
        # index: 0 1 2 3 4 5 6
        # value: 2 6 4 1 3 1 5
        # chain: 0 -> 2 -> 4 -> 3 -> 1 -> 6 -> 5 -> 1
        #                            ^              ^
        self.assertEqual(find_duplicate([2, 6, 4, 1, 3, 1, 5]), 1)

    def test_rest_of_element_form_a_loop(self):
        # index: 0 1 2 3 4
        # value: 3 1 3 4 2
        # chain: 0 -> 3 -> 4 -> 2 -> 3
        #             ^              ^
        self.assertEqual(find_duplicate([3, 1, 3, 4, 2]), 3)

    def test_rest_of_element_are_sorted(self):
        # index: 0 1 2 3 4
        # value: 4 1 2 3 4
        # chain: 0 -> 4 -> 4
        #             ^    ^
        self.assertEqual(find_duplicate([4, 1, 2, 3, 4]), 4)
    
    def test_number_duplicate_more_than_twice(self):
        # index: 0 1 2 3 4 5 6 7 8 9
        # value: 2 5 9 6 9 3 8 9 7 1
        # chain: 0 -> 2 -> 9 -> 1 -> 5 -> 3 -> 6 -> 8 -> 7 -> 9
        #                  ^                                  ^
        self.assertEqual(find_duplicate([2, 5, 9, 6, 9, 3, 8, 9, 7, 1]), 9)


if __name__ == '__main__':
    unittest.main(exit=False)

Sep 5, 2019 [Medium] In-place Array Rotation


Question: Write a function that rotates a list by k elements.

For example, [1, 2, 3, 4, 5, 6] rotated by two becomes [3, 4, 5, 6, 1, 2].

Try solving this without creating a copy of the list. How many swap or move operations do you need?

My thoughts: Test different examples until find pattern.

Experiment 1: How do you shift one element all the way till the end? You might want to do something like the following:

[0, 1, 2, 3] => [1, 0, 2, 3] => [1, 2, 0, 3] => [1, 2, 3, 0]
 ^                  ^                  ^                  ^

Experiment 2: What about two elements?

[0, 1, 2, 3] => [2, 3, 0, 1]
 ^  ^                  ^  ^

Experiment 3: If the array is able to fit in two windows with size k, we can shrink the problem into smaller one:

Suppose k = 3

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] => [3, 4, 5, 0, 1, 2, 6, 7, 8, 9]
 ^  ^  ^                                    ^  ^  ^

The problem become swap the following array with window size 3
[0, 1, 2, 6, 7, 8, 9] => [6, 7, 8, 0, 1, 2, 9]
 ^  ^  ^                           ^  ^  ^

Until we are not able to fit two windows of size k
[0, 1, 2, 9]
 ^  ^  ^

Experiment 4: If we are not able to fit two windows of size k, we can shift the element backwards

Suppose k = 3:

Since we are not able to fit two windows of size k
[0, 1, 2, 4, 5]
 ^  ^  ^

We instead shift the remaining elements backwards:
[0, 1, 2, 4, 5] => [0, 4, 5, 1, 2] 
          ^  ^         ^  ^

The problem size shrink again:
[0, 4, 5]
    ^  ^

As we cannot longer backward shift, we shift the remaining element forward
[0, 4, 5] => [4, 0, 5] => [4, 5, 0] 
 ^               ^               ^

that is:
[4, 5, 0, 1, 2] 

Experiment 5: Let’s shift forwards and backwards multiple times with the following example

Suppose k = 5:

k = 5, Forwards >>>
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] => [5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 10, 11, 12] 
 ^  ^  ^  ^  ^  ^                                             ^  ^  ^  ^  ^

k = 3, Backwards <<<
[0, 1, 2, 3, 4, 10, 11, 12] => [0, 1, 10, 11, 12, 2, 3, 4]
                ^^  ^^  ^^            ^^  ^^  ^^
that is [5, 6, 7, 8, 9, 0, 1, 10, 11, 12, 2, 3, 4]

k = 2, Forwards >>>
[0, 1, 10, 11, 12] => [10, 11, 0, 1, 12]
 ^  ^                          ^  ^
that is [5, 6, 7, 8, 9, 10, 11, 0, 1, 12, 2, 3, 4]

k = 1, Backwards <<<
[0, 1, 12] => [12, 0, 1] 
       ^^      ^^
that is [5, 6, 7, 8, 9, 10, 11, 12, 0, 1, 2, 3, 4]

Solution with Two Pointers: https://repl.it/@trsong/In-place-Array-Rotation

import unittest

def swap_array(nums, pos1, pos2, size):
    for i in xrange(size):
        nums[pos1 + i], nums[pos2 + i] = nums[pos2 + i], nums[pos1 + i]
 

def rotate(nums, k):
    if not nums: return []
    k = k % len(nums)
    left = 0
    right = len(nums) - 1
    while left < right and k > 0:
        # Forward swap
        while left + 2*k - 1 <= right:
            swap_array(nums, left, left + k, k)
            left += k
        k = right - left + 1 - k
        if k == 0: break

        # Backward swap
        while right + 1 - 2*k >= left:
            swap_array(nums, right + 1 - k, right + 1 - 2*k, k)
            right -= k
        k = right - left + 1 - k
    return nums


class RotateSpec(unittest.TestCase):
    def test_example(self):
        self.assertEqual(rotate([1, 2, 3, 4, 5, 6], k=2), [3, 4, 5, 6, 1, 2])

    def test_rotate_0_position(self):
        self.assertEqual(rotate([0, 1, 2, 3], k=0), [0, 1, 2, 3])

    def test_empty_array(self):
        self.assertEqual(rotate([], k=10), [])

    def test_shift_negative_position(self):
        self.assertEqual(rotate([0, 1, 2, 3], k=-1), [3, 0, 1, 2])

    def test_shift_more_than_array_size(self):
        self.assertEqual(rotate([1, 2, 3, 4, 5, 6], k=8), [3, 4, 5, 6, 1, 2])

    def test_multiple_round_of_forward_and_backward_shift(self):
        nums = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
        k = 5
        expected = [5, 6, 7, 8, 9, 10, 11, 12, 0, 1, 2, 3, 4]
        self.assertEqual(rotate(nums, k), expected)
        

if __name__ == '__main__':
    unittest.main(exit=False)

Sep 4, 2019 [Hard] Reverse Words Keep Delimiters


Question: Given a string and a set of delimiters, reverse the words in the string while maintaining the relative order of the delimiters. For example, given “hello/world:here”, return “here/world:hello”

Follow-up: Does your solution work for the following cases: “hello/world:here/”, “hello//world:here”

My thoughts: Tokenize the word into multiple smaller words with each of them either being a normal word or delimiter word. While performing tokenization, memorize the index of normal words. After that just reverse order of normal words using that index of normal words. Finally combine all tokens to form a new word.

Solution: https://repl.it/@trsong/Reverse-Words-Keep-Delimiters

import unittest

def reverse_list_wit_indices(lst, indices):
    i = 0
    j = len(indices) - 1
    while i < j:
        index_i = indices[i]
        index_j = indices[j]
        lst[index_i], lst[index_j] = lst[index_j], lst[index_i]
        i += 1
        j -= 1

def reverse_words_keep_delimiters(s, delimiters):
    if not s: return ""
    delimiter_set = set(delimiters)
    res = []
    word_indices = []
    i = 0
    while i < len(s):
        tmp = []
        while i < len(s) and s[i] not in delimiter_set:
            tmp.append(s[i])
            i += 1
        if tmp:
            word_indices.append(len(res))
            res.append(tmp)
                
        tmp = []
        while i < len(s) and s[i] in delimiter_set:
            tmp.append(s[i])
            i += 1
        if tmp:
            res.append(tmp)

    reverse_list_wit_indices(res, word_indices)
    return ''.join(map(lambda lst: ''.join(lst), res))
     

class ReverseWordsKeepDelimiterSpec(unittest.TestCase):
    def test_example1(self):
        self.assertEqual(reverse_words_keep_delimiters("hello/world:here", ['/', ':']), "here/world:hello")
    
    def test_example2(self):
        self.assertEqual(reverse_words_keep_delimiters("hello/world:here/", ['/', ':']), "here/world:hello/")

    def test_example3(self):
        self.assertEqual(reverse_words_keep_delimiters("hello//world:here", ['/', ':']), "here//world:hello")

    def test_only_has_delimiters(self):
        self.assertEqual(reverse_words_keep_delimiters("--++--+++", ['-', '+']), "--++--+++")

    def test_without_delimiters(self):
        self.assertEqual(reverse_words_keep_delimiters("--++--+++", []), "--++--+++")
        self.assertEqual(reverse_words_keep_delimiters("--++--+++", ['a', 'b']), "--++--+++")

    def test_first_delimiter_then_word(self):
        self.assertEqual(reverse_words_keep_delimiters("///a/b", ['/']), "///b/a")
    
    def test_first_word_then_delimiter(self):
        self.assertEqual(reverse_words_keep_delimiters("a///b///", ['/']), "b///a///")


if __name__ == '__main__':
    unittest.main(exit=False)

Similar Question: LC 151 [Medium] Reverse Words in a String


Question: Given an input string, reverse the string word by word.

Note:

  • A word is defined as a sequence of non-space characters.
  • Input string may contain leading or trailing spaces. However, your reversed string should not contain leading or trailing spaces.
  • You need to reduce multiple spaces between two words to a single space in the reversed string.

Example 1:

Input: "the sky is blue"
Output: "blue is sky the"

Example 2:

Input: "  hello world!  "
Output: "world! hello"
Explanation: Your reversed string should not contain leading or trailing spaces.

Example 3:

Input: "a good   example"
Output: "example good a"
Explanation: You need to reduce multiple spaces between two words to a single space in the reversed string.

My thoughts: Solution with space complexity O(n) can be easily achieved through two pointers iterate from the back of string and a buffer to store each word along the way. However in-place solution with O(1) space complexity using C++ requires some trick:

Suppose the original string is "the sky is blue"

  1. reverse entire sentence. "eulb si yks eht"
  2. reverse each word in that sentence. "blue is sky the"

Solution with Two Pointers: https://repl.it/@trsong/Reverse-Words-in-a-String

import unittest

def reverse_word(s):
    if not s: return ""
    res = []
    i = j = len(s) - 1
    while j >= 0:
        if j >= 0 and s[j] == ' ':
            j -= 1
            i -= 1
        elif i >= 0 and s[i] != ' ':
            i -= 1
        else:
            for k in xrange(i+1, j+1):
                res.append(s[k])
            res.append(' ')
            j = i
    if res:
        # pop the last whitespace
        res.pop()
    return ''.join(res)


class ReverseWordSpec(unittest.TestCase):
    def test_example1(self):
        self.assertEqual(reverse_word("the sky is blue"), "blue is sky the")

    def test_example2(self):
        self.assertEqual(reverse_word("  hello world!  "), "world! hello")

    def test_example3(self):
        self.assertEqual(reverse_word("a good   example"), "example good a")

    def test_mutliple_whitespaces(self):
        self.assertEqual(reverse_word("   "), "")
        self.assertEqual(reverse_word(""), "")

    def test_even_number_of_words(self):
        self.assertEqual(reverse_word(" car cat"), "cat car")
        self.assertEqual(reverse_word("car cat "), "cat car")

    def test_no_whitespaces(self):
        self.assertEqual(reverse_word("asparagus"), "asparagus")


if __name__ == '__main__':
    unittest.main(exit=False)

Sep 3, 2019 [Medium] Direction and Position Rule Verification


Question: A rule looks like this:

A NE B

This means this means point A is located northeast of point B.

A SW C

means that point A is southwest of C.

Given a list of rules, check if the sum of the rules validate. For example:

A N B
B NE C
C N A

does not validate, since A cannot be both north and south of C.

A NW B
A N B

is considered valid.

My thoughts: A rule is considered invalid if there exists an opposite rule either implicitly or explicitly. eg. 'A N B', 'B N C' and 'C N A'. But that doesn’t mean we need to scan through the rules over and over again to check if any pair of two rules are conflicting with each other. We can simply scan through the list of rules to build a directional graph for each direction. i.e. E, S, W, N

Then what about diagonal directions. i.e. NE, NW, SE, SW.? As the nature of those rules are ‘AND’ relation, we can break one diagonal rules into two normal rules. eg, 'A NE B' => 'A N B' and 'A E B'.

For each direction we build a directional graph, with nodes being the points and edge represents a rule. And each rule will be added to two graphs with opposite directions. e.g. 'A N B' added to both ‘N’ graph and ‘S’ graph. If doing this rule forms a cycle, we simply return False. And if otherwise for all rules, then we return True in the end.

Solution with DFS: https://repl.it/@trsong/Direction-and-Position-Rule-Verification

import unittest

class Direction:
    E = 'E'
    S = 'S'
    W = 'W'
    N = 'N'
    
    @staticmethod
    def get_opposite_direction(d):
        if d == Direction.E: return Direction.W
        if d == Direction.W: return Direction.E
        if d == Direction.S: return Direction.N
        if d == Direction.N: return Direction.S
        return None
        

def DFS_check_connection(neighbors, start, end):
    visited = set()
    stack = [start]
    while stack:
        cur = stack.pop()
        if cur == end:
            return True

        if cur not in visited:
            if cur not in neighbors: continue
            stack.extend(neighbors[cur])
            visited.add(cur)
    return False
        

def direction_rule_validate(rules):
    direction_neighbor = { d:{} for d in [Direction.E, Direction.S, Direction.W, Direction.N] }
    for rule in rules:
        p1, directions, p2 = rule.split(' ')
        for d in directions:
            neighbors = direction_neighbor[d]
            # Before add edge (p1, p2), check connection between (p2, p1) to detect cycle
            if DFS_check_connection(neighbors, p2, p1):
                return False
            
            if p1 not in neighbors:
                neighbors[p1] = []
            neighbors[p1].append(p2)

            opposite_neighbor = direction_neighbor[Direction.get_opposite_direction(d)]
            if p2 not in opposite_neighbor:
                opposite_neighbor[p2] = []
            opposite_neighbor[p2].append(p1)
    return True


class DirectionRuleValidationSpec(unittest.TestCase):
    def test_example1(self):
        self.assertFalse(direction_rule_validate([
            "A N B",
            "B NE C",
            "C N A"
        ]))

    def test_example2(self):
        self.assertTrue(direction_rule_validate([
            "A NW B",
            "A N B"
        ]))

    def test_ambigious_rules(self):
        self.assertTrue(direction_rule_validate([
            "A SE B",
            "C SE B",
            "C SE A",
            "A N C"
        ]))

    def test_conflict_diagonal_directions(self):
        self.assertFalse(direction_rule_validate([
            "B NW A",
            "C SE A",
            "C NE B"
        ]))

    def test_paralllel_rules(self):
        self.assertTrue(direction_rule_validate([
            "A N B",
            "C N D",
            "C E B",
            "B W D",
            "B S D",
            "A N C",
            "D N B",
            "C E A"
        ]))


if __name__ == '__main__':
    unittest.main(exit=False)

Sep 2, 2019 LC 937 [Easy] Reorder Log Files


Question: You have an array of logs. Each log is a space delimited string of words.

For each log, the first word in each log is an alphanumeric identifier. Then, either:

  • Each word after the identifier will consist only of lowercase letters, or;
  • Each word after the identifier will consist only of digits. We will call these two varieties of logs letter-logs and digit-logs. It is guaranteed that each log has at least one word after its identifier.

Reorder the logs so that all of the letter-logs come before any digit-log. The letter-logs are ordered lexicographically ignoring identifier, with the identifier used in case of ties. The digit-logs should be put in their original order.

Return the final order of the logs.

Example:

Input: ["a1 9 2 3 1","g1 act car","zo4 4 7","ab1 off key dog","a8 act zoo"]
Output: ["g1 act car","a8 act zoo","ab1 off key dog","a1 9 2 3 1","zo4 4 7"]

Solution with Customized Sort Function: https://repl.it/@trsong/Reorder-Log-Files

import unittest

def is_number(char):
    return '0' <= char <= '9'

def compare_string(s1, begin1, end1, s2, begin2, end2):
    i = 0
    while begin1 + i < end1 and begin2 + i < end2:
        char1 = s1[begin1 + i]
        char2 = s2[begin2 + i]
        if char1 < char2:
            return -1
        elif char1 > char2:
            return 1
        i += 1
    
    len1 = end1 - begin1
    len2 = end2 - begin2
    if len1 < len2:
        return -1
    elif len1 > len2:
        return 1
    else:
        return 0

def compare(log1_with_index, log2_with_index):
    log1, pos1 = log1_with_index
    log2, pos2 = log2_with_index
    is_log1_digit = is_number(log1[pos1])
    is_log2_digit = is_number(log2[pos2])
    if is_log1_digit and is_log2_digit:
        return 0
    elif is_log1_digit:
        return 1
    elif is_log2_digit:
        return -1
    else:
        log_res = compare_string(log1, pos1, len(log1), log2, pos2, len(log2))
        if log_res != 0:
            return log_res
        else:
            return compare_string(log1, 0, pos1, log2, 0, pos2)

def reorder_log_files(logs):
    logs_with_index = map(lambda log: (log, log.index(' ') + 1), logs)
    logs_with_index.sort(cmp = compare)
    return map(lambda x: x[0], logs_with_index)


class ReorderLogFileSpec(unittest.TestCase):
    dlog1 = "a1 9 2 3 1"
    dlog2 = "zo4 4 7"
    llog1 = "g1 act car"
    llog2 = "ab1 off key dog"
    llog3 = "a8 act zoo"
    llog4 = "g1 act car jet jet jet"
    llog5 = "hhh1 act car"
    llog6 = "g1 act car jet"

    def test_example(self):
        self.assertEqual(
            reorder_log_files([self.dlog1, self.llog1, self.dlog2, self.llog2, self.llog3]), 
            [self.llog1,self.llog3, self.llog2, self.dlog1, self.dlog2])

    def test_empty_logs(self):
        self.assertEqual(reorder_log_files([]), [])

    def test_digit_logs_maintaining_same_order(self):
        self.assertEqual(reorder_log_files([self.dlog1, self.dlog2]), [self.dlog1, self.dlog2])
        self.assertEqual(reorder_log_files([self.dlog2, self.dlog1, self.dlog2]), [self.dlog2, self.dlog1, self.dlog2])

    def test_when_letter_logs_have_a_tie(self):
        self.assertEqual(
            reorder_log_files([self.llog6, self.llog4, self.llog1, self.llog5]), 
            [self.llog1, self.llog5, self.llog6, self.llog4])
    

if __name__ == '__main__':
    unittest.main(exit=False)

Sep 1, 2019 LT 892 [Medium] Alien Dictionary


Question: There is a new alien language which uses the latin alphabet. However, the order among letters are unknown to you. You receive a list of non-empty words from the dictionary, where words are sorted lexicographically by the rules of this new language. Derive the order of letters in this language.

  • You may assume all letters are in lowercase.

  • You may assume that if a is a prefix of b, then a must appear before b in the given dictionary.

  • If the order is invalid, return an empty string. There may be multiple valid order of letters, return the smallest in normal lexicographical order

Example 1:

Input: ["wrt", "wrf", "er", "ett", "rftt"]
Output: "wertf"
Explanation
from "wrt" and "wrf", we can get 't'<'f'
from "wrt" and "er", we can get 'w'<'e'
from "er" and "ett", we can get 'r'<'t'
from "ett" and "rtff", we can get 'e'<'r'
So return "wertf"

Example 2:

Input: ["z", "x"]
Output: "zx"
Explanation
from "z" and "x"we can get 'z' < 'x'
So return "zx"

My thoughts: As the alien letters are topologically sorted, we can just mimic what topological sort with numbers and try to find pattern.

Suppose the dictionary contains: 01234. Then the words can be 023, 024, 12, 133, 2433. Notice that we can only find the relative order by finding first unequal letters between consecutive words. eg. 023, 024 => 3 < 4. 024, 12 => 0 < 1. 12, 133 => 2 < 3

With relative relation, we can build a graph with each occurring letters being veteces and edge (u, v) represents u < v. If there exists a loop that means we have something like a < b < c < a and total order not exists. Otherwise we preform a topological sort to generate the total order which reveals the alien dictionary.

Solution with Topological Sort: https://repl.it/@trsong/Alien-Dictionary

import unittest

class NodeState(object):
    UNVISITED = 0
    VISITING = 1
    VISITED = 2

def alien_dict_order(words):
    neigbor = {}
    for i in xrange(1, len(words)):
        prev_word, cur_word = words[i-1], words[i]
        for j in xrange(min(len(prev_word), len(cur_word))):
            prev_word_char, cur_word_char = prev_word[j], cur_word[j]
            if prev_word_char != cur_word_char:
                if prev_word_char not in neigbor:
                    neigbor[prev_word_char] = []
                neigbor[prev_word_char].append(cur_word_char)
                break
    
    for key in neigbor:
        neigbor[key].sort()

    node_states = {}
    stack = []
    tsort_stack = []
    for word in words:
        for char in word:
            if char not in neigbor:
                neigbor[char] = []
            node_states[char] = NodeState.UNVISITED


    for node in sorted(neigbor.keys(), reverse=True):
        if node_states[node] != NodeState.VISITED:
            stack.append(node)

        while stack:
            cur = stack[-1]
            if node_states[cur] == NodeState.VISITING:
                node_states[cur] = NodeState.VISITED
            elif node_states[cur] == NodeState.UNVISITED: 
                node_states[cur] = NodeState.VISITING
                for nei in neigbor[cur]:
                    if node_states[nei] == NodeState.VISITING:
                        return ""
                    elif node_states[nei] == NodeState.UNVISITED:
                        stack.append(nei)
            else:
                tsort_stack.append(stack.pop())

    top_order = []
    while tsort_stack:
        top_order.append(tsort_stack.pop())
        
    return "".join(top_order)
            

class AlienDictOrderSpec(unittest.TestCase):
    def test_example1(self):
        # 01234
        # wertf
        # decode array result become 023, 024, 12, 133, 2433
        self.assertEqual(alien_dict_order(["wrt", "wrf", "er", "ett", "rftt"]), "wertf")

    def test_example2(self):
        self.assertEqual(alien_dict_order(["z", "x"]), "zx")

    def test_invalid_order(self):
        self.assertEqual(alien_dict_order(["a", "b", "a"]), "")

    def test_invalid_order2(self):
        # 012
        # abc
        # decode array result become 210, 211, 212, 012
        self.assertEqual(alien_dict_order(["cba", "cbb", "cbc", "abc"]), "")

    def test_invalid_order3(self):
        # 012
        # abc
        # decode array result become 10, 11, 211, 22, 20 
        self.assertEqual(alien_dict_order(["ba", "bb", "cbb", "cc", "ca"]), "")

    def test_valid_order(self):
        # 012
        # abc
        # decode array result become 01111, 122, 20
        self.assertEqual(alien_dict_order(["abbbb", "bcc", "ca"]), "abc")

    def test_valid_order2(self):
        # 0123
        # bdac
        # decode array result become 022, 2031, 2032, 320, 321
        self.assertEqual(alien_dict_order(["baa", "abcd", "abca", "cab", "cad"]), "bdac")

    def test_multiple_valid_result(self):
        self.assertEqual(alien_dict_order(["edcba"]), "abcde")

    def test_multiple_valid_result2(self):
        # 01
        # ab
        # cd
        self.assertEqual(alien_dict_order(["aa", "ab", "cc", "cd"]), "abcd")

    def test_multiple_valid_result3(self):
        # 01
        # ab
        #  c
        #  d
        self.assertEqual(alien_dict_order(["aaaaa", "aaad", "aab", "ac"]), "abcd")


if __name__ == '__main__':
    unittest.main(exit=False)

Aug 31, 2019 [Hard] Encode and Decode Array of Strings


Question: Given an array of string, write function “encode” to convert an array of strings into a single string and function “decode” to restore the original array.

Hint: string can be encoded as <length>:<contents>

Follow-up: what about array of integers, strings, and dictionaries?

My thoughts: There are many ways to encode/decode. Our solution use BEncode, the encoding used by BitTorrent.

According to BEncode Wiki: https://en.wikipedia.org/wiki/Bencode and specification of BitTorrent: http://www.bittorrent.org/beps/bep_0003.html. BEncode works as following:

  • Integer num is encoded as "i{num}e". eg. 42 => "i42e", -32 => "i-32e", 0 => "i0e"
  • String s is encoded as "{len(s)}:{s}". eg. "abc" => "3:abc", "" => "0:", "s" => "1:s", "doge" => "4:doge"
  • List lst is encoded as "l{encode(lst[0])}{encode(lst[1])}....{encode(lst[n-1])}e". e.g. [] => "le", [42, "cat"] => "li42e3:cate", [[11], [22], [33]] => lli11eeli22eeli33eee
  • Dictionary d is encoded as "d{encode(key1)}{encode(val1)}...{encode(key_n)}{encode(val_n)}e" e.g. {'bar': 'spam','foo': 42} => "d3:bar4:spam3:fooi42ee"

Solution with BEncode: https://repl.it/@trsong/Encode-and-Decode-Array-of-Strings

import unittest

"""
Encode Utilities
"""
def encode_int(num):
    return ["i", str(num), "e"]

def encode_str(string):
    return [str(len(string)), ":", string]

def encode_list(lst):
    res = ["l"]
    for e in lst:
        res.extend(encode_func[type(e)](e))
    res.append("e")
    return res

def encode_dict(dicionary):
    res = ["d"]
    for k, v in sorted(dicionary.items()):
        res.extend(encode_str(k))
        res.extend(encode_func[type(v)](v))
    res.append("e")
    return res

encode_func = {
    int: encode_int,
    str: encode_str,
    list: encode_list,
    dict: encode_dict
}

"""
Decode Utilities
"""
def decode_int(encoded, pos):
    pos += 1
    new_pos = encoded.index('e', pos)
    num = int(encoded[pos:new_pos])
    return (num, new_pos + 1)

def decode_str(encoded, pos):
    colon = encoded.index(':', pos)
    n = int(encoded[pos:colon])
    str_start = colon + 1
    return (encoded[str_start:str_start + n], str_start + n)

def decode_list(encoded, pos):
    res = []
    pos += 1
    while encoded[pos] != 'e':
        val, new_pos = decode_func[encoded[pos]](encoded, pos)
        pos = new_pos
        res.append(val)
    return (res, pos + 1)

def decode_dict(encoded, pos):
    res = {}
    pos += 1
    while encoded[pos] != 'e':
        key, key_end_pos = decode_str(encoded, pos)
        val, val_end_pos = decode_func[encoded[key_end_pos]](encoded, key_end_pos)
        pos = val_end_pos
        res[key] = val
    return (res, pos + 1)

decode_func = {
    'l': decode_list,
    'd': decode_dict,
    'i': decode_int
}
decode_func.update({
    chr(ord('0') + i): decode_str for i in xrange(10) # 0-9 all use decode_str
})


class BEncode(object):
    @staticmethod
    def encode(obj):
        return "".join(encode_func[type(obj)](obj))

    
    @staticmethod
    def decode(encoded):
        obj, _ = decode_func[encoded[0]](encoded, 0)
        return obj


class BEncodeSpec(unittest.TestCase):
    def assert_BEncode_result(self, obj):
        encoded_str = BEncode.encode(obj)
        decoded_obj = BEncode.decode(encoded_str)
        self.assertEqual(decoded_obj, obj)

    def test_positive_integer(self):
        self.assert_BEncode_result(42)  # encode as "i42e"
    
    def test_negative_integer(self):
        self.assert_BEncode_result(-32)  # encode as "i-32e"

    def test_zero(self):
        self.assert_BEncode_result(0)  # encode as "i0e"

    def test_empty_string(self):
        self.assert_BEncode_result("")  # encode as "0:""

    def test_string_with_whitespaces(self):
        self.assert_BEncode_result(" ")  # encode as "1: "
        self.assert_BEncode_result(" a ")  # encode as "3: a "
        self.assert_BEncode_result("a b  c   1 2 3 ")  # encode as "15:a b  c   1 2 3 "

    def test_string_with_delimiter_characters(self):
        self.assert_BEncode_result("i42ei42e")  # encode as "8:i42ei42e"
        self.assert_BEncode_result("4:spam")  # encode as "6:4:spam"
        self.assert_BEncode_result("d3:bar4:spam3:fooi42ee")  # encode as "22:d3:bar4:spam3:fooi42ee"
    
    def test_string_with_special_characters(self):
        self.assert_BEncode_result("!@#$%^&*(){}[]|\;:'',.?/`~") # encode as "26:!@#$%^&*(){}[]|\;:'',.?/`~"

    def test_empty_list(self):
        self.assert_BEncode_result([])  # encode as "le"

    def test_list_of_empty_strings(self):
        self.assert_BEncode_result(["", "", ""])  # encode as "l0:0:0:e"

    def test_nested_empty_lists(self):
        self.assert_BEncode_result([[], [[]], [[[]]]]) # encoded as "llelleellleee"

    def test_list_of_strings(self):
        self.assert_BEncode_result(['a', '', 'abc']) # encode as "l1:a0:3:abce"

    def test_nested_lists(self):
        self.assert_BEncode_result([0, ["a", 1], "ab", [[2], "c"]]) # encode as "li0el1:ai1ee2:ablli2ee1:cee"

    def test_empty_dictionary(self):
        self.assert_BEncode_result({}) # encode as "de"

    def test_dictionary(self):
        self.assert_BEncode_result({
            'bar': 'spam',
            'foo': 42
        })  # encode as "d3:bar4:spam3:fooi42ee"

    def test_nested_dictionary(self):
        self.assert_BEncode_result([
            "s",
            42, 
            { 
                'a': 12,
                'list': [
                    'b', 
                    {
                        'c': [[1], 2, [3, [4]]],
                        'd': 12
                    }]
            }]) # encode as 'l1:si42ed1:ai12e4:listl1:bd1:clli1eei2eli3eli4eeee1:di12eeeee'
 

if __name__ == '__main__':
    unittest.main(exit=False)

Aug 30, 2019 LT 623 [Hard] K Edit Distance


Question: Given a set of strings which just has lower case letters and a target string, output all the strings for each the edit distance with the target no greater than k. You have the following 3 operations permitted on a word:

  • Insert a character
  • Delete a character
  • Replace a character

Example 1:

Given words = ["abc", "abd", "abcd", "adc"] and target = "ac", k = 1
Return ["abc", "adc"]
Explanation:
- "abc" remove "b"
- "adc" remove "d"

Example 2:

Given words = ["acc","abcd","ade","abbcd"] and target = "abc", k = 2
Return ["acc","abcd","ade","abbcd"]
Explanation:
- "acc" turns "c" into "b"
- "abcd" remove "d"
- "ade" turns "d" into "b" turns "e" into "c"
- "abbcd" gets rid of "b" and "d"

My thoughts: The brutal force way is to calculate the distance between each word and target and filter those qualified words. However, notice that word might have exactly the same prefix and that share the same DP array. So we can build a prefix tree that contains all words and calculate the DP array along the way.

Solution with Trie and DFS: https://repl.it/@trsong/K-Edit-Distance

import unittest

class Trie(object):
    def __init__(self):
        self.count = 0
        self.word = None
        self.edit_distance_dp = None
        self.children = None

    def insert(self, word): 
        t = self
        for char in word:
            if not t.children:
                t.children = {}
            if char not in t.children:
                t.children[char] = Trie()
            t = t.children[char]
        t.count += 1
        t.word = word


def filter_k_edit_distance(words, target, k):
    trie = Trie()
    n = len(target)
    filtered_word = filter(lambda word: n - k <= len(word) <= n + k, words)
    for word in filtered_word:
        trie.insert(word)
        
    trie.edit_distance_dp = [i for i in xrange(n+1)] # edit distance between "" and target[:i] equals i (insert i letters)
    stack = [trie]
    res = []
    while stack:
        parent = stack.pop()

        parent_dp = parent.edit_distance_dp
        if parent.word is not None and parent_dp[n] <= k:
            res.extend([parent.word] * parent.count)

        if not parent.children:
            continue

        for char, child in parent.children.items():
            dp = [0] * (n+1)
            dp[0] = parent_dp[0] + 1
            for j in xrange(1, n+1):
                if char == target[j-1]:
                    dp[j] = parent_dp[j-1]
                else:
                    dp[j] = min(1 + parent_dp[j-1], 1 + dp[j-1], 1 + parent_dp[j])
            child.edit_distance_dp = dp
            stack.append(child)
    
    return res


class FilterKEditDistanceSpec(unittest.TestCase):
    def assert_k_distance_array(self, res, expected):
        self.assertEqual(sorted(res), sorted(expected))

    def test_example1(self):
        words =["abc", "abd", "abcd", "adc"] 
        target = "ac"
        k = 1
        expected = ["abc", "adc"]
        self.assert_k_distance_array(filter_k_edit_distance(words, target, k), expected)
    
    def test_example2(self):
        words = ["acc","abcd","ade","abbcd"]
        target = "abc"
        k = 2
        expected = ["acc","abcd","ade","abbcd"]
        self.assert_k_distance_array(filter_k_edit_distance(words, target, k), expected)

    def test_duplicated_words(self):
        words = ["a","b","a","c", "bb", "cc"]
        target = ""
        k = 1
        expected = ["a","b","a","c"]
        self.assert_k_distance_array(filter_k_edit_distance(words, target, k), expected)

    def test_empty_words(self):
        words = ["", "", "", "c", "bbbbb", "cccc"]
        target = "ab"
        k = 2
        expected = ["", "", "", "c"]
        self.assert_k_distance_array(filter_k_edit_distance(words, target, k), expected)

    def test_same_word(self):
        words = ["ab", "ab", "ab"]
        target = "ab"
        k = 1000
        expected = ["ab", "ab", "ab"]
        self.assert_k_distance_array(filter_k_edit_distance(words, target, k), expected)

    def test_unqualified_words(self):
        words = ["", "a", "aa", "aaa", "aaaa", "aaaaa", "aaaaaa", "aaaaaaa", "aaaaaaaa"]
        target = "aaaaa"
        k = 2
        expected = ["aaa", "aaaa", "aaaaa", "aaaaaa", "aaaaaaa"]
        self.assert_k_distance_array(filter_k_edit_distance(words, target, k), expected)


if __name__ == '__main__':
    unittest.main(exit=False)

Aug 29, 2019 [Easy] Flip Bit to Get Longest Sequence of 1s


Question: Given an integer, can you flip exactly one bit from a 0 to a 1 to get the longest sequence of 1s? Return the longest possible length of 1s after flip.

Example:

Input: 183 (or binary: 10110111)
Output: 6
Explanation: 10110111 => 10111111. The longest sequence of 1s is of length 6.

Solution: https://repl.it/@trsong/Flip-Bit-to-Get-Longest-Sequence-of-1s

import unittest

def flip_bits(num):
    prev = 0
    cur = 0
    max_len = 0
    while num > 0:
        last_digit = num & 1
        if last_digit == 1:
            cur += 1
        else:
            max_len = max(max_len, cur + prev + 1)
            prev = cur
            cur = 0
        num >>= 1
    return max(max_len, cur + prev + 1)


class FlipBitSpec(unittest.TestCase):
    def test_example(self):
        self.assertEqual(flip_bits(0b10110111), 6)  # 10110111 => 10111111

    def test_not_exist_ones(self):
        self.assertEqual(flip_bits(0), 1)  # 0 => 1

    def test_flip_last_digit(self):
        self.assertEqual(flip_bits(0b100110), 3)  # 100110 => 100111

    def test_three_zeros(self):
        self.assertEqual(flip_bits(0b1011110110111), 7)  # 1011110110111 => 1011111110111

    def test_one(self):
        self.assertEqual(flip_bits(1), 2)  # 01 => 11


if __name__ == '__main__':
    unittest.main(exit=False)

Aug 28, 2019 LC 103 [Medium] Binary Tree Zigzag Level Order Traversal


Question: Given a binary tree, return the zigzag level order traversal of its nodes’ values. (ie, from left to right, then right to left for the next level and alternate between).

For example:

Given following binary tree:
    3
   / \
  9  20
    /  \
   15   7
return its zigzag level order traversal as:
[
  [3],
  [20,9],
  [15,7]
]

Solution with BFS and Two Stacks: https://repl.it/@trsong/Binary-Tree-Zigzag-Level-Order-Traversal

import unittest

class Node(object):
    def __init__(self, val, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


def zig_zag_level_order(root):
    if not root:
        return []
    stack = [root]
    res = []
    is_left_first = True
    while stack:
        level = []
        next_stack = []
        for _ in xrange(len(stack)):
            cur = stack.pop()
            if cur:
                level.append(cur.val)
                if is_left_first:
                    next_stack.append(cur.left)
                    next_stack.append(cur.right)
                else:
                    next_stack.append(cur.right)
                    next_stack.append(cur.left)
        stack = next_stack
        is_left_first ^= True # flip the boolean flag
        if level:
            res.append(level)
    return res


class ZigZagLevelOrderSpec(unittest.TestCase):
    def test_example(self):
        """
            3
           / \
          9  20
            /  \
           15   7
        """
        n20 = Node(20, Node(15), Node(7))
        n3 = Node(3, Node(9), n20)
        self.assertEqual(zig_zag_level_order(n3), [
            [3],
            [20, 9],
            [15, 7]
        ])
    
    def test_complete_tree(self):
        """
             1
           /   \
          3     2
         / \   /  
        4   5 6  
        """
        n3 = Node(3, Node(4), Node(5))
        n2 = Node(2, Node(6))
        n1 = Node(1, n3, n2)
        self.assertEqual(zig_zag_level_order(n1), [
            [1],
            [2, 3],
            [4, 5, 6]
        ])

    def test_sparse_tree(self):
        """
             1
           /   \
          3     2
           \   /  
            4 5
           /   \
          7     6
           \   /  
            8 9
        """
        n3 = Node(3, right=Node(4, Node(7, right=Node(8))))
        n2 = Node(2, Node(5, right=Node(6, Node(9))))
        n1 = Node(1, n3, n2)
        self.assertEqual(zig_zag_level_order(n1), [
            [1],
            [2, 3],
            [4, 5],
            [6, 7],
            [8, 9]
        ])


if __name__ == '__main__':
    unittest.main(exit=False)

Aug 27, 2019 [Hard] Minimum Appends to Craft a Palindrome


Question: Given a string s we need to append (insertion at end) minimum characters to make a string palindrome.

Follow-up: Don’t use Manacher’s Algorithm, even though Longest Palindromic Substring can be efficiently solved with that algorithm.

Example 1:

Input : s = "abede"
Output : "abedeba"
We can make string palindrome as "abedeba" by adding ba at the end of the string.

Example 2:

Input : s = "aabb"
Output : "aabbaa"
We can make string palindrome as"aabbaa" by adding aa at the end of the string.

My thoughts: An efficient way to solve this problem is to find the max len of suffix that is a palindrome. We can use a rolling hash function to quickly convert string into a number and by comparing the forward and backward hash value we can easily tell if a string is a palidrome or not. Example 1:

Hash("123") = 123, Hash("321") = 321. Not Palindrome 

Example 2:

Hash("101") = 101, Hash("101") = 101. Palindrome.

Rolling hashes are amazing, they provide you an ability to calculate the hash values without rehashing the whole string. eg. Hash(“123”) = Hash(“12”) ~ 3. ~ is some function that can efficient using previous hashing value to build new caching value.

However, we should not use Hash(“123”) = 123 as when the number become too big, the hash value be come arbitrarily big. Thus we use the following formula for rolling hash:

hash("1234") = (1*p0^3 + 2*p0^2 + 3*p0^1 + 4*p0^0) % p1. where p0 is a much smaller prime and p1 is relatively large prime. 

There might be some hashing collision. However by choosing a much smaller p0 and relatively large p1, such collison is highly unlikely. Here I choose to remember a special large prime number 666667 and smaller number you can just use any smaller prime number, it shouldn’t matter.

Solution with Rolling Hash: https://repl.it/@trsong/Minimum-Appends-to-Craft-a-Palindrome

import unittest

def craft_palindrome_with_min_appends(input_string):
    p0 = 17
    p1 = 666667  # large prime number worth to remember 
    reversed_string = input_string[::-1]
    forward_hash = 0  # right-most is most significant digit
    backward_hash = 0  # left-most is most significant digit
    max_len_palindrome_suffix = 0
    for i, char in enumerate(reversed_string):
        ord_char = ord(char)
        forward_hash = (forward_hash + ord_char * pow(p0, i)) % p1
        backward_hash = (p0 * backward_hash + ord_char) % p1
        if forward_hash == backward_hash:
            max_len_palindrome_suffix = i + 1

    return input_string + reversed_string[max_len_palindrome_suffix:]
  

class CraftPalindromeWithMinAppendSpec(unittest.TestCase):
    def test_example1(self):
        self.assertEqual(craft_palindrome_with_min_appends('abede'), 'abedeba')

    def test_example2(self):
        self.assertEqual(craft_palindrome_with_min_appends('aabb'), 'aabbaa')

    def test_empty_string(self):
        self.assertEqual(craft_palindrome_with_min_appends(''), '')
    
    def test_already_palindrome(self):
        self.assertEqual(craft_palindrome_with_min_appends('147313741'), '147313741')
        self.assertEqual(craft_palindrome_with_min_appends('328823'), '328823')

    def test_ascending_sequence(self):
        self.assertEqual(craft_palindrome_with_min_appends('12345'), '123454321')

    def test_binary_sequence(self):
        self.assertEqual(craft_palindrome_with_min_appends('10001001'), '100010010001')
        self.assertEqual(craft_palindrome_with_min_appends('100101'), '100101001')
        self.assertEqual(craft_palindrome_with_min_appends('010101'), '0101010')


if __name__ == '__main__':
    unittest.main(exit=False)

Aug 26, 2019 [Hard] Find Next Greater Permutation


Question: Given a number represented by a list of digits, find the next greater permutation of a number, in terms of lexicographic ordering. If there is not greater permutation possible, return the permutation with the lowest value/ordering.

For example, the list [1,2,3] should return [1,3,2]. The list [1,3,2] should return [2,1,3]. The list [3,2,1] should return [1,2,3].

Can you perform the operation without allocating extra memory (disregarding the input memory)?

My thoughts: Imagine the list as a number, if it’s in descending order, then there will be no number greater than that and we have to return the number in ascending order, that is, the smallest number. e.g. 321 will become 123.

Leave first part untouched. If the later part of array are first increasing then decreasing, like 1321, then based on previous observation, we know the descending part will change from largest to smallest, we want the last increasing digit to increase as little as possible, i.e. slightly larger number on the right. e.g. 2113

Here are all the steps:

  1. Find last increase number
  2. Find the slightly larger number. i.e. the smallest one among all number greater than the last increase number on the right
  3. Swap the slightly larger number with last increase number
  4. Turn the descending array on right to be ascending array

Solution: https://repl.it/@trsong/Find-Next-Greater-Permutation

import unittest

def next_greater_permutation(num_lst):
    n = len(num_lst)
    last_increase_index = n - 2

    # Step1: Find last increase number
    while last_increase_index >= 0:
        if num_lst[last_increase_index] >= num_lst[last_increase_index + 1]:
            last_increase_index -= 1
        else:
            break

    if last_increase_index >= 0:
        # Step2: Find the slightly larger number. i.e. the smallest one among all number greater than the last increase number on the right
        larger_num_index = n - 1
        while num_lst[larger_num_index] <= num_lst[last_increase_index]:
            larger_num_index -= 1

        # Step3: Swap the slightly larger number with last increase number
        num_lst[larger_num_index], num_lst[last_increase_index] = num_lst[last_increase_index], num_lst[larger_num_index]

    # Step4: Turn the descending array on right to be ascending array 
    i, j = last_increase_index + 1, n - 1
    while i < j:
        num_lst[i], num_lst[j] = num_lst[j], num_lst[i]
        i += 1
        j -= 1
    return num_lst


class NextGreaterPermutationSpec(unittest.TestCase):
    def test_example1(self):
        self.assertEqual(next_greater_permutation([1, 2, 3]), [1, 3, 2])
    
    def test_example2(self):
        self.assertEqual(next_greater_permutation([1, 3, 2]), [2, 1, 3])

    def test_example3(self):
        self.assertEqual(next_greater_permutation([3, 2, 1]), [1, 2, 3])

    def test_empty_array(self):
        self.assertEqual(next_greater_permutation([]), [])

    def test_one_elem_array(self):
        self.assertEqual(next_greater_permutation([1]), [1])

    def test_decrease_increase_decrease_array(self):
        self.assertEqual(next_greater_permutation([3, 2, 1, 6, 5, 4]), [3, 2, 4, 1, 5, 6])
        self.assertEqual(next_greater_permutation([3, 2, 4, 6, 5, 4]), [3, 2, 5, 4, 4, 6])

    def test_increasing_decreasing_increasing_array(self):
        self.assertEqual(next_greater_permutation([4, 5, 6, 1, 2, 3]), [4, 5, 6, 1, 3, 2])

    def test_multiple_decreasing_and_increasing_array(self):
        self.assertEqual(next_greater_permutation([5, 3, 4, 9, 7, 6]), [5, 3, 6, 4, 7, 9])


if __name__ == '__main__':
    unittest.main(exit=False)

Aug 25, 2019 [Medium] Longest Subarray with Sum Divisible by K


Question: Given an arr[] containing n integers and a positive integer k. The problem is to find the length of the longest subarray with sum of the elements divisible by the given value k.

Example:

Input : arr[] = {2, 7, 6, 1, 4, 5}, k = 3
Output : 4
The subarray is {7, 6, 1, 4} with sum 18, which is divisible by 3.

My thoughts: Recall the way to efficiently calculate subarray sum is to calculate prefix sum, prefix_sum[i] = arr[0] + arr[1] + ... + arr[i] and prefix_sum[i] = prefix_sum[i-1] + arr[i]. The subarray sum between index i and j, arr[i] + arr[i+1] + ... + arr[j] = prefix_sum[j] - prefix_sum[i-1].

But this question is asking to find subarray whose sum is divisible by 3, that is, (prefix_sum[j] - prefix_sum[i-1]) mod k == 0 which implies prefix_sum[j] % k == prefix_sum[j-1] % k. So we just need to generate prefix_modulo array and find i,j such that j - i reaches max and prefix_modulo[j] == prefix_modulo[i-1]. As j > i and we must have value of prefix_modulo[i-1] already when we reach j. We can use a map to store the first occurance of certain prefix_modulo. This feels similar to Two-Sum question in a sense that we use map to store previous reached element and is able to quickly tell if current element satisfies or not.

Solution: https://repl.it/@trsong/Longest-Subarray-with-Sum-Divisible-by-K

import unittest

def longest_subarray(nums, k):
    if not nums: return 0
    n = len(nums)
    prefix_modulo = [0] * n
    mod_so_far = 0
    for i in xrange(n):
        mod_so_far = (mod_so_far + nums[i] % k) % k
        prefix_modulo[i] = mod_so_far
    
    mod_first_occur_map = {0: -1}
    max_len = 0
    for i, prefix_mod in enumerate(prefix_modulo):
        if prefix_mod not in mod_first_occur_map:
            mod_first_occur_map[prefix_mod] = i
        else:
            max_len = max(max_len, i - mod_first_occur_map[prefix_mod])
    return max_len


class LongestSubarraySpec(unittest.TestCase):
    def test_example(self):
        # Modulo 3, prefix array = [2, 0, 0, 1, 2, 1]. max (end - start) = 4 such that prefix[end] - prefix[start-1] = 0
        self.assertEqual(longest_subarray([2, 7, 6, 1, 4, 5], 3), 4)  # sum([7, 6, 1, 4]) = 18 (18 % 3 = 0)

    def test_empty_array(self):
        self.assertEqual(longest_subarray([], 10), 0)
    
    def test_no_existance_of_such_subarray(self):
        self.assertEqual(longest_subarray([1, 2, 3, 4], 11), 0)
    
    def test_entire_array_qualify(self):
        # Modulo 4, prefix array: [0, 1, 2, 3, 3, 2, 1, 0]. max (end - start) = 8 such that prefix[end] - prefix[start-1] = 0
        self.assertEqual(longest_subarray([0, 1, 1, 1, 0, -1, -1, -1], 4), 8)  # entire array sum = 0
        self.assertEqual(longest_subarray([4, 5, 9, 17, 8, 3, 7, -1], 4), 8)  # entire array sum = 52 (52 % 4 = 0)

    def test_unique_subarray(self):
        # Modulo 6, prefix array: [0, 1, 1, 2, 3, 2, 4, 4, 5]. max (end - start) = 2 such that prefix[end] - prefix[start-1] = 0
        self.assertEqual(longest_subarray([0, 1, 0, 1, 1, -1, 2, 0, 1], 6), 2)  #  sum([1, -1]) = 0
        self.assertEqual(longest_subarray([6, 7, 12, 7, 13, 5, 8, 36, 19], 6), 2)  #  sum([13, 5]) = 18 (18 % 6 = 0)
    

if __name__ == '__main__':
    unittest.main(exit=False)

Additional Question: LC 560 [Medium] Subarray Sum Equals K


Question: Given an array of integers and an integer k, you need to find the total number of continuous subarrays whose sum equals to k.

Example:

Input: nums = [1, 1, 1], k = 2
Output: 2

My thoughts: Just like how we efficiently calculate prefix_sum in previous question. We want to find how many index i exists such that prefix[j] - prefix[i] = k. As j > i, when we reach j, we pass i already, so we can store prefix[i] in a map and put value as occurance of prefix[i], that is why this question feels similar to Two Sum question.

Solution: https://repl.it/@trsong/Subarray-Sum-Equals-K

import unittest

def subarray_sum(nums, k):
    sum_so_far = 0
    prefix_sum_occur_map = {0: 1}
    res = 0
    for num in nums:
        sum_so_far += num
        target_sum = sum_so_far - k
        res += prefix_sum_occur_map.get(target_sum, 0)
        prefix_sum_occur_map[sum_so_far] = prefix_sum_occur_map.get(sum_so_far, 0) + 1
    return res


class SubarraySumSpec(unittest.TestCase):
    def test_example(self):
        self.assertEqual(subarray_sum([1, 1, 1], 2), 2)  # [1, 1] and [1, 1]
    
    def test_empty_array(self):
        self.assertEqual(subarray_sum([], 2), 0) 

    def test_target_is_zero(self):
        self.assertEqual(subarray_sum([0, 0], 0), 3) # [0], [0], [0, 0]

    def test_array_with_one_elem(self):
        self.assertEqual(subarray_sum([1], 0), 0)
        self.assertEqual(subarray_sum([1], 1), 1) # [1]

    def test_array_with_unique_target_prefix(self):
        # suppose the prefix_sum = [1, 2, 3, 3, 2, 1]
        self.assertEqual(subarray_sum([1, 1, 1, 0, -1, -1], 2), 4)  # [1, 1], [1, ,1], [1, 1, 0], [1, 1, 1, 0, -1]


if __name__ == '__main__':
    unittest.main(exit=False)

Aug 24, 2019 LC 358 [Hard] Rearrange String K Distance Apart


Question: Given a non-empty string str and an integer k, rearrange the string such that the same characters are at least distance k from each other.

All input strings are given in lowercase letters. If it is not possible to rearrange the string, return an empty string “”.

Example 1:

str = "aabbcc", k = 3
Result: "abcabc"
The same letters are at least distance 3 from each other.

Example 2:

str = "aaabc", k = 3 
Answer: ""
It is not possible to rearrange the string.

Example 3:

str = "aaadbbcc", k = 2
Answer: "abacabcd"
Another possible answer is: "abcabcda"
The same letters are at least distance 2 from each other.

My thoughts: The problem is just a variant of yesterday’s Task Scheduler problem. The idea is to greedily choose the character with max remaining number for each window k. If no such character satisfy return empty string directly.

Solution with Greedy Algorithm: https://repl.it/@trsong/Rearrange-String-K-Distance-Apart

import unittest
from Queue import PriorityQueue

def rearrange_string(input_string, k):
    if not input_string or k <= 0: return ""
    histogram = {}
    for c in input_string:
        # use negative key with min-heap to achieve max heap
        histogram[c] = histogram.get(c, 0) + 1
    
    max_heap = PriorityQueue()
    for c, count in histogram.items():
        max_heap.put((-count, c))

    res = []
    while not max_heap.empty():
        remaining_char = []
        for _ in xrange(k):
            # Greedily choose the char with max remaining count
            if max_heap.empty() and not remaining_char:
                break
            elif max_heap.empty():
                return ""
            neg_count, char = max_heap.get()
            count = -neg_count - 1
            res.append(char)
            if count > 0:
                remaining_char.append((-count, char))
        for count_char in remaining_char:
            max_heap.put(count_char)
    return ''.join(res)


class RearrangeStringSpec(unittest.TestCase):
    def assert_k_distance_apart(self, rearranged_string, original_string, k):
        # Test same length
        self.assertTrue(len(original_string) == len(rearranged_string))

        # Test containing all characters
        self.assertTrue(sorted(original_string) == sorted(rearranged_string))

        # Test K distance apart
        last_occur_map = {}
        for i, c in enumerate(rearranged_string):
            last_occur = last_occur_map.get(c, float('-inf'))
            self.assertTrue(i - last_occur >= k)
            last_occur_map[c] = i
    
    def test_utility_function_is_correct(self):
        original_string = "aaadbbcc"
        k = 2
        ans1 = "abacabcd"
        ans2 = "abcabcda"
        self.assert_k_distance_apart(ans1, original_string, k)
        self.assert_k_distance_apart(ans2, original_string, k)
        self.assertRaises(AssertionError, self.assert_k_distance_apart, original_string, original_string, k)

    def test_example1(self):
        original_string = "aabbcc"
        k = 3
        target_string = rearrange_string(original_string, k)
        self.assert_k_distance_apart(target_string, original_string, k)
    
    def test_example2(self):
        original_string = "aaabc"
        self.assertEqual(rearrange_string(original_string, 3),"")
    
    def test_example3(self):
        original_string = "aaadbbcc"
        k = 2
        target_string = rearrange_string(original_string, k)
        self.assert_k_distance_apart(target_string, original_string, k)
    
    def test_large_distance(self):
        original_string = "abcd"
        k = 10
        rearranged_string = rearrange_string(original_string, k)
        self.assert_k_distance_apart(rearranged_string, original_string, k)

    def test_empty_input_string(self):
        self.assertEqual(rearrange_string("", 1),"")
    
    def test_impossible_to_rearrange(self):
        self.assertEqual(rearrange_string("aaaabbbcc", 3), "")

    def test_k_too_small(self):
        self.assertEqual(rearrange_string("a", 0), "")
        self.assertEqual(rearrange_string("a", -1), "")
    

if __name__ == '__main__':
    unittest.main(exit=False)

Aug 23, 2019 LC 621 [Medium] Task Scheduler


Question: Given a char array representing tasks CPU need to do. It contains capital letters A to Z where different letters represent different tasks. Tasks could be done without original order. Each task could be done in one interval. For each interval, CPU could finish one task or just be idle.

However, there is a non-negative cooling interval n that means between two same tasks, there must be at least n intervals that CPU are doing different tasks or just be idle.

You need to return the least number of intervals the CPU will take to finish all the given tasks.

Example:

Input: tasks = ["A", "A", "A", "B", "B", "B"], n = 2
Output: 8
Explanation: A -> B -> idle -> A -> B -> idle -> A -> B.

My thoughts: Treat n+1 as the size of each window. For each window, we try to fit as many tasks as possible following the max number of remaining tasks. If all tasks are chosen, we instead use idle.

Solution with Greedy Algorithm: https://repl.it/@trsong/Task-Scheduler

import unittest
from Queue import PriorityQueue

def least_interval(tasks, n):
    if not tasks: return 0
    occurrence = {}
    for task in tasks:
        occurrence[task] = occurrence.get(task, 0) + 1
    
    max_heap = PriorityQueue()
    for task, occur in occurrence.items():
        # use negative key with min-heap to achieve max heap
        max_heap.put((-occur, task))
    
    res = 0
    while not max_heap.empty():
        remaining_tasks = []
        for _ in xrange(n+1):
            if max_heap.empty() and not remaining_tasks:
                break
            elif not max_heap.empty():
                # Greedily choose the task with max occurrence 
                negative_occur, task = max_heap.get()
                occur = -negative_occur - 1
                if occur > 0:
                    remaining_tasks.append((-occur, task))
            res += 1
        for task_occur in remaining_tasks:
            max_heap.put(task_occur)
    
    return res


class LeastIntervalSpec(unittest.TestCase):
    def test_example(self):
        tasks = ["A", "A", "A", "B", "B", "B"]
        n = 2
        self.assertEqual(least_interval(tasks, n), 8) # A -> B -> idle -> A -> B -> idle -> A -> B

    def test_no_tasks(self):
        self.assertEqual(least_interval([], 0), 0)
        self.assertEqual(least_interval([], 2), 0)
    
    def test_same_task_and_idle(self):
        tasks = ["A", "A", "A"]
        n = 1
        self.assertEqual(least_interval(tasks, n), 5)  # A -> idle -> A -> idle -> A

    def test_three_kind_tasks_no_idle(self):
        tasks = ["A", "B", "A", "C"]
        n = 1
        self.assertEqual(least_interval(tasks, n), 4)  # A -> B -> A -> C
    
    def test_three_kind_tasks_with_one_idle(self):
        tasks = ["A", "A", "A", "B", "C", "C"]
        n = 2
        self.assertEqual(least_interval(tasks, n), 7)  # A -> C -> B -> A -> C -> idle -> A

    def test_each_kind_one_task(self):
        tasks = ["A", "B", "C", "D"]
        n = 10
        self.assertEqual(least_interval(tasks, n), 4)  # A -> B -> C -> D


if __name__ == '__main__':
    unittest.main(exit=False)

Aug 22, 2019 [Medium] Amazing Number


Question: Define amazing number as: its value is less than or equal to its index. Given a circular array, find the starting position, such that the total number of amazing numbers in the array is maximized.

Follow-up: Should get a solution with time complexity less than O(N^2)

Example 1:

Input: [0, 1, 2, 3]
Ouptut: 0. When starting point at position 0, all the elements in the array are equal to its index. So all the numbers are amazing number.

Example 2:

Input: [1, 0, 0]
Output: 1. When starting point at position 1, the array becomes 0, 0, 1. All the elements are amazing number.
If there are multiple positions, return the smallest one.

My thoughts: We can use Brute-force Solution to get answer in O(n^2). But can we do better? Well, some smart observation is needed.

  • First, we know that 0 and all negative numbers are amazing numbers.
  • Second, if a number is too big, i.e. greater than the length of array, then there is no way for that number to be an amazing number.
  • Finally, if a number is neither too small nor too big, i.e. between (0, n-1), then we can define “dangerous” range as [i - nums[i] + 1, i] and its complement: “safe” range [i + 1, i - nums[i]] should be safe. So we store all safe intervals to an array.

We accumlate those intervals by using interval counting technique: define interval_accu array, for each interval (start, end), interval_accu[start] += 1 and interval_accu[end+1] -= 1 so that when we can make interval accumulation by interval_accu[i] += interval_accu[i-1] for all i.

Find max safe interval along the interval accumulation, i.e. the index that has maximum safe interval overlapping.

Brute-force Solution: https://repl.it/@trsong/Amazing-Number-Brute-force

def max_amazing_number_index(nums):
    n = len(nums)
    max_count = 0
    max_count_index = 0
    for i in xrange(n):
        count = 0
        for j in xrange(i, i + n):
            index = (j - i) % n
            if nums[j % n] <= index:
                count += 1
        
        if count > max_count:
            max_count = count
            max_count_index = i
    return max_count_index

Efficient Solution with Interval Count: https://repl.it/@trsong/Amazing-Number

import unittest

def max_amazing_number_index(nums):
    n = len(nums)
    valid_intervals = []
    for i in xrange(n):
        # invalid zone starts from i - nums[i] + 1 and ends at i
        # 0 0 0 0 0 3 0 0 0 0 0 
        #       ^ ^ ^
        #       invalid
        # thus the valid zone is the complement [i + 1, i - nums[i]]
        if nums[i] > n:
            continue
        elif nums[i] < 0:
            valid_intervals.append([0, n-1])
        else:
            valid_intervals.append([(i + 1) % n, (i - nums[i]) % n])

    interval_accumulation = [0] * n
    for start, end in valid_intervals:
        # valid interval [start, end] is circular, i.e. end < start
        # thus can be broken into [0, end] and [start, n-1]
        interval_accumulation[start] += 1

        # with one exception: end > start, when the number is too small, like smaller than 0,
        # if that's the case, we don't count 
        if start > end:
            interval_accumulation[0] += 1

        if end + 1 < n:
            interval_accumulation[end + 1] -= 1

    max_count = interval_accumulation[0]
    max_count_index = 0
    for i in xrange(1, n):
        interval_accumulation[i] += interval_accumulation[i-1]
        if interval_accumulation[i] > max_count:
            max_count = interval_accumulation[i]
            max_count_index = i
    return max_count_index 


class MaxAmazingNumberIndexSpec(unittest.TestCase):
    def test_example1(self):
        self.assertEqual(max_amazing_number_index([0, 1, 2, 3]), 0)  # max # amazing number = 4 at [0, 1, 2, 3]

    def test_example2(self):
        self.assertEqual(max_amazing_number_index([1, 0, 0]), 1)  # max # amazing number = 3 at [0, 0, 1]

    def test_non_descending_array(self):
        self.assertEqual(max_amazing_number_index([0, 0, 0, 1, 2, 3]), 0)  # max # amazing number = 0 at [0, 0, 0, 1, 2, 3]

    def test_random_array(self):
        self.assertEqual(max_amazing_number_index([1, 4, 3, 2]), 1)  # max # amazing number = 2 at [4, 3, 2, 1]

    def test_non_ascending_array(self):
        self.assertEqual(max_amazing_number_index([3, 3, 2, 1, 0]), 2)  # max # amazing number = 4 at [2, 1, 0, 3, 3]

    def test_return_smallest_index_when_no_amazing_number(self):
        self.assertEqual(max_amazing_number_index([99, 99, 99, 99]), 0)  # max # amazing number = 0 thus return smallest possible index

    def test_negative_number(self):
        self.assertEqual(max_amazing_number_index([3, -99, -99, -99]), 1)  # max # amazing number = 4 at [-1, -1, -1, 3])

if __name__ == '__main__':
    unittest.main(exit=False)

Aug 21, 2019 LC 273 [Hard] Integer to English Words


Question: Convert a non-negative integer to its English word representation.

Example 1:

Input: 123
Output: "One Hundred Twenty Three"

Example 2:

Input: 12345
Output: "Twelve Thousand Three Hundred Forty Five"

Example 3:

Input: 1234567
Output: "One Million Two Hundred Thirty Four Thousand Five Hundred Sixty Seven"

Example 4:

Input: 1234567891
Output: "One Billion Two Hundred Thirty Four Million Five Hundred Sixty Seven Thousand Eight Hundred Ninety One"

My thoughts: The main difficulty of this problem comes from edge case scenarios from breaking large number into smaller ones and conquer them separately. Includes but not limit to “Zero”, “Ten”, “Twenty One” and other edge cases from missing millions, thousands and hundreds, like “Thirty Billion Two Million” and “Fifty Billion Two Hundred”.

Solution: https://repl.it/@trsong/Integer-to-English-Words

import unittest

word_lookup = {
    0: 'Zero', 1: 'One', 2: 'Two', 3: 'Three', 4: 'Four', 5: 'Five',
    6: 'Six', 7: 'Seven', 8: 'Eight', 9: 'Nine', 10: 'Ten',
    11: 'Eleven', 12: 'Twelve', 13: 'Thirteen', 14: 'Fourteen', 15: 'Fifteen',
    16: 'Sixteen', 17: 'Seventeen', 18: 'Eighteen', 19: 'Nineteen', 20: 'Twenty',
    30: 'Thirty', 40: 'Forty', 50: 'Fifty', 60: 'Sixty', 
    70: 'Seventy', 80: 'Eighty', 90: 'Ninety',
    100: 'Hundred', 1000: 'Thousand', 1000000: 'Million', 1000000000: 'Billion'
}

def read_hundreds(num):
    # Helper function to read num between 1 and 999
    global word_lookup
    if num == 0: return []
    res = []
    if num >= 100:
        res.append(word_lookup[num / 100])
        res.append(word_lookup[100])
    
    num %= 100
    if 21 <= num <= 99:
        res.append(word_lookup[num - num % 10])
        if num % 10 > 0:
            res.append(word_lookup[num % 10])
    elif num > 0:
        res.append(word_lookup[num])
        
    return res


def number_to_words(num):
    global word_lookup
    if num == 0: return word_lookup[0]
    res = []
    separators = [1000000000, 1000000, 1000] # 'Billion', 'Million', 'Thousand'
    for sep in separators:
        if num >= sep:
            res += read_hundreds(num/sep)
            res.append(word_lookup[sep])
            num %= sep
    if num > 0:
        res += read_hundreds(num)
    return ' '.join(res)


class NumberToWordSpec(unittest.TestCase):
    def test_example1(self):
        self.assertEqual(number_to_words(123), "One Hundred Twenty Three")

    def test_example2(self):
        self.assertEqual(number_to_words(12345), "Twelve Thousand Three Hundred Forty Five")

    def test_example3(self):
        self.assertEqual(number_to_words(1234567), "One Million Two Hundred Thirty Four Thousand Five Hundred Sixty Seven")

    def test_example4(self):
        self.assertEqual(number_to_words(1234567891), "One Billion Two Hundred Thirty Four Million Five Hundred Sixty Seven Thousand Eight Hundred Ninety One")

    def test_zero(self):
        self.assertEqual(number_to_words(0), "Zero")

    def test_one_digit(self):
        self.assertEqual(number_to_words(8), "Eight")

    def test_two_digits(self):
        self.assertEqual(number_to_words(21), "Twenty One")
        self.assertEqual(number_to_words(10), "Ten")
        self.assertEqual(number_to_words(20), "Twenty")
        self.assertEqual(number_to_words(16), "Sixteen")
        self.assertEqual(number_to_words(32), "Thirty Two")
        self.assertEqual(number_to_words(30), "Thirty")

    def test_ignore_thousand_part(self):
        self.assertEqual(number_to_words(30002000000), "Thirty Billion Two Million")

    def test_ignore_million_part(self):
        self.assertEqual(number_to_words(50000000200), "Fifty Billion Two Hundred")


if __name__ == '__main__':
    unittest.main(exit=False)

Aug 20, 2019 LC 297 [Hard] Serialize and Deserialize Binary Tree


Question: Serialization is the process of converting a data structure or object into a sequence of bits so that it can be stored in a file or memory buffer, or transmitted across a network connection link to be reconstructed later in the same or another computer environment.

Design an algorithm to serialize and deserialize a binary tree. There is no restriction on how your serialization/deserialization algorithm should work. You just need to ensure that a binary tree can be serialized to a string and this string can be deserialized to the original tree structure.

Example 1:

You may serialize the following tree:

    1
   / \
  2   3
     / \
    4   5

as "[1,2,3,null,null,4,5]"

Example 2:

You may serialize the following tree:

       5
      / \ 
     4   7
    /   /
   3   2
  /   /
-1   9

as "[5,4,7,3,null,2,null,-1,null,9]"

Solution with BFS: https://repl.it/@trsong/Serialize-and-Deserialize-Binary-Tree

import unittest

class TreeNode(object):
    def __init__(self, val, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right
    
    def __eq__(self, other):
        return other and other.val == self.val and other.left == self.left and other.right == self.right

class BinaryTreeSerializer(object):
    @staticmethod
    def serialize(tree):
        if not tree: return "[]"
        res = []
        queue = [tree]
        is_done = False
        while queue and not is_done:
            level_size = len(queue)
            is_done = True
            for _ in xrange(level_size):
                cur = queue.pop(0)
                if not cur:
                    res.append("null")
                else:
                    res.append(str(cur.val))
                    if cur.left or cur.right:
                        is_done = False
                    queue.append(cur.left)
                    queue.append(cur.right)
        return "[" + ",".join(res) + "]"

    @staticmethod
    def deserialize(encoded_string):
        if encoded_string == "[]": return None
        nums = encoded_string[1:-1].split(',')
        tree = TreeNode(int(nums[0]))
        i = 1
        queue = [tree]
        while queue:
            level_size = len(queue)
            for _ in xrange(level_size):
                cur = queue.pop(0)
                if i < len(nums) and nums[i] != "null":
                    cur.left = TreeNode(int(nums[i]))
                    queue.append(cur.left)
                i += 1

                if i < len(nums) and nums[i] != "null":
                    cur.right = TreeNode(int(nums[i]))
                    queue.append(cur.right)
                i += 1

                if i > len(nums):
                    break
        return tree
   

class BinaryTreeSerializerSpec(unittest.TestCase):
    def test_example1(self):
        """
            1
           / \
          2   3
             / \
            4   5
        """
        n3 = TreeNode(3, TreeNode(4), TreeNode(5))
        n1 = TreeNode(1, TreeNode(2), n3)
        encoded = BinaryTreeSerializer.serialize(n1)
        decoded = BinaryTreeSerializer.deserialize(encoded)
        self.assertEqual(decoded, n1)

    def test_example2(self):
        """
               5
              / \ 
             4   7
            /   /
           3   2
          /   /
        -1   9
        """
        n4 = TreeNode(4, TreeNode(3, TreeNode(-1)))
        n7 = TreeNode(7, TreeNode(2, TreeNode(9)))
        n5 = TreeNode(5, n4, n7)
        encoded = BinaryTreeSerializer.serialize(n5)
        decoded = BinaryTreeSerializer.deserialize(encoded)
        self.assertEqual(decoded, n5)

    def test_serialize_empty_tree(self):
        encoded = BinaryTreeSerializer.serialize(None)
        decoded = BinaryTreeSerializer.deserialize(encoded)
        self.assertIsNone(decoded)

    def test_serialize_left_heavy_tree(self):
        """
            1
           /
          2
         /
        3
        """
        tree = TreeNode(1, TreeNode(2, TreeNode(3)))
        encoded = BinaryTreeSerializer.serialize(tree)
        decoded = BinaryTreeSerializer.deserialize(encoded)
        self.assertEqual(decoded, tree)

    def test_serialize_right_heavy_tree(self):
        """
        1
         \
          2
         /
        3
        """
        tree = TreeNode(1, right=TreeNode(2, TreeNode(3)))
        encoded = BinaryTreeSerializer.serialize(tree)
        decoded = BinaryTreeSerializer.deserialize(encoded)
        self.assertEqual(decoded, tree) 
        

        
if __name__ == '__main__':
    unittest.main(exit=False)

Additional Question: [Medium] M Smallest in K Sorted Lists


Question: Given k sorted arrays of possibly different sizes, find m-th smallest value in the merged array.

Example 1:

Input: [[1, 3], [2, 4, 6], [0, 9, 10, 11]], m = 5
Output: 4
Explanation: The merged array would be [0, 1, 2, 3, 4, 6, 9, 10, 11].  
The 5-th smallest element in this merged array is 4.

Example 2:

Input: [[1, 3, 20], [2, 4, 6]], m = 2
Output: 2

Example 3:

Input: [[1, 3, 20], [2, 4, 6]], m = 6
Output: 20

My thoughts: This problem is almost the same as merge k sorted list. The idea is to leverage priority queue to keep track of minimum element among all k sorted list.

Solution: https://repl.it/@trsong/M-Smallest-in-K-Sorted-Lists

import unittest
from Queue import PriorityQueue

def find_m_smallest(ksorted_list, m):
    pq = PriorityQueue()
    for lst in ksorted_list:
        if lst:
            pq.put((lst[0], [0, lst]))
    
    res = None
    while not pq.empty() and m >= 1:
        index, lst = pq.get()[1]
        if m == 1:
            res = lst[index]
            break
        m -= 1
        if index + 1 < len(lst):
            pq.put((lst[index + 1], [index + 1, lst]))
    return res


class FindMSmallestSpec(unittest.TestCase):
    def test_example1(self):
        self.assertEqual(find_m_smallest([[1, 3], [2, 4, 6], [0, 9, 10, 11]], m=5), 4) 

    def test_example2(self):
        self.assertEqual(find_m_smallest([[1, 3, 20], [2, 4, 6]], m=2), 2) 

    def test_example3(self):
        self.assertEqual(find_m_smallest([[1, 3, 20], [2, 4, 6]], m=6), 20)

    def test_empty_sublist(self):
        self.assertEqual(find_m_smallest([[1], [], [0, 2]], m=2), 1)

    def test_one_sublist(self):
        self.assertEqual(find_m_smallest([[1, 2, 3, 4, 5]], m=5), 5)

    def test_target_out_of_boundary(self):
        self.assertIsNone(find_m_smallest([[1, 2, 3], [4, 5, 6]], 7))


if __name__ == '__main__':
    unittest.main(exit=False)

Aug 19, 2019 [Medium] Jumping Numbers


Question: Given a positive int n, print all jumping numbers smaller than or equal to n. A number is called a jumping number if all adjacent digits in it differ by 1. For example, 8987 and 4343456 are jumping numbers, but 796 and 89098 are not. All single digit numbers are considered as jumping numbers.

Example:

Input: 105
Output: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 21, 23, 32, 34, 43, 45, 54, 56, 65, 67, 76, 78, 87, 89, 98, 101]

My thoughts: We can use Brutal Force to search from 0 to given upperbound to find jumping numbers which might not be so efficient. Or we can take advantage of the property of jummping number: a jumping number is:

  • either one of all single digit numbers
  • or 10 * some jumping number + last digit of that jumping number +/- by 1

For example,

1 -> 10, 12.
2 -> 21, 23.
3 -> 32, 34.
...
10 -> 101
12 -> 121, 123

We can get all qualified jumping number by BFS searching for all candidates.

Solution with BFS: https://repl.it/@trsong/Jumping-Numbers

import unittest

def generate_jumping_numbers(upper_bound):
    if upper_bound < 0: return []
    queue = [x for x in xrange(1, 10)]
    res = [0]
    while queue:
        # Apply BFS to search for jumping numbers
        cur = queue.pop(0)
        if cur > upper_bound:
            break
        res.append(cur)
        last_digit = cur % 10
        if last_digit > 0:
            queue.append(10 * cur + last_digit - 1)
        
        if last_digit < 9:
            queue.append(10 * cur + last_digit + 1)
    return res


class GenerateJumpingNumberSpec(unittest.TestCase):
    def test_zero_as_upperbound(self):
        self.assertEqual(generate_jumping_numbers(0), [0])

    def test_single_digits_are_all_jummping_numbers(self):
        self.assertEqual(generate_jumping_numbers(9), [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

    def test_five_as_upperbound(self):
        self.assertEqual(generate_jumping_numbers(5), [0, 1, 2, 3, 4, 5])

    def test_not_always_contains_upperbound(self):
        self.assertEqual(generate_jumping_numbers(13), [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12])

    def test_example(self):
        expected = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 21, 23, 32, 34, 43, 45, 54, 56, 65, 67, 76, 78, 87, 89, 98, 101]
        self.assertEqual(generate_jumping_numbers(105), expected)
    
    def test_negative_upperbound(self):
        self.assertEqual(generate_jumping_numbers(-1), [])


if __name__ == '__main__':
    unittest.main(exit=False)

Additional Question: [Easy] Swap Even and Odd Nodes


Question: Given the head of a singly linked list, swap every two nodes and return its head.

Note: Make sure it’s acutally nodes that get swapped not value.

Example:

given 1 -> 2 -> 3 -> 4, return 2 -> 1 -> 4 -> 3.

Solution with Recursion: https://repl.it/@trsong/Swap-Even-and-Odd-Nodes

import unittest

class ListNode(object):
    def __init__(self, val, next=None):
        self.val = val
        self.next = next


def swap_list(lst):
    if not lst or not lst.next:
        return lst
    first = lst
    second = first.next
    third = second.next

    second.next = first
    first.next = swap_list(third)
    return second
  

class SwapListSpec(unittest.TestCase):
    def assert_lists(self, lst, node_seq):
        p = lst
        for node in node_seq:
            if p != node: print (p.data if p else "None"), (node.data if node else "None")
            self.assertTrue(p == node)
            p = p.next
        self.assertTrue(p is None)

    def test_empty(self):
        self.assert_lists(swap_list(None), [])

    def test_one_elem_list(self):
        n1 = ListNode(1)
        self.assert_lists(swap_list(n1), [n1])

    def test_two_elems_list(self):
        # 1 -> 2
        n2 = ListNode(2)
        n1 = ListNode(1, n2)
        self.assert_lists(swap_list(n1), [n2, n1])

    def test_three_elems_list(self):
        # 1 -> 2 -> 3
        n3 = ListNode(3)
        n2 = ListNode(2, n3)
        n1 = ListNode(1, n2)
        self.assert_lists(swap_list(n1), [n2, n1, n3])

    def test_four_elems_list(self):
        # 1 -> 2 -> 3 -> 4
        n4 = ListNode(4)
        n3 = ListNode(3, n4)
        n2 = ListNode(2, n3)
        n1 = ListNode(1, n2)
        self.assert_lists(swap_list(n1), [n2, n1, n4, n3])


if __name__ == '__main__':
    unittest.main(exit=False)

Solution2 with Iteration: https://repl.it/@trsong/Swap-Even-and-Odd-Nodes-Iterative

def swap_list(lst):
    dummy = ListNode(-1, lst)
    prev = dummy
    p = lst
    while p and p.next:
        first = p
        second = first.next

        first.next = second.next
        second.next = first
        prev.next = second
        prev = first
        p = prev.next
    return dummy.next

Aug 18, 2019 LT 612 [Medium] K Closest Points


Question: Given some points and a point origin in two dimensional space, find k points out of the some points which are nearest to origin.

Return these points sorted by distance, if they are same with distance, sorted by x-axis, otherwise sorted by y-axis.

Example:

Given points = [[4, 6], [4, 7], [4, 4], [2, 5], [1, 1]], origin = [0, 0], k = 3
return [[1, 1], [2, 5], [4, 4]]

My thoguhts: This problem can be easily solved with k Max-heap with key being the distance and value being the point. First heapify first k elements to form a k max-heap. Then for the remaining n - k element, replace top of heap with smaller-distance point.

Solution with k Max-Heap: https://repl.it/@trsong/K-Closest-Points

import unittest
from Queue import PriorityQueue

def distance2(p1, p2):
    dx = p1[0] - p2[0]
    dy = p1[1] - p2[1]
    return dx * dx + dy * dy 

def k_closest_points(points, origin, k):
    if not points and k == 0: return []
    elif len(points) < k: return None

    max_heap = PriorityQueue()
    for i in xrange(k):
        max_heap.put((-distance2(points[i], origin), points[i]))

    for i in xrange(k, len(points)):
        dist = distance2(points[i], origin)
        top = max_heap.queue[0]
        if -top[0] > dist:   
            max_heap.get()
            max_heap.put((-dist, points[i]))
            
    res = [None] * k
    for i in xrange(k-1, -1, -1):
        res[i] = max_heap.get()[1]
    return res


class KClosestPointSpec(unittest.TestCase):
    def assert_points(self, result, expected):
        self.assertEqual(sorted(result), sorted(expected))

    def test_example(self):
        points = [[4, 6], [4, 7], [4, 4], [2, 5], [1, 1]]
        origin = [0, 0]
        k = 3
        expected = [[1, 1], [2, 5], [4, 4]]
        self.assert_points(k_closest_points(points, origin, k), expected)

    def test_empty_points(self):
        self.assert_points(k_closest_points([], [0, 0], 0), [])
        self.assertIsNone(k_closest_points([], [0, 0], 1))

    def test_descending_distance(self):
        points = [[1, 6], [1, 5], [1, 4], [1, 3], [1, 2], [1, 1]]
        origin = [1, 1]
        k = 2
        expected = [[1, 2], [1, 1]]
        self.assert_points(k_closest_points(points, origin, k), expected)

    def test_ascending_distance(self):
        points = [[-1, -1], [-2, -1], [-3, -1], [-4, -1], [-5, -1], [-6, -1]]
        origin = [-1, -1]
        k = 1
        expected = [[-1, -1]]
        self.assert_points(k_closest_points(points, origin, k), expected)

    def test_duplicated_distance(self):
        points = [[1, 0], [0, 1], [-1, -1], [1, 1], [2, 1], [-2, 0]]
        origin = [0, 0]
        k = 5
        expected = [[1, 0], [0, 1], [-1, -1], [1, 1], [-2, 0]]
        self.assert_points(k_closest_points(points, origin, k), expected)


if __name__ == '__main__':
    unittest.main(exit=False)

Additional Question: [Medium] Swap Even and Odd Bits


Question: Given an unsigned 8-bit integer, swap its even and odd bits. The 1st and 2nd bit should be swapped, the 3rd and 4th bit should be swapped, and so on.

Example:

10101010 should be 01010101. 11100010 should be 11010001.

Bonus: Can you do this in one line?

Solution: https://repl.it/@trsong/Swap-Even-and-Odd-Bits

import unittest

def swap_bits(num):
    # 1010 is 0xa and  0101 is 0x5
    # 32 bit has 8 bits (4 * 8 = 32)
    return (num & 0xaaaaaaaa) >> 1 | (num & 0x55555555) << 1

class SwapBitSpec(unittest.TestCase):
    def test_example1(self):
        self.assertEqual(swap_bits(0b10101010), 0b01010101)

    def test_example2(self):
        self.assertEqual(swap_bits(0b11100010), 0b11010001)

    def test_zero(self):
        self.assertEqual(swap_bits(0), 0)
    
    def test_one(self):
        self.assertEqual(swap_bits(0b1), 0b10)

    def test_odd_digits(self):
        self.assertEqual(swap_bits(0b111), 0b1011)

    def test_large_number(self):
        self.assertEqual(swap_bits(0xffffffff), 0xffffffff)
        self.assertEqual(swap_bits(0x55555555), 0xaaaaaaaa)
        self.assertEqual(swap_bits(0xaaaaaaaa), 0x55555555)


if __name__ == '__main__':
    unittest.main(exit=False)

Aug 17, 2019 [Medium] Deep Copy Linked List with Pointer to Random Node


Question: Make a deep copy of a linked list that has a random link pointer and a next pointer.

My thoughts: The way we solve this problem is to mingle old nodes and cloned nodes so that every odd node is original node and every even node is clone node which will allow us to access both nodes through node.next and node.next.next. And we then build random pointer and finally connect every other node to build cloned node’s next as well as restore original node’s next.

Solution: https://repl.it/@trsong/Deep-Copy-Linked-List-with-Pointer-to-Random-Node

import unittest

class ListNode(object):
    def __init__(self, val, next=None, random=None):
        self.val = val
        self.next = next
        self.random = random
    
    def __eq__(self, other):
        if other is not None:
            is_random_valid = self.random is None and other.random is None or self.random is not None and other.random is not None and self.random.val == other.random.val
            return is_random_valid and self.val == other.val and self.next == other.next
        else:
            return False

def deep_copy(lst):
    if not lst: return None
    
    # Insert cloned node into original list
    # Now every odd pointer is old node and every even pointer is cloned node
    node = lst
    while node:
        node.next = ListNode(node.val, node.next)
        node = node.next.next

    # Build cloned node's random pointer
    node = lst
    while node:
        node.next.random = node.random.next if node.random else None
        node = node.next.next

    # Build cloned node's next and restore old node's next
    node = lst
    res = lst.next
    while node:
        cloned_node = node.next
        old_next = cloned_node.next
        if old_next:
            cloned_node.next = old_next.next
        node.next = old_next
        node = old_next
    
    return res


class DeepCopySpec(unittest.TestCase):
    def test_empty_list(self):
        self.assertIsNone(deep_copy(None))
    
    def test_list_with_random_point_to_itself(self):
        n = ListNode(1)
        n.random = n
        self.assertEqual(deep_copy(n), n)

    def test_list_with_forward_random_pointers(self):
        # 1 -> 2 -> 3 -> 4
        n4 = ListNode(4)
        n3 = ListNode(3, n4)
        n2 = ListNode(2, n3)
        n1 = ListNode(1, n2)

        # random pointer:
        # 1 -> 3
        # 2 -> 3
        # 3 -> 4
        n1.random = n3
        n2.random = n3
        n3.random = n4
        self.assertEqual(deep_copy(n1), n1)

    def test_list_with_backward_random_pointers(self):
        # 1 -> 2 -> 3 -> 4
        n4 = ListNode(4)
        n3 = ListNode(3, n4)
        n2 = ListNode(2, n3)
        n1 = ListNode(1, n2)

        # random pointer:
        # 1 -> 1
        # 2 -> 1
        # 3 -> 2
        # 4 -> 1
        n1.random = n1
        n2.random = n1
        n3.random = n2
        n4.random = n1
        self.assertEqual(deep_copy(n1), n1)

    def test_list_with_both_forward_and_backward_pointers(self):
        # 1 -> 2 -> 3 -> 4
        n4 = ListNode(4)
        n3 = ListNode(3, n4)
        n2 = ListNode(2, n3)
        n1 = ListNode(1, n2)

        # random pointer:
        # 1 -> 3
        # 2 -> 1
        # 3 -> 4
        # 4 -> 3
        n1.random = n3
        n2.random = n2
        n3.random = n4
        n4.random = n3
        self.assertEqual(deep_copy(n1), n1)
        

if __name__ == '__main__':
    unittest.main(exit=False)

Aug 16, 2019 [Medium] Longest Substring without Repeating Characters


Question: Given a string, find the length of the longest substring without repeating characters.

Note: Can you find a solution in linear time?

Example:

lengthOfLongestSubstring("abrkaabcdefghijjxxx") # => 10 as len("abcdefghij") == 10

My thoughts: This is a typical sliding window problem. The idea is to mantain a last occurance map while proceeding the sliding window. Such window is bounded by indices (i, j), whenever we process next character j, we check the last occurance map to see if the current character a[j] is duplicated within the window (i, j), ie. i <= k < j, if that’s the case, we move i to k + 1 so that a[j] no longer exists in window. And we mantain the largest window size j - i + 1 as the longest substring without repeating characters.

Solution with Sliding Window: https://repl.it/@trsong/Longest-Substring-without-Repeating-Characters

import unittest

def longest_nonrepeated_substring(input_string):
    if not input_string: return 0
    last_occur = {}
    max_len = 0
    i = 0
    for j in xrange(len(input_string)):
        cur = input_string[j]
        last_index = last_occur.get(cur, -1)
        if last_index >= i:
            i = last_index + 1
        last_occur[cur] = j
        max_len = max(max_len, j - i + 1)
    return max_len
    

class LongestNonrepeatedSubstringSpec(unittest.TestCase):
    def test_example(self):
        self.assertEqual(longest_nonrepeated_substring("abrkaabcdefghijjxxx"), 10) # "abcdefghij"

    def test_empty_string(self):
        self.assertEqual(longest_nonrepeated_substring(""), 0)

    def test_string_with_repeated_characters(self):
        self.assertEqual(longest_nonrepeated_substring("aabbafacbbcacbfa"), 4) # "facb"

    def test_some_random_string(self):
        self.assertEqual(longest_nonrepeated_substring("ABDEFGABEF"), 6) # "ABDEFG"

    def test_all_repated_characters(self):
        self.assertEqual(longest_nonrepeated_substring("aaa"), 1) # "a"
    
    def test_non_repated_characters(self):
        self.assertEqual(longest_nonrepeated_substring("abcde"), 5) # "abcde"

if __name__ == '__main__':
    unittest.main(exit=False)

Aug 15, 2019 [Hard] Largest Rectangle


Question: Given an N by M matrix consisting only of 1’s and 0’s, find the largest rectangle containing only 1’s and return its area.

For Example:

Given the following matrix:

[[1, 0, 0, 0],
 [1, 0, 1, 1],
 [1, 0, 1, 1],
 [0, 1, 0, 0]]

Return 4. As the following 1s form the largest rectangle containing only 1s:
 [1, 1],
 [1, 1]

My thoughts: This problem is an application of finding largest rectangle in histogram. That question gives you an array of height of bar in histogram and find the largest area of rectangle bounded by the bar. (consider bar width as 1)

Example:

largest_rectangle_in_histogram([3, 4, 5, 4, 3]) # return 15 as max at height 3 * width 5

Now the way we take advantage of largest_rectangle_in_histogram is that, we can calculate the histogram of each row with each cell value being the accumulated value since last saw 1.

Example:

Suppose the table looks like the following:

[
    [0, 1, 0, 1],
    [1, 1, 1, 0],
    [0, 1, 1, 0]
]

The histogram of first row is just itself:
    [0, 1, 0, 1]  # largest_rectangle_in_histogram => 1 as max at height 1 * width 1
The histogram of second row is:
    [0, 1, 0, 1]
    +
    [1, 1, 1, 0]
    =
    [1, 2, 1, 0]  # largest_rectangle_in_histogram => 3 as max at height 1 * width 3
              ^ will not accumulate 0
The histogram of third row is:
    [1, 2, 1, 0]       
    +
    [0, 1, 1, 0]
    =
    [0, 3, 2, 0]  # largest_rectangle_in_histogram => 4 as max at height 2 * width 2
              ^ will not accumulate 0
     ^ will not accumulate 0

Therefore, the largest rectangle has 4 1s in it.

Solution with DP: https://repl.it/@trsong/Largest-Rectangle

import unittest

def largest_rectangle_in_histogram(histogram):
    stack = []
    i = 0
    max_area = 0
    while i < len(histogram) or stack:
        if not stack or i < len(histogram) and histogram[stack[-1]] <= histogram[i]:
            # maintain an ascending stack
            stack.append(i)
            i += 1
        else:
            # if stack starts decreasing,
            # then left boundary must be stack[-2] and right boundary must be i. Note both boundaries are exclusive
            # and height is stack[-1]
            height = histogram[stack.pop()]
            left_boundary = stack[-1] if stack else -1
            right_boundary = i
            current_area = height * (right_boundary - left_boundary - 1)
            max_area = max(max_area, current_area)
    return max_area


class LargestRectangleInHistogramSpec(unittest.TestCase):
    def test_ascending_sequence(self):
        self.assertEqual(largest_rectangle_in_histogram([0, 1, 2, 3, 4]), 6) # max at height 2 * width 3

    def test_descending_sequence(self):
        self.assertEqual(largest_rectangle_in_histogram([4, 3, 2, 1, 0]), 6) # max at height 3 * width 2

    def test_sequence3(self):
        self.assertEqual(largest_rectangle_in_histogram([3, 4, 5, 4, 3]), 15) # max at height 3 * width 5

    def test_sequence4(self):
        self.assertEqual(largest_rectangle_in_histogram([3, 10, 4, 10, 5, 10, 4, 10, 3]), 28)  # max at height 4 * width 7

    def test_sequence5(self):
        self.assertEqual(largest_rectangle_in_histogram([6, 2, 5, 4, 5, 1, 6]), 12)  # max at height 4 * width 3


def largest_rectangle(table):
    if not table or not table[0]: return 0
    n, m = len(table), len(table[0])
    max_area = largest_rectangle_in_histogram(table[0])
    for r in xrange(1, n):
        for c in xrange(m):
            # calculate the histogram of each row since last saw 1 at same column
            if table[r][c] == 1:
                table[r][c] = table[r-1][c] + 1
        max_area = max(max_area, largest_rectangle_in_histogram(table[r]))
    return max_area
    

class LargestRectangleSpec(unittest.TestCase):
    def test_empty_table(self):
        self.assertEqual(largest_rectangle([]), 0)
        self.assertEqual(largest_rectangle([[]]), 0)

    def test_example(self):
        self.assertEqual(largest_rectangle([
            [1, 0, 0, 0],
            [1, 0, 1, 1],
            [1, 0, 1, 1],
            [0, 1, 0, 0]
        ]), 4)

    def test_table2(self):
        self.assertEqual(largest_rectangle([
            [0, 1, 0, 1],
            [1, 1, 1, 0],
            [0, 1, 1, 0]
        ]), 4)

    def test_table3(self):
        self.assertEqual(largest_rectangle([
            [0, 1, 1, 1, 0],
            [1, 1, 0, 1, 1],
            [0, 1, 1, 1, 0],
        ]), 3)

    def test_table4(self):
        self.assertEqual(largest_rectangle([
            [0, 0, 1, 0, 1],
            [0, 1, 1, 1, 1],
            [0, 0, 1, 0, 1],
        ]), 4)

    def test_table5(self):
        self.assertEqual(largest_rectangle([
            [0, 1, 1, 0],
            [1, 1, 1, 1],
            [1, 1, 1, 1],
            [1, 1, 0, 0]
        ]), 8)


if __name__ == '__main__':
    unittest.main(exit=False)

Aug 14, 2019 LC 375 [Medium] Guess Number Higher or Lower II


Question: We are playing the Guess Game. The game is as follows:

I pick a number from 1 to n. You have to guess which number I picked.

Every time you guess wrong, I’ll tell you whether the number I picked is higher or lower.

However, when you guess a particular number x, and you guess wrong, you pay $x. You win the game when you guess the number I picked.

Example:

n = 10, I pick 8.

First round:  You guess 5, I tell you that it's higher. You pay $5.
Second round: You guess 7, I tell you that it's higher. You pay $7.
Third round:  You guess 9, I tell you that it's lower. You pay $9.

Game over. 8 is the number I picked.

You end up paying $5 + $7 + $9 = $21.

Given a particular n ≥ 1, find out how much money you need to have to guarantee a win.

My thoughts: This question looks really similar to LC 312 [Hard] Burst Balloons in a sense that it choose the best candidate at each step and deligate the subproblem to recursive calls. eg. guess_number_cost_between(i, j) = max(k + guess_number_cost_between(i, k-1), guess_number_cost_between(k+1, j)) for all k between i+1 and j-1 inclusive.

Take a look at base case:

  • when 1 number to pick, the min $ to secure a win is $0
  • when 2 numbers to pick, the min $ to secure a win is to choose the smaller one,
  • when 3 numbers to pick, the min $ to secure a win is to choose the middle one

Solution with Top-down DP(Recursion with Cache): https://repl.it/@trsong/Guess-Number-Higher-or-Lower-II

import unittest
import sys

def guess_number_cost(n):
    cache = [[None for _ in xrange(n+1)] for _ in xrange(n+1)]
    return guess_number_cost_with_cache(1, n, cache)


def guess_number_cost_between(i, j, cache):
    if i == j: return 0
    elif i + 1 == j: return i
    elif i + 2 == j: return i + 1
    cost = sys.maxint
    for k in xrange(i+1, j):
        pick_k_cost = k + guess_number_cost_with_cache(i, k-1, cache) + guess_number_cost_with_cache(k+1, j, cache)
        cost = min(cost, pick_k_cost)
    return cost


def guess_number_cost_with_cache(i, j, cache):
    if cache[i][j] is None:
        cache[i][j] = guess_number_cost_between(i, j, cache)
    return cache[i][j]


class GuessNumberCostSpec(unittest.TestCase):
    def test_n_equals_one(self):
        self.assertEqual(guess_number_cost(1), 0)

    def test_n_equals_two(self):
        self.assertEqual(guess_number_cost(2), 1) # pick: 1

    def test_n_equals_three(self):
        self.assertEqual(guess_number_cost(3), 2) # pick: 2

    def test_n_equals_four(self):
        self.assertEqual(guess_number_cost(4), 4) # pick: 1, 3

    def test_n_equals_five(self):
        self.assertEqual(guess_number_cost(5), 6) # pick: 2, 4

    def test_n_equals_six(self):
        self.assertEqual(guess_number_cost(6), 9) # pick 1, 3, 5


if __name__ == '__main__':
    unittest.main(exit=False)

Aug 13, 2019 LC 727 [Hard] Minimum Window Subsequence


Question: Given strings S and T, find the minimum (contiguous) substring W of S, so that T is a subsequence of W.

If there is no such window in S that covers all characters in T, return the empty string “”. If there are multiple such minimum-length windows, return the one with the left-most starting index.

Example:

Input: 
S = "abcdebdde", T = "bde"
Output: "bcde"

Explanation: 
"bcde" is the answer because it occurs before "bdde" which has the same length.
"deb" is not a smaller window because the elements of T in the window must occur in order.

My thoughts: Have you noticed the pattern that substring of s always has the same first char as t. i.e. s = "abcdebdde", t = "bde", substring = "bcde", substring[0] == t[0], we can take advantage of that to keep track of previous index such that t[0] == s[index] and we can do that recursively for the rest of t and s. We get the following recursive definition

Let dp[i][j] = index where index represents index such that s[index:i] has subsequence t[0:j].

dp[i][j] = dp[i-1][j-1] if there s[i-1] matches t[j-1] 
         = dp[i-1][j]   otherwise

And the final solution is to find index where len of t <= index <= len of s such that index - dp[index][len of t] i.e. the length of substring, reaches minimum.

Solution with DP: https://repl.it/@trsong/Minimum-Window-Subsequence

import unittest
import sys

def min_window_subsequence(s, t):
    if len(s) < len(t): return ""
    n, m = len(s), len(t)

    # let dp[i][j] = index where index represents index such that s[index:i] has subsequence t[0:j]
    # Then s[start: end] where start = dp[i][m], end = i such that len = i - dp[i][m] reaches minimum
    dp = [[-1 for _ in xrange(m+1)] for _ in xrange(n+1)]
    for i in xrange(n):
        dp[i][0] = i

    for i in xrange(1, n+1):
        for j in xrange(1, m+1):
            if s[i-1] == t[j-1]:
                dp[i][j] = dp[i-1][j-1]
            else:
                dp[i][j] = dp[i-1][j]

    start = -1
    min_len = sys.maxint
    for i in xrange(m, n+1):
        if dp[i][m] != -1:
            cur_len = i - dp[i][m]
            if cur_len < min_len:
                start = dp[i][m]
                min_len = cur_len
    return s[start: start + min_len] if start != -1 else ""


class MinWindowSubsequenceSpec(unittest.TestCase):
    def test_example(self):
        self.assertEqual(min_window_subsequence("abcdebdde", "bde"), "bcde")

    def test_target_too_long(self):
        self.assertEqual(min_window_subsequence("a", "aaa"), "")

    def test_duplicated_char_in_target(self):
        self.assertEqual(min_window_subsequence("abbbbabbbabbababbbb", "aa"), "aba")

    def test_duplicated_char_but_no_matching(self):
        self.assertEqual(min_window_subsequence("ccccabbbbabbbabbababbbbcccc", "aca"), "")

    def test_match_last_char(self):
        self.assertEqual(min_window_subsequence("abcdef", "f"), "f")

    def test_match_first_char(self):
        self.assertEqual(min_window_subsequence("abcdef", "a"), "a")

    def test_equal_length_string(self):
        self.assertEqual(min_window_subsequence("abc", "abc"), "abc")
        self.assertEqual(min_window_subsequence("abc", "bca"), "")


if __name__ == '__main__':
    unittest.main(exit=False)

Aug 12, 2019 LC 230 [Medium] Kth Smallest Element in a BST


Question: Given a binary search tree, write a function kthSmallest to find the kth smallest element in it.

Example 1:

Input: 
   3
  / \
 1   4
  \
   2

k = 1
Output: 1

Example 2:

Input: 
       5
      / \
     3   6
    / \
   2   4
  /
 1

k = 3
Output: 3

My thoughts: In BST, the in-order traversal presents orders from smallest to largest. Thus you can use both recursion with global variable k or the following template for iterative in-order traversal.

Template for Iterative In-order Traversal:

while True:
    if t is not None:
        stack.append(t)
        t = t.left
    elif stack:
        t = stack.pop()
        print t.val # this will give node value follows in-order traversal    
        t = t.right
    else:
        break
return None

Solution with Iterative In-order Traversal: https://repl.it/@trsong/Kth-Smallest-Element-in-a-BST

import unittest

class TreeNode(object):
    def __init__(self, val, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right

def kth_smallest(tree, k):
    t = tree
    stack = []

    while True:
        if t is not None:
            stack.append(t)
            t = t.left
        elif stack:
            t = stack.pop()
            if k == 1:
                return t.val
            else:
                k -= 1
            t = t.right
        else:
            break
    return None


class KthSmallestSpec(unittest.TestCase):
    def test_example1(self):
        """
           3
          / \
         1   4
          \
           2
        """
        n1 = TreeNode(1, right=TreeNode(2))
        tree = TreeNode(3, n1, TreeNode(4))
        check_expected = [1, 2, 3, 4]
        for e in check_expected:
            self.assertEqual(kth_smallest(tree, e), e)

    def test_example2(self):
        """
              5
             / \
            3   6
           / \
          2   4
         /
        1
        """
        n2 = TreeNode(2, TreeNode(1))
        n3 = TreeNode(3, n2, TreeNode(4))
        tree = TreeNode(5, n3, TreeNode(6))
        check_expected = [1, 2, 3, 4, 5, 6]
        for e in check_expected:
            self.assertEqual(kth_smallest(tree, e), e)


if __name__ == '__main__':
    unittest.main(exit=False)

Aug 11, 2019 LC 684 [Medium] Redundant Connection


Question: In this problem, a tree is an undirected graph that is connected and has no cycles.

The given input is a graph that started as a tree with N nodes (with distinct values 1, 2, …, N), with one additional edge added. The added edge has two different vertices chosen from 1 to N, and was not an edge that already existed.

The resulting graph is given as a 2D-array of edges. Each element of edges is a pair [u, v] with u < v, that represents an undirected edge connecting nodes u and v.

Return an edge that can be removed so that the resulting graph is a tree of N nodes. If there are multiple answers, return the answer that occurs last in the given 2D-array. The answer edge [u, v] should be in the same format, with u < v.

Example 1:

Input: [[1,2], [1,3], [2,3]]
Output: [2,3]
Explanation: The given undirected graph will be like this:
  1
 / \
2 - 3

Example 2:

Input: [[1,2], [2,3], [3,4], [1,4], [1,5]]
Output: [1,4]
Explanation: The given undirected graph will be like this:
5 - 1 - 2
    |   |
    4 - 3

My thoughts: Process edges one by one, when we encouter an edge that has two ends already connected then adding current edge will form a cycle, then we return such edge.

In order to efficiently checking connection between any two nodes. The idea is to keep track of all nodes that are already connected. Disjoint-set(Union Find) is what we are looking for. Initially UF makes all nodes disconnected, and whenever we encounter an edge, connect both ends. And we do that for all edges.

Solution with Disjoint-Set(Union-Find): https://repl.it/@trsong/CharmingCluelessApplets

import unittest

class UnionFind(object):
    def __init__(self, size):
        self.parent = range(size)
        self.rank = [0] * size
    
    def find(self, v):
        p = v
        while self.parent[p] != p:
            p = self.parent[p]
        self.parent[v] = p
        return p
    
    def union(self, v1, v2):
        p1 = self.find(v1)
        p2 = self.find(v2)
        if p1 != p2:
            if self.rank[p1] > self.rank[p2]:
                self.parent[p2] = p1
                self.rank[p1] += 1
            else:
                self.parent[p1] = p2
                self.rank[p2] += 1            
            
    def is_connected(self, v1, v2):
        return self.find(v1) == self.find(v2)

def find_redundant_connection(edges):
    uf = UnionFind(len(edges) + 1)
    for u, v in edges:
        if uf.is_connected(u, v):
            return [u, v]
        else:
            uf.union(u, v)
    return None


class FindRedundantConnectionSpec(unittest.TestCase):
    def test_example1(self):
        self.assertEqual(find_redundant_connection([[1,2], [1,3], [2,3]]), [2, 3])

    def test_example2(self):
        self.assertEqual(find_redundant_connection([[1,2], [2,3], [3,4], [1,4], [1,5]]), [1,4])


if __name__ == '__main__':
    unittest.main(exit=False)

Aug 10, 2019 LC 308 [Hard] Range Sum Query 2D - Mutable


Question: Given a 2D matrix matrix, find the sum of the elements inside the rectangle defined by its upper left corner (row1, col1) and lower right corner (row2, col2).

Example:

Given matrix = [
  [3, 0, 1, 4, 2],
  [5, 6, 3, 2, 1],
  [1, 2, 0, 1, 5],
  [4, 1, 0, 1, 7],
  [1, 0, 3, 0, 5]
]

sumRegion(2, 1, 4, 3) -> 8
update(3, 2, 2)
sumRegion(2, 1, 4, 3) -> 10

Solution with 2D Binary Indexed Tree: https://repl.it/@trsong/Range-Sum-Query-2D-Mutable

import unittest

class RangeSumQuery(object):
    def __init__(self, matrix):
        n, m = len(matrix), len(matrix[0])
        self.bit_matrix = [[0 for _ in xrange(m+1)] for _ in xrange(n+1)]
        for r in xrange(n):
            for c in xrange(m):
                self.update(r, c, matrix[r][c])

    def sumOriginToPosition(self, position):
        row, col = position
        res = 0
        rIdx = row + 1
        while rIdx > 0:
            cIdx = col + 1
            while cIdx > 0:
                res += self.bit_matrix[rIdx][cIdx]
                cIdx -= cIdx & -cIdx
            rIdx -= rIdx & -rIdx
        return res

    def sumRegion(self, row1, col1, row2, col2):
        top_left = (row1 - 1, col1 - 1)
        top_right = (row1 - 1, col2)
        bottom_left = (row2, col1 - 1)
        bottom_right = (row2, col2)
        top_left_sum = self.sumOriginToPosition(top_left)
        top_right_sum = self.sumOriginToPosition(top_right)
        bottom_left_sum = self.sumOriginToPosition(bottom_left)
        bottom_right_sum = self.sumOriginToPosition(bottom_right)
        return bottom_right_sum - bottom_left_sum - top_right_sum + top_left_sum

    def update(self, row, col, val):
        diff = val - self.sumRegion(row, col, row, col)
        n, m = len(self.bit_matrix), len(self.bit_matrix[0])
        rIdx = row + 1
        while rIdx < n:
            cIdx = col + 1
            while cIdx < m:
                self.bit_matrix[rIdx][cIdx] += diff
                cIdx += cIdx & -cIdx
            rIdx += rIdx & -rIdx


class RangeSumQuerySpec(unittest.TestCase):
    def test_example(self):
        matrix = [
            [3, 0, 1, 4, 2],
            [5, 6, 3, 2, 1],
            [1, 2, 0, 1, 5],
            [4, 1, 0, 1, 7],
            [1, 0, 3, 0, 5]
        ]
        rsq = RangeSumQuery(matrix)
        self.assertEqual(rsq.sumRegion(2, 1, 4, 3), 8)
        rsq.update(3, 2, 2)
        self.assertEqual(rsq.sumRegion(2, 1, 4, 3), 10)

    def test_non_square_matrix(self):
        matrix = [
            [1, 1, 1, 1, 1],
            [1, 1, 1, 1, 1]
        ]
        rsq = RangeSumQuery(matrix)
        self.assertEqual(rsq.sumRegion(0, 1, 1, 3), 6)
        rsq.update(0, 2, 2)
        self.assertEqual(rsq.sumRegion(0, 1, 1, 3), 7)


if __name__ == '__main__':
    unittest.main(exit=False)

Additional Question: LC 54 [Medium] Spiral Matrix


Question: Given a matrix of n x m elements (n rows, m columns), return all elements of the matrix in spiral order.

Example 1:

Input:
[
 [ 1, 2, 3 ],
 [ 4, 5, 6 ],
 [ 7, 8, 9 ]
]
Output: [1, 2, 3, 6, 9, 8, 7, 4, 5]

Example 2:

Input:
[
  [1,  2,  3,  4],
  [5,  6,  7,  8],
  [9, 10, 11, 12]
]
Output: [1, 2, 3, 4, 8, 12, 11, 10, 9, 5, 6, 7]

Solution: https://repl.it/@trsong/Spiral-Matrix

import unittest

def spiral_order(matrix):
    if not matrix or not matrix[0]: return []
    n, m = len(matrix), len(matrix[0])
    row_lower_bound = 0
    row_upper_bound = n - 1
    col_lower_bound = 0
    col_upper_bound = m - 1
    res = []
        
    while row_lower_bound < row_upper_bound and col_lower_bound < col_upper_bound:
        r, c = row_lower_bound, col_lower_bound
        # top
        while c < col_upper_bound:
            res.append(matrix[r][c])
            c += 1
        
        # right
        while r < row_upper_bound:
            res.append(matrix[r][c])
            r += 1
        
        # bottom
        while c > col_lower_bound:
            res.append(matrix[r][c])
            c -= 1
        
        # left 
        while r > row_lower_bound:
            res.append(matrix[r][c])
            r -= 1
    
        col_upper_bound -= 1
        col_lower_bound += 1
        row_upper_bound -= 1
        row_lower_bound += 1
        
    r, c = row_lower_bound, col_lower_bound
    if row_lower_bound == row_upper_bound: 
        # Edge Case 1: when remaining block is 1xk
        for col in xrange(col_lower_bound, col_upper_bound + 1):
            res.append(matrix[r][col])
    elif col_lower_bound == col_upper_bound:
        # Edge Case 2: when remaining block is kx1
        for row in xrange(row_lower_bound, row_upper_bound + 1):
            res.append(matrix[row][c])
            
    return res


class SpiralOrderSpec(unittest.TestCase):
    def test_example1(self):
        self.assertEqual(spiral_order([
            [ 1, 2, 3 ],
            [ 4, 5, 6 ],
            [ 7, 8, 9 ]
        ]), [1, 2, 3, 6, 9, 8, 7, 4, 5])

    def test_example2(self):
        self.assertEqual(spiral_order([
            [1,  2,  3,  4],
            [5,  6,  7,  8],
            [9, 10, 11, 12]
        ]), [1, 2, 3, 4, 8, 12, 11, 10, 9, 5, 6, 7])

    def test_empty_table(self):
        self.assertEqual(spiral_order([]), [])
        self.assertEqual(spiral_order([[]]), [])

    def test_two_by_two_table(self):
        self.assertEqual(spiral_order([
            [1, 2],
            [4, 3]
        ]), [1, 2, 3, 4])

    def test_one_element_table(self):
        self.assertEqual(spiral_order([[1]]), [1])

    def test_one_by_k_table(self):
        self.assertEqual(spiral_order([
            [1, 2, 3, 4]
        ]), [1, 2, 3, 4])

    def test_k_by_one_table(self):
        self.assertEqual(spiral_order([
            [1],
            [2],
            [3]
        ]), [1, 2, 3])


if __name__ == '__main__':
    unittest.main(exit=False)

Aug 9, 2019 LC 307 [Medium] Range Sum Query - Mutable


Question: Given an integer array nums, find the sum of the elements between indices i and j (i ≤ j), inclusive.

The update(i, val) function modifies nums by updating the element at index i to val.

Example:

Given nums = [1, 3, 5]

sumRange(0, 2) -> 9
update(1, 2)
sumRange(0, 2) -> 8

Solution with Binary Indexed Tree: https://repl.it/@trsong/Range-Sum-Query-Mutable

import unittest

class RangeSumQuery(object):
    @staticmethod
    def last_bit(num):
        return num & -num

    def __init__(self, nums):
        n = len(nums)
        self._BITree = [0] * (n + 1)
        for i in xrange(n):
            self.update(i, nums[i])

    def prefixSum(self, i):
        """Get sum of value from index 0 to i """
        # BITree starts from index 1
        index = i + 1
        res = 0
        while index > 0:
            res += self._BITree[index]
            index -= RangeSumQuery.last_bit(index)
        return res

    def rangeSum(self, i, j):
        return self.prefixSum(j) - self.prefixSum(i-1)

    def update(self, i, val):
        """Update the sum by add delta on top of result"""
        # BITree starts from index 1
        delta = val - self.rangeSum(i, i)
        index = i + 1
        while index < len(self._BITree):
            self._BITree[index] += delta
            index += RangeSumQuery.last_bit(index)


class RangeSumQuerySpec(unittest.TestCase):
    def test_example(self):
        rsq = RangeSumQuery([1, 3, 5])
        self.assertEqual(rsq.rangeSum(0, 2), 9)
        rsq.update(1, 2)
        self.assertEqual(rsq.rangeSum(0, 2), 8)

    def test_one_elem_array(self):
        rsq = RangeSumQuery([8])
        rsq.update(0, 2)
        self.assertEqual(rsq.rangeSum(0, 0), 2)

    def test_update_all_elements(self):
        req = RangeSumQuery([1, 4, 2, 3])
        self.assertEqual(req.rangeSum(0, 3), 10)
        req.update(0, 0)
        req.update(2, 0)
        req.update(1, 0)
        req.update(3, 0)
        self.assertEqual(req.rangeSum(0, 3), 0)
        req.update(2, 1)
        self.assertEqual(req.rangeSum(0, 1), 0)
        self.assertEqual(req.rangeSum(1, 2), 1)
        self.assertEqual(req.rangeSum(2, 3), 1)
        self.assertEqual(req.rangeSum(3, 3), 0)


if __name__ == '__main__':
    unittest.main(exit=False)

Additional Question: LC 114 [Medium] Flatten Binary Tree to Linked List


Question: Given a binary tree, flatten it to a linked list in-place.

For example, given the following tree:

    1
   / \
  2   5
 / \   \
3   4   6

The flattened tree should look like:

1
 \
  2
   \
    3
     \
      4
       \
        5
         \
          6

Solution with Recursion: https://repl.it/@trsong/Flatten-Binary-Tree-to-Linked-List

import unittest

class TreeNode(object):
    def __init__(self, val, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right

    def __eq__(self, other):
        return other and other.val == self.val and other.left == self.left and other.right == self.right


def flatten(tree):
    flatten_and_get_leaf(tree)


def flatten_and_get_leaf(tree):
    # flatten the tree and return the leaf node of that tree
    if not tree or not tree.left and not tree.right: return tree
    right_leaf = flatten_and_get_leaf(tree.right)
    left_leaf = flatten_and_get_leaf(tree.left)

    if left_leaf:
        # append right tree to the end of left tree leaf 
        left_leaf.right = tree.right
    if tree.left:
        # move left tree to right tree
        tree.right = tree.left
    tree.left = None

    # return tree right leaf if exits otherwise return left one 
    return right_leaf if right_leaf else left_leaf


class FlattenSpec(unittest.TestCase):
    def list_to_tree(self, lst):
        p = dummy = TreeNode(-1)
        for num in lst:
            p.right = TreeNode(num)
            p = p.right
        return dummy.right

    def test_example(self):
        """
            1
           / \
          2   5
         / \   \
        3   4   6
        """
        n2 = TreeNode(2, TreeNode(3), TreeNode(4))
        n5 = TreeNode(5, right = TreeNode(6))
        tree = TreeNode(1, n2, n5)
        flatten_list = [1, 2, 3, 4, 5, 6]
        flatten(tree)
        self.assertEqual(tree, self.list_to_tree(flatten_list))

    def test_empty_tree(self):
        tree = None
        flatten(tree)
        self.assertIsNone(tree)

    def test_only_right_child(self):
        """
        1
         \
          2
           \
            3
        """
        n2 = TreeNode(2, right=TreeNode(3))
        tree = TreeNode(1, right = n2)
        flatten_list = [1, 2, 3]
        flatten(tree)
        self.assertEqual(tree, self.list_to_tree(flatten_list))

    def test_only_left_child(self):
        """
            1
           /
          2
         /
        3
        """
        n2 = TreeNode(2, TreeNode(3))
        tree = TreeNode(1, n2)
        flatten_list = [1, 2, 3]
        flatten(tree)
        self.assertEqual(tree, self.list_to_tree(flatten_list))  


if __name__ == '__main__':
    unittest.main(exit=False)

Aug 8, 2019 [Medium] Delete Columns to Make Sorted II


Question: You are given an N by M 2D matrix of lowercase letters. The task is to count the number of columns to be deleted so that all the rows are lexicographically sorted.

Example 1:

Given the following table:
hello
geeks

Your function should return 1 as deleting column 1 (index 0)
Now both strings are sorted in lexicographical order:
ello
eeks

Example 2:

Given the following table:
xyz
lmn
pqr

Your function should return 0. All rows are already sorted lexicographically.

My thoughts: This problem feels like 2D version of The Longest Increasing Subsequence Problem (LIP) (check Jul 2, 2019 problem for details). The LIP says find longest increasing subsequence. e.g. 01212342345 gives 01[2]1234[23]45, with 2,2,3 removed. So if we only have 1 row, we can simply find longest increasing subsequence and use that to calculate how many columns to remove i.e. # of columns to remove = m - LIP. Similarly, for n by m table, we can first find longest increasing sub-columns and use that to calculate which columns to remove. Which can be done using DP:

let dp[i] represents max number of columns to keep at ends at column i.

  • dp[i] = max(dp[j]) + 1 where j < i if all characters in column i have lexicographical order larger than column j
  • dp[0] = 1

Solution with DP: https://repl.it/@trsong/Delete-Columns-to-Make-Sorted-II

import unittest

def delete_column(table):
    # let dp[i] represents max number of columns to keep at ends at column i
    # i.e. column i is the last column to keep
    # dp[i] = max(dp[j]) + 1 where j < i  if all e in column i have lexicographical order larger than column j
    m = len(table[0])
    dp = [1] * m
    for i in xrange(1, m):
        for j in xrange(i):
            if all(r[j] <= r[i] for r in table):
                dp[i] = max(dp[i], dp[j] + 1)
    return m - max(dp)


class DeleteColumnSpec(unittest.TestCase):
    def test_example1(self):
        self.assertEqual(delete_column([
            'hello',
            'geeks'
        ]), 1)

    def test_example2(self):
        self.assertEqual(delete_column([
            'xyz',
            'lmn',
            'pqr'
        ]), 0)

    def test_table_with_one_row(self):
        self.assertEqual(delete_column([
            '01212342345' # 01[2]1234[23]45   
        ]), 3)  

    def test_table_with_two_rows(self):
        self.assertEqual(delete_column([
            '01012',  # [0] 1 [0] 1 2
            '20101'   # [2] 0 [1] 0 1
        ]), 2)  


if __name__ == '__main__':
    unittest.main(exit=False)

Additional Question: LT 640 [Medium] One Edit Distance


Question: Given two strings S and T, determine if they are both one edit distance apart.

Example 1:

Input: s = "aDb", t = "adb" 
Output: True

Example 2:

Input: s = "ab", t = "ab" 
Output: False
Explanation:
s=t, so they aren't one edit distance apart

My thougths: The trick for this problem is that one insertion of shorter string is equivalent to one removal of longer string. And if both string of same length, then we only allow one replacement. The other cases are treated as more than one edit distance between two strings.

Solution: https://repl.it/@trsong/One-Edit-Distance

import unittest

def is_one_edit_distance_between(s1, s2):
    len1, len2 = len(s1), len(s2)
    if abs(len1 - len2) > 1: return False
    i = distance = 0
    (shorter_str, longer_str) = (s1, s2) if len1 < len2 else (s2, s1)
    len_shorter_str = len(shorter_str)
    for c in longer_str:
        if distance > 1: return False
        if i >= len_shorter_str or c != shorter_str[i]:
            distance += 1
            if len1 != len2:
                # when two strings of different length and there is mismatch, only proceed the pointer to longer string
                i -= 1
        i += 1
    return distance == 1


class IsOneEditDistanceBetweenSpec(unittest.TestCase):
    def test_example1(self):
        self.assertTrue(is_one_edit_distance_between('aDb', 'adb'))

    def test_example2(self):
        self.assertFalse(is_one_edit_distance_between('ab', 'ab'))

    def test_empty_string(self):
        self.assertFalse(is_one_edit_distance_between('', ''))
        self.assertTrue(is_one_edit_distance_between('', 'a'))
        self.assertFalse(is_one_edit_distance_between('', 'ab'))

    def test_one_insert_between_two_strings(self):
        self.assertTrue(is_one_edit_distance_between('abc', 'ac'))
        self.assertFalse(is_one_edit_distance_between('abcd', 'ad'))

    def test_one_remove_between_two_strings(self):
        self.assertTrue(is_one_edit_distance_between('abcd', 'abd'))
        self.assertFalse(is_one_edit_distance_between('abcd', 'cd'))

    def test_one_replace_between_two_string(self):
        self.assertTrue(is_one_edit_distance_between('abc', 'abd'))
        self.assertFalse(is_one_edit_distance_between('abc', 'ddc'))

    def test_length_difference_greater_than_one(self):
        self.assertFalse(is_one_edit_distance_between('abcd', 'abcdef'))


if __name__ == '__main__':
    unittest.main(exit=False)

Aug 7, 2019 [Easy] Delete Columns to Make Sorted I


Question: You are given an N by M 2D matrix of lowercase letters. Determine the minimum number of columns that can be removed to ensure that each row is ordered from top to bottom lexicographically. That is, the letter at each column is lexicographically later as you go down each row. It does not matter whether each row itself is ordered lexicographically.

Example 1:

Given the following table:
cba
daf
ghi

This is not ordered because of the a in the center. We can remove the second column to make it ordered:
ca
df
gi

So your function should return 1, since we only needed to remove 1 column.

Example 2:

Given the following table:
abcdef

Your function should return 0, since the rows are already ordered (there's only one row).

Example 3:

Given the following table:
zyx
wvu
tsr

Your function should return 3, since we would need to remove all the columns to order it.

My thoughts: Wordy this problem may be, but extremely easy to be solved. We will take a look at Delete Columns to Make Sorted II tomorrow.

Solution: https://repl.it/@trsong/Delete-Columns-to-Make-Sorted-I

import unittest

def columns_to_delete(table):
    if not table or not table[0]: return 0
    n, m = len(table), len(table[0])
    count = 0
    for c in xrange(m):
        for r in xrange(1, n):
            if table[r][c] < table[r-1][c]:
                count += 1
                break
    return count

class ColumnToDeleteSpec(unittest.TestCase):
    def test_empty_table(self):
        self.assertEqual(columns_to_delete([]), 0)
        self.assertEqual(columns_to_delete([""]), 0)

    def test_example1(self):
        self.assertEqual(columns_to_delete([
            'cba',
            'daf',
            'ghi'
        ]), 1)

    def test_example2(self):
        self.assertEqual(columns_to_delete([
            'abcdef'
        ]), 0)

    def test_example3(self):
        self.assertEqual(columns_to_delete([
            'zyx',
            'wvu',
            'tsr'
        ]), 3)


if __name__ == '__main__':
    unittest.main(exit=False)     

Aug 6, 2019 LC 236 [Medium] Lowest Common Ancestor of a Binary Tree


Question: Given a binary tree, find the lowest common ancestor (LCA) of two given nodes in the tree.

Example:

     1
   /   \
  2     3
 / \   / \
4   5 6   7

LCA(4, 5) = 2
LCA(4, 6) = 1
LCA(3, 4) = 1
LCA(2, 4) = 2

My thoughts: Notice that only the nodes at the same level can find common ancestor with same number of tracking backward. e.g. Consider 3 and 4 in above example: the common ancestor is 1, 3 needs 1 tracking backward, but 4 need 2 tracking backward. So the idea is to move those two nodes to the same level and then tacking backward until hit the common ancestor. The algorithm works as below:

We can use BFS to find target nodes and their depth. And by tracking backward the parent of the deeper node, we can make sure both of nodes are on the same level. Finally, we can tracking backwards until hit a common ancestor.

Solution with BFS and Backward Tracking Ancestor: https://repl.it/@trsong/Lowest-Common-Ancestor-of-a-Binary-Tree

import unittest

class TreeNode(object):
    def __init__(self, val, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


def LCA(tree, v1, v2):
    if v1 == v2: return v1
    parent = {}
    n1 = n2 = None
    lv1 = lv2 = lv = 0
    queue = [tree]
    
    # Run BFS to find node with value v1 and v2 and its depth
    while queue and (n1 is None or n2 is None):
        level_size = len(queue)
        for _ in xrange(level_size):
            node = queue.pop(0)
            if node.val == v1:
                n1 = node
                lv1 = lv
            elif node.val == v2:
                n2 = node
                lv2 = lv
            
            if node.left:
                parent[node.left] = node
                queue.append(node.left)
            if node.right:
                parent[node.right] = node
                queue.append(node.right)
        lv += 1
    
    # Backtrack the parent of deeper node up until at the same level as the other node
    (deeper_node, other_node) = (n1, n2) if lv1 > lv2 else (n2, n1)
    for _ in xrange(abs(lv1 - lv2)):
        deeper_node = parent[deeper_node]

    # Find the ancestor of both nodes recursively until find the common ancestor
    while deeper_node != other_node:
        deeper_node = parent[deeper_node]
        other_node = parent[other_node]

    return deeper_node.val


class LCASpec(unittest.TestCase):
    def setUp(self):
        """
             1
           /   \
          2     3
         / \   / \
        4   5 6   7
        """
        n2 = TreeNode(2, TreeNode(4), TreeNode(5))
        n3 = TreeNode(3, TreeNode(6), TreeNode(7))
        self.tree = TreeNode(1, n2, n3)

    def test_both_nodes_on_leaves(self):
        self.assertEqual(LCA(self.tree, 4, 5), 2)
        self.assertEqual(LCA(self.tree, 6, 7), 3)
        self.assertEqual(LCA(self.tree, 4, 6), 1)

    def test_nodes_on_different_levels(self):
        self.assertEqual(LCA(self.tree, 4, 2), 2)
        self.assertEqual(LCA(self.tree, 4, 3), 1)
        self.assertEqual(LCA(self.tree, 4, 1), 1)

    def test_same_nodes(self):
        self.assertEqual(LCA(self.tree, 2, 2), 2)
        self.assertEqual(LCA(self.tree, 6, 6), 6)


if __name__ == '__main__':
    unittest.main(exit=False)

Aug 5, 2019 [Easy] Single Bit Switch


Question: Given three 32-bit integers x, y, and b, return x if b is 1 and y if b is 0, using only mathematical or bit operations. You can assume b can only be 1 or 0.

Solution: https://repl.it/@trsong/Single-Bit-Switch

import unittest

def single_bit_switch_1(b, x, y):
    return b * x + (1 - b) * y


def single_bit_switch_2(b, x, y):
    # When b = 0001b,
    # -b = 1111b, ~-b = 0000b
    # When b = 0000b
    # -b = 0000b, ~-b = 1111b
    return (x & -b) | (y & ~-b) 


class SingleBitSwitchSpec(unittest.TestCase):
    def test_b_is_zero(self):
        b, x, y = 0, 8, 16
        self.assertEqual(single_bit_switch_1(b, x, y), y)
        self.assertEqual(single_bit_switch_2(b, x, y), y)

    def test_b_is_one(self):
        b, x, y = 1, 8, 16
        self.assertEqual(single_bit_switch_1(b, x, y), x)
        self.assertEqual(single_bit_switch_2(b, x, y), x)

    def test_negative_numbers(self):
        b0, b1, x, y = 0, 1, -1, -2
        self.assertEqual(single_bit_switch_1(b0, x, y), y)
        self.assertEqual(single_bit_switch_2(b1, x, y), x)


if __name__ == '__main__':
    unittest.main(exit=False)

Aug 4, 2019 LC 392 [Medium] Is Subsequence


Question: Given a string s and a string t, check if s is subsequence of t.

A subsequence of a string is a new string which is formed from the original string by deleting some (can be none) of the characters without disturbing the relative positions of the remaining characters. (ie, “ace” is a subsequence of “abcde” while “aec” is not).

Example 1:

s = "abc", t = "ahbgdc"
Return true.

Example 2:

s = "axc", t = "ahbgdc"
Return false.

My thoughts: Just base on the definition of subsequence, mantaining an pointer of s to the next letter we are about to checking and check it against all letter of t.

Solution: https://repl.it/@trsong/Is-Subsequence

import unittest

def isSubsequence(s, t):
    if len(s) > len(t): return False
    if not len(s): return True
    i = 0
    for c in t:
        if i >= len(s):
            break
        if c == s[i]:
            i += 1
    return i >= len(s)


class IsSubsequenceSpec(unittest.TestCase):
    def test_empty_s(self):
        self.assertTrue(isSubsequence("", ""))
        self.assertTrue(isSubsequence("", "a"))

    def test_empty_t(self):
        self.assertFalse(isSubsequence("a", ""))

    def test_s_longer_than_t(self):
        self.assertFalse(isSubsequence("ab", "a"))

    def test_size_one_input(self):
        self.assertTrue(isSubsequence("a", "a"))
        self.assertFalse(isSubsequence("a", "b"))

    def test_end_with_same_letter(self):
        self.assertTrue(isSubsequence("ab", "aaaaccb"))

    def test_example(self):
        self.assertTrue(isSubsequence("abc", "ahbgdc"))

    def test_example2(self):
        self.assertFalse(isSubsequence("axc", "ahbgdc"))


if __name__ == '__main__':
    unittest.main(exit=False)

Aug 3, 2019 [Medium] Toss Biased Coin


Question: Assume you have access to a function toss_biased() which returns 0 or 1 with a probability that’s not 50-50 (but also not 0-100 or 100-0). You do not know the bias of the coin. Write a function to simulate an unbiased coin toss.

My thoughts: Suppose the biased toss has probablilty p to return 0 and (1-p) to get 1. Then the probability to get:

  • 0, 0 is p * p
  • 1, 1 is (1-p) * (1-p)
  • 1, 0 is (1-p) * p
  • 0, 1 is p * (1-p)

Thus we can take advantage that 1, 0 and 0, 1 has same probility to get unbiased toss. Of course, above logic works only if p is neither 0% nor 100%.

Solution: https://repl.it/@trsong/Toss-Biased-Coin

from random import randint

def toss_biased():
    # suppose the toss has 1/4 chance to get 0 and 3/4 to get 1
    return 0 if randint(0, 3) == 0 else 1


def toss_unbiased():
    while True:
        t1 = toss_biased()
        t2 = toss_biased()
        if t1 != t2:
            return t1


def print_distribution(repeat):
    histogram = {}
    for _ in xrange(repeat):
        res = toss_unbiased()
        if res not in histogram:
            histogram[res] = 0
        histogram[res] += 1
    print histogram


def main():
    # Distribution looks like {0: 99931, 1: 100069}
    print_distribution(repeat=200000)


if __name__ == '__main__':
    main()

Aug 2, 2019 [Medium] The Tower of Hanoi


Question: The Tower of Hanoi is a puzzle game with three rods and n disks, each a different size.

All the disks start off on the first rod in a stack. They are ordered by size, with the largest disk on the bottom and the smallest one at the top.

The goal of this puzzle is to move all the disks from the first rod to the last rod while following these rules:

  • You can only move one disk at a time.
  • A move consists of taking the uppermost disk from one of the stacks and placing it on top of another stack.
  • You cannot place a larger disk on top of a smaller disk.

Write a function that prints out all the steps necessary to complete the Tower of Hanoi.

  • You should assume that the rods are numbered, with the first rod being 1, the second (auxiliary) rod being 2, and the last (goal) rod being 3.

Example:

with n = 3, we can do this in 7 moves:

Move 1 to 3
Move 1 to 2
Move 3 to 2
Move 1 to 3
Move 2 to 1
Move 2 to 3
Move 1 to 3

My thoughts: Think about the problem backwards, like what is the most significant states to reach the final state. There are three states coming into my mind:

  • First state, we move all disks except for last one from rod 1 to rod 2. i.e. [[3], [1, 2], []].
  • Second state, we move the last disk from rod 1 to rod 3. i.e. [[], [1, 2], [3]]
  • Third state, we move all disks from rod 2 to rod 3. i.e. [[], [], [1, 2, 3]]

There is a clear recrusive relationship between game with size n and size n - 1. So we can perform above stategy recursively for game with size n - 1 which gives the following implementation.

Solution with Recursion: https://repl.it/@trsong/The-Tower-of-Hanoi

import unittest

class HanoiGame(object):
    def __init__(self, num_disks):
        self.num_disks = num_disks
        self.reset()
        
    def reset(self):
        self.rods = [[disk for disk in xrange(self.num_disks, 0, -1)], [], []]

    def move(self, src, dst):
        disk = self.rods[src].pop()
        self.rods[dst].append(disk)

    def is_feasible_move(self, src, dst):
        return 0 <= src <= 2 and 0 <= dst <= 2 and self.rods[src] and (not self.rods[dst] or self.rods[src][-1] < self.rods[dst][-1])

    def is_game_finished(self):
        return len(self.rods[-1]) == self.num_disks

    def can_moves_finish_game(self, actions):
        self.reset()
        for src, dst in actions:
            if not self.is_feasible_move(src, dst):
                return False
            else:
                self.move(src, dst)
        return self.is_game_finished()
    

class HanoiGameSpec(unittest.TestCase):
    def test_example_moves(self):
        game = HanoiGame(3)
        moves = [(0, 2), (0, 1), (2, 1), (0, 2), (1, 0), (1, 2), (0, 2)]
        self.assertTrue(game.can_moves_finish_game(moves))

    def test_invalid_moves(self):
        game = HanoiGame(3)
        moves = [(0, 1), (0, 1)]
        self.assertFalse(game.can_moves_finish_game(moves))

    def test_unfinished_moves(self):
        game = HanoiGame(3)
        moves = [(0, 1)]
        self.assertFalse(game.can_moves_finish_game(moves))


def hanoi_moves(n):
    moves = []

    def hanoi_moves_recur(n, src, dst):
        if n <= 0: return
        other = 3 - src - dst

        # Step1: move n - 1 disks from src to 'other' to allow last disk move to dst
        hanoi_moves_recur(n-1, src, other)

        # Step2: move last disk from src to dst
        moves.append((src, dst))

        # Step3: move n - 1 disks from 'other' to dst
        hanoi_moves_recur(n-1, other, dst)

    hanoi_moves_recur(n, 0, 2)
    return moves


class HanoiMoveSpec(unittest.TestCase):
    def assert_hanoi_moves(self, n, moves):
        game = HanoiGame(n)
        self.assertTrue(game.can_moves_finish_game(moves))

    def test_three_disks(self):
        moves = hanoi_moves(3)
        self.assert_hanoi_moves(3, moves)

    def test_one_disk(self):
        moves = hanoi_moves(1)
        self.assert_hanoi_moves(1, moves)

    def test_ten_disks(self):
        moves = hanoi_moves(10)
        self.assert_hanoi_moves(10, moves)


if __name__ == '__main__':
    unittest.main(exit=False)

Aug 1, 2019 [Medium] All Root to Leaf Paths in Binary Tree


Question: Given a binary tree, return all paths from the root to leaves.

For example, given the tree:

   1
  / \
 2   3
    / \
   4   5

Return [[1, 2], [1, 3, 4], [1, 3, 5]].

Solution with Recursion: https://repl.it/@trsong/All-Root-to-Leaf-Paths-in-Binary-Tree

import unittest

class TreeNode(object):
    def __init__(self, val, left=None, right=None):
        self.val = val
        self.left = left
        self.right= right


def path_to_leaves(tree):
    if not tree: return []
    res = []
    def path_to_leaves_recur(tree, root_to_parent):
        root_to_current = root_to_parent + [tree.val]
        if not tree.left and not tree.right: 
            res.append(root_to_current)
        else:
            if tree.left:
                path_to_leaves_recur(tree.left, root_to_current)
            if tree.right:
                path_to_leaves_recur(tree.right, root_to_current)
    
    path_to_leaves_recur(tree, [])
    return res


class PathToLeavesSpec(unittest.TestCase):
    def test_empty_tree(self):
        self.assertEqual(path_to_leaves(None), [])

    def test_one_level_tree(self):
        self.assertEqual(path_to_leaves(TreeNode(1)), [[1]])

    def test_two_level_tree(self):
        tree = TreeNode(1, TreeNode(2), TreeNode(3))
        self.assertEqual(path_to_leaves(tree), [[1, 2], [1, 3]])

    def test_example(self):
        """
          1
         / \
        2   3
           / \
          4   5
        """
        n3 = TreeNode(3, TreeNode(4), TreeNode(5))
        tree = TreeNode(1, TreeNode(2), n3)
        self.assertEqual(path_to_leaves(tree), [[1, 2], [1, 3, 4], [1, 3, 5]])


if __name__ == '__main__':
    unittest.main(exit=False)