Daily Coding Problems 2020 Nov to Jan
- Daily Coding Problems
- Enviroment Setup
- Jan 31, 2021 LC 766 [Easy] Toeplitz Matrix
- Jan 30, 2021 LC 65 [Hard] Valid Number
- Jan 29, 2021 [Medium] Running Median of a Number Stream
- Jan 28, 2021 [Easy] URL Shortener
- Jan 27, 2021 LC 89 [Medium] Generate Gray Code
- Jan 26, 2021 [Easy] Rotate Linked List
- Jan 25, 2021 [Hard] Partition Array to Reach Mininum Difference
- Jan 24, 2021 [Hard] Smallest Stab Set
- Jan 23, 2021 [Easy] Valid UTF-8 Encoding
- Jan 22, 2021 [Easy] Palindrome Integers
- Jan 21, 2021 [Easy] Power Set
- Jan 20, 2021 [Medium] Most Frequent Subtree Sum
- Jan 19, 2021 [Medium] Friend Cycle Problem
- Jan 18, 2021 [Medium] Regular Numbers
- Jan 17, 2021 [Hard] Count Elements in Sorted Matrix
- Jan 16, 2021 [Easy] Exists Overlap Rectangle
- Jan 15, 2021 [Medium] Satisfactory Playlist
- Jan 14, 2021 LC 1344 [Easy] Angle between Clock Hands
- Jan 13, 2021 [Hard] Longest Common Subsequence of 3 Strings
- Jan 12, 2021 LC 138 [Medium] Deepcopy List with Random Pointer
- Jan 11, 2021 [Easy] Spreadsheet Columns
- Jan 10, 2021 [Medium] Multiset Partition
- Jan 9, 2021 [Easy] Compare Version Numbers
- Jan 8, 2021 [Easy] Step Word Anagram
- Jan 7, 2021 [Medium] Normalize Pathname
- Jan 6, 2021 LC 692 [Medium] Top K Frequent words
- Jan 5, 2021 [Hard] Anagram to Integer
- Jan 4, 2021 [Medium] Sort a K-Sorted Array
- Jan 3, 2021 [Medium] Detect Linked List Cycle
- Jan 2, 2021 [Easy] Determine If Linked List is Palindrome
- Jan 1, 2021 [Easy] Map Digits to Letters
- Dec 31, 2020 [Medium] Find Minimum Element in a Sorted and Rotated Array
- Dec 30, 2020 LC 77 [Medium] Combinations
- Dec 29, 2020 [Easy] Permutations
- Dec 28, 2020 [Medium] Index of Larger Next Number
- Dec 27, 2020 LC 218 [Hard] City Skyline
- Dec 26, 2020 [Medium] Largest Rectangular Area in a Histogram
- Dec 25, 2020 [Medium] Longest Consecutive Sequence in an Unsorted Array
- Dec 24, 2020 [Easy] Smallest Sum Not Subset Sum
- Dec 23, 2020 LT 879 [Medium] NBA Playoff Matches
- Dec 22, 2020 LC 301 [Hard] Remove Invalid Parentheses
- Dec 21, 2020 [Easy] Invalid Parentheses to Remove
- Dec 20, 2020 [Easy] Longest Consecutive 1s in Binary Representation
- Dec 19, 2020 [Medium] Craft Sentence
- Dec 18, 2020 [Medium] Reverse Coin Change
- Dec 17, 2020 LC 352 [Hard] Data Stream as Disjoint Intervals
- Dec 16, 2020 [Easy] Max and Min with Limited Comparisons
- Dec 15, 2020 LC 307 [Medium] Range Sum Query - Mutable
- Dec 14, 2020 LC 554 [Medium] Brick Wall
- Dec 13, 2020 LC 240 [Medium] Search a 2D Matrix II
- Dec 12, 2020 [Hard] Construct Cartesian Tree from Inorder Traversal
- Dec 11, 2020 [Hard] Find Next Sparse Number
- Dec 10, 2020 [Easy] Distribute Bonuses
- Dec 9, 2020 [Hard] Power Supply to All Cities
- Dec 8, 2020 LC 743 [Medium] Network Delay Time
- Dec 7, 2020 [Medium] Rearrange String with Repeated Characters
- Dec 6, 2020 [Easy] Implement Prefix Map Sum
- Dec 5, 2020 LC 274 [Medium] H-Index
- Dec 4, 2020 [Hard] Sliding Puzzle
- Dec 3, 2020 [Hard] Knight’s Tour Problem
- Dec 2, 2020 LC 127 [Medium] Word Ladder
- Dec 1, 2020 [Easy] Intersection of N Arrays
- Nov 30, 2020 LC 228 [Easy] Extract Range
- Nov 29, 2020 [Medium] Implement Soundex
- Nov 28, 2020 LC 151 [Medium] Reverse Words in a String
- Nov 27, 2020 [Medium] Direction and Position Rule Verification
- Nov 26, 2020 [Hard] Inversion Pairs
- Nov 25, 2020 [Hard] Minimum Cost to Construct Pyramid with Stones
- Nov 24, 2020 [Easy] Reconstruct a Jumbled Array
- Nov 23, 2020 LC 286 [Medium] Walls and Gates
- Nov 22, 2020 LC 212 [Hard] Word Search II
- Nov 21, 2020 [Hard] Find the Element That Appears Once While Others Occur 3 Times
- Nov 20, 2020 [Easy] Word Ordering in a Different Alphabetical Order
- Nov 19, 2020 [Easy] Count Visible Nodes in Binary Tree
- Nov 18, 2020 [Hard] Non-adjacent Subset Sum
- Nov 17, 2020 [Easy] Count Number of Unival Subtrees
- Nov 16, 2020 LC 1647 [Medium] Minimum Deletions to Make Character Frequencies Unique
- Nov 15, 2020 [Medium] Number of Flips to Make Binary String
- Nov 14, 2020 [Medium] Isolated Islands
- Nov 13, 2020 [Medium] Find Missing Positive
- Nov 12, 2020 [Medium] Max Value of Coins to Collect in a Matrix
- Nov 11, 2020 LC 859 [Easy] Buddy Strings
- Nov 10, 2020 [Medium] All Root to Leaf Paths in Binary Tree
- Nov 9, 2020 [Medium] Invert a Binary Tree
- Nov 8, 2020 [Medium] Similar Websites
- Nov 7, 2020 [Medium] Largest Square
- Nov 6, 2020 [Medium] M Smallest in K Sorted Lists
- Nov 5, 2020 [Easy] Maximum Subarray Sum
- Nov 4, 2020 [Easy] Largest Path Sum from Root To Leaf
- Nov 3, 2020 LC 121 [Easy] Best Time to Buy and Sell Stock
- Nov 2, 2020 LC 403 [Hard] Frog Jump
- Nov 1, 2020 [Medium] The Tower of Hanoi
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
Jan 31, 2021 LC 766 [Easy] Toeplitz Matrix
Question: In linear algebra, a Toeplitz matrix is one in which the elements on any given diagonal from top left to bottom right are identical.
Write a program to determine whether a given input is a Toeplitz matrix.
Example of Toeplitz Matrix:
1 2 3 4 8
5 1 2 3 4
4 5 1 2 3
7 4 5 1 2
Solution: https://repl.it/@trsong/Toeplitz-Matrix
import unittest
def is_valid_toeplitz(matrix):
if not matrix or not matrix[0]:
return True
n, m = len(matrix), len(matrix[0])
for r in xrange(1, n):
for c in xrange(1, m):
if matrix[r][c] != matrix[r - 1][c - 1]:
return False
return True
class IsValidToeplitz(unittest.TestCase):
def test_example(self):
matrix = [
[1, 2, 3, 4, 8],
[5, 1, 2, 3, 4],
[4, 5, 1, 2, 3],
[7, 4, 5, 1, 2]]
self.assertTrue(is_valid_toeplitz(matrix))
def test_empty_matrix(self):
self.assertTrue(is_valid_toeplitz([]))
self.assertTrue(is_valid_toeplitz([[]]))
def test_one_elem_matrix(self):
matrix = [[1]]
self.assertTrue(is_valid_toeplitz(matrix))
def test_matrix_of_one_row(self):
matrix = [[1, 2, 3]]
self.assertTrue(is_valid_toeplitz(matrix))
def test_matrix_of_one_column(self):
matrix = [
[1],
[2],
[3]]
self.assertTrue(is_valid_toeplitz(matrix))
def test_valid_toeplitz(self):
matrix = [
[1, 1, 0],
[1, 1, 1]
]
self.assertTrue(is_valid_toeplitz(matrix))
def test_invalid_toeplitz(self):
matrix = [
[1, 1, 1],
[1, 1, 0],
[1, 1, 1]
]
self.assertFalse(is_valid_toeplitz(matrix))
def test_invalid_toeplitz2(self):
matrix = [
[1, 0, 1],
[0, 1, 1]
]
self.assertFalse(is_valid_toeplitz(matrix))
if __name__ == '__main__':
unittest.main(exit=False, verbosity=2)
Jan 30, 2021 LC 65 [Hard] Valid Number
Question: Given a string, return whether it represents a number. Here are the different kinds of numbers:
- “10”, a positive integer
- “-10”, a negative integer
- “10.1”, a positive real number
- “-10.1”, a negative real number
- “1e5”, a number in scientific notation
And here are examples of non-numbers:
- “a”
- “x 1”
- “a -2”
- ”-“
Solution: https://repl.it/@trsong/Valid-Number
import unittest
def is_valid_number(raw_num):
"""
(WHITE_SPACE) (SIGN) DIGITS (DOT DIGITS) (e (SIGN) DIGITS) (WHITE_SPACE)
or
(WHITE_SPACE) (SIGN) DOT DIGITS (e (SIGN) DIGITS) (WHITE_SPACE)
"""
start = 0
end = len(raw_num) - 1
while start < len(raw_num) and raw_num[start].isspace():
start += 1
while end >= 0 and raw_num[end].isspace():
end -= 1
if start > end:
return False
has_sign = False
has_dot = False
has_exp = False
has_digits = False
for i in xrange(start, end + 1):
ch = raw_num[i]
if ch in ('-', '+'):
if has_sign or has_digits or has_dot:
return False
has_sign = True
elif ch.isdigit():
has_digits = True
elif ch in ('e', 'E'):
if has_exp or not has_digits:
return False
has_exp = True
has_sign = False
has_dot = False
has_digits = False
elif ch == '.':
if has_dot or has_exp:
return False
has_dot = True
else:
return False
return has_digits
class IsValidNumberSpec(unittest.TestCase):
def test_example1(self):
self.assertTrue(is_valid_number("123")) # Integer
def test_example2(self):
self.assertTrue(is_valid_number("12.3")) # Floating point
def test_example3(self):
self.assertTrue(is_valid_number("-123")) # Negative numbers
def test_example4(self):
self.assertTrue(is_valid_number("-.3")) # Negative floating point
def test_example5(self):
self.assertTrue(is_valid_number("1.5e5")) # Scientific notation
def test_example6(self):
self.assertFalse(is_valid_number("12a")) # No letters
def test_example7(self):
self.assertFalse(is_valid_number("1 2")) # No space between numbers
def test_example8(self):
self.assertFalse(is_valid_number("1e1.2")) # Exponent can only be an integer (positive or negative or 0)
def test_empty_string(self):
self.assertFalse(is_valid_number(""))
def test_blank_string(self):
self.assertFalse(is_valid_number(" "))
def test_just_signs(self):
self.assertFalse(is_valid_number("+"))
def test_zero(self):
self.assertTrue(is_valid_number("0"))
def test_contains_no_number(self):
self.assertFalse(is_valid_number("e"))
def test_contains_white_spaces(self):
self.assertTrue(is_valid_number(" -123.456 "))
def test_scientific_notation(self):
self.assertTrue(is_valid_number("2e10"))
def test_scientific_notation2(self):
self.assertFalse(is_valid_number("10e5.4"))
def test_scientific_notation3(self):
self.assertTrue(is_valid_number("-24.35e-10"))
def test_scientific_notation4(self):
self.assertFalse(is_valid_number("1e1e1"))
def test_scientific_notation5(self):
self.assertTrue(is_valid_number("+.5e-23"))
def test_scientific_notation6(self):
self.assertFalse(is_valid_number("+e-23"))
def test_scientific_notation7(self):
self.assertFalse(is_valid_number("0e"))
def test_scientific_notation8(self):
self.assertTrue(is_valid_number("1E9"))
def test_multiple_signs(self):
self.assertFalse(is_valid_number("+-2"))
def test_multiple_signs2(self):
self.assertFalse(is_valid_number("-2-2-2-2"))
def test_multiple_signs3(self):
self.assertFalse(is_valid_number("6+1"))
def test_multiple_dots(self):
self.assertFalse(is_valid_number("10.24.25"))
def test_sign_and_dot(self):
self.assertFalse(is_valid_number(".-4"))
if __name__ == '__main__':
unittest.main(exit=False, verbosity=2)
Jan 29, 2021 [Medium] Running Median of a Number Stream
Question: Compute the running median of a sequence of numbers. That is, given a stream of numbers, print out the median of the list so far on each new element.
Recall that the median of an even-numbered list is the average of the two middle numbers.
For example, given the sequence
[2, 1, 5, 7, 2, 0, 5]
, your algorithm should print out:
2
1.5
2
3.5
2
2
2
My thoughts: Given a sorted list, the median of a list is either the element in the middle of the list or average of left-max and right-min if we break the original list into left and right part:
* 1, [2], 3
* 1, [2], [3], 4
Notice that, as we get elem from stream 1-by-1, we don’t need to keep the list sorted. If only we could partition the list into two equally large list and mantain the max of the left part and min of right part. We should be good to go.
i,e,
[5, 1, 7] [8 , 20, 10]
^ left-max ^ right-min
A max-heap plus a min-heap will make it a lot easier to mantain the value we are looking for: A max-heap on the left mantaining the largest on left half and a min-heap on the right holding the smallest on right half. And most importantly, we mantain the size of max-heap and min-heap while reading data. (Left and right can at most off by 1).
Solution with Priority Queue: https://repl.it/@trsong/Running-Median-of-a-Number-Stream
import unittest
from Queue import PriorityQueue
def generate_running_median(num_stream):
min_heap = PriorityQueue()
max_heap = PriorityQueue()
for num in num_stream:
min_heap.put(num)
if min_heap.qsize() - max_heap.qsize() > 1:
max_heap.put(-min_heap.get())
if min_heap.qsize() == max_heap.qsize():
yield (-max_heap.queue[0] + min_heap.queue[0]) / 2.0
else:
yield min_heap.queue[0]
class GenerateRunningMedian(unittest.TestCase):
def test_example(self):
num_stream = iter([2, 1, 5, 7, 2, 0, 5])
expected = [2, 1.5, 2, 3.5, 2, 2, 2]
self.assertEqual(expected, list(generate_running_median(num_stream)))
def test_empty_stream(self):
self.assertEqual([], list(generate_running_median(iter([]))))
def test_unique_value(self):
num_stream = iter([1, 1, 1, 1, 1])
expected = [1, 1, 1, 1, 1]
self.assertEqual(expected, list(generate_running_median(num_stream)))
def test_contains_zero(self):
num_stream = iter([0, 1, 1, 0, 0])
expected = [0, 0.5, 1, 0.5, 0]
self.assertEqual(expected, list(generate_running_median(num_stream)))
def test_contains_zero2(self):
num_stream = iter([2, 0, 1])
expected = [2, 1, 1]
self.assertEqual(expected, list(generate_running_median(num_stream)))
def test_even_iteration_gives_average(self):
num_stream = iter([3, 0, 1, 2])
expected = [3, 1.5, 1, 1.5]
self.assertEqual(expected, list(generate_running_median(num_stream)))
if __name__ == '__main__':
unittest.main(exit=False, verbosity=2)
Jan 28, 2021 [Easy] URL Shortener
Question: Implement a URL shortener with the following methods:
shorten(url)
, which shortens the url into a six-character alphanumeric string, such aszLg6wl
.restore(short)
, which expands the shortened string into the original url. If no such shortened string exists, returnnull
.Follow-up: What if we enter the same URL twice?
Solution: https://repl.it/@trsong/URL-Shortener
import unittest
import random
class URLShortener(object):
CHAR_SET = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
def __init__(self):
self.short_to_long = {}
self.long_to_short = {}
def shorten(self, url):
if url in self.long_to_short:
return self.long_to_short[url]
short = None
while not short or short in self.short_to_long:
short = URLShortener.encode(url)
self.long_to_short[url] = short
self.short_to_long[short] = url
return short
def restore(self, short):
return self.short_to_long.get(short, None)
@staticmethod
def encode(url):
return "".join(random.choice(URLShortener.CHAR_SET) for _ in xrange(6))
class URLShortenerSpec(unittest.TestCase):
def test_should_be_able_to_init(self):
URLShortener()
def test_restore_should_not_fail_when_url_not_exists(self):
url_shortener = URLShortener()
self.assertIsNone(url_shortener.restore("oKImts"))
def test_shorten_result_into_six_letters(self):
url_shortener = URLShortener()
res = url_shortener.shorten("http://magic_url")
self.assertEqual(6, len(res))
def test_restore_short_url_gives_original(self):
url_shortener = URLShortener()
original_url = "http://magic_url"
short_url = url_shortener.shorten(original_url)
self.assertEqual(original_url, url_shortener.restore(short_url))
def test_shorten_different_url_gives_different_results(self):
url_shortener = URLShortener()
url1 = "http://magic_url_1"
res1 = url_shortener.shorten(url1)
url2 = "http://magic_url_2"
res2 = url_shortener.shorten(url2)
self.assertNotEqual(res1, res2)
def test_shorten_same_url_gives_same_result(self):
url_shortener = URLShortener()
url = "http://magic_url_1"
res1 = url_shortener.shorten(url)
res2 = url_shortener.shorten(url)
self.assertEqual(res1, res2)
if __name__ == '__main__':
unittest.main(exit=False, verbosity=2)
Jan 27, 2021 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-the-Gray-Code
import unittest
def gray_code(n):
"""
0
/ \
0 1
/ \ / \
00 01 11 10
/ \ / \ / \ / \
000 001 011 010 110 111 101 100
"""
res = [0] * (2 ** n)
for step in xrange(n):
for i in xrange(2 ** step - 1, -1, -1):
prev = res[i]
prev_append_zero = prev << 1
prev_append_one = prev_append_zero + 1
if i % 2 == 0:
res[2 * i] = prev_append_zero
res[2 * i + 1] = prev_append_one
else:
res[2 * i] = prev_append_one
res[2 * i + 1] = prev_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, verbosity=2)
Jan 26, 2021 [Easy] Rotate Linked List
Question: Given a linked list and a positive integer
k
, rotate the list to the right by k places.For example, given the linked list
7 -> 7 -> 3 -> 5
andk = 2
, it should become3 -> 5 -> 7 -> 7
.Given the linked list
1 -> 2 -> 3 -> 4 -> 5
andk = 3
, it should become3 -> 4 -> 5 -> 1 -> 2
.
Solution with Fast-slow Pointers: https://repl.it/@trsong/Rotate-Singly-Linked-List
import unittest
def rotate_list(head, k):
if not head:
return None
n = list_len(head)
k %= n
if k == 0:
return head
fast = slow = head
for _ in xrange(k):
fast = fast.next
while fast.next:
fast = fast.next
slow = slow.next
res = slow.next
fast.next = head
slow.next = None
return res
def list_len(lst):
res = 0
while lst:
lst = lst.next
res += 1
return res
class Node(object):
def __init__(self, val, next=None):
self.val = val
self.next = next
def __eq__(self, other):
return other and self.val == other.val and self.next == other.next
def __repr__(self):
return "%d -> %s" % (self.val, self.next)
@staticmethod
def List(*vals):
p = dummy = Node(-1)
for val in vals:
p.next = Node(val)
p = p.next
return dummy.next
class RotateListSpec(unittest.TestCase):
def test_example(self):
k, lst = 2, Node.List(1, 2, 3, 4)
expected = Node.List(3, 4, 1, 2)
self.assertEqual(expected, rotate_list(lst, k))
def test_empty_list(self):
self.assertIsNone(rotate_list(None, 42))
def test_single_elem_list(self):
k, lst = 42, Node.List(1)
expected = Node.List(1)
self.assertEqual(expected, rotate_list(lst, k))
def test_k_greater_than_length_of_list(self):
k, lst = 5, Node.List(1, 2)
expected = Node.List(2, 1)
self.assertEqual(expected, rotate_list(lst, k))
def test_k_equal_length_of_list(self):
k, lst = 3, Node.List(1, 2, 3)
expected = Node.List(1, 2, 3)
self.assertEqual(expected, rotate_list(lst, k))
def test_k_less_than_length_of_list(self):
k, lst = 5, Node.List(0, 1, 2, 3, 4, 5)
expected = Node.List(1, 2, 3, 4, 5, 0)
self.assertEqual(expected, rotate_list(lst, k))
def test_array_contains_duplicates(self):
k, lst = 1, Node.List(1, 2, 2, 3, 3, 3, 4)
expected = Node.List(4, 1, 2, 2, 3, 3, 3)
self.assertEqual(expected, rotate_list(lst, k))
if __name__ == '__main__':
unittest.main(exit=False, verbosity=2)
Jan 25, 2021 [Hard] Partition Array to Reach Mininum Difference
Question: Given an array of positive integers, divide the array into two subsets such that the difference between the sum of the subsets is as small as possible.
For example, given
[5, 10, 15, 20, 25]
, return the sets[10, 25]
and[5, 15, 20]
, which has a difference of5
, which is the smallest possible difference.
Solution with DP: https://repl.it/@trsong/Partition-Array-to-Reach-Min-Difference
import unittest
def min_partition_difference(nums):
nums_sum = sum(nums)
n = len(nums)
# Let dp[i][sum] represents exists subset sum for nums[:i]
# dp[i][sum] = dp[i-1][sum] if exclude nums[i - 1]
# = dp[i-1][sum - nums[i-1]] if include nums[i - 1]
dp = [[False for _ in xrange(nums_sum // 2 + 1)] for _ in xrange(n + 1)]
for i in xrange(n + 1):
dp[i][0] = True
for i in xrange(1, n + 1):
elem = nums[i-1]
for s in xrange(1, nums_sum // 2 + 1):
# Exclude current element, as we can already reach sum s
dp[i][s] = dp[i - 1][s]
if s - elem >= 0:
# As all number are positive, include current elem cannot exceed current sum
dp[i][s] = dp[i - 1][s - elem]
"""
Let's do some math here:
Let s1, s2 be the size to two subsets after partition and assume s1 >= s2
We can have s1 + s2 = sum_nums and we want to get min{s1 - s2} where s1 >= s2:
min{s1 - s2}
= min{s1 + s2 - s2 - s2}
= min{sum_nums - 2 * s2} in this step sum_nums - 2 * s2 >=0, gives s2 <= sum_nums/ 2
= sum_nums - 2 * max{s2} where s2 <= sum_nums/2
"""
for s in xrange(nums_sum // 2, -1, -1):
if dp[n][s]:
return nums_sum - 2 * s
return None
class MinPartitionDifferenceSpec(unittest.TestCase):
def test_example(self):
# Partition: [10, 25] and [5, 15, 20]
self.assertEqual(5, min_partition_difference([5, 10, 15, 20, 25]))
def test_empty_array(self):
self.assertEqual(0, min_partition_difference([]))
def test_array_with_one_element(self):
self.assertEqual(42, min_partition_difference([42]))
def test_array_with_two_elements(self):
self.assertEqual(0, min_partition_difference([42, 42]))
def test_unsorted_array_with_duplicated_numbers(self):
# Partition: [3, 4] and [1, 2, 2, 1]
self.assertEqual(1, min_partition_difference([3, 1, 4, 2, 2, 1]))
def test_unsorted_array_with_unique_numbers(self):
# Partition: [11] and [1, 5, 6]
self.assertEqual(1, min_partition_difference([1, 6, 11, 5]))
def test_sorted_array_with_duplicated_numbers(self):
# Partition: [1, 2, 2] and [4]
self.assertEqual(1, min_partition_difference([1, 2, 2, 4]))
def test_min_partition_difference_is_zero(self):
# Partition: [1, 8, 2, 7] and [3, 6, 4, 5]
self.assertEqual(0, min_partition_difference([1, 2, 3, 4, 5, 6, 7, 8]))
if __name__ == '__main__':
unittest.main(exit=False, verbosity=2)
Jan 24, 2021 [Hard] Smallest Stab Set
Question: Let
X
be a set ofn
intervals on the real line. We say that a set of pointsP
“stabs”X
if every interval inX
contains at least one point inP
. Compute the smallest set of points that stabs X.For example, given the intervals
[(1, 4), (4, 5), (7, 9), (9, 12)]
, you should return[4, 9]
.
My thoughts: Sort intevals by end time. Greedily choose earlist end time of a interval. Skip all intervals that covers that end time unless not covered, then move onto earilst end time of next interval.
Solution with Greedy Approach: https://repl.it/@trsong/Smallest-Stab-Set
import unittest
def smallest_stub_set(intervals):
intervals.sort(key=lambda interval: (interval[1], interval[0]))
smallest_end = None
res = []
for start, end in intervals:
if start > smallest_end:
res.append(end)
smallest_end = end
return res
class SmallestStubSetSpec(unittest.TestCase):
def assert_result(self, expected, intervals):
res = smallest_stub_set(intervals)
self.assertEqual(len(expected), len(res), "Expected result like {} but given {}".format(expected, res))
for num in res:
self.assertTrue(any(lo <= num <= hi for lo, hi in intervals))
def test_example(self):
intervals = [(1, 4), (4, 5), (7, 9), (9, 12)]
expected = [4, 9]
self.assert_result(expected, intervals)
def test_empty_intervals(self):
self.assertEqual([], smallest_stub_set([]))
def test_optimal_result(self):
intervals = [(1, 5), (5, 10), (2, 9)]
expected = [5]
self.assert_result(expected, intervals)
def test_optimal_result2(self):
intervals = [(2, 8), (8, 15), (1, 5), (5, 10), (10, 20)]
expected = [5, 10]
self.assert_result(expected, intervals)
def test_have_to_include_all_ends(self):
intervals = [(2, 2), (8, 8), (1, 1), (5, 5), (2, 2)]
expected = [1, 2, 5, 8]
self.assert_result(expected, intervals)
def test_multiple_solutions(self):
intervals = [(1, 2), (1, 3), (1, 4), (3, 5)]
expected = [2, 5] # other solution also work
self.assert_result(expected, intervals)
def test_multiple_solutions2(self):
intervals = [(1, 5), (2, 5), (3, 5), (4, 10)]
expected = [5] # other solution also work
self.assert_result(expected, intervals)
if __name__ == '__main__':
unittest.main(exit=False, verbosity=2)
Jan 23, 2021 [Easy] Valid UTF-8 Encoding
Question: UTF-8 is a character encoding that maps each symbol to one, two, three, or four bytes.
Write a program that takes in an array of integers representing byte values, and returns whether it is a valid UTF-8 encoding.
For example, the Euro sign, €, corresponds to the three bytes 11100010 10000010 10101100. The rules for mapping characters are as follows:
- For a single-byte character, the first bit must be zero.
- For an n-byte character, the first byte starts with n ones and a zero. The other n - 1 bytes all start with 10. Visually, this can be represented as follows.
Bytes | Byte format
-----------------------------------------------
1 | 0xxxxxxx
2 | 110xxxxx 10xxxxxx
3 | 1110xxxx 10xxxxxx 10xxxxxx
4 | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
Solution: https://repl.it/@trsong/Valid-UTF-8-Encoding
import unittest
MAX_BYTE = 0b11111111
FOLLOWING_BYTE_PREFIX = 0b10
def validate_utf8(byte_arr):
if not byte_arr:
return False
first_byte = byte_arr[0]
if len(byte_arr) == 1:
return first_byte >> 7 == 0
if not validate_byte_value(byte_arr):
return False
num_bytes = count_prefix_ones(first_byte)
if num_bytes != len(byte_arr):
return False
return validate_following_bytes(byte_arr[1:])
def validate_byte_value(byte_arr):
return all(byte <= MAX_BYTE for byte in byte_arr)
def count_prefix_ones(byte):
count = 0
for i in xrange(7, -1, -1):
if byte & 1 << i == 0:
break
count += 1
return count
def validate_following_bytes(byte_arr):
return all(FOLLOWING_BYTE_PREFIX == (byte >> 6) for byte in byte_arr)
class ValidateUtf8Spec(unittest.TestCase):
def test_example(self):
byte_arr = [0b11100010, 0b10000010, 0b10101100]
self.assertTrue(validate_utf8(byte_arr))
def test_empty_arr(self):
byte_arr = []
self.assertFalse(validate_utf8(byte_arr))
def test_valid_one_byte(self):
byte_arr = [0b0]
self.assertTrue(validate_utf8(byte_arr))
def test_valid_one_byte2(self):
byte_arr = [0b01111111]
self.assertTrue(validate_utf8(byte_arr))
def test_invalid_one_byte(self):
byte_arr = [0b10101010]
self.assertFalse(validate_utf8(byte_arr))
def test_invalid_one_byte2(self):
byte_arr = [0b0, 0b10000010]
self.assertFalse(validate_utf8(byte_arr))
def test_valid_two_bytes(self):
byte_arr = [0b11000000, 0b10000000]
self.assertTrue(validate_utf8(byte_arr))
def test_valid_two_bytes2(self):
byte_arr = [0b11011111, 0b10111111]
self.assertTrue(validate_utf8(byte_arr))
def test_valid_three_bytes(self):
byte_arr = [0b11100000, 0b10000000, 0b10000000]
self.assertTrue(validate_utf8(byte_arr))
def test_valid_three_bytes2(self):
byte_arr = [0b11101111, 0b10111111, 0b10111111]
self.assertTrue(validate_utf8(byte_arr))
def test_valid_four_bytes(self):
byte_arr = [0b11110000, 0b10000000, 0b10000000, 0b10000000]
self.assertTrue(validate_utf8(byte_arr))
def test_valid_four_bytes2(self):
byte_arr = [0b11110111, 0b10111111, 0b10111111, 0b10111111]
self.assertTrue(validate_utf8(byte_arr))
def test_short_on_bytes(self):
byte_arr = [0b11110111, 0b10111111, 0b10111111]
self.assertFalse(validate_utf8(byte_arr))
def test_more_than_necessary_bytes(self):
byte_arr = [0b11000111, 0b10111111, 0b10111111]
self.assertFalse(validate_utf8(byte_arr))
def test_following_bytes_with_incorrect_prefix(self):
byte_arr = [0b11100000, 0b11000000, 0b11000000]
self.assertFalse(validate_utf8(byte_arr))
if __name__ == '__main__':
unittest.main(exit=False, verbosity=2)
Jan 22, 2021 [Easy] Palindrome Integers
Question: Write a program that checks whether an integer is a palindrome. For example,
121
is a palindrome, as well as888
. But neither678
nor80
is a palindrome. Do not convert the integer into a string.
Solution: https://repl.it/@trsong/Determine-Palindrome-Integer
import unittest
def is_palindrome_integer(num):
return num >= 0 and num == reverse_number(num)
def reverse_number(num):
res = 0
while num > 0:
res = 10 * res + num % 10
num //= 10
return res
class IsPalindromeIntegerSpec(unittest.TestCase):
def test_example1(self):
self.assertTrue(is_palindrome_integer(121))
def test_example2(self):
self.assertTrue(is_palindrome_integer(88))
def test_example3(self):
self.assertFalse(is_palindrome_integer(678))
def test_example4(self):
self.assertFalse(is_palindrome_integer(80))
def test_zero(self):
self.assertTrue(is_palindrome_integer(0))
def test_single_digit_number(self):
self.assertTrue(is_palindrome_integer(7))
def test_non_palindrome1(self):
self.assertFalse(is_palindrome_integer(123421))
def test_non_palindrome2(self):
self.assertFalse(is_palindrome_integer(21010112))
def test_negative_number1(self):
self.assertFalse(is_palindrome_integer(-1))
def test_negative_number2(self):
self.assertFalse(is_palindrome_integer(-222))
if __name__ == '__main__':
unittest.main(exit=False, verbosity=2)
Jan 21, 2021 [Easy] Power Set
Question: The power set of a set is the set of all its subsets. Write a function that, given a set, generates its power set.
For example, given a set represented by a list
[1, 2, 3]
, it should return[[], [1], [2], [3], [1, 2], [1, 3], [2, 3], [1, 2, 3]]
representing the power set.
My thoughts: There are multiple ways to solve this problem. My solution recursively subsets incrementally.
Let’s calculate the first few terms and try to figure out the pattern
power_set([]) => [[]]
power_set([1]) => [[], [1]]
power_set([1, 2]) => [[], [1], [2], [1, 2]]
power_set([1, 2, 3]) => [[], [1], [2], [1, 2], [3], [1, 3], [2, 3], [1, 2, 3]] which is power_set([1, 2]) + append powerSet([1, 2]) with new elem 3
...
power_set([1, 2, ..., n]) => power_set([1, 2, ..., n - 1]) + append powerSet([1, 2, ..., n - 1]) with new elem n
Solution: https://repl.it/@trsong/Power-Set
import unittest
def generate_power_set(nums):
res = [[]]
for num in nums:
include = []
for existing_set in res:
include.append(existing_set + [num])
res.extend(include)
return res
class GeneratePowerSetSpec(unittest.TestCase):
def test_example(self):
nums = [1, 2, 3]
expected = [[], [1], [2], [3], [1, 2], [2, 3], [1, 3], [1, 2, 3]]
self.assertItemsEqual(expected, generate_power_set(nums))
def test_empty_set(self):
nums = []
expected = [[]]
self.assertItemsEqual(expected, generate_power_set(nums))
def test_one_elem_set(self):
nums = [1]
expected = [[], [1]]
self.assertItemsEqual(expected, generate_power_set(nums))
def test_two_elem_set(self):
nums = [1, 2]
expected = [[], [1], [2], [1, 2]]
self.assertItemsEqual(expected, generate_power_set(nums))
if __name__ == '__main__':
unittest.main(exit=False, verbosity=2)
Jan 20, 2021 [Medium] Most Frequent Subtree Sum
Question: Given a binary tree, find the most frequent subtree sum.
If there is a tie between the most frequent sum, return the smaller one.
Example:
3
/ \
1 -3
The above tree has 3 subtrees.:
The root node with 3, and the 2 leaf nodes, which gives us a total of 3 subtree sums.
The root node has a sum of 1 (3 + 1 + -3).
The left leaf node has a sum of 1, and the right leaf node has a sum of -3.
Therefore the most frequent subtree sum is 1.
Solution: https://repl.it/@trsong/Find-Most-Frequent-Subtree-Sum
import unittest
def most_freq_tree_sum(tree):
if not tree:
return None
freq = {}
node_sum(tree, freq)
return max(freq.keys(), key=lambda k: (freq[k], -k))
def node_sum(node, freq):
if not node:
return 0
left_res = node_sum(node.left, freq)
right_res = node_sum(node.right, freq)
res = node.val + left_res + right_res
freq[res] = freq.get(res, 0) + 1
return res
class TreeNode(object):
def __init__(self, val, left=None, right=None):
self.val = val
self.left = left
self.right = right
class MostFreqTreeSumSpec(unittest.TestCase):
def test_example1(self):
"""
3
/ \
2 -3
"""
t = TreeNode(3, TreeNode(2), TreeNode(-3))
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))
def test_return_smaller_freq_when_there_is_a_tie(self):
"""
-2
/ \
0 1
/ \ / \
0 0 1 1
"""
l = TreeNode(0, TreeNode(0), TreeNode(0))
r = TreeNode(1, TreeNode(1), TreeNode(1))
t = TreeNode(-2, l, r)
self.assertEqual(0, most_freq_tree_sum(t))
def test_return_smaller_freq_when_there_is_a_tie2(self):
"""
3
/ \
2 -1
"""
t = TreeNode(3, TreeNode(2), TreeNode(-1))
self.assertEqual(-1, most_freq_tree_sum(t))
if __name__ == '__main__':
unittest.main(exit=False)
Jan 19, 2021 [Medium] Friend Cycle Problem
Question: A classroom consists of
N
students, whose friendships can be represented in an adjacency list. For example, the following descibes a situation where0
is friends with1
and2
,3
is friends with6
, and so on.
{0: [1, 2],
1: [0, 5],
2: [0],
3: [6],
4: [],
5: [1],
6: [3]}
Each student can be placed in a friend group, which can be defined as the transitive closure of that student’s friendship relations. In other words, this is the smallest set such that no student in the group has any friends outside this group. For the example above, the friend groups would be
{0, 1, 2, 5}
,{3, 6}
,{4}
.Given a friendship list such as the one above, determine the number of friend groups in the class.
Solution with DisjointSet(Union-Find): https://repl.it/@trsong/Friend-Cycle-Problem
import unittest
def find_cycles(friendships):
capacity = len(friendships)
uf = DisjointSet(capacity)
for p1, friends in friendships.items():
for p2 in friends:
uf.union(p1, p2)
return uf.groups
class DisjointSet(object):
def __init__(self, capacity):
self.parent = range(capacity)
self.groups = capacity
def find(self, p):
while p != self.parent[p]:
self.parent[p] = self.find(self.parent[p])
p = self.parent[p]
return p
def union(self, p1, p2):
parent1 = self.find(p1)
parent2 = self.find(p2)
if parent1 != parent2:
self.parent[parent1] = parent2
self.groups -= 1
class FindCycleSpec(unittest.TestCase):
def test_example(self):
friendships = {
0: [1, 2],
1: [0, 5],
2: [0],
3: [6],
4: [],
5: [1],
6: [3]
}
expected = 3 # [0, 1, 2, 5], [3, 6], [4]
self.assertEqual(expected, find_cycles(friendships))
def test_no_friends(self):
friendships = {
0: [],
1: [],
2: []
}
expected = 3 # [0], [1], [2]
self.assertEqual(expected, find_cycles(friendships))
def test_all_friends(self):
friendships = {
0: [1, 2],
1: [0, 2],
2: [0, 1]
}
expected = 1 # [0, 1, 2]
self.assertEqual(expected, find_cycles(friendships))
def test_common_friend(self):
friendships = {
0: [1],
1: [0, 2, 3],
2: [1],
3: [1],
4: []
}
expected = 2 # [0, 1, 2, 3], [4]
self.assertEqual(expected, find_cycles(friendships))
if __name__ == '__main__':
unittest.main(exit=False, verbosity=2)
Jan 18, 2021 [Medium] Regular Numbers
Question: A regular number in mathematics is defined as one which evenly divides some power of
60
. Equivalently, we can say that a regular number is one whose only prime divisors are2
,3
, and5
.These numbers have had many applications, from helping ancient Babylonians keep time to tuning instruments according to the diatonic scale.
Given an integer N, write a program that returns, in order, the first N regular numbers.
Solution: https://repl.it/@trsong/Regular-Numbers
import unittest
def generate_regular_numbers(n):
if n == 0:
return []
res = [1]
i, j, k = 0, 0, 0
for _ in xrange(n - 1):
num = min(2 * res[i], 3 * res[j], 5 * res[k])
res.append(num)
if num % 2 == 0:
i += 1
if num % 3 == 0:
j += 1
if num % 5 == 0:
k += 1
return res
# Source: https://en.wikipedia.org/wiki/Regular_number
REGULAR_NUMBERS = [
1, 2, 3, 4, 5, 6, 8, 9, 10, 12, 15, 16, 18, 20, 24, 25, 27, 30, 32, 36, 40,
45, 48, 50, 54, 60, 64, 72, 75, 80, 81, 90, 96, 100, 108, 120, 125, 128,
135, 144, 150, 160, 162, 180, 192, 200, 216, 225, 240, 243, 250, 256, 270,
288, 300, 320, 324, 360, 375, 384, 400, 405
]
class GenerateRegularNumberSpec(unittest.TestCase):
def test_empty_result(self):
self.assertEqual([], generate_regular_numbers(0))
def test_5_elements(self):
self.assertEqual(REGULAR_NUMBERS[:5], generate_regular_numbers(5))
def test_10_elements(self):
self.assertEqual(REGULAR_NUMBERS[:10], generate_regular_numbers(10))
def test_15_elements(self):
self.assertEqual(REGULAR_NUMBERS[:15], generate_regular_numbers(15))
def test_20_elements(self):
self.assertEqual(REGULAR_NUMBERS[:20], generate_regular_numbers(20))
if __name__ == '__main__':
unittest.main(exit=False)
Jan 17, 2021 [Hard] Count Elements in Sorted Matrix
Question: Let A be an
N
byM
matrix in which every row and every column is sorted.Given
i1
,j1
,i2
, andj2
, compute the number of elements smaller thanA[i1, j1]
and larger thanA[i2, j2]
.
Example:
Given the following matrix:
[[ 1, 3, 6, 10, 15, 20],
[ 2, 7, 9, 14, 22, 25],
[ 3, 8, 10, 15, 25, 30],
[10, 11, 12, 23, 30, 35],
[20, 25, 30, 35, 40, 45]]
And i1 = 1, j1 = 1, i2 = 3, j2 = 3
return 15 as there are 15 numbers in the matrix smaller than 7 or greater than 23.
My thoughts: The trick is to start from top-right cell. Either go left or go down, we can quickly figure out smaller elements within linear time O(N + M)
. Once know how to find smaller, finding greater is just using total elements minus smaller.
Solution with Two-Pointers: https://repl.it/@trsong/Count-Elements-in-Sorted-Matrix
import unittest
def count_elements(matrix, pos1, pos2):
n, m = len(matrix), len(matrix[0])
v1 = matrix[pos1[0]][pos1[1]]
v2 = matrix[pos2[0]][pos2[1]]
if v1 > v2:
return n * m
less_than_v1 = count_smaller_elements(matrix, v1)
not_greater_than_v2 = count_smaller_elements(matrix, v2, exclusive=False)
return less_than_v1 + n * m - not_greater_than_v2
def count_smaller_elements(matrix, target, exclusive=True):
n, m = len(matrix), len(matrix[0])
res = 0
row = 0
for col in xrange(m - 1, -1, -1):
while row < n:
if matrix[row][col] > target or exclusive and matrix[row][col] == target:
break
row += 1
res += row
return res
class CountElementSpec(unittest.TestCase):
def test_example(self):
matrix = [
[1, 3, 6, 10, 15, 20],
[2, 7, 9, 14, 22, 25],
[3, 8, 10, 15, 25, 30],
[10, 11, 12, 23, 30, 35],
[20, 25, 30, 35, 40, 45]]
pos1, pos2 = (1, 1), (3, 3)
expected = 15
self.assertEqual(expected, count_elements(matrix, pos1, pos2))
def test_no_elem_found(self):
matrix = [
[1, 2],
[3, 6]
]
pos1, pos2 = (0, 0), (1, 1)
expected = 0
self.assertEqual(expected, count_elements(matrix, pos1, pos2))
def test_top_right_bottom_left(self):
matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]]
pos1, pos2 = (0, 2), (2, 0)
expected = 4
self.assertEqual(expected, count_elements(matrix, pos1, pos2))
def test_covers_entire_matrix(self):
matrix = [
[1, 2, 3],
[2, 3, 4],
[9, 10, 11]
]
pos1, pos2 = (2, 2), (0, 0)
expected = 9
self.assertEqual(expected, count_elements(matrix, pos1, pos2))
def test_identical_pos(self):
matrix = [
[1, 2, 3],
[2, 3, 4],
[9, 10, 11]
]
pos1, pos2 = (1, 1), (1, 1)
expected = 7
self.assertEqual(expected, count_elements(matrix, pos1, pos2))
if __name__ == '__main__':
unittest.main(exit=False, verbosity=2)
Jan 16, 2021 [Easy] Exists Overlap Rectangle
Question: You are given a list of rectangles represented by min and max x- and y-coordinates. Compute whether or not a pair of rectangles overlap each other. If one rectangle completely covers another, it is considered overlapping.
For example, given the following rectangles:
{ "top_left": (1, 4), "dimensions": (3, 3) # width, height }, { "top_left": (-1, 3), "dimensions": (2, 1) }, { "top_left": (0, 5), "dimensions": (4, 3) }
return true as the first and third rectangle overlap each other.
Solution: https://repl.it/@trsong/Exists-Overlap-Rectangles
import unittest
def exists_overlap_rectangle(rectangles):
rects = map(Rectangle.from_object, rectangles)
# sort by x_min, then y_min
rects.sort(key=Rectangle.get_bottom_left)
n = len(rects)
for i, rect1 in enumerate(rects):
for j in xrange(i + 1, n):
rect2 = rects[j]
if rect2.x_min >= rect1.x_max:
break
if rect1.is_overlapped(rect2):
return True
return False
class Rectangle(object):
def __init__(self, top_left, dimension):
self.x_min = top_left[0]
self.x_max = top_left[0] + dimension[0]
self.y_min = top_left[1] - dimension[1]
self.y_max = top_left[1]
def __repr__(self):
return "Rectangle(x_min=%d, x_max=%d, y_min=%d, y_max=%d)" % (self.x_min, self.x_max, self.y_min, self.y_max)
def is_overlapped(self, other):
return (max(self.x_min, other.x_min) < min(self.x_max, other.x_max) and
max(self.y_min, other.y_min) < min(self.y_max, other.y_max))
@staticmethod
def from_object(obj):
return Rectangle(obj.get("top_left"), obj.get("dimensions"))
@staticmethod
def get_bottom_left(rect):
return (rect.x_min, rect.y_min)
class ExistsOverlapRectangleSpec(unittest.TestCase):
def test_example(self):
rectangles = [
{
"top_left": (1, 4),
"dimensions": (3, 3) # width, height
},
{
"top_left": (-1, 3),
"dimensions": (2, 1)
},
{
"top_left": (0, 5),
"dimensions": (4, 3)
}
]
self.assertTrue(exists_overlap_rectangle(rectangles))
def test_empty_rectangle_list(self):
self.assertFalse(exists_overlap_rectangle([]))
def test_two_overlap_rectangle(self):
rectangles = [
{
"top_left": (0, 1),
"dimensions": (1, 3) # width, height
},
{
"top_left": (-1, 0),
"dimensions": (3, 1)
}
]
self.assertTrue(exists_overlap_rectangle(rectangles))
def test_two_overlap_rectangle_form_a_cross(self):
rectangles = [
{
"top_left": (-1, 1),
"dimensions": (3, 2) # width, height
},
{
"top_left": (0, 0),
"dimensions": (1, 1)
}
]
self.assertTrue(exists_overlap_rectangle(rectangles))
def test_same_y_coord_not_overlap(self):
rectangles = [
{
"top_left": (0, 0),
"dimensions": (1, 1) # width, height
},
{
"top_left": (1, 0),
"dimensions": (2, 2)
},
{
"top_left": (3, 0),
"dimensions": (5, 2)
}
]
self.assertFalse(exists_overlap_rectangle(rectangles))
def test_same_y_coord_overlap(self):
rectangles = [
{
"top_left": (0, 0),
"dimensions": (1, 1) # width, height
},
{
"top_left": (1, 0),
"dimensions": (2, 2)
},
{
"top_left": (3, 0),
"dimensions": (5, 2)
}
]
self.assertFalse(exists_overlap_rectangle(rectangles))
def test_rectangles_in_different_quadrant(self):
rectangles = [
{
"top_left": (1, 1),
"dimensions": (2, 2) # width, height
},
{
"top_left": (-1, 1),
"dimensions": (2, 2)
},
{
"top_left": (1, -1),
"dimensions": (2, 2)
},
{
"top_left": (-1, -1),
"dimensions": (2, 2)
}
]
self.assertFalse(exists_overlap_rectangle(rectangles))
if __name__ == '__main__':
unittest.main(exit=False)
Jan 15, 2021 [Medium] Satisfactory Playlist
Question: You have access to ranked lists of songs for various users. Each song is represented as an integer, and more preferred songs appear earlier in each list. For example, the list
[4, 1, 7]
indicates that a user likes song4
the best, followed by songs1
and7
.Given a set of these ranked lists, interleave them to create a playlist that satisfies everyone’s priorities.
For example, suppose your input is
[[1, 7, 3], [2, 1, 6, 7, 9], [3, 9, 5]]
. In this case a satisfactory playlist could be[2, 1, 6, 7, 3, 9, 5]
.
My thoughts: create a graph with vertex being song and edge (u, v)
representing that u is more preferred than v. A topological order will make sure that all more preferred song will go before less preferred ones. Thus gives a list that satisfies everyone’s priorities, if there is one (no cycle).
Solution with Topological Sort: https://repl.it/@trsong/Satisfactory-Playlist-for-Everyone
import unittest
def calculate_satisfactory_playlist(preference):
neighbors = {}
inbound = {}
for songs in preference:
prev = None
for cur_song in songs:
inbound[cur_song] = inbound.get(cur_song, 0)
if prev:
neighbors[prev] = neighbors.get(prev, set())
if cur_song not in neighbors[prev]:
neighbors[prev].add(cur_song)
inbound[cur_song] += 1
prev = cur_song
queue = []
for song, count in inbound.items():
if count == 0:
queue.append(song)
res = []
while queue:
cur = queue.pop(0)
res.append(cur)
if cur not in neighbors:
continue
for nb in neighbors[cur]:
inbound[nb] -= 1
if inbound[nb] == 0:
queue.append(nb)
return res if len(res) == len(inbound) else None
class CalculateSatisfactoryPlaylistSpec(unittest.TestCase):
def validate_result(self, preference, suggested_order):
song_set = set([song for songs in preference for song in songs])
self.assertEqual(
song_set,
set(suggested_order),
"Missing song: " + str(str(song_set - set(suggested_order))))
for i in xrange(len(suggested_order)):
for j in xrange(i+1, len(suggested_order)):
for lst in preference:
song1, song2 = suggested_order[i], suggested_order[j]
if song1 in lst and song2 in lst:
self.assertLess(
lst.index(song1),
lst.index(song2),
"Suggested order {} conflict: {} cannot be more popular than {}".format(suggested_order, song1, song2))
def test_example(self):
preference = [[1, 7, 3], [2, 1, 6, 7, 9], [3, 9, 5]]
# possible order: 2, 1, 6, 7, 3, 9, 5
suggested_order = calculate_satisfactory_playlist(preference)
self.validate_result(preference, suggested_order)
def test_preference_contains_duplicate(self):
preference = [[1, 2], [1, 2], [1, 2]]
# possible order: 1, 2
suggested_order = calculate_satisfactory_playlist(preference)
self.validate_result(preference, suggested_order)
def test_empty_graph(self):
self.assertEqual([], calculate_satisfactory_playlist([]))
def test_cyclic_graph(self):
preference = [[1, 2, 3], [1, 3, 2]]
self.assertIsNone(calculate_satisfactory_playlist(preference))
def test_acyclic_graph(self):
preference = [[1, 2], [2, 3], [1, 3, 5], [2, 5], [2, 4]]
# possible order: 1, 2, 3, 4, 5
suggested_order = calculate_satisfactory_playlist(preference)
self.validate_result(preference, suggested_order)
def test_disconnected_graph(self):
preference = [[0, 1], [2, 3], [3, 4]]
# possible order: 0, 1, 2, 3, 4
suggested_order = calculate_satisfactory_playlist(preference)
self.validate_result(preference, suggested_order)
def test_disconnected_graph2(self):
preference = [[0, 1], [2], [3]]
# possible order: 0, 1, 2, 3
suggested_order = calculate_satisfactory_playlist(preference)
self.validate_result(preference, suggested_order)
if __name__ == '__main__':
unittest.main(exit=False, verbosity=2)
Jan 14, 2021 LC 1344 [Easy] Angle between Clock Hands
Question: Given a clock time in
hh:mm
format, determine, to the nearest degree, the angle between the hour and the minute hands.
Example:
Input: "9:00"
Output: 90
Solution: https://repl.it/@trsong/Calculate-Angle-between-Clock-Hands
import unittest
def clock_angle(hhmm):
h, m = map(int, hhmm.split(':'))
h %= 12
unit_hr = 360 / 12.0
unit_min = 360 / 60.0
hr_hand = h * unit_hr + m * unit_hr / 60.0
min_hand = m * unit_min
angle = abs(min_hand - hr_hand)
return min(angle, 360 - angle)
class ClockAngleSpec(unittest.TestCase):
def test_minute_point_zero(self):
hhmm = "12:00"
angle = 0
self.assertEqual(angle, clock_angle(hhmm))
def test_minute_point_zero2(self):
hhmm = "1:00"
angle = 30
self.assertEqual(angle, clock_angle(hhmm))
def test_minute_point_zero3(self):
hhmm = "9:00"
angle = 90
self.assertEqual(angle, clock_angle(hhmm))
def test_minute_point_zero4(self):
hhmm = "6:00"
angle = 180
self.assertEqual(angle, clock_angle(hhmm))
def test_half_pass_hour(self):
hhmm = "12:30"
angle = 165
self.assertEqual(angle, clock_angle(hhmm))
def test_half_pass_hour2(self):
hhmm = "3:30"
angle = 75
self.assertEqual(angle, clock_angle(hhmm))
def test_irregular_time(self):
hhmm = "16:20"
angle = 10
self.assertEqual(angle, clock_angle(hhmm))
def test_irregular_time3(self):
hhmm = "3:15"
angle = 7.5
self.assertEqual(angle, clock_angle(hhmm))
if __name__ == '__main__':
unittest.main(exit=False, verbosity=2)
Jan 13, 2021 [Hard] Longest Common Subsequence of 3 Strings
Question: Write a program that computes the length of the longest common subsequence of three given strings.
For example, given
"epidemiologist"
,"refrigeration"
, and"supercalifragilisticexpialodocious"
, it should return5
, since the longest common subsequence is"eieio"
.
Solution with DP: https://repl.it/@trsong/Longest-Common-Subsequence-of-3-Strings
import unittest
def lcs(seq1, seq2, seq3):
n, m, k = len(seq1), len(seq2), len(seq3)
# Let dp[n][m][k] represents lcs for seq1[:n], seq2[:m], seq3[:k]
# dp[n][m][k] = dp[n-1][m-1][k-1] + 1 if all of following matches: seq[n-1], seq2[m-1], seq3[k-1]
# = max(dp[n-1][m][k], dp[n][m-1][k], dp[n][m][k-1]) otherwise
dp = [[[0 for _ in xrange(k + 1)] for _ in xrange(m + 1)] for _ in xrange(n + 1)]
for i in xrange(1, n + 1):
for j in xrange(1, m + 1):
for t in xrange(1, k + 1):
if seq1[i-1] == seq2[j-1] == seq3[t-1]:
dp[i][j][t] = dp[i-1][j-1][t-1] + 1
else:
dp[i][j][t] = max(dp[i-1][j][t], dp[i][j-1][t], dp[i][j][t-1])
return dp[n][m][k]
class LCSSpec(unittest.TestCase):
def test_empty_sequences(self):
self.assertEqual(0, lcs("", "", ""))
def test_example(self):
self.assertEqual(5, lcs(
"epidemiologist",
"refrigeration",
"supercalifragilisticexpialodocious")) # "eieio"
def test_match_last_position(self):
self.assertEqual(1, lcs("abcdz", "efghijz", "123411111z")) # z
def test_match_first_position(self):
self.assertEqual(1, lcs("aefgh", "aijklmnop", "a12314213")) # a
def test_off_by_one_position(self):
self.assertEqual(4, lcs("10101", "01010", "0010101")) # 0101
def test_off_by_one_position2(self):
self.assertEqual(3, lcs("12345", "1235", "2535")) # 235
def test_off_by_one_position3(self):
self.assertEqual(2, lcs("1234", "1243", "2431")) # 24
def test_off_by_one_position4(self):
self.assertEqual(4, lcs("12345", "12340", "102030400")) # 1234
def test_multiple_matching(self):
self.assertEqual(5, lcs("afbgchdie",
"__a__b_c__de___f_g__h_i___", "/a/b/c/d/e")) # abcde
def test_ascending_vs_descending(self):
self.assertEqual(1, lcs("01234", "_4__3___2_1_0__", "4_3_2_1_0")) # 0
def test_multiple_ascending(self):
self.assertEqual(5, lcs("012312342345", "012345", "0123401234")) # 01234
def test_multiple_descending(self):
self.assertEqual(5, lcs("54354354421", "5432432321", "54321")) # 54321
def test_same_length_strings(self):
self.assertEqual(2, lcs("ABCD", "EACB", "AFBC")) # AC
if __name__ == '__main__':
unittest.main(exit=False, verbosity=2)
Jan 12, 2021 LC 138 [Medium] Deepcopy List with Random Pointer
Question: A linked list is given such that each node contains an additional random pointer which could point to any node in the list or null.
Return a deep copy of the list.
Example:
Input:
{"$id":"1","next":{"$id":"2","next":null,"random":{"$ref":"2"},"val":2},"random":{"$ref":"2"},"val":1}
Explanation:
Node 1's value is 1, both of its next and random pointer points to Node 2.
Node 2's value is 2, its next pointer points to null and its random pointer points to itself.
Solution: https://repl.it/@trsong/Create-Deepcopy-List-with-Random
import unittest
import copy
def deep_copy(head):
if not head:
return None
# Step1: Duplicate node to every other position
p = head
while p:
p.next = ListNode(p.val, p.next)
p = p.next.next
# Step2: Copy random attribute
p = head
while p:
if p.random:
cur_copy = p.next
cur_copy.random = p.random.next
p = p.next.next
# Step3: Partition even and odd nodes to restore original list and get result
copy_head = head.next
p = head
while p:
if p.next.next:
cur_copy = p.next
cur_copy.next = p.next.next
p.next = p.next.next
p = p.next
return copy_head
#####################
# Testing Utiltities
#####################
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 not other:
return False
is_random_none = self.random is None and other.random is None
is_random_equal = self.random and other.random and self.random.val == other.random.val
is_random_valid = is_random_none or is_random_equal
return is_random_valid and self.val == other.val and self.next == other.next
def __repr__(self):
lst = []
random = []
p = self
while p:
lst.append(str(p.val))
if p.random:
random.append(str(p.random.val))
else:
random.append("N")
p = p.next
return "\nList: [{}].\nRandom: [{}]".format(','.join(lst), ','.join(random))
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
expected = copy.deepcopy(n)
self.assertEqual(expected, deep_copy(n))
self.assertEqual(expected, n)
def test_random_pointer_is_None(self):
# 1 -> 2 -> 3 -> 4
n4 = ListNode(4)
n3 = ListNode(3, n4)
n2 = ListNode(2, n3)
n1 = ListNode(1, n2)
expected = copy.deepcopy(n1)
self.assertEqual(expected, deep_copy(n1))
self.assertEqual(expected, n1)
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
expected = copy.deepcopy(n1)
self.assertEqual(expected, deep_copy(n1))
self.assertEqual(expected, 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
expected = copy.deepcopy(n1)
self.assertEqual(expected, deep_copy(n1))
self.assertEqual(expected, 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
expected = copy.deepcopy(n1)
self.assertEqual(expected, deep_copy(n1))
self.assertEqual(expected, n1)
def test_list_with_both_forward_and_backward_pointers2(self):
# 1 -> 2 -> 3 -> 4 -> 5 -> 6
n6 = ListNode(6)
n5 = ListNode(5, n6)
n4 = ListNode(4, n5)
n3 = ListNode(3, n4)
n2 = ListNode(2, n3)
n1 = ListNode(1, n2)
# random pointer:
# 1 -> 1
# 2 -> 1
# 3 -> 4
# 4 -> 5
# 5 -> 3
# 6 -> None
n1.random = n1
n2.random = n1
n3.random = n4
n4.random = n5
n5.random = n3
expected = copy.deepcopy(n1)
self.assertEqual(expected, deep_copy(n1))
self.assertEqual(expected, n1)
if __name__ == '__main__':
unittest.main(exit=False, verbosity=2)
Jan 11, 2021 [Easy] Spreadsheet Columns
Question: In many spreadsheet applications, the columns are marked with letters. From the 1st to the 26th column the letters are A to Z. Then starting from the 27th column it uses AA, AB, …, ZZ, AAA, etc.
Given a number n, find the n-th column name.
Examples:
Input Output
26 Z
51 AY
52 AZ
80 CB
676 YZ
702 ZZ
705 AAC
My thoughts: Map each digit to letter with value of digit - 1 won’t work. E.g. 1 - 1 = 0 -> 'A'
. But 27 - 1 = 26 -> "AB" != 'AA'
. The trick is to treat 'Z'
as zero while printing and treat it as 26 while doing calculations. e.g. 1 -> A. 2 -> B. 25 -> Y. 0 -> Z.
Solution: https://repl.it/@trsong/Find-Spreadsheet-Columns
import unittest
def spreadsheet_columns(n):
base = 26
res = []
while n > 0:
remainder = n % base
letter_index = (remainder - 1) % base
letter = chr(ord('A') + letter_index)
res.append(letter)
n //= base
if remainder == 0:
n -= 1
res.reverse()
return "".join(res)
class SpreadsheetColumnSpec(unittest.TestCase):
def test_trivial_example(self):
self.assertEqual("A", spreadsheet_columns(1))
def test_example1(self):
self.assertEqual("Z", spreadsheet_columns(26))
def test_example2(self):
self.assertEqual("AY", spreadsheet_columns(51))
def test_example3(self):
self.assertEqual("AZ", spreadsheet_columns(52))
def test_example4(self):
self.assertEqual("CB", spreadsheet_columns(80))
def test_example5(self):
self.assertEqual("YZ", spreadsheet_columns(676))
def test_example6(self):
self.assertEqual("ZZ", spreadsheet_columns(702))
def test_example7(self):
self.assertEqual("AAC", spreadsheet_columns(705))
if __name__ == '__main__':
unittest.main(verbosity=2, exit=False)
Jan 10, 2021 [Medium] Multiset Partition
Question: Given a multiset of integers, return whether it can be partitioned into two subsets whose sums are the same.
For example, given the multiset {15, 5, 20, 10, 35, 15, 10}, it would return true, since we can split it up into {15, 5, 10, 15, 10} and {20, 35}, which both add up to 55.
Given the multiset {15, 5, 20, 10, 35}, it would return false, since we can’t split it up into two subsets that add up to the same sum.
Solution with DP: https://repl.it/@trsong/Multiset-Partition-into-Equal-Sum
import unittest
def has_equal_sum_partition(multiset):
num_sum = sum(multiset)
return num_sum % 2 == 0 and has_subset_sum(multiset, num_sum // 2)
def has_subset_sum(nums, target):
n = len(nums)
# Let dp[target] represents if there exists a subset sum target
dp = [False] * (target + 1)
dp[0] = True
for i in xrange(1, n + 1):
cur_num = nums[i - 1]
for sum in xrange(target, cur_num - 1, -1):
dp[sum] |= dp[sum - cur_num]
return dp[target]
class HasEqualSumPartitionSpec(unittest.TestCase):
def test_example(self):
nums = [15, 5, 20, 10, 35, 15, 10]
# Partition into [15, 5, 10, 15, 10] and [20, 35]
self.assertTrue(has_equal_sum_partition(nums))
def test_example2(self):
nums = [15, 5, 20, 10, 35]
self.assertFalse(has_equal_sum_partition(nums))
def test_empty_sets(self):
nums = []
self.assertTrue(has_equal_sum_partition(nums))
def test_partition_set_has_same_elements(self):
nums = [5, 1, 5, 1]
self.assertTrue(has_equal_sum_partition(nums))
def test_unable_to_partition(self):
nums = [1, 2, 2]
self.assertFalse(has_equal_sum_partition(nums))
def test_has_distinct_elements(self):
nums = [1, 2, 4, 3, 8]
# Partition into [1, 8] and [2, 4, 3]
self.assertTrue(has_equal_sum_partition(nums))
if __name__ == '__main__':
unittest.main(exit=False, verbosity=2)
Jan 9, 2021 [Easy] Compare Version Numbers
Question: Version numbers are strings that are used to identify unique states of software products. A version number is in the format a.b.c.d. and so on where a, b, etc. are numeric strings separated by dots. These generally represent a hierarchy from major to minor changes.
Given two version numbers version1 and version2, conclude which is the latest version number. Your code should do the following:
- If version1 > version2 return 1.
- If version1 < version2 return -1.
- Otherwise return 0.
Note that the numeric strings such as a, b, c, d, etc. may have leading zeroes, and that the version strings do not start or end with dots. Unspecified level revision numbers default to 0.
Example 1:
Input:
version1 = "1.0.33"
version2 = "1.0.27"
Output: 1
#version1 > version2
Example 2:
Input:
version1 = "0.1"
version2 = "1.1"
Output: -1
#version1 < version2
Example 3:
Input:
version1 = "1.01"
version2 = "1.001"
Output: 0
#ignore leading zeroes, 01 and 001 represent the same number.
Example 4:
Input:
version1 = "1.0"
version2 = "1.0.0"
Output: 0
#version1 does not have a 3rd level revision number, which
defaults to "0"
Solution: https://repl.it/@trsong/Compare-Two-Version-Numbers
import unittest
def compare(v1, v2):
version_seq1 = map(int, (v1 or '0').split('.'))
version_seq2 = map(int, (v2 or '0').split('.'))
len1, len2 = len(version_seq1), len(version_seq2)
for i in xrange(max(len1, len2)):
num1 = version_seq1[i] if i < len1 else 0
num2 = version_seq2[i] if i < len2 else 0
if num1 < num2:
return -1
elif num1 > num2:
return 1
return 0
class VersionNumberCompareSpec(unittest.TestCase):
def test_example1(self):
version1 = "1.0.33"
version2 = "1.0.27"
self.assertEqual(1, compare(version1, version2))
def test_example2(self):
version1 = "0.1"
version2 = "1.1"
self.assertEqual(-1, compare(version1, version2))
def test_example3(self):
version1 = "1.01"
version2 = "1.001"
self.assertEqual(0, compare(version1, version2))
def test_example4(self):
version1 = "1.0"
version2 = "1.0.0"
self.assertEqual(0, compare(version1, version2))
def test_unspecified_version_numbers(self):
self.assertEqual(0, compare("", ""))
self.assertEqual(-1, compare("", "1"))
self.assertEqual(1, compare("2", ""))
def test_unaligned_zeros(self):
version1 = "00000.00000.00000.0"
version2 = "0.00000.000.00.00000.000.000.0"
self.assertEqual(0, compare(version1, version2))
def test_same_version_yet_unaligned(self):
version1 = "00001.001"
version2 = "1.000001.0000000.0000"
self.assertEqual(0, compare(version1, version2))
def test_different_version_numbers(self):
version1 = "1.2.3.4"
version2 = "1.2.3.4.5"
self.assertEqual(-1, compare(version1, version2))
def test_different_version_numbers2(self):
version1 = "3.2.1"
version2 = "3.1.2.3"
self.assertEqual(1, compare(version1, version2))
def test_different_version_numbers3(self):
version1 = "00001.001.0.1"
version2 = "1.000001.0000000.0000"
self.assertEqual(1, compare(version1, version2))
def test_without_dots(self):
version1 = "32123"
version2 = "3144444"
self.assertEqual(-1, compare(version1, version2))
if __name__ == '__main__':
unittest.main(exit=False, verbosity=2)
Jan 8, 2021 [Easy] Step Word Anagram
Question: A step word is formed by taking a given word, adding a letter, and anagramming the result. For example, starting with the word
"APPLE"
, you can add an"A"
and anagram to get"APPEAL"
.Given a dictionary of words and an input word, create a function that returns all valid step words.
Solution: https://repl.it/@trsong/Step-Word-Anagram
import unittest
def find_step_anagrams(word, dictionary):
word_histogram = generate_histogram(word)
step_anagram_distance = 1
is_step_anagram = lambda w: (
step_anagram_distance ==
len(w) - len(word) ==
histogram_distance_between(generate_histogram(w), word_histogram))
return filter(is_step_anagram, dictionary)
def generate_histogram(word):
histogram = {}
for ch in word:
histogram[ch] = histogram.get(ch, 0) + 1
return histogram
def histogram_distance_between(hst1, hst2):
dist = 0
for key in hst1.keys():
dist += max(0, hst1[key] - hst2.get(key, 0))
return dist
class FindStepAnagramSpec(unittest.TestCase):
def test_example(self):
word = 'APPLE'
dictionary = ['APPEAL', 'CAPPLE', 'PALPED']
expected = ['APPEAL', 'CAPPLE', 'PALPED']
self.assertEqual(sorted(expected), sorted(find_step_anagrams(word, dictionary)))
def test_empty_word(self):
word = ''
dictionary = ['A', 'B', 'AB', 'ABC']
expected = ['A', 'B']
self.assertEqual(sorted(expected), sorted(find_step_anagrams(word, dictionary)))
def test_empty_dictionary(self):
word = 'ABC'
dictionary = []
expected = []
self.assertEqual(sorted(expected), sorted(find_step_anagrams(word, dictionary)))
def test_no_match(self):
word = 'ABC'
dictionary = ['BBB', 'ACCC']
expected = []
self.assertEqual(sorted(expected), sorted(find_step_anagrams(word, dictionary)))
def test_no_match2(self):
word = 'AA'
dictionary = ['ABB']
expected = []
self.assertEqual(sorted(expected), sorted(find_step_anagrams(word, dictionary)))
def test_repeated_chars(self):
word = 'AAA'
dictionary = ['A', 'AA', 'AAA', 'AAAA', 'AAAAB', 'AAB', 'AABA']
expected = ['AAAA', 'AABA']
self.assertEqual(sorted(expected), sorted(find_step_anagrams(word, dictionary)))
if __name__ == '__main__':
unittest.main(exit=False, verbosity=2)
Jan 7, 2021 [Medium] Normalize Pathname
Question: Given an absolute pathname that may have
"."
or".."
as part of it, return the shortest standardized path.For example, given
"/usr/bin/../bin/./scripts/../"
, return"/usr/bin"
.
Solution with Stack: https://repl.it/@trsong/Normalize-to-Absolute-Pathname
import unittest
def normalize_pathname(path):
stack = []
for file in path.split('/'):
if not file or file == '.':
continue
elif stack and file == '..':
stack.pop()
else:
stack.append(file)
return '/' + '/'.join(stack)
class NormalizePathnameSpec(unittest.TestCase):
def test_example(self):
original_path = 'usr/bin/../bin/./scripts/../'
normalized_path = '/usr/bin'
self.assertEqual(normalized_path, normalize_pathname(original_path))
def test_empty_path(self):
original_path = ''
normalized_path = '/'
self.assertEqual(normalized_path, normalize_pathname(original_path))
def test_file_in_root_directory(self):
original_path = 'bin'
normalized_path = '/bin'
self.assertEqual(normalized_path, normalize_pathname(original_path))
def test_parent_of_root_dirctory(self):
original_path = '/a/b/..'
normalized_path = '/a'
self.assertEqual(normalized_path, normalize_pathname(original_path))
def test_parent_of_root_dirctory2(self):
original_path = '../../'
normalized_path = '/'
self.assertEqual(normalized_path, normalize_pathname(original_path))
def test_parent_of_root_dirctory3(self):
original_path = '../../../../a/'
normalized_path = '/a'
self.assertEqual(normalized_path, normalize_pathname(original_path))
def test_current_directory(self):
original_path = '.'
normalized_path = '/'
self.assertEqual(normalized_path, normalize_pathname(original_path))
def test_current_directory2(self):
original_path = './a/./b/c/././d/e/.'
normalized_path = '/a/b/c/d/e'
self.assertEqual(normalized_path, normalize_pathname(original_path))
def test_parent_of_current_directory(self):
original_path = 'a/b/c/././.././../'
normalized_path = '/a'
self.assertEqual(normalized_path, normalize_pathname(original_path))
def test_hidden_file(self):
original_path = './home/.bashrc'
normalized_path = '/home/.bashrc'
self.assertEqual(normalized_path, normalize_pathname(original_path))
def test_file_with_extention(self):
original_path = 'home/autorun.inf'
normalized_path = '/home/autorun.inf'
self.assertEqual(normalized_path, normalize_pathname(original_path))
def test_directory_with_dots(self):
original_path = 'home/work/com.myPythonProj.ui/src/../.git/'
normalized_path = '/home/work/com.myPythonProj.ui/.git'
self.assertEqual(normalized_path, normalize_pathname(original_path))
def test_parent_of_directory_with_dots(self):
original_path = 'home/work/com.myPythonProj.db.server.test/./.././com.myPythonProj.db.server/./src/./.git/../.git/.gitignore'
normalized_path = '/home/work/com.myPythonProj.db.server/src/.git/.gitignore'
self.assertEqual(normalized_path, normalize_pathname(original_path))
"""
A unix file system support consecutive slashes.
Consecutive slashes is equivalent to single slash.
eg. 'a//////b////c//' is equivalent to '/a/b/c'
"""
def test_consecutive_slashes(self):
original_path = 'a/.//////b///..//c//'
normalized_path = '/a/c'
self.assertEqual(normalized_path, normalize_pathname(original_path))
def test_consecutive_slashes2(self):
original_path = '///////'
normalized_path = '/'
self.assertEqual(normalized_path, normalize_pathname(original_path))
def test_consecutive_slashes3(self):
original_path = '/../..////..//././//.//../a/////'
normalized_path = '/a'
self.assertEqual(normalized_path, normalize_pathname(original_path))
def test_consecutive_slashes4(self):
original_path = '//////.a//////'
normalized_path = '/.a'
self.assertEqual(normalized_path, normalize_pathname(original_path))
if __name__ == '__main__':
unittest.main(exit=False, verbosity=2)
Jan 6, 2021 LC 692 [Medium] Top K Frequent words
Question: Given a non-empty list of words, return the k most frequent words. The output should be sorted from highest to lowest frequency, and if two words have the same frequency, the word with lower alphabetical order comes first. Input will contain only lower-case letters.
Example 1:
Input: ["i", "love", "leapcode", "i", "love", "coding"], k = 2
Output: ["i", "love"]
Explanation: "i" and "love" are the two most frequent words.
Note that "i" comes before "love" due to a lower alphabetical order.
Example 2:
Input: ["the", "day", "is", "sunny", "the", "the", "the", "sunny", "is", "is"], k = 4
Output: ["the", "is", "sunny", "day"]
Explanation: "the", "is", "sunny" and "day" are the four most frequent words,
with the number of occurrence being 4, 3, 2 and 1 respectively.
Solution with Priority Queue: https://repl.it/@trsong/Find-Top-K-Frequent-Elements
import unittest
from Queue import PriorityQueue
def top_k_freq_words(words, k):
histogram = {}
for word in words:
histogram[word] = histogram.get(word, 0) + 1
max_heap = PriorityQueue()
for word, count in histogram.items():
max_heap.put((-count, word))
res = []
for _ in xrange(k):
_, word = max_heap.get()
res.append(word)
return res
class TopKFreqWordSpec(unittest.TestCase):
def test_example1(self):
input = ["i", "love", "leapcode", "i", "love", "coding"]
k = 2
expected = ["i", "love"]
self.assertEqual(expected, top_k_freq_words(input, k))
def test_example2(self):
input = ["the", "day", "is", "sunny", "the", "the", "the", "sunny", "is", "is"]
k = 4
expected = ["the", "is", "sunny", "day"]
self.assertEqual(expected, top_k_freq_words(input, k))
def test_same_count_words(self):
input = ["c", "cb", "cba", "cdbaa"]
k = 3
expected = ["c", "cb", "cba"]
self.assertEqual(expected, top_k_freq_words(input, k))
def test_same_count_words2(self):
input = ["a", "c", "b", "d"]
k = 3
expected = ["a", "b", "c"]
self.assertEqual(expected, top_k_freq_words(input, k))
if __name__ == '__main__':
unittest.main(exit=False)
Jan 5, 2021 [Hard] Anagram to Integer
Question: You are given a string formed by concatenating several words corresponding to the integers
zero
throughnine
and then anagramming.For example, the input could be
'niesevehrtfeev'
, which is an anagram of'threefiveseven'
. Note that there can be multiple instances of each integer.Given this string, return the original integers in sorted order. In the example above, this would be
357
.
Solution with Backtracking: https://repl.it/@trsong/Convert-Anagram-to-Integer
import unittest
DIGITS = ['zero', 'one', 'two', 'three', 'four',
'five', 'six', 'seven', 'eight', 'nine']
def anagram_to_integer(s):
if not s:
return 0
input_freq = calculate_char_freq(s)
digit_freq_map = map(calculate_char_freq, DIGITS)
return int(backtrack([], 0, input_freq, digit_freq_map))
def calculate_char_freq(s):
char_freq = {}
for ch in s:
char_freq[ch] = char_freq.get(ch, 0) + 1
return char_freq
def backtrack(accu, digit_index, input_freq, digit_freq_map):
if digit_index >= len(DIGITS):
return None
elif not input_freq:
return "".join(accu)
else:
for digit in xrange(digit_index, len(DIGITS)):
max_repeats = float('inf')
for ch, ch_count in digit_freq_map[digit].items():
max_repeats = min(max_repeats, input_freq.get(ch, 0) // ch_count)
for repeats in xrange(max_repeats, 0, -1):
for ch, ch_count in digit_freq_map[digit].items():
input_freq[ch] -= ch_count * repeats
if input_freq[ch] == 0:
del input_freq[ch]
accu.append(str(digit) * repeats)
res = backtrack(accu, digit_index + 1, input_freq, digit_freq_map)
if res is not None:
return res
accu.pop()
for ch, ch_count in digit_freq_map[digit].items():
input_freq[ch] = input_freq.get(ch, 0) + ch_count * repeats
return None
class AnagramToIntegerSpec(unittest.TestCase):
def test_example(self):
s = 'niesevehrtfeev'
expected = 357
self.assertEqual(expected, anagram_to_integer(s))
def test_empty_string(self):
self.assertEqual(0, anagram_to_integer(''))
def test_contains_duplicate_characters(self):
s = 'nininene'
expected = 99
self.assertEqual(expected, anagram_to_integer(s))
def test_contains_duplicate_characters2(self):
s = 'twoonefourfourtwoone'
expected = 112244
self.assertEqual(expected, anagram_to_integer(s))
def test_char_in_sorted_order(self):
s = 'eeeffhioorrttuvw'
expected = 2345
self.assertEqual(expected, anagram_to_integer(s))
def test_zero(self):
s = 'zero'
expected = 0
self.assertEqual(expected, anagram_to_integer(s))
def test_should_omit_zero(self):
s = 'onetwothreefourfivesixseveneightnine'
expected = 123456789
self.assertEqual(expected, anagram_to_integer(s))
def test_unique_character(self):
s = 'oneoneoneone'
expected = 1111
self.assertEqual(expected, anagram_to_integer(s))
def test_one_not_exists(self):
s = 'twonine'
expected = 29
self.assertEqual(expected, anagram_to_integer(s))
if __name__ == '__main__':
unittest.main(exit=False, verbosity=2)
Jan 4, 2021 [Medium] Sort a K-Sorted Array
Question: You are given a list of
N
numbers, in which each number is located at mostk
places away from its sorted position. For example, ifk = 1
, a given element at index4
might end up at indices3
,4
, or5
.Come up with an algorithm that sorts this list in O(N log k) time.
Example:
Input: [3, 2, 6, 5, 4], k=2
Output: [2, 3, 4, 5, 6]
As seen above, every number is at most 2 indexes away from its proper sorted index.
Solution with Priority Queue: https://repl.it/@trsong/Sort-a-K-Sorted-Array
import unittest
from Queue import PriorityQueue
def sort_partial_list(nums, k):
pq = PriorityQueue()
n = len(nums)
for i in xrange(min(n, k + 1)):
pq.put(nums[i])
res = []
for i in xrange(k + 1, n):
res.append(pq.get())
pq.put(nums[i])
while not pq.empty():
res.append(pq.get())
return res
class SortPartialListSpec(unittest.TestCase):
def test_example(self):
k, nums = 2, [3, 2, 6, 5, 4]
self.assertEqual(sorted(nums), sort_partial_list(nums, k))
def test_empty_list(self):
self.assertEqual([], sort_partial_list([], 0))
self.assertEqual([], sort_partial_list([], 3))
def test_unsorted_list(self):
k, nums = 10, [8, 7, 6, 5, 3, 1, 2, 4]
self.assertEqual(sorted(nums), sort_partial_list(nums, k))
def test_k_is_3(self):
k, nums = 3, [6, 5, 3, 2, 8, 10, 9]
self.assertEqual(sorted(nums), sort_partial_list(nums, k))
def test_another_example_k_is_3(self):
k, nums = 3, [2, 6, 3, 12, 56, 8]
self.assertEqual(sorted(nums), sort_partial_list(nums, k))
def test_k_is_4(self):
k, nums = 4, [10, 9, 8, 7, 4, 70, 60, 50]
self.assertEqual(sorted(nums), sort_partial_list(nums, k))
def test_list_with_duplicated_values(self):
k, nums = 3, [3, 2, 2, 4, 4, 3]
self.assertEqual(sorted(nums), sort_partial_list(nums, k))
if __name__ == '__main__':
unittest.main(exit=False, verbosity=2)
Jan 3, 2021 [Medium] Detect Linked List Cycle
Question: Given a linked list, determine if the linked list has a cycle in it.
Example:
Input: 4 -> 3 -> 2 -> 1 -> 3 ...
Output: True
Solution with Fast and Slow Pointers: https://repl.it/@trsong/Detect-Linked-List-Cycle
import unittest
def contains_cycle(lst):
fast = slow = lst
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow == fast:
return True
return False
class ListNode(object):
def __init__(self, val, next=None):
self.val = val
self.next = next
@staticmethod
def List(intersect_index, vals):
dummy = p = ListNode(-1)
intersect_node = None
for val in vals:
p.next = ListNode(val)
p = p.next
if intersect_index == 0:
intersect_node = p
intersect_index -= 1
p.next = intersect_node
return dummy.next
class ContainsCycleSpec(unittest.TestCase):
def test_example(self):
lst = ListNode.List(intersect_index = 1, vals=[4, 3, 2, 1])
self.assertTrue(contains_cycle(lst))
def test_empty_list(self):
self.assertFalse(contains_cycle(None))
def test_list_with_self_pointing_node(self):
lst = ListNode.List(intersect_index = 0, vals=[1])
self.assertTrue(contains_cycle(lst))
def test_acyclic_list_with_duplicates(self):
lst = ListNode.List(intersect_index = -1, vals=[1, 1, 1, 1, 1, 1])
self.assertFalse(contains_cycle(lst))
def test_acyclic_list_with_duplicates2(self):
lst = ListNode.List(intersect_index = -1, vals=[1, 2, 3, 1, 2, 3])
self.assertFalse(contains_cycle(lst))
def test_cyclic_list_with_duplicates(self):
lst = ListNode.List(intersect_index = 0, vals=[1, 2, 3, 1, 2, 3])
self.assertTrue(contains_cycle(lst))
def test_cyclic_list(self):
lst = ListNode.List(intersect_index = 6, vals=[0, 1, 2, 3, 4, 5, 6, 7])
self.assertTrue(contains_cycle(lst))
if __name__ == '__main__':
unittest.main(exit=False)
Jan 2, 2021 [Easy] Determine If Linked List is Palindrome
Question: You are given a doubly linked list. Determine if it is a palindrome.
Solution: https://repl.it/@trsong/Determine-If-Linked-List-is-Palindrome
import unittest
def is_palindrome(lst):
if not lst:
return True
first = last = lst
length = 1
while last.next:
last = last.next
length += 1
for _ in xrange(length // 2):
if first.val != last.val:
return False
first = first.next
last = last.prev
return True
class ListNode(object):
def __init__(self, val, prev=None, next=None):
self.val = val
self.prev = prev
self.next = next
@staticmethod
def List(*vals):
dummy = cur = ListNode(-1)
for val in vals:
cur.next = ListNode(val, cur)
cur = cur.next
dummy.next.prev = None
return dummy.next
class IsPalindromeSpec(unittest.TestCase):
def test_empty_list(self):
self.assertTrue(is_palindrome(None))
def test_one_element_list(self):
self.assertTrue(is_palindrome(ListNode.List(42)))
def test_two_element_list(self):
self.assertFalse(is_palindrome(ListNode.List(1, 2)))
def test_two_element_palindrome(self):
self.assertTrue(is_palindrome(ListNode.List(6, 6)))
def test_three_element_list(self):
self.assertFalse(is_palindrome(ListNode.List(1, 2, 3)))
def test_three_element_list2(self):
self.assertFalse(is_palindrome(ListNode.List(1, 1, 2)))
def test_three_element_list3(self):
self.assertFalse(is_palindrome(ListNode.List(1, 2, 2)))
def test_three_element_palindrome(self):
self.assertTrue(is_palindrome(ListNode.List(1, 2, 1)))
def test_three_element_palindrome2(self):
self.assertTrue(is_palindrome(ListNode.List(1, 1, 1)))
def test_even_element_list(self):
self.assertFalse(is_palindrome(ListNode.List(1, 2, 3, 4, 2, 1)))
def test_even_element_list2(self):
self.assertTrue(is_palindrome(ListNode.List(1, 2, 3, 3, 2, 1)))
def test_odd_element_list(self):
self.assertTrue(is_palindrome(ListNode.List(1, 2, 3, 2, 1)))
def test_odd_element_list2(self):
self.assertFalse(is_palindrome(ListNode.List(1, 2, 3, 3, 1)))
if __name__ == '__main__':
unittest.main(exit=False, verbosity=2)
Jan 1, 2021 [Easy] Map Digits to Letters
Question: Given a mapping of digits to letters (as in a phone number), and a digit string, return all possible letters the number could represent. You can assume each valid number in the mapping is a single digit.
Example:
Input: {'2': ['a', 'b', 'c'], '3': ['d', 'e', 'f']}, '23'
Output: ['ad', 'ae', 'af', 'bd', 'be', 'bf', 'cd', 'ce', 'cf']
Soltion with Backtracking: https://repl.it/@trsong/Map-Digits-to-All-Possible-Letters
import unittest
def digits_to_letters(digits, dictionary):
if not digits:
return []
res = []
backtrack(res, [], digits, 0, dictionary)
return res
def backtrack(res, accu, digits, digit_index, dictionary):
if digit_index >= len(digits):
res.append(''.join(accu))
else:
for ch in dictionary[digits[digit_index]]:
accu.append(ch)
backtrack(res, accu, digits, digit_index + 1, dictionary)
accu.pop()
class DigitsToLetterSpec(unittest.TestCase):
def assert_letters(self, res, expected):
self.assertEqual(sorted(res), sorted(expected))
def test_empty_digits(self):
self.assert_letters(digits_to_letters("", {}), [])
def test_one_digit(self):
dictionary = {'2': ['a', 'b', 'c'], '3': ['d', 'e', 'f']}
self.assert_letters(
['a', 'b', 'c'],
digits_to_letters("2", dictionary))
def test_repeated_digits(self):
dictionary = {'2': ['a', 'b']}
self.assert_letters(
['aa', 'ab', 'ba', 'bb'],
digits_to_letters("22", dictionary))
def test_example(self):
dictionary = {'2': ['a', 'b', 'c'], '3': ['d', 'e', 'f']}
self.assert_letters(
['ad', 'ae', 'af', 'bd', 'be', 'bf', 'cd', 'ce', 'cf'],
digits_to_letters("23", dictionary))
def test_early_google_url(self):
dictionary = {'2': ['a', 'b', 'c'], '3': ['d', 'e', 'f'], '4': ['g', 'h', 'i'], '5': ['j', 'k', 'l'], '6': ['m', 'n', 'o']}
self.assertTrue('google' in digits_to_letters("466453", dictionary))
if __name__ == '__main__':
unittest.main(exit=False)
Dec 31, 2020 [Medium] Find Minimum Element in a Sorted and Rotated Array
Question: Suppose an array sorted in ascending order is rotated at some pivot unknown to you beforehand. Find the minimum element in
O(log N)
time. You may assume the array does not contain duplicates.For example, given
[5, 7, 10, 3, 4]
, return3
.
My thoughts: the idea of binary search is all about how to shrink searching domain into half. When mid element is smaller than right, always go left. Or else, there must be a rotation pivot between. In this case, go right.
Solution with Binary Search: https://repl.it/@trsong/Find-Minimum-Element-in-a-Sorted-and-Rotated-Array
import unittest
def find_min_index(nums):
if not nums:
return None
lo, hi = 0, len(nums) - 1
while lo < hi:
mid = lo + (hi - lo) // 2
if nums[mid] > nums[hi]:
lo = mid + 1
else:
hi = mid
return lo
class FindMinIndexSpec(unittest.TestCase):
def test_example(self):
self.assertEqual(3, find_min_index([5, 7, 10, 3, 4]))
def test_array_without_rotation(self):
self.assertIsNone(find_min_index([]))
def test_array_without_rotation2(self):
self.assertEqual(0, find_min_index([1, 2, 3]))
def test_array_without_rotation3(self):
self.assertEqual(0, find_min_index([1, 3]))
def test_array_with_one_rotation(self):
self.assertEqual(3, find_min_index([4, 5, 6, 1, 2, 3]))
def test_array_with_one_rotation2(self):
self.assertEqual(3, find_min_index([13, 18, 25, 2, 8, 10]))
def test_array_with_one_rotation3(self):
self.assertEqual(1, find_min_index([6, 1, 2, 3, 4, 5]))
def test_array_with_one_rotation4(self):
self.assertEqual(2, find_min_index([5, 6, 1, 2, 3, 4]))
if __name__ == '__main__':
unittest.main(exit=False, verbosity=2)
Dec 30, 2020 LC 77 [Medium] Combinations
Question: Given two integers
n
andk
, return all possible combinations ofk
numbers out of1 ... n
.
Example:
Input: n = 4, k = 2
Output:
[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]
Solution with Backtracking: https://repl.it/@trsong/Combinations
import unittest
def generate_combinations(n, k):
if k > n or k <= 0:
return []
res = []
nums = range(1, n + 1)
backtrack(res, [], 0, k, nums)
return res
def backtrack(res, chosen, current_index, k, nums):
n = len(nums)
if k == 0:
res.append(chosen[:])
elif n - current_index >= k:
# Case 1: choose current num
chosen.append(nums[current_index])
backtrack(res, chosen, current_index + 1, k - 1, nums)
chosen.pop()
# Case 2: not choose current num
backtrack(res, chosen, current_index + 1, k, nums)
class GenerateCombinationSpec(unittest.TestCase):
def test_example(self):
n, k = 4, 2
expected = [[2, 4], [3, 4], [2, 3], [1, 2], [1, 3], [1, 4]]
self.assertEqual(sorted(expected), generate_combinations(n, k))
def test_choose_zero(self):
n, k = 10, 0
expected = []
self.assertEqual(sorted(expected), generate_combinations(n, k))
def test_choose_one(self):
n, k = 6, 1
expected = [[1], [2], [3], [4], [5], [6]]
self.assertEqual(sorted(expected), generate_combinations(n, k))
def test_choose_all(self):
n, k = 6, 6
expected = [[1, 2, 3, 4, 5, 6]]
self.assertEqual(sorted(expected), generate_combinations(n, k))
def test_choose_three(self):
n, k = 4, 3
expected = [[1, 2, 3], [1, 2, 4], [1, 3, 4], [2, 3, 4]]
self.assertEqual(sorted(expected), generate_combinations(n, k))
def test_choose_more_than_supply(self):
n, k = 4, 10
expected = []
self.assertEqual(sorted(expected), generate_combinations(n, k))
if __name__ == '__main__':
unittest.main(verbosity=2, exit=False)
Dec 29, 2020 [Easy] Permutations
Question: Given a number in the form of a list of digits, return all possible permutations.
For example, given
[1,2,3]
, return[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
.
My thoughts: total number of permutations equals n * n-1 * n-2 * ... * 2 * 1
. So in Step 1: swap all n
number with index 0
. And in Step 2: swap the rest n - 1
numbers with index 1
… and so on.
For problem with size k
, we swap the n - k
th element with the result from problem with size n - k
.
Example:
Suppose the input is [0, 1, 2]
├ swap(0, 0)
│ ├ swap(1, 1)
│ │ └ swap(2, 2) gives [0, 1, 2]
│ └ swap(1, 2)
│ └ swap(2, 2) gives [0, 2, 1]
├ swap(0, 1)
│ ├ swap(1, 1)
│ │ └ swap(2, 2) gives [1, 0, 2]
│ └ swap(1, 2)
│ └ swap(2, 2) gives [1, 2, 0]
└ swap(0, 2)
├ swap(1, 1)
│ └ swap(2, 2) gives [2, 1, 0]
└ swap(1, 2)
└ swap(2, 2) gives [2, 0, 1]
Solution with Backtracking: https://repl.it/@trsong/Generate-Permutations
import unittest
def generate_permutations(nums):
res = []
backtrack(res, 0, nums)
return res
def backtrack(res, current_index, nums):
n = len(nums)
if current_index >= n :
res.append(nums[:])
else:
for i in xrange(current_index, n):
nums[current_index], nums[i] = nums[i], nums[current_index]
backtrack(res, current_index + 1, nums)
nums[current_index], nums[i] = nums[i], nums[current_index]
class CalculatePermutationSpec(unittest.TestCase):
def test_permuation_of_empty_array(self):
self.assertEqual( [[]], generate_permutations([]))
def test_permuation_of_2(self):
self.assertEqual(
sorted([[0, 1], [1, 0]]),
sorted(generate_permutations([0, 1])))
def test_permuation_of_3(self):
self.assertEqual(
sorted([[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]),
sorted(generate_permutations([1, 2, 3])))
if __name__ == '__main__':
unittest.main(exit=False)
Dec 28, 2020 [Medium] Index of Larger Next Number
Question: Given a list of numbers, for each element find the next element that is larger than the current element. Return the answer as a list of indices. If there are no elements larger than the current element, then use
-1
instead.
Example:
larger_number([3, 2, 5, 6, 9, 8])
# return [2, 2, 3, 4, -1, -1]
My thoughts: The idea is to iterate backwards and only store large element along the way with a stack. Doing such will mantain the stack in ascending order. We can then treat the stack as a history of larger element on the right. The algorithm work in the following way:
For each element, we push current element in stack. And the the same time, we pop all elements that are smaller than current element until we find a larger element that is the next larger element in the list.
Note that in worst case scenario, each element can only be pushed and poped from stack once, leaves the time complexity being O(n)
.
Solution with Stack: https://repl.it/@trsong/Find-the-Index-of-Larger-Next-Number
import unittest
def larger_number(nums):
stack = []
n = len(nums)
res = [-1] * n
for i in xrange(n - 1, -1, -1):
num = nums[i]
while stack and nums[stack[-1]] <= num:
stack.pop()
if stack:
res[i] = stack[-1]
stack.append(i)
return res
class LargerNumberSpec(unittest.TestCase):
def test_example(self):
nums = [3, 2, 5, 6, 9, 8]
expected = [2, 2, 3, 4, -1, -1]
self.assertEqual(expected, larger_number(nums))
def test_empty_list(self):
self.assertEqual([], larger_number([]))
def test_asecending_list(self):
nums = [0, 1, 2, 2, 3, 3, 3, 4, 5]
expected = [1, 2, 4, 4, 7, 7, 7, 8, -1]
self.assertEqual(expected, larger_number(nums))
def test_descending_list(self):
nums = [9, 8, 8, 7, 4, 3, 2, 1, 0, -1]
expected = [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1]
self.assertEqual(expected, larger_number(nums))
def test_up_down_up(self):
nums = [0, 1, 2, 1, 2, 3, 4, 5]
expected = [1, 2, 5, 4, 5, 6, 7, -1]
self.assertEqual(expected, larger_number(nums))
def test_up_down_up2(self):
nums = [0, 4, -1, 2]
expected = [1, -1, 3, -1]
self.assertEqual(expected, larger_number(nums))
def test_down_up_down(self):
nums = [9, 5, 6, 3]
expected = [-1, 2, -1, -1]
self.assertEqual(expected, larger_number(nums))
def test_up_down(self):
nums = [11, 21, 31, 3]
expected = [1, 2, -1, -1]
self.assertEqual(expected, larger_number(nums))
def test_random_order(self):
nums = [4, 3, 5, 2, 4, 7]
expected = [2, 2, 5, 4, 5, -1]
self.assertEqual(expected, larger_number(nums))
if __name__ == '__main__':
unittest.main(exit=False)
Dec 27, 2020 LC 218 [Hard] City Skyline
Question: Given a list of building in the form of
(left, right, height)
, return what the skyline should look like. The skyline should be in the form of a list of(x-axis, height)
, where x-axis is the point where there is a change in height starting from 0, and height is the new height starting from the x-axis.
Example:
Input: [(2, 8, 3), (4, 6, 5)]
Output: [(2, 3), (4, 5), (7, 3), (9, 0)]
Explanation:
2 2 2
2 2
1 1 2 1 2 1 1
1 2 2 1
1 2 2 1
pos: 0 1 2 3 4 5 6 7 8 9
We have two buildings: one has height 3 and the other 5. The city skyline is just the outline of combined looking.
The result represents the scanned height of city skyline from left to right.
Solution with Priority Queue: https://repl.it/@trsong/Find-City-Skyline
import unittest
from Queue import PriorityQueue
def scan_city_skyline(buildings):
bar_set = []
for left, right, _ in buildings:
# Critial positions are left end and right end + 1 of each building
bar_set.append(left)
bar_set.append(right + 1)
i = 0
res = []
prev_height = 0
max_heap = PriorityQueue()
for bar in sorted(bar_set):
while i < len(buildings):
# Add all buildings whose starts before the bar
left, right, height = buildings[i]
if left > bar:
break
max_heap.put((-height, right))
i += 1
while not max_heap.empty():
# Remove building that ends before the bar
_, right = max_heap.queue[0]
if right >= bar:
break
max_heap.get()
# We want to make sure we get max height of building and bar is between both ends
height = abs(max_heap.queue[0][0]) if not max_heap.empty() else 0
if height != prev_height:
res.append((bar, height))
prev_height = height
return res
class ScanCitySkylineSpec(unittest.TestCase):
def test_example(self):
"""
2 2 2
2 2
1 1 2 2 1 1
1 2 2 1
1 2 2 1
pos: 0 1 2 3 4 5 6 7 8 9
"""
buildings = [(2, 8, 3), (4, 6, 5)]
expected = [(2, 3), (4, 5), (7, 3), (9, 0)]
self.assertEqual(expected, scan_city_skyline(buildings))
def test_multiple_building_overlap(self):
buildings = [(2, 9, 10), (3, 7, 15), (5, 12, 12), (15, 20, 10), (19, 24, 8)]
expected = [(2, 10), (3, 15), (8, 12), (13, 0), (15, 10), (21, 8), (25, 0)]
self.assertEqual(expected, scan_city_skyline(buildings))
def test_empty_land(self):
self.assertEqual([], scan_city_skyline([]))
def test_length_one_building(self):
buildings = [(0, 0, 1), (0, 0, 10)]
self.assertEqual([(0, 10), (1, 0)], scan_city_skyline(buildings))
def test_upward_staircase(self):
"""
4 4 4 4 4
3 3 3 3 4
2 2 2 3 4
1 1 1 1 1 3 4
1 2 2 1 3 4
1 2 2 1 3 4
pos: 0 1 2 3 4 5 6 7 8 9
"""
buildings = [(1, 5, 3), (2, 4, 4), (3, 6, 5), (4, 8, 6)]
expected = [(1, 3), (2, 4), (3, 5), (4, 6), (9, 0)]
self.assertEqual(expected, scan_city_skyline(buildings))
def test_downward_staircase(self):
"""
1 1 1 1 1
1 1
1 2 2 2 2 2 2
1 2 3 3 3 3 3 3
1 2 3 1 2 3
pos: 0 1 2 3 4 5 6 7 8 9
"""
buildings = [(0, 4, 5), (1, 6, 3), (3, 8, 2)]
expected = [(0, 5), (5, 3), (7, 2), (9, 0)]
self.assertEqual(expected, scan_city_skyline(buildings))
def test_same_height_overlap_skyline(self):
"""
1 1 1 2 2 2 2 2 3 3 2 2 4 4 4
1 2 1 3 3 2 4 4
pos: 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6
"""
buildings = [(0, 4, 2), (3, 11, 2), (8, 9, 2), (13, 15, 2)]
expected = [(0, 2), (12, 0), (13, 2), (16, 0)]
self.assertEqual(expected, scan_city_skyline(buildings))
def test_non_overlap_sky_line(self):
"""
5 5 5
1 1 1 5 5
1 1 2 2 2 3 3 5 5
1 1 2 2 3 3 4 4 5 5
pos: 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7
"""
buildings = [(1, 3, 3), (4, 6, 2), (7, 8, 2), (11, 12, 1), (14, 16, 4)]
expected = [(1, 3), (4, 2), (9, 0), (11, 1), (13, 0), (14, 4), (17, 0)]
self.assertEqual(expected, scan_city_skyline(buildings))
def test_down_hill_and_up_hill(self):
"""
1 1 1 1 1 4 4
1 1 4 4
1 2 2 2 2 2 2 3 3 4 4 3
1 2 1 3 2 4 4 3
pos: 0 1 2 3 4 5 6 7 8 9 0 1 2 3
"""
buildings = [(1, 5, 4), (2, 8, 2), (7, 12, 2), (10, 11, 4)]
expected = [(1, 4), (6, 2), (10, 4), (12, 2), (13, 0)]
self.assertEqual(expected, scan_city_skyline(buildings))
def test_height_zero_buildings(self):
buildings = [(0, 1, 0), (0, 2, 0), (2, 11, 0), (4, 8, 0), (8, 11, 0), (11, 200, 0), (300, 400, 0)]
expected = []
self.assertEqual(expected, scan_city_skyline(buildings))
if __name__ == '__main__':
unittest.main(verbosity=2, exit=False)
Dec 26, 2020 [Medium] Largest Rectangular Area in a Histogram
Question: You are given a histogram consisting of rectangles of different heights. Determine the area of the largest rectangle that can be formed only from the bars of the histogram.
Example:
These heights are represented in an input list, such that [1, 3, 2, 5] corresponds to the following diagram:
x
x
x x
x x x
x x x x
For the diagram above, for example, this would be six, representing the 2 x 3 area at the bottom right.
Solution with Stack: https://repl.it/@trsong/Find-Largest-Rectangular-Area-in-a-Histogram
import unittest
def largest_rectangle_in_histogram(histogram):
stack = []
res = 0
n = len(histogram)
i = 0
while i < n or stack:
if not stack or i < n and histogram[stack[-1]] < histogram[i]:
# mantain stack in non-descending order
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_bound = stack[-1] if stack else -1 #
width = i - left_bound - 1
area = height * width
res = max(res, area)
return res
class LargestRectangleInHistogramSpec(unittest.TestCase):
def test_example(self):
"""
x
x
x x
X X X
x X X X
"""
histogram = [1, 3, 2, 5]
expected = 2 * 3
self.assertEqual(expected, largest_rectangle_in_histogram(histogram))
def test_empty_histogram(self):
self.assertEqual(0, largest_rectangle_in_histogram([]))
def test_width_one_rectangle(self):
"""
X
X
X
X
x X
x x X
x x x X
"""
histogram = [1, 3, 2, 7]
expected = 7 * 1
self.assertEqual(expected, largest_rectangle_in_histogram(histogram))
def test_ascending_sequence(self):
"""
x
x x
X X X
x X X X
"""
histogram = [0, 1, 2, 3, 4]
expected = 2 * 3
self.assertEqual(expected, largest_rectangle_in_histogram(histogram))
def test_descending_sequence(self):
"""
x
X X
X X x
X X x x
"""
histogram = [4, 3, 2, 1, 0]
expected = 3 * 2
self.assertEqual(expected, largest_rectangle_in_histogram(histogram))
def test_sequence3(self):
"""
x
x x x
X X X X X
X X X X X
X X X X X
"""
histogram = [3, 4, 5, 4, 3]
expected = 3 * 5
self.assertEqual(expected, largest_rectangle_in_histogram(histogram))
def test_sequence4(self):
"""
x x x x
x x x x
x x x x
x x x x
x x x x x
x x x x x
X X X X X X X
x X X X X X X X x
x X X X X X X X x
x X X X X X X X x
"""
histogram = [3, 10, 4, 10, 5, 10, 4, 10, 3]
expected = 4 * 7
self.assertEqual(expected, largest_rectangle_in_histogram(histogram))
def test_sequence5(self):
"""
x x
x x x x
x X X X x
x X X X x
x x X X X x
x x X X X x x
"""
histogram = [6, 2, 5, 4, 5, 1, 6]
expected = 4 * 3
self.assertEqual(expected, largest_rectangle_in_histogram(histogram))
if __name__ == '__main__':
unittest.main(exit=False, verbosity=2)
Dec 25, 2020 [Medium] Longest Consecutive Sequence in an Unsorted Array
Question: Given an array of integers, return the largest range, inclusive, of integers that are all included in the array.
For example, given the array
[9, 6, 1, 3, 8, 10, 12, 11]
, return(8, 12)
since8, 9, 10, 11, and 12
are all in the array.
My thoughts: We can use DFS to find max length of consecutive sequence. Two number are neighbor if they differ by 1. Thus for any number we can scan though its left-most and right-most neighbor recursively.
Solution with DFS: https://repl.it/@trsong/Longest-Consecutive-Sequence
import unittest
def longest_consecutive_seq(nums):
if not nums:
return None
num_set = set(nums)
max_len = 0
res = None
for center in nums:
if center not in num_set:
continue
num_set.remove(center)
lower = center - 1
higher = center + 1
while lower in num_set:
lower -= 1
while higher in num_set:
higher += 1
len = higher - lower - 1
if len > max_len:
res = (lower + 1, higher - 1)
max_len = len
return res
class LongestConsecutiveSeqSpec(unittest.TestCase):
def test_example(self):
nums = [9, 6, 1, 3, 8, 10, 12, 11]
expected = (8, 12)
self.assertEqual(expected, longest_consecutive_seq(nums))
def test_example2(self):
nums = [2, 10, 3, 12, 5, 4, 11, 8, 7, 6, 15]
expected = (2, 8)
self.assertEqual(expected, longest_consecutive_seq(nums))
def test_empty_array(self):
self.assertIsNone(longest_consecutive_seq([]))
def test_no_consecutive_sequence(self):
nums = [1, 3, 5, 7]
possible_solutions = [(1, 1), (3, 3), (5, 5), (7, 7)]
self.assertIn(longest_consecutive_seq(nums), possible_solutions)
def test_more_than_one_solution(self):
nums = [0, 3, 4, 5, 9, 10, 13, 14, 15, 19, 20, 1]
possible_solutions = [(3, 5), (13, 15)]
self.assertIn(longest_consecutive_seq(nums), possible_solutions)
def test_longer_array(self):
nums = [10, 21, 45, 22, 7, 2, 67, 19, 13, 45, 12, 11, 18, 16, 17, 100, 201, 20, 101]
expected = (16, 22)
self.assertEqual(expected, longest_consecutive_seq(nums))
def test_entire_array_is_continous(self):
nums = [0, 1, 2, 3, 4, 5]
expected = (0, 5)
self.assertEqual(expected, longest_consecutive_seq(nums))
def test_array_with_duplicated_numbers(self):
nums = [0, 0, 3, 3, 2, 2, 1, 4, 7, 8, 10]
expected = (0, 4)
self.assertEqual(expected, longest_consecutive_seq(nums))
if __name__ == '__main__':
unittest.main(exit=False, verbosity=2)
Dec 24, 2020 [Easy] Smallest Sum Not Subset Sum
Question: Given a sorted list of positive numbers, find the smallest positive number that cannot be a sum of any subset in the list.
Example:
Input: [1, 2, 3, 8, 9, 10]
Output: 7
Numbers 1 to 6 can all be summed by a subset of the list of numbers, but 7 cannot.
My thoughts: Suppose the array is sorted and all elements are positive, then max positive subset sum is the prefix_sum
of the array. Thus the min number subset sum cannot reach is prefix_sum + 1
.
We can prove above statement by Math Induction.
Case 1: We want to show that if each elem is less than prefix_sum
, the subset sum range from 0
to prefix_sum
of array:
- Base case: for empty array, subset sum max is
0
which equals prefix sum0
. - Inductive Hypothesis: for the
i-th element
, if i-th element smaller than prefix sum, then subset sum range is0
toprefix_sum[i]
ie. sum(nums[0..i]). - Induction Step: upon
i-th
step, the range is0
toprefix_sum[i]
. If the(i + 1)-th
elementnums[i + 1]
is within that range, then smaller subset sum is still0
. Largest subset sum isprefix_sum[i] + nums[i + 1]
which equalsprefix_sum[i + 1]
Case 2: If the i-th element is greater than prefix_sum
, then we can omit the result of element as there is a hole in that range.
Because the previous subset sum ranges from [0, prefix_sum]
, then for nums[i] > prefix_sum
, there is a hole that prefix sum + 1
cannot be covered with the introduction of new element.
As the max positive sum we can reach is prefix sum, the min positive subset sum we cannot reach is prefix sum + 1.
Solution with DP: https://repl.it/@trsong/Smallest-Sum-Not-Subset-Sum
import unittest
def smallest_non_subset_sum(nums):
# Initially sum has range [0, 1)
upper_bound = 1
for num in nums:
if num <= upper_bound:
# Previously sum has range [0, upper_bound),
# with introduction of num, that range becomes
# new range [0, upper_bound + num)
upper_bound += num
else:
return upper_bound
return upper_bound
class SmallestNonSubsetSumSpec(unittest.TestCase):
def test_example(self):
nums = [1, 2, 3, 8, 9, 10]
expected = 7
self.assertEqual(expected, smallest_non_subset_sum(nums))
def test_empty_array(self):
nums = []
expected = 1
self.assertEqual(expected, smallest_non_subset_sum(nums))
def test_first_num_not_one(self):
nums = [2]
expected = 1
self.assertEqual(expected, smallest_non_subset_sum(nums))
def test_array_with_duplicated_numbers(self):
nums = [1, 1, 1, 1, 1]
expected = 6
self.assertEqual(expected, smallest_non_subset_sum(nums))
def test_result_larger_than_sum_of_all(self):
nums = [1, 1, 3, 4]
expected = 10
self.assertEqual(expected, smallest_non_subset_sum(nums))
def test_result_larger_than_sum_of_all2(self):
nums = [1, 2, 3, 4, 5, 6]
expected = 22
self.assertEqual(expected, smallest_non_subset_sum(nums))
def test_result_smaller_than_max(self):
nums = [1, 3, 6, 10, 11, 15]
expected = 2
self.assertEqual(expected, smallest_non_subset_sum(nums))
def test_result_smaller_than_max2(self):
nums = [1, 2, 5, 10, 20, 40]
expected = 4
self.assertEqual(expected, smallest_non_subset_sum(nums))
if __name__ == '__main__':
unittest.main(exit=False, verbosity=2)
Dec 23, 2020 LT 879 [Medium] NBA Playoff Matches
Question: During the NBA playoffs, we always arrange the rather strong team to play with the rather weak team, like make the rank 1 team play with the rank nth team, which is a good strategy to make the contest more interesting. Now, you’re given n teams, and you need to output their final contest matches in the form of a string.
The n teams are given in the form of positive integers from 1 to n, which represents their initial rank. (Rank 1 is the strongest team and Rank n is the weakest team.) We’ll use parentheses () and commas , to represent the contest team pairing - parentheses () for pairing and commas , for partition. During the pairing process in each round, you always need to follow the strategy of making the rather strong one pair with the rather weak one.
We ensure that the input n can be converted into the form
2^k
, where k is a positive integer.
Example 1:
Input: 2
Output: "(1,2)"
Example 2:
Input: 4
Output: "((1,4),(2,3))"
Explanation:
In the first round, we pair the team 1 and 4, the team 2 and 3 together, as we need to make the strong team and weak team together.
And we got (1,4),(2,3).
In the second round, the winners of (1,4) and (2,3) need to play again to generate the final winner, so you need to add the paratheses outside them.
And we got the final answer ((1,4),(2,3)).
Example 3:
Input: 8
Output: "(((1,8),(4,5)),((2,7),(3,6)))"
Explanation:
First round: (1,8),(2,7),(3,6),(4,5)
Second round: ((1,8),(4,5)),((2,7),(3,6))
Third round: (((1,8),(4,5)),((2,7),(3,6)))
Solution: https://repl.it/@trsong/Print-NBA-Playoff-Matches
import unittest
def NBA_Playoff_Matches(n):
res = map(str, range(1, n + 1))
while n > 1:
for i in xrange(n):
res[i] = "(%s,%s)" % (res[i], res[n - i - 1])
n //= 2
return res[0]
class NBAPlayoffMatcheSpec(unittest.TestCase):
def test_2_teams(self):
expected = "(1,2)"
self.assertEqual(expected, NBA_Playoff_Matches(2))
def test_4_teams(self):
expected = "((1,4),(2,3))"
self.assertEqual(expected, NBA_Playoff_Matches(4))
def test_8_teams(self):
expected = "(((1,8),(4,5)),((2,7),(3,6)))"
self.assertEqual(expected, NBA_Playoff_Matches(8))
def test_16_teams(self):
# round1: (1, 16), (2, 15), (3, 14), (4, 13), (5, 12), (6, 11), (7, 10), (8, 9)
# round2: ((1, 16), (8, 9)), ((2, 15), (7, 10)), ((3, 14), (6, 11)), ((4, 13), (5, 12))
# round3: ((((1,16),(8,9)),((4,13),(5,12))),(((2,15),(7,10)),((3,14),(6,11))))
expected = "((((1,16),(8,9)),((4,13),(5,12))),(((2,15),(7,10)),((3,14),(6,11))))"
self.assertEqual(expected, NBA_Playoff_Matches(16))
if __name__ == '__main__':
unittest.main(verbosity=2, exit=False)
Dec 22, 2020 LC 301 [Hard] Remove Invalid Parentheses
Question: Remove the minimum number of invalid parentheses in order to make the input string valid. Return all possible results.
Note: The input string may contain letters other than the parentheses ( and ).
Example 1:
Input: "()())()"
Output: ["()()()", "(())()"]
Example 2:
Input: "(a)())()"
Output: ["(a)()()", "(a())()"]
Example 3:
Input: ")("
Output: [""]
My thoughts: What makes a string with parenthese invalid? There must be an index such that number of open parentheses is less than close parentheses or in the end all open and close parentheses are not equal. Now we can count how many parentheses are invalid so that we can remove those invalid ones during backtracking.
We can define: Number of invalid open is equal to total open - total close. Number of invalid close is equal to number of close exceed previous open.
During backtracking, each open and close could be invalid one, so give a try to remove those and that will decrese the invalid count and we can hope all the best that our solution works. If it works, ie. the final string is valid, then add to result, else backtrack.
How to avoid duplicates? For each candidate of invalid ones, we only remove the first one and skip the duplicates.
eg. ((()((())
We have 6
open and 3
close gives 6 - 3 = 3
invalid open and we have no invalid close.
((()((())
^^^
Removing any two of above gives same result
To avoid duplicate, we only remove first two:
((()((())
^^
Solution with Backtracking: https://repl.it/@trsong/Ways-to-Remove-Invalid-Parentheses
import unittest
def remove_invalid_parenthese(s):
invalid_open = invalid_close = 0
for ch in s:
if invalid_open == 0 and ch == ')':
invalid_close += 1
elif ch == '(':
invalid_open += 1
elif ch == ')':
invalid_open -= 1
res = []
backtrack(s, res, 0, invalid_open, invalid_close)
return res
def backtrack(s, res, next_index, invalid_open, invalid_close):
if invalid_open == invalid_close == 0:
if is_valid(s):
res.append(s)
else:
for i in xrange(next_index, len(s)):
if i > next_index and s[i] == s[i - 1]:
continue
elif s[i] == '(' and invalid_open > 0:
backtrack(s[:i] + s[i + 1:], res, i, invalid_open - 1,
invalid_close)
elif s[i] == ')' and invalid_close > 0:
backtrack(s[:i] + s[i + 1:], res, i, invalid_open,
invalid_close - 1)
def is_valid(s):
count = 0
for ch in s:
if count < 0:
return False
elif ch == '(':
count += 1
elif ch == ')':
count -= 1
return count == 0
class RemoveInvalidParentheseSpec(unittest.TestCase):
def assert_result(self, expected, output):
self.assertEqual(sorted(expected), sorted(output))
def test_example1(self):
input = "()())()"
expected = ["()()()", "(())()"]
self.assert_result(expected, remove_invalid_parenthese(input))
def test_example2(self):
input = "(a)())()"
expected = ["(a)()()", "(a())()"]
self.assert_result(expected, remove_invalid_parenthese(input))
def test_example3(self):
input = ")("
expected = [""]
self.assert_result(expected, remove_invalid_parenthese(input))
def test_valid_string1(self):
input = "(a)((b))(c)"
expected = ["(a)((b))(c)"]
self.assert_result(expected, remove_invalid_parenthese(input))
def test_empty_string(self):
input = ""
expected = [""]
self.assert_result(expected, remove_invalid_parenthese(input))
def test_unique_result1(self):
input = "(a)(((a)"
expected = ["(a)(a)"]
self.assert_result(expected, remove_invalid_parenthese(input))
def test_unique_result2(self):
input = "()))((()"
expected = ["()()"]
self.assert_result(expected, remove_invalid_parenthese(input))
def test_unique_result3(self):
input = "a))b))c)d"
expected = ["abcd"]
self.assert_result(expected, remove_invalid_parenthese(input))
def test_multiple_results(self):
input = "a(b(c(d)"
expected = ["a(bcd)", "ab(cd)", "abc(d)"]
self.assert_result(expected, remove_invalid_parenthese(input))
def test_multiple_results2(self):
input = "(a)b)c)d)"
expected = ["(a)bcd", "(ab)cd", "(abc)d", "(abcd)"]
self.assert_result(expected, remove_invalid_parenthese(input))
if __name__ == '__main__':
unittest.main(exit=False)
Dec 21, 2020 [Easy] Invalid Parentheses to Remove
Question: Given a string of parentheses, write a function to compute the minimum number of parentheses to be removed to make the string valid (i.e. each open parenthesis is eventually closed).
For example, given the string
"()())()"
, you should return1
. Given the string")("
, you should return2
, since we must remove all of them.
Solution: https://repl.it/@trsong/Count-Invalid-Parentheses-to-Remove
import unittest
def count_invalid_parentheses(input_str):
balance = 0
invalid = 0
for ch in input_str:
if ch == '(':
balance += 1
elif balance > 0:
balance -= 1
else:
invalid += 1
return balance + invalid
class CountInvalidParentheseSpec(unittest.TestCase):
def test_incomplete_parentheses(self):
self.assertEqual(1, count_invalid_parentheses("(()"))
def test_incomplete_parentheses2(self):
self.assertEqual(2, count_invalid_parentheses("(()("))
def test_overflown_close_parentheses(self):
self.assertEqual(2, count_invalid_parentheses("()))"))
def test_overflown_close_parentheses2(self):
self.assertEqual(2, count_invalid_parentheses(")()("))
def test_valid_parentheses(self):
self.assertEqual(0, count_invalid_parentheses("((()))"))
def test_valid_parentheses2(self):
self.assertEqual(0, count_invalid_parentheses("()()()"))
def test_valid_parentheses3(self):
self.assertEqual(0, count_invalid_parentheses("((())(()))"))
def test_valid_parentheses4(self):
self.assertEqual(0, count_invalid_parentheses(""))
if __name__ == '__main__':
unittest.main(verbosity=2, exit=False)
Dec 20, 2020 [Easy] Longest Consecutive 1s in Binary Representation
Question: Given an integer n, return the length of the longest consecutive run of 1s in its binary representation.
Example:
Input: 156
Output: 3
Exaplanation: 156 in binary is 10011100
My thoughts: For any number x
, bitwise AND itself with x << 1
eliminates last 1 for each consecutive 1’s:
10011100 &
10011100
= 000011000
And
11000 &
1100
= 010000
We can keep doing this until all 1’s are exhausted.
Solution: https://repl.it/@trsong/Longest-Consecutive-1s-in-Binary-Representation
import unittest
def count_longest_consecutive_ones(num):
count = 0
while num > 0:
num &= num << 1
count += 1
return count
class CountLongestConsecutiveOneSpec(unittest.TestCase):
def test_example(self):
expected, num = 3, 0b10011100
self.assertEqual(expected, count_longest_consecutive_ones(num))
def test_zero(self):
expected, num = 0, 0b0
self.assertEqual(expected, count_longest_consecutive_ones(num))
def test_one(self):
expected, num = 1, 0b1
self.assertEqual(expected, count_longest_consecutive_ones(num))
def test_every_other_one(self):
expected, num = 1, 0b1010101
self.assertEqual(expected, count_longest_consecutive_ones(num))
def test_all_ones(self):
expected, num = 5, 0b11111
self.assertEqual(expected, count_longest_consecutive_ones(num))
def test_should_return_longest(self):
expected, num = 4, 0b10110111011110111010101
self.assertEqual(expected, count_longest_consecutive_ones(num))
def test_consecutive_zeros(self):
expected, num = 2, 0b100010001100010010001001
self.assertEqual(expected, count_longest_consecutive_ones(num))
if __name__ == '__main__':
unittest.main(exit=False)
Dec 19, 2020 [Medium] Craft Sentence
Question: Write an algorithm to justify text. Given a sequence of words and an integer line length k, return a list of strings which represents each line, fully justified.
More specifically, you should have as many words as possible in each line. There should be at least one space between each word. Pad extra spaces when necessary so that each line has exactly length k. Spaces should be distributed as equally as possible, with the extra spaces, if any, distributed starting from the left.
If you can only fit one word on a line, then you should pad the right-hand side with spaces.
Each word is guaranteed not to be longer than k.
For example, given the list of words
["the", "quick", "brown", "fox", "jumps", "over", "the", "lazy", "dog"]
andk = 16
, you should return the following:
["the quick brown",
"fox jumps over",
"the lazy dog"]
Solution: https://repl.it/@trsong/Craft-Sentence-and-Adjust-Text-Width
import unittest
def craft_sentence(words, k):
buffer = []
res = []
sentence_size = 0
for word in words:
if sentence_size + len(word) > k:
sentence = craft_one_sentence(buffer, k)
res.append(sentence)
buffer = []
sentence_size = 0
buffer.append(word)
sentence_size += len(word) + 1
if buffer:
res.append(craft_one_sentence(buffer, k))
return res
def craft_one_sentence(words, k):
n = len(words)
if n == 1:
return words[0] + " " * (k - len(words[0]))
num_char = sum(map(len, words))
white_spaces = k - num_char
padding = white_spaces // (n - 1)
extra_padding = white_spaces % (n - 1)
res = [words[0]]
for i in xrange(1, n):
res.append(" " * padding)
if extra_padding > 0:
res.append(" ")
extra_padding -= 1
res.append(words[i])
return ''.join(res)
class CraftSentenceSpec(unittest.TestCase):
def test_fit_only_one_word(self):
k, words = 7, ["test", "same", "length", "string"]
expected = [
"test ",
"same ",
"length ",
"string "]
self.assertEqual(expected, craft_sentence(words, k))
def test_fit_only_one_word2(self):
k, words = 6, ["test", "same", "length", "string"]
expected = [
"test ",
"same ",
"length",
"string"]
self.assertEqual(expected, craft_sentence(words, k))
def test_no_padding(self):
k, words = 2, ["to", "be"]
expected = ["to", "be"]
self.assertEqual(expected, craft_sentence(words, k))
def test_fit_two_words(self):
k, words = 6, ["To", "be", "or", "not", "to", "be"]
expected = [
"To be",
"or not",
"to be"]
self.assertEqual(expected, craft_sentence(words, k))
def test_fit_two_words2(self):
k, words = 11, ["Greed", "is", "not", "good"]
expected = [
"Greed is",
"not good"]
self.assertEqual(expected, craft_sentence(words, k))
def test_fit_more_words(self):
k, words = 16, ["the", "quick", "brown", "fox", "jumps",
"over", "the", "lazy", "dog"]
expected = [
"the quick brown",
"fox jumps over",
"the lazy dog"]
self.assertEqual(expected, craft_sentence(words, k))
def test_longer_sentence(self):
k, words = 16, ["This", "is", "an", "example", "of", "text", "justification."]
expected = [
"This is an",
"example of text",
"justification. "]
self.assertEqual(expected, craft_sentence(words, k))
def test_longer_sentence2(self):
k, words = 16, ["What", "must", "be", "acknowledgment",
"shall", "be"]
expected = [
"What must be",
"acknowledgment ",
"shall be"]
self.assertEqual(expected, craft_sentence(words, k))
def test_longer_sentence3(self):
k, words = 20, ["Science", "is", "what", "we", "understand",
"well", "enough", "to", "explain", "to", "a", "computer.",
"Art", "is", "everything", "else", "we", "do"]
expected = [
"Science is what we",
"understand well",
"enough to explain to",
"a computer. Art is",
"everything else we",
"do "]
self.assertEqual(expected, craft_sentence(words, k))
if __name__ == '__main__':
unittest.main(verbosity=2, exit=False)
Dec 18, 2020 [Medium] Reverse Coin Change
Question: You are given an array of length
N
, where each elementi
represents the number of ways we can producei
units of change. For example,[1, 0, 1, 1, 2]
would indicate that there is only one way to make0, 2, or 3
units, and two ways of making4
units.Given such an array, determine the denominations that must be in use. In the case above, for example, there must be coins with value
2, 3, and 4
.
Thoughts: Thinking backwards. Given a base, we use dp[num] += dp[num - base_num] for all base_num in base
. Likewise, whenver we discover a base_num, dp[num] -= dp[num - base_num]
to eliminate base’s effect.
Solution with DP: https://repl.it/@trsong/Reverse-Coin-Change
import unittest
def reverse_coin_change(coin_ways):
if not coin_ways:
return []
n = len(coin_ways)
base = []
for base_num in xrange(1, n):
if coin_ways[base_num] == 0:
continue
base.append(base_num)
for num in xrange(n - 1, base_num - 1, -1):
coin_ways[num] -= coin_ways[num - base_num]
return base
class ReverseCoinChangeSpec(unittest.TestCase):
@staticmethod
def generate_coin_ways(base, size=None):
max_num = size if size is not None else max(base)
coin_ways = [0] * (max_num + 1)
coin_ways[0] = 1
for base_num in base:
for num in xrange(base_num, max_num + 1):
coin_ways[num] += coin_ways[num - base_num]
return coin_ways
def test_example(self):
coin_ways = [1, 0, 1, 1, 2]
# 0: 0
# 1:
# 2: one 2
# 3: one 3
# 4: two 2's or one 4
# Therefore: [2, 3, 4] as base produces above coin ways
expected = [2, 3, 4]
self.assertEqual(expected, reverse_coin_change(coin_ways))
def test_empty_input(self):
self.assertEqual([], reverse_coin_change([]))
def test_empty_base(self):
self.assertEqual([], reverse_coin_change([1, 0, 0, 0, 0, 0]))
def test_one_number_base(self):
coin_ways = [1, 1, 1, 1, 1, 1]
expected = [1]
self.assertEqual(expected, reverse_coin_change(coin_ways))
def test_prime_number_base(self):
# ReverseCoinChangeSpec.generate_coin_ways([2, 3, 5, 7], 10)
coin_ways = [1, 0, 1, 1, 1, 2, 2, 3, 3, 4, 5]
expected = [2, 3, 5, 7]
self.assertEqual(expected, reverse_coin_change(coin_ways))
def test_composite_base(self):
# ReverseCoinChangeSpec.generate_coin_ways([2, 4, 6], 10)
coin_ways = [1, 0, 1, 0, 2, 0, 3, 0, 5, 0, 6]
expected = [2, 4, 6, 8]
self.assertEqual(expected, reverse_coin_change(coin_ways))
def test_all_number_bases(self):
# ReverseCoinChangeSpec.generate_coin_ways([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
coin_ways = [1, 1, 2, 3, 5, 7, 11, 15, 22, 30, 42]
expected = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
self.assertEqual(expected, reverse_coin_change(coin_ways))
if __name__ == '__main__':
unittest.main(verbosity=2, exit=False)
Dec 17, 2020 LC 352 [Hard] Data Stream as Disjoint Intervals
Question: Given a data stream input of non-negative integers
a1, a2, ..., an, ...
, summarize the numbers seen so far as a list of disjoint intervals.For example, suppose the integers from the data stream are
1, 3, 7, 2, 6, ...
, then the summary will be:
[1, 1]
[1, 1], [3, 3]
[1, 1], [3, 3], [7, 7]
[1, 3], [7, 7]
[1, 3], [6, 7]
My thoughts: Using heap to store all intervals. When get intervals, pop element with smallest start time one by one, there are only 3 cases:
- Element Overlapping w/ previous interval, then we do nothing
- Element will update existing interval’s start or end time, then we update the interval
- Element will cause two intervals to merge.
Solution with Priority Queue: https://repl.it/@trsong/Print-Data-Stream-as-Disjoint-Intervals
import unittest
from Queue import PriorityQueue
class SummaryRanges(object):
def __init__(self):
self.min_heap = PriorityQueue()
self.num_set = set()
def add_num(self, val):
if val not in self.num_set:
self.num_set.add(val)
self.min_heap.put((val, [val, val]))
def get_intervals(self):
res = []
while not self.min_heap.empty():
_, interval = self.min_heap.get()
prev = res[-1] if res else None
if prev is None or prev[1] + 1 < interval[0]:
res.append(interval)
else:
prev[1] = max(prev[1], interval[1])
for interval in res:
self.min_heap.put((interval[0], interval))
return res
class SummaryRangesSpec(unittest.TestCase):
def test_sample(self):
sr = SummaryRanges()
sr.add_num(1)
self.assertEqual([[1, 1]], sr.get_intervals())
sr.add_num(3)
self.assertEqual([[1, 1], [3, 3]], sr.get_intervals())
sr.add_num(7)
self.assertEqual([[1, 1], [3, 3], [7, 7]], sr.get_intervals())
sr.add_num(2)
self.assertEqual([[1, 3], [7, 7]], sr.get_intervals())
sr.add_num(6)
self.assertEqual([[1, 3], [6, 7]], sr.get_intervals())
def test_none_overlapping(self):
sr = SummaryRanges()
sr.add_num(3)
sr.add_num(1)
sr.add_num(5)
self.assertEqual([[1, 1], [3, 3], [5, 5]], sr.get_intervals())
def test_val_in_existing_intervals(self):
sr = SummaryRanges()
sr.add_num(3)
sr.add_num(2)
sr.add_num(1)
sr.add_num(5)
sr.add_num(6)
sr.add_num(7)
self.assertEqual([[1, 3], [5, 7]], sr.get_intervals())
sr.add_num(6)
self.assertEqual([[1, 3], [5, 7]], sr.get_intervals())
def test_val_join_two_intervals(self):
sr = SummaryRanges()
sr.add_num(3)
sr.add_num(2)
sr.add_num(1)
sr.add_num(5)
sr.add_num(6)
self.assertEqual([[1, 3], [5, 6]], sr.get_intervals())
sr.add_num(4)
self.assertEqual([[1, 6]], sr.get_intervals())
if __name__ == '__main__':
unittest.main(verbosity=2, exit=False)
Dec 16, 2020 [Easy] Max and Min with Limited Comparisons
Question: Given a list of numbers of size
n
, wheren
is greater than3
, find the maximum and minimum of the list using less than2 * (n - 1)
comparisons.
Example:
Input: [3, 5, 1, 2, 4, 8]
Output: (1, 8)
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 Divide And Conquer: https://repl.it/@trsong/Find-the-Max-and-Min-with-Limited-Comparisons
import unittest
def find_min_max(nums):
if not nums:
return None
return find_min_max_recur(nums, 0, len(nums) - 1)
def find_min_max_recur(nums, i, j):
if j - i <= 1:
return (nums[i], nums[j]) if nums[i] < nums[j] else (nums[j], nums[i])
mid = i + (j - i) // 2
left_min, left_max = find_min_max_recur(nums, i, mid)
right_min, right_max = find_min_max_recur(nums, mid + 1, j)
return min(left_min, right_min), max(left_max, right_max)
class GetMinMaxSpec(unittest.TestCase):
def assert_find_min_max(self, nums):
min_val, max_val = min(nums), max(nums)
self.assertEqual((min_val, max_val), find_min_max(nums))
def test_empty_list(self):
self.assertIsNone(find_min_max([]))
def test_list_with_one_element(self):
self.assert_find_min_max([-1])
def test_list_with_two_elements(self):
self.assert_find_min_max([1, 2])
def test_increasing_list(self):
self.assert_find_min_max([1, 2, 3, 4])
def test_list_with_duplicated_element(self):
self.assert_find_min_max([-1, 1, -1, 1])
def test_long_list(self):
self.assert_find_min_max([1, 2, 3, 4, 5, 6, 7, 7, 6, 5, 4, 3, 2, 1])
if __name__ == '__main__':
unittest.main(exit=False)
Dec 15, 2020 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 Segment Tree: https://repl.it/@trsong/Mutable-Range-Sum-Query
import unittest
class RangeSumQuery(object):
def __init__(self, nums):
self.size = len(nums)
self.tree = RangeSumQuery.build_segment_tree(nums)
@staticmethod
def build_segment_tree(nums):
if not nums:
return []
n = len(nums)
tree = [0] * n + nums
for i in xrange(n-1, -1, -1):
tree[i] = tree[2 * i] + tree[2 * i + 1]
return tree
def update(self, i, val):
pos = i + self.size
self.tree[pos] = val
while pos > 0:
left = pos
right = pos
if pos % 2 == 0:
# pos is left child
right = pos + 1
else:
# pos is right child
left = pos - 1
# parent is updated after child is updated
self.tree[pos // 2] = self.tree[left] + self.tree[right]
pos //= 2
def range_sum(self, i, j):
# 1~7
# / \
# / \
# 1~3 4~7
# / \ / \
# 1 2~3 4~5 6~7
# / | | | / \
# 2 3 4 5 6 7
n = self.size
l, r = i + n, j + n
sum = 0
while l <= r:
# case1: suppose l is right child that represents 2~3, we sum value(2~3) then move l right to 4~5
# case2: suppose l is left child do nothing
if l % 2 == 1:
# l bound is right child
sum += self.tree[l]
l += 1
# case3: suppose r is right child that represents 6~7, do nothing
# case4: suppose r is left child that represents 4~5, we sum value(4~5) then move r left to 2~3
if r % 2 == 0:
# r bound is left child
sum += self.tree[r]
r -= 1
# Mov l and r to parent and continue
l //=2
r //=2
return sum
class RangeSumQuerySpec(unittest.TestCase):
def test_example(self):
rsq = RangeSumQuery([1, 3, 5])
self.assertEqual(rsq.range_sum(0, 2), 9)
rsq.update(1, 2)
self.assertEqual(rsq.range_sum(0, 2), 8)
def test_one_elem_array(self):
rsq = RangeSumQuery([8])
rsq.update(0, 2)
self.assertEqual(rsq.range_sum(0, 0), 2)
def test_update_all_elements(self):
req = RangeSumQuery([1, 4, 2, 3])
self.assertEqual(req.range_sum(0, 3), 10)
req.update(0, 0)
req.update(2, 0)
req.update(1, 0)
req.update(3, 0)
self.assertEqual(req.range_sum(0, 3), 0)
req.update(2, 1)
self.assertEqual(req.range_sum(0, 1), 0)
self.assertEqual(req.range_sum(1, 2), 1)
self.assertEqual(req.range_sum(2, 3), 1)
self.assertEqual(req.range_sum(3, 3), 0)
if __name__ == '__main__':
unittest.main(verbosity=2, exit=False)
Dec 14, 2020 LC 554 [Medium] Brick Wall
Question: A wall consists of several rows of bricks of various integer lengths and uniform height. Your goal is to find a vertical line going from the top to the bottom of the wall that cuts through the fewest number of bricks. If the line goes through the edge between two bricks, this does not count as a cut.
For example, suppose the input is as follows, where values in each row represent the lengths of bricks in that row:
[[3, 5, 1, 1],
[2, 3, 3, 2],
[5, 5],
[4, 4, 2],
[1, 3, 3, 3],
[1, 1, 6, 1, 1]]
The best we can we do here is to draw a line after the eighth brick, which will only require cutting through the bricks in the third and fifth row.
Given an input consisting of brick lengths for each row such as the one above, return the fewest number of bricks that must be cut to create a vertical line.
Solution: https://repl.it/@trsong/Min-Cut-of-Wall-Bricks
import unittest
def least_cut_bricks(wall):
histogram = {}
for row in wall:
pos = 0
for i in xrange(len(row) - 1):
pos += row[i]
histogram[pos] = histogram.get(pos, 0) + 1
max_pos = max(histogram.values()) if histogram else 0
return len(wall) - max_pos
class LeastCutBrickSpec(unittest.TestCase):
def test_example(self):
wall = [
[3, 5, 1, 1],
[2, 3, 3, 2],
[5, 5],
[4, 4, 2],
[1, 3, 3, 3],
[1, 1, 6, 1, 1]]
self.assertEqual(2, least_cut_bricks(wall)) # cut at col 8
def test_empty_wall(self):
self.assertEqual(0, least_cut_bricks([]))
def test_properly_align_all_bricks(self):
wall = [
[1, 2, 3],
[1, 2, 3],
[1, 2, 3]
]
self.assertEqual(0, least_cut_bricks(wall))
def test_one_column_properly_aligned(self):
wall = [
[1, 1, 1, 1],
[2, 2],
[1, 1, 2],
[2, 1, 1]
]
self.assertEqual(0, least_cut_bricks(wall)) # cut at col 2
def test_local_answer_is_not_optimal(self):
wall = [
[1, 1, 2, 1],
[3, 2],
[2, 3]
]
self.assertEqual(1, least_cut_bricks(wall)) # cut at col 2
if __name__ == '__main__':
unittest.main(verbosity=2, exit=False)
Dec 13, 2020 LC 240 [Medium] Search a 2D Matrix II
Question: Write an efficient algorithm that searches for a value in an m x n matrix. This matrix has the following properties:
- Integers in each row are sorted in ascending from left to right.
- Integers in each column are sorted in ascending from top to bottom.
Example:
Consider the following matrix:
[
[ 1, 4, 7, 11, 15],
[ 2, 5, 8, 12, 19],
[ 3, 6, 9, 16, 22],
[10, 13, 14, 17, 24],
[18, 21, 23, 26, 30]
]
Given target = 5, return True.
Given target = 20, return False.
My thoughts: This problem can be solved using Divide and Conquer. First we find a mid-point (mid of row and column point). We break the matrix into 4 sub-matrices: top-left, top-right, bottom-left, bottom-right. And notice the following properties:
- number in top-left matrix is strictly is less than mid-point
- number in bottom-right matrix is strictly greater than mid-point
- number in the other two could be greater or smaller than mid-point, we cannot say until find out
So each time when we find a mid-point in recursion, if target number is greater than mid-point then we can say that it cannot be in top-left matrix (property 1). So we check all other 3 matrices except top-left.
Or if the target number is smaller than mid-point then we can say it cannot be in bottom-right matrix (property 2). We check all other 3 matrices except bottom-right.
Therefore we have T(mn) = 3/4 * (mn/4) + O(1)
. By Master Theorem, the time complexity is O(log(mn)) = O(log(m) + log(n))
Solution with Divide and Conquer: https://repl.it/@trsong/Search-in-a-Sorted-2D-Matrix
import unittest
class Square(object):
def __init__(self, rlo, rhi, clo, chi):
self.rlo = rlo
self.rhi = rhi
self.clo = clo
self.chi = chi
def search_matrix(matrix, target):
if not matrix or not matrix[0]:
return False
n, m = len(matrix), len(matrix[0])
stack = [Square(0, n - 1, 0, m - 1)]
while stack:
sqr = stack.pop()
if sqr.rlo > sqr.rhi or sqr.clo > sqr.chi:
continue
rmid = sqr.rlo + (sqr.rhi - sqr.rlo) // 2
cmid = sqr.clo + (sqr.chi - sqr.clo) // 2
if matrix[rmid][cmid] == target:
return True
elif matrix[rmid][cmid] < target:
# target not in top left
bottom_left = Square(rmid + 1, sqr.rhi, sqr.clo, cmid)
right = Square(sqr.rlo, sqr.rhi, cmid + 1, sqr.chi)
stack.extend([bottom_left, right])
else:
# target not in bottom right
top_right = Square(sqr.rlo, rmid - 1, cmid, sqr.chi)
left = Square(sqr.rlo, sqr.rhi, sqr.clo, cmid - 1)
stack.extend([top_right, left])
return False
class SearchMatrixSpec(unittest.TestCase):
def test_empty_matrix(self):
self.assertFalse(search_matrix([], target=0))
self.assertFalse(search_matrix([[]], target=0))
def test_example(self):
matrix = [
[ 1, 4, 7,11,15],
[ 2, 5, 8,12,19],
[ 3, 6, 9,16,22],
[10,13,14,17,24],
[18,21,23,26,30]
]
self.assertTrue(search_matrix(matrix, target=5))
self.assertFalse(search_matrix(matrix, target=20))
def test_mid_less_than_top_right(self):
matrix = [
[ 1, 2, 3, 4, 5],
[ 6, 7, 8, 9,10],
[11,12,13,14,15],
[16,17,18,19,20],
[21,22,23,24,25]
]
self.assertTrue(search_matrix(matrix, target=5))
def test_mid_greater_than_top_right(self):
matrix = [
[5 , 6,10,14],
[6 ,10,13,18],
[10,13,18,19]
]
self.assertTrue(search_matrix(matrix, target=14))
def test_mid_less_than_bottom_right(self):
matrix = [
[1,4],
[2,5]
]
self.assertTrue(search_matrix(matrix, target=5))
def test_element_out_of_matrix_range(self):
matrix = [
[ 1, 4, 7,11,15],
[ 2, 5, 8,12,19],
[ 3, 6, 9,16,22],
[10,13,14,17,24],
[18,21,23,26,30]
]
self.assertFalse(search_matrix(matrix, target=-1))
self.assertFalse(search_matrix(matrix, target=31))
if __name__ == '__main__':
unittest.main(verbosity=2, exit=False)
Dec 12, 2020 [Hard] Construct Cartesian Tree from Inorder Traversal
Question: A Cartesian tree with sequence S is a binary tree defined by the following two properties:
- It is heap-ordered, so that each parent value is strictly less than that of its children.
- An in-order traversal of the tree produces nodes with values that correspond exactly to S.
Given a sequence S, construct the corresponding Cartesian tree.
Example:
Given the sequence [3, 2, 6, 1, 9], the resulting Cartesian tree would be:
1
/ \
2 9
/ \
3 6
My thoughts: The root of min heap is always the smallest element. In order to maintain the given inorder traversal order: we can find the min element and recursively build the tree based on subarray on the left and right.
Solution with Inorder Traversal: https://repl.it/@trsong/Construct-Cartesian-Tree-from-Inorder-Traversal
import unittest
def construct_cartesian_tree(nums):
return construct_cartesian_tree_recur(nums, 0, len(nums) - 1)
def construct_cartesian_tree_recur(nums, start, end):
if start > end:
return None
local_min, local_min_index = find_local_min_and_index(nums, start, end)
left_res = construct_cartesian_tree_recur(nums, start, local_min_index - 1)
right_res = construct_cartesian_tree_recur(nums, local_min_index + 1, end)
return TreeNode(local_min, left_res, right_res)
def find_local_min_and_index(nums, start, end):
min_val, min_index = nums[start], start
for i in xrange(start + 1, end + 1):
if nums[i] < min_val:
min_val = nums[i]
min_index = i
return min_val, min_index
#####################
# Testing Utilities
#####################
class TreeNode(object):
def __init__(self, val, left=None, right=None):
self.val = val
self.left = left
self.right = right
def __repr__(self):
stack = [(self, 0)]
res = []
while stack:
node, depth = stack.pop()
res.append("\n" + "\t" * depth)
if not node:
res.append("* None")
continue
res.append("* " + str(node.val))
for child in [node.right, node.left]:
stack.append((child, depth + 1))
return "\n" + "".join(res) + "\n"
def is_heap(self):
for child in [self.left, self.right]:
if child and (self.val > child.val or not child.is_heap()):
return False
return True
def traversal(self):
res = []
if self.left:
res.extend(self.left.traversal())
res.append(self.val)
if self.right:
res.extend(self.right.traversal())
return res
class ConstructCartesianTree(unittest.TestCase):
def assert_result(self, res, nums):
self.assertEqual(nums, res.traversal())
self.assertTrue(res.is_heap(), res)
def test_example(self):
"""
1
/ \
2 9
/ \
3 6
"""
nums = [3, 2, 6, 1, 9]
res = construct_cartesian_tree(nums)
self.assert_result(res, nums)
def test_example2(self):
"""
1
/ \
3 5
/ \ /
9 7 8
\
10
/ \
12 15
/ \
20 18
"""
nums = [9, 3, 7, 1, 8, 12, 10, 20, 15, 18, 5]
res = construct_cartesian_tree(nums)
self.assert_result(res, nums)
def test_empty_array(self):
self.assertEqual(None, construct_cartesian_tree([]))
def test_ascending_array(self):
"""
1
\
2
\
3
"""
nums = [1, 2, 3]
res = construct_cartesian_tree(nums)
self.assert_result(res, nums)
def test_descending_array(self):
"""
1
/
2
/
3
/
4
"""
nums = [1, 2, 3, 4]
res = construct_cartesian_tree(nums)
self.assert_result(res, nums)
def test_ascending_descending_array(self):
"""
1
/
1
\
2
\
2
/
3
"""
nums = [1, 2, 3, 2, 1]
res = construct_cartesian_tree(nums)
self.assert_result(res, nums)
def test_descending_ascending_array(self):
"""
1
/ \
2 2
/ \
3 3
"""
nums = [3, 2, 1, 2, 3]
res = construct_cartesian_tree(nums)
self.assert_result(res, nums)
if __name__ == '__main__':
unittest.main(exit=False)
Dec 11, 2020 [Hard] Find Next Sparse Number
Question: We say a number is sparse if there are no adjacent ones in its binary representation. For example,
21 (10101)
is sparse, but22 (10110)
is not.For a given input
N
, find the smallest sparse number greater than or equal toN
.Do this in faster than
O(N log N)
time.
My thoughts: Whenever we see sub-binary string 011
mark it as 100
and set all bit on the right to 0. eg. 100110
=> 101000
, 101101
=> 1000000
Solution: https://repl.it/@trsong/Find-the-Next-Spare-Number
import unittest
def next_sparse_number(num):
target_pattern = 0b011
window = 0b111
i = 0
last_adj = 0
while num >= (target_pattern << i):
if num & (window << i) == (target_pattern << i):
# Change window from 011 to 100
num ^= (window << i)
last_adj = i
i += 1
# Set all bits on the right to 0
num &= ~0 << last_adj
return num
class NextSparseNumberSpec(unittest.TestCase):
def test_example(self):
self.assertEqual(0b10101, next_sparse_number(0b10101))
def test_no_bit_is_set(self):
self.assertEqual(0, next_sparse_number(0))
def test_next_sparse_is_itself(self):
self.assertEqual(0b100, next_sparse_number(0b100))
def test_adjacent_bit_is_set(self):
self.assertEqual(0b1000, next_sparse_number(0b110))
def test_adjacent_bit_is_set2(self):
self.assertEqual(0b101000, next_sparse_number(0b100110))
def test_bit_shift_cause_another_bit_shift(self):
self.assertEqual(0b1000000, next_sparse_number(0b101101))
def test_complicated_number(self):
self.assertEqual(0b1010010000000, next_sparse_number(0b1010001011101))
def test_all_bit_is_one(self):
self.assertEqual(0b1000, next_sparse_number(0b111))
if __name__ == '__main__':
unittest.main(verbosity=2, exit=False)
Dec 10, 2020 [Easy] Distribute Bonuses
Question: MegaCorp wants to give bonuses to its employees based on how many lines of codes they have written. They would like to give the smallest positive amount to each worker consistent with the constraint that if a developer has written more lines of code than their neighbor, they should receive more money.
Given an array representing a line of seats of employees at MegaCorp, determine how much each one should get paid.
Example:
Input: [10, 40, 200, 1000, 60, 30]
Output: [1, 2, 3, 4, 2, 1].
Solution: https://repl.it/@trsong/Distribute-Bonuses
import unittest
def distribute_bonus(line_of_codes):
n = len(line_of_codes)
res = [1] * n
for i in xrange(1, n):
if line_of_codes[i] == line_of_codes[i-1]:
res[i] = max(res[i], res[i-1])
elif line_of_codes[i] > line_of_codes[i-1]:
res[i] = max(res[i], 1 + res[i-1])
j = n - 1 - i
if line_of_codes[j] == line_of_codes[j+1]:
res[j] = max(res[j], res[j+1])
elif line_of_codes[j] > line_of_codes[j+1]:
res[j] = max(res[j], 1 + res[j+1])
return res
class DistributeBonusSpec(unittest.TestCase):
def test_example(self):
line_of_codes = [10, 40, 200, 1000, 60, 30]
expected = [1, 2, 3, 4, 2, 1]
self.assertEqual(expected, distribute_bonus(line_of_codes))
def test_example2(self):
line_of_codes = [10, 40, 200, 1000, 900, 800, 30]
expected = [1, 2, 3, 4, 3, 2, 1]
self.assertEqual(expected, distribute_bonus(line_of_codes))
def test_empty_array(self):
line_of_codes = []
expected = []
self.assertEqual(expected, distribute_bonus(line_of_codes))
def test_one_employee(self):
line_of_codes = [42]
expected = [1]
self.assertEqual(expected, distribute_bonus(line_of_codes))
def test_two_employees(self):
line_of_codes = [99, 42]
expected = [2, 1]
self.assertEqual(expected, distribute_bonus(line_of_codes))
def test_reach_tie_on_lines_of_code(self):
line_of_codes = [3, 3, 3, 4, 4, 4, 2, 2, 2]
expected = [1, 1, 1, 2, 2, 2, 1, 1, 1]
self.assertEqual(expected, distribute_bonus(line_of_codes))
def test_reach_tie_on_lines_of_code2(self):
line_of_codes = [1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4]
expected = [1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4]
self.assertEqual(expected, distribute_bonus(line_of_codes))
def test_reach_tie_on_lines_of_code4(self):
line_of_codes = [4, 4, 3, 3, 2, 2, 1, 1]
expected = [4, 4, 3, 3, 2, 2, 1, 1]
self.assertEqual(expected, distribute_bonus(line_of_codes))
if __name__ == '__main__':
unittest.main(exit=False, verbosity=2)
Dec 9, 2020 [Hard] Power Supply to All Cities
Question: Given a graph of possible electricity connections (each with their own cost) between cities in an area, find the cheapest way to supply power to all cities in the area.
Example 1:
Input: cities = ['Vancouver', 'Richmond', 'Burnaby']
cost_btw_cities = [
('Vancouver', 'Richmond', 1),
('Vancouver', 'Burnaby', 1),
('Richmond', 'Burnaby', 2)]
Output: 2
Explanation:
Min cost to supply all cities is to connect the following cities with total cost 1 + 1 = 2:
(Vancouver, Burnaby), (Vancouver, Richmond)
Example 2:
Input: cities = ['Toronto', 'Mississauga', 'Waterloo', 'Hamilton']
cost_btw_cities = [
('Mississauga', 'Toronto', 1),
('Toronto', 'Waterloo', 2),
('Waterloo', 'Hamilton', 3),
('Toronto', 'Hamilton', 2),
('Mississauga', 'Hamilton', 1),
('Mississauga', 'Waterloo', 2)]
Output: 4
Explanation: Min cost to connect to all cities is 4:
(Toronto, Mississauga), (Toronto, Waterloo), (Mississauga, Hamilton)
My thoughts: This question is an undirected graph problem asking for total cost of minimum spanning tree. Both of the follwing algorithms can solve this problem: Kruskal’s and Prim’s MST Algorithm. First one keeps choosing edges whereas second one starts from connecting vertices. Either one will work.
Solution with Kruskal’s MST Algorithm: https://repl.it/@trsong/Design-Power-Supply-to-All-Cities
import unittest
class DisjointSet(object):
def __init__(self, size):
self.parent = range(size)
def find(self, p):
while self.parent[p] != p:
self.parent[p] = self.parent[self.parent[p]]
p = self.parent[p]
return p
def union(self, p1, p2):
root1 = self.find(p1)
root2 = self.find(p2)
if root1 != root2:
self.parent[root1] = root2
def is_connected(self, p1, p2):
return self.find(p1) == self.find(p2)
def min_cost_power_supply(cities, cost_btw_cities):
city_id_lookup = {city: index for index, city in enumerate(cities)}
uf = DisjointSet(len(cities))
cost_btw_cities.sort(key=lambda uvw: uvw[-1])
res = 0
for city1, city2, cost in cost_btw_cities:
id1, id2 = city_id_lookup[city1], city_id_lookup[city2]
if uf.is_connected(id1, id2):
continue
uf.union(id1, id2)
res += cost
return res
class MinCostPowerSupplySpec(unittest.TestCase):
def test_k3_graph(self):
cities = ['Vancouver', 'Richmond', 'Burnaby']
cost_btw_cities = [
('Vancouver', 'Richmond', 1),
('Vancouver', 'Burnaby', 1),
('Richmond', 'Burnaby', 2)
]
# (Vancouver, Burnaby), (Vancouver, Richmond)
self.assertEqual(2, min_cost_power_supply(cities, cost_btw_cities))
def test_k4_graph(self):
cities = ['Toronto', 'Mississauga', 'Waterloo', 'Hamilton']
cost_btw_cities = [
('Mississauga', 'Toronto', 1),
('Toronto', 'Waterloo', 2),
('Waterloo', 'Hamilton', 3),
('Toronto', 'Hamilton', 2),
('Mississauga', 'Hamilton', 1),
('Mississauga', 'Waterloo', 2)
]
# (Toronto, Mississauga), (Toronto, Waterloo), (Mississauga, Hamilton)
self.assertEqual(4, min_cost_power_supply(cities, cost_btw_cities))
def test_connected_graph(self):
cities = ['Shanghai', 'Nantong', 'Suzhou', 'Hangzhou', 'Ningbo']
cost_btw_cities = [
('Shanghai', 'Nantong', 1),
('Nantong', 'Suzhou', 1),
('Suzhou', 'Shanghai', 1),
('Suzhou', 'Hangzhou', 3),
('Hangzhou', 'Ningbo', 2),
('Hangzhou', 'Shanghai', 2),
('Ningbo', 'Shanghai', 2)
]
# (Shanghai, Nantong), (Shanghai, Suzhou), (Shanghai, Hangzhou), (Shanghai, Nantong)
self.assertEqual(6, min_cost_power_supply(cities, cost_btw_cities))
if __name__ == '__main__':
unittest.main(exit=False)
Dec 8, 2020 LC 743 [Medium] Network Delay Time
Question: A network consists of nodes labeled 0 to N. You are given a list of edges
(a, b, t)
, describing the timet
it takes for a message to be sent from nodea
to nodeb
. Whenever a node receives a message, it immediately passes the message on to a neighboring node, if possible.Assuming all nodes are connected, determine how long it will take for every node to receive a message that begins at node 0.
Example:
given N = 5, and the following edges:
edges = [
(0, 1, 5),
(0, 2, 3),
(0, 5, 4),
(1, 3, 8),
(2, 3, 1),
(3, 5, 10),
(3, 4, 5)
]
You should return 9, because propagating the message from 0 -> 2 -> 3 -> 4 will take that much time.
Solution with Dijkstra’s Algorithm: https://repl.it/@trsong/Find-Network-Delay-Time
import unittest
import sys
from Queue import PriorityQueue
def max_network_delay(times, nodes):
neighbors = [None] * (1 + nodes)
for u, v, t in times:
neighbors[u] = neighbors[u] or []
neighbors[u].append((v, t))
distance = [sys.maxint] * (1 + nodes)
pq = PriorityQueue()
pq.put((0, 0))
while not pq.empty():
cur_time, cur = pq.get()
if distance[cur] != sys.maxint:
continue
distance[cur] = cur_time
if neighbors[cur] is None:
continue
for nb, t in neighbors[cur]:
alt_time = distance[cur] + t
if distance[nb] > alt_time:
pq.put((alt_time, nb))
max_delay = max(distance)
return max_delay if max_delay != sys.maxint else -1
class MaxNetworkDelay(unittest.TestCase):
def test_example(self):
times = [
(0, 1, 5), (0, 2, 3), (0, 5, 4), (1, 3, 8),
(2, 3, 1), (3, 5, 10), (3, 4, 5)
]
self.assertEqual(9, max_network_delay(times, nodes=5)) # max path: 0 - 2 - 3 - 4
def test_discounted_graph(self):
self.assertEqual(-1, max_network_delay([], nodes=2))
def test_disconnected_graph2(self):
"""
0(start) 3
| |
v v
2 1
"""
times = [(0, 2, 1), (3, 1, 2)]
self.assertEqual(-1, max_network_delay(times, nodes=3))
def test_unreachable_node(self):
"""
1
|
v
2
|
v
0 (start)
|
v
3
"""
times = [(1, 2, 1), (2, 0, 2), (0, 3, 3)]
self.assertEqual(-1, max_network_delay(times, nodes=3))
def test_given_example(self):
"""
(start)
0 --> 3
| |
v v
1 2
"""
times = [(0, 1, 1), (0, 3, 1), (3, 2, 1)]
self.assertEqual(2, max_network_delay(times, nodes=3))
def test_exist_alternative_path(self):
"""
(start) 1
0 ---> 3
1 | \ 4 | 2
v \ v
2 -> 1
"""
times = [(0, 2, 1), (0, 3, 1), (0, 1, 4), (3, 1, 2)]
self.assertEqual(3, max_network_delay(times, nodes=3)) # max path: 0 - 3 - 1
def test_graph_with_cycle(self):
"""
(start)
0 --> 2
^ |
| v
1 <-- 3
"""
times = [(0, 2, 1), (2, 3, 1), (3, 1, 1), (1, 0, 1)]
self.assertEqual(3, max_network_delay(times, nodes=3)) # max path: 0 - 2 - 3
def test_multiple_paths(self):
"""
0 (start)
/|\
/ | \
1| 2| 3|
v v v
2 3 4
2| 3| 1|
v v v
5 6 1
"""
times = [(0, 2, 1), (0, 3, 2), (0, 4, 3), (2, 5, 2), (3, 6, 3), (4, 1, 1)]
self.assertEqual(5, max_network_delay(times, nodes=6)) # max path: 0 - 3 - 6
if __name__ == '__main__':
unittest.main(exit=False)
Dec 7, 2020 [Medium] Rearrange String with Repeated Characters
Question: Given a string with repeated characters, rearrange the string so that no two adjacent characters are the same. If this is not possible, return None.
For example, given
"aaabbc"
, you could return"ababac"
. Given"aaab"
, returnNone
.
My thoughts: This prblem is a special case of Rearrange String K Distance Apart. Just Greedily choose the character with max remaining number for each window size 2. If no such character satisfy return None instead.
Solution with Greedy Algorithm: https://repl.it/@trsong/Rearrange-String-with-Repeated-Characters
import unittest
from Queue import PriorityQueue
def rearrange_string(s):
histogram = {}
for ch in s:
histogram[ch] = histogram.get(ch, 0) + 1
max_heap = PriorityQueue()
for ch, count in histogram.items():
max_heap.put((-count, ch))
res = []
while not max_heap.empty():
remainings = []
for _ in xrange(2):
if max_heap.empty() and not remainings:
break
if max_heap.empty():
return None
neg_count, ch = max_heap.get()
res.append(ch)
if abs(neg_count) > 1:
remainings.append((ch, abs(neg_count) - 1))
for ch, count in remainings:
max_heap.put((-count, ch))
return "".join(res)
class RearrangeStringSpec(unittest.TestCase):
def assert_result(self, original):
res = rearrange_string(original)
self.assertEqual(sorted(original), sorted(res))
for i in xrange(1, len(res)):
self.assertNotEqual(res[i], res[i-1])
def test_example1(self):
# possible solution: ababac
self.assert_result("aaabbc")
def test_example2(self):
self.assertIsNone(rearrange_string("aaab"))
def test_example3(self):
# possible solution: ababacdc
self.assert_result("aaadbbcc")
def test_unable_to_rearrange(self):
self.assertIsNone(rearrange_string("aaaaaaaaa"))
def test_unable_to_rearrange2(self):
self.assertIsNone(rearrange_string("121211"))
def test_empty_input_string(self):
self.assert_result("")
def test_possible_to_arrange(self):
# possible solution: ababababab
self.assert_result("aaaaabbbbb")
def test_possible_to_arrange2(self):
# possible solution: 1213141
self.assert_result("1111234")
def test_possible_to_arrange3(self):
# possible solution: 1212
self.assert_result("1122")
if __name__ == '__main__':
unittest.main(verbosity=2, exit=False)
Dec 6, 2020 [Easy] Implement Prefix Map Sum
Question: Implement a PrefixMapSum class with the following methods:
insert(key: str, value: int)
: Set a given key’s value in the map. If the key already exists, overwrite the value.sum(prefix: str)
: Return the sum of all values of keys that begin with a given prefix.
Example:
mapsum.insert("columnar", 3)
assert mapsum.sum("col") == 3
mapsum.insert("column", 2)
assert mapsum.sum("col") == 5
Solution with Trie: https://repl.it/@trsong/Implement-Prefix-Map-Sum
import unittest
class Trie(object):
def __init__(self):
self.count = 0
self.children = None
class PrefixMap(object):
def __init__(self):
self.root = Trie()
self.record = {}
def insert(self, word, val):
updated_val = val - self.record.get(word, 0)
self.record[word] = val
p = self.root
for ch in word:
p.count += updated_val
p.children = p.children or {}
if ch not in p.children:
p.children[ch] = Trie()
p = p.children[ch]
p.count += updated_val
def sum(self, word):
p = self.root
for ch in word:
if not p or not p.children:
return 0
p = p.children.get(ch, None)
return p.count if p else 0
class PrefixMapSpec(unittest.TestCase):
def test_example(self):
prefix_map = PrefixMap()
prefix_map.insert("columnar", 3)
self.assertEqual(3, prefix_map.sum("col"))
prefix_map.insert("column", 2)
self.assertEqual(5, prefix_map.sum("col"))
def test_empty_map(self):
prefix_map = PrefixMap()
self.assertEqual(0, prefix_map.sum(""))
self.assertEqual(0, prefix_map.sum("unknown"))
def test_same_prefix(self):
prefix_map = PrefixMap()
prefix_map.insert("a", 1)
prefix_map.insert("aa", 2)
prefix_map.insert("aaa", 3)
self.assertEqual(0, prefix_map.sum("aaaa"))
self.assertEqual(3, prefix_map.sum("aaa"))
self.assertEqual(5, prefix_map.sum("aa"))
self.assertEqual(6, prefix_map.sum("a"))
self.assertEqual(6, prefix_map.sum(""))
def test_same_prefix2(self):
prefix_map = PrefixMap()
prefix_map.insert("aa", 1)
prefix_map.insert("a", 2)
prefix_map.insert("b", 1)
self.assertEqual(0, prefix_map.sum("aaa"))
self.assertEqual(1, prefix_map.sum("aa"))
self.assertEqual(3, prefix_map.sum("a"))
self.assertEqual(4, prefix_map.sum(""))
def test_double_prefix(self):
prefix_map = PrefixMap()
prefix_map.insert("abc", 1)
prefix_map.insert("abd", 2)
prefix_map.insert("abzz", 1)
prefix_map.insert("bazz", 1)
self.assertEqual(4, prefix_map.sum("ab"))
self.assertEqual(0, prefix_map.sum("abq"))
self.assertEqual(4, prefix_map.sum("a"))
self.assertEqual(1, prefix_map.sum("b"))
def test_update_value(self):
prefix_map = PrefixMap()
prefix_map.insert('a', 1)
prefix_map.insert('ab', 1)
prefix_map.insert('abc', 1)
prefix_map.insert('ab', 100)
self.assertEqual(102, prefix_map.sum('a'))
if __name__ == '__main__':
unittest.main(verbosity=2, exit=False)
Dec 5, 2020 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.
Solution: https://repl.it/@trsong/Calculate-H-Index
import unittest
def calculate_h_index(citations):
citations.sort(reverse=True)
count = 0
for num in citations:
if num <= count:
return count
count += 1
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_h_index_appear_once(self):
self.assertEqual(5, calculate_h_index([5, 10, 10, 10, 10]))
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)
Dec 4, 2020 [Hard] Sliding Puzzle
Question: An 8-puzzle is a game played on a 3 x 3 board of tiles, with the ninth tile missing. The remaining tiles are labeled 1 through 8 but shuffled randomly. Tiles may slide horizontally or vertically into an empty space, but may not be removed from the board.
Design a class to represent the board, and find a series of steps to bring the board to the state
[[1, 2, 3], [4, 5, 6], [7, 8, None]]
.
Solution with A-Star Search: https://repl.it/@trsong/Solve-Sliding-Puzzle
import unittest
from copy import deepcopy
from Queue import PriorityQueue
class SlidingPuzzle(object):
BLANK_VALUE = 9
GOAL_STATE = [
[1, 2, 3],
[4, 5, 6],
[7, 8, None]
]
DIRECTIONS = [(-1, 0), (1, 0), (0, 1), (0, -1)]
@staticmethod
def solve(grid):
"""
Given a grid and returns a sequence of moves to reach to goal state
"""
pq = PriorityQueue()
end_hash = SlidingPuzzle.hash(SlidingPuzzle.GOAL_STATE)
start_hash = SlidingPuzzle.hash(grid)
visited = set()
pq.put((0, start_hash))
prev_states = {start_hash: (None, None)}
actual_cost = {start_hash: 0}
while not pq.empty():
_, cur_hash = pq.get()
if cur_hash == end_hash:
break
if cur_hash in visited:
continue
visited.add(cur_hash)
for neihbor_hash, neighbor_heuristic, move in SlidingPuzzle.neighbor_costs(cur_hash):
if neihbor_hash in visited:
continue
actual_cost[neihbor_hash] = actual_cost[cur_hash] + 1
pq.put((actual_cost[neihbor_hash] + neighbor_heuristic, neihbor_hash))
prev_states[neihbor_hash] = (cur_hash, move)
moves = []
while end_hash:
prev_hash, move = prev_states[end_hash]
if move:
moves.append(move)
end_hash = prev_hash
moves.reverse()
return moves
@staticmethod
def neighbor_costs(grid_hash):
"""
Given a grid hash, returns next grid's hash value, cost and move
"""
blank_row, blank_col = 0, 0
grid = [[None for _ in xrange(3)] for _ in xrange(3)]
for r in xrange(2, -1, -1):
for c in xrange(2, -1, -1):
grid[r][c] = grid_hash % 10
grid_hash //= 10
if grid[r][c] == SlidingPuzzle.BLANK_VALUE:
grid[r][c] = None
blank_row, blank_col = r, c
for dr, dc in SlidingPuzzle.DIRECTIONS:
new_r, new_c = blank_row + dr, blank_col + dc
if 0 <= new_r < 3 and 0 <= new_c < 3:
move = grid[new_r][new_c]
grid[new_r][new_c] = None
grid[blank_row][blank_col] = move
yield SlidingPuzzle.hash(grid), SlidingPuzzle.heuristc(grid), move
grid[new_r][new_c] = move
grid[blank_row][blank_col] = None
@staticmethod
def hash(grid):
"""
Flatten grid and then convert to an integer
"""
res = 0
for row in grid:
for num in row:
# treat None as 9
res = res * 10 + (num or SlidingPuzzle.BLANK_VALUE)
return res
@staticmethod
def heuristc(grid):
"""
Estimation of remaining cost based on current grid
"""
cost = 0
for r in xrange(3):
for c in xrange(3):
# treat None as 9
num = grid[r][c] or SlidingPuzzle.BLANK_VALUE
expected_r = (num - 1) // 3
expected_c = (num - 1) % 3
cost += abs(r - expected_r) + abs(c - expected_c)
return cost
class SlidingPuzzleSpec(unittest.TestCase):
###################
# Testing Utility
###################
@staticmethod
def validate(grid, steps):
blank_row = map(lambda row: None in row, grid).index(True)
blank_col = grid[blank_row].index(None)
for num in steps:
for dr, dc in SlidingPuzzle.DIRECTIONS:
new_r, new_c = blank_row + dr, blank_col + dc
if 0 <= new_r < 3 and 0 <= new_c < 3 and grid[new_r][new_c] == num:
grid[blank_row][blank_col] = grid[new_r][new_c]
grid[new_r][new_c] = None
blank_row = new_r
blank_col = new_c
break
return grid == SlidingPuzzle.GOAL_STATE
def assert_result(self, grid):
user_grid = deepcopy(grid)
steps = SlidingPuzzle.solve(user_grid)
self.assertTrue(SlidingPuzzleSpec.validate(grid, steps), user_grid)
def test_heuristic_function_should_not_overestimate(self):
# Optimial solution: [1, 2, 3, 6, 5, 4, 7, 8]
self.assert_result([
[None, 1, 2],
[5, 6, 3],
[4, 7, 8],
])
def test_random_grid(self):
# Optimial solution: [7, 4, 5, 6, 2, 5, 6, 1, 4, 7, 8, 6, 5, 2, 1, 4, 7, 8]
self.assert_result([
[6, 2, 3],
[5, 4, 8],
[1, 7, None]
])
def test_random_grid2(self):
# Optimial solution: [3, 6, 8, 4, 7, 5, 2, 1, 4, 7, 5, 3, 6, 8, 7, 4, 1, 2, 3, 6]
self.assert_result([
[1, 2, 5],
[8, 4, 7],
[6, 3, None]
])
if __name__ == '__main__':
unittest.main(exit=False)
Dec 3, 2020 [Hard] Knight’s Tour Problem
Question: A knight’s tour is a sequence of moves by a knight on a chessboard such that all squares are visited once.
Given N, write a function to return the number of knight’s tours on an N by N chessboard.
Solution with Backtracking: https://repl.it/@trsong/Knights-Tour-Problem
import unittest
KNIGHT_MOVES = [
(1, 2), (2, 1),
(-1, 2), (-2, 1),
(-1, -2), (-2, -1),
(1, -2), (2, -1)
]
def knights_tours(n):
if n == 0:
return 0
grid = [[False for _ in xrange(n)] for _ in xrange(n)]
count = 0
for r in xrange(n):
for c in xrange(n):
grid[r][c] = True
count += backtrack(1, grid, (r, c))
grid[r][c] = False
return count
def backtrack(step, grid, pos):
n = len(grid)
if step == n * n:
return 1
else:
count = 0
r, c = pos
for dr, dc in KNIGHT_MOVES:
new_r, new_c = r + dr, c + dc
if 0 <= new_r < n and 0 <= new_c < n and not grid[new_r][new_c]:
grid[new_r][new_c] = True
count += backtrack(step + 1, grid, (new_r, new_c))
grid[new_r][new_c] = False
return count
class KnightsTourSpec(unittest.TestCase):
"""
Kngiths Tours answer adapts from wiki: https://en.wikipedia.org/wiki/Knight%27s_tour
"""
def test_size_zero_grid(self):
self.assertEqual(0, knights_tours(0))
def test_size_one_grid(self):
self.assertEqual(1, knights_tours(1))
def test_size_two_grid(self):
self.assertEqual(0, knights_tours(2))
def test_size_three_grid(self):
self.assertEqual(0, knights_tours(3))
def test_size_four_grid(self):
self.assertEqual(0, knights_tours(4))
# Long running execution: took 235 sec
def test_size_five_grid(self):
self.assertEqual(1728, knights_tours(5))
if __name__ == '__main__':
unittest.main(exit=False)
Dec 2, 2020 LC 127 [Medium] Word Ladder
Question: Given a
start
word, anend
word, and a dictionary of valid words, find the shortest transformation sequence fromstart
toend
such that only one letter is changed at each step of the sequence, and each transformed word exists in the dictionary. If there is no possible transformation, return null. Each word in the dictionary have the same length as start and end and is lowercase.For example, given
start = "dog"
,end = "cat"
, anddictionary = {"dot", "dop", "dat", "cat"}
, return["dog", "dot", "dat", "cat"]
.Given
start = "dog"
,end = "cat"
, anddictionary = {"dot", "tod", "dat", "dar"}
, returnnull
as there is no possible transformation fromdog
tocat
.
Solution with BFS: https://repl.it/@trsong/Word-Ladder
import unittest
from Queue import Queue
def word_ladder(start, end, word_set):
parents = {}
visited = set()
queue = Queue()
queue.put((start, None))
while not queue.empty():
for _ in xrange(queue.qsize()):
cur, prev = queue.get()
if cur in visited:
continue
visited.add(cur)
parents[cur] = prev
if cur == end:
return find_path(parents, end)
for word in word_set:
if word in visited or not is_neighbor(cur, word):
continue
queue.put((word, cur))
return None
def find_path(parents, start):
res = []
while start:
res.append(start)
start = parents.get(start, None)
res.reverse()
return res
def is_neighbor(word1, word2):
count = 0
for c1, c2 in zip(word1, word2):
if c1 != c2:
count += 1
if count > 1:
return False
return count == 1
class WordLadderSpec(unittest.TestCase):
def test_example(self):
start = 'dog'
end = 'cat'
word_set = {'dot', 'dop', 'dat', 'cat'}
expected = ['dog', 'dot', 'dat', 'cat']
self.assertEqual(expected, word_ladder(start, end, word_set))
def test_example2(self):
start = 'dog'
end = 'cat'
word_set = {'dot', 'tod', 'dat', 'dar'}
self.assertIsNone(word_ladder(start, end, word_set))
def test_empty_dict(self):
self.assertIsNone(word_ladder('start', 'end', {}))
def test_example3(self):
start = 'hit'
end = 'cog'
word_set = {'hot', 'dot', 'dog', 'lit', 'log', 'cog'}
expected = ['hit', 'hot', 'dot', 'dog', 'cog']
self.assertEqual(expected, word_ladder(start, end, word_set))
def test_end_word_not_in_dictionary(self):
start = 'hit'
end = 'cog'
word_set = ['hot', 'dot', 'dog', 'lot', 'log']
self.assertIsNone(word_ladder(start, end, word_set))
def test_long_example(self):
start = 'coder'
end = 'goner'
word_set = {
'lover', 'coder', 'comer', 'toner', 'cover', 'tower', 'coyer',
'bower', 'honer', 'poles', 'hover', 'lower', 'homer', 'boyer',
'goner', 'loner', 'boner', 'cower', 'never', 'sower', 'asian'
}
expected = ['coder', 'cower', 'lower', 'loner', 'goner']
self.assertEqual(expected, word_ladder(start, end, word_set))
def test_long_example2(self):
start = 'coder'
end = 'goner'
word_set = {
'lover', 'coder', 'comer', 'toner', 'cover', 'tower', 'coyer',
'bower', 'honer', 'poles', 'hover', 'lower', 'homer', 'boyer',
'goner', 'loner', 'boner', 'cower', 'never', 'sower', 'asian'
}
expected = ['coder', 'cower', 'lower', 'loner', 'goner']
self.assertEqual(expected, word_ladder(start, end, word_set))
if __name__ == '__main__':
unittest.main(exit=False)
Dec 1, 2020 [Easy] Intersection of N Arrays
Question: Given n arrays, find the intersection of them.
Example:
intersection([1, 2, 3, 4], [2, 4, 6, 8], [3, 4, 5]) # returns [4]
Solution: https://repl.it/@trsong/Intersection-of-N-Arrays
import unittest
def intersection(*num_lsts):
if not num_lsts:
return []
n = len(num_lsts)
sorted_lsts = map(sorted, num_lsts)
positions = [0] * n
res = []
while True:
min_val = float('inf')
for i, pos in enumerate(positions):
if pos >= len(sorted_lsts[i]):
return res
min_val = min(min_val, sorted_lsts[i][pos])
count = 0
for i, pos in enumerate(positions):
if sorted_lsts[i][pos] == min_val:
count += 1
positions[i] += 1
if count == n:
res.append(min_val)
return None
class IntersectionSpec(unittest.TestCase):
def test_example(self):
list1 = [1, 2, 3, 4]
list2 = [2, 4, 6, 8]
list3 = [3, 4, 5]
expected = [4]
self.assertEqual(expected, intersection(list1, list2, list3))
def test_example2(self):
list1 = [1, 5, 10, 20, 40, 80]
list2 = [6, 7, 20, 80, 100]
list3 = [3, 4, 15, 20, 30, 70, 80, 120]
expected = [20, 80]
self.assertEqual(expected, intersection(list1, list2, list3))
def test_example3(self):
list1 = [1, 5, 6, 7, 10, 20]
list2 = [6, 7, 20, 80]
list3 = [3, 4, 5, 7, 15, 20]
expected = [7, 20]
self.assertEqual(expected, intersection(list1, list2, list3))
def test_empty_array(self):
self.assertEqual([], intersection())
def test_one_array(self):
self.assertEqual([1, 2], intersection([1, 2]))
def test_two_arrays(self):
list1 = [1, 2, 3]
list2 = [5, 3, 1]
expected = [1, 3]
self.assertEqual(expected, intersection(list1, list2))
def test_reverse_order(self):
list1 = [4, 3, 2, 1]
list2 = [2, 4, 6, 8]
list3 = [5, 4, 3]
expected = [4]
self.assertEqual(expected, intersection(list1, list2, list3))
def test_contains_duplicate(self):
list1 = [1, 5, 5]
list2 = [3, 4, 5, 5, 10]
list3 = [5, 5, 10, 20]
expected = [5, 5]
self.assertEqual(expected, intersection(list1, list2, list3))
def test_different_length_lists(self):
list1 = [1, 5, 10, 20, 30]
list2 = [5, 13, 15, 20]
list3 = [5, 20]
expected = [5, 20]
self.assertEqual(expected, intersection(list1, list2, list3))
def test_empty_list(self):
list1 = [1, 2, 3, 4, 5]
list2 = [4, 5, 6, 7]
list3 = []
expected = []
self.assertEqual(expected, intersection(list1, list2, list3))
def test_empty_list2(self):
self.assertEqual([], intersection([], [], []))
if __name__ == '__main__':
unittest.main(exit=False)
Nov 30, 2020 LC 228 [Easy] Extract Range
Question: Given a sorted list of numbers, return a list of strings that represent all of the consecutive numbers.
Example:
Input: [0, 1, 2, 5, 7, 8, 9, 9, 10, 11, 15]
Output: ['0->2', '5', '7->11', '15']
Solution: https://repl.it/@trsong/Extract-Range
import unittest
def extract_range(nums):
if not nums:
return []
res = []
base = nums[0]
n = len(nums)
for i in xrange(1, n+1):
if i < n and (nums[i] - nums[i-1]) <= 1:
continue
if base == nums[i-1]:
res.append(str(base))
else:
res.append("%d->%d" % (base, nums[i-1]))
if i < n:
base = nums[i]
return res
class ExtractRangeSpec(unittest.TestCase):
def test_example(self):
nums = [0, 1, 2, 5, 7, 8, 9, 9, 10, 11, 15]
expected = ['0->2', '5', '7->11', '15']
self.assertEqual(expected, extract_range(nums))
def test_empty_array(self):
self.assertEqual([], extract_range([]))
def test_one_elem_array(self):
self.assertEqual(['42'], extract_range([42]))
def test_duplicates(self):
nums = [1, 1, 1, 1]
expected = ['1']
self.assertEqual(expected, extract_range(nums))
def test_duplicates2(self):
nums = [1, 1, 2, 2]
expected = ['1->2']
self.assertEqual(expected, extract_range(nums))
def test_duplicates3(self):
nums = [1, 1, 3, 3, 5, 5, 5]
expected = ['1', '3', '5']
self.assertEqual(expected, extract_range(nums))
def test_first_elem_in_range(self):
nums = [1, 2, 3, 10, 11]
expected = ['1->3', '10->11']
self.assertEqual(expected, extract_range(nums))
def test_first_elem_not_in_range(self):
nums = [-5, -3, -2]
expected = ['-5', '-3->-2']
self.assertEqual(expected, extract_range(nums))
def test_last_elem_in_range(self):
nums = [0, 15, 16, 17]
expected = ['0', '15->17']
self.assertEqual(expected, extract_range(nums))
def test_last_elem_not_in_range(self):
nums = [-42, -1, 0, 1, 2, 15]
expected = ['-42', '-1->2', '15']
self.assertEqual(expected, extract_range(nums))
def test_entire_array_in_range(self):
nums = list(range(-10, 10))
expected = ['-10->9']
self.assertEqual(expected, extract_range(nums))
def test_no_range_at_all(self):
nums = [1, 3, 5]
expected = ['1', '3', '5']
self.assertEqual(expected, extract_range(nums))
def test_range_and_not_range(self):
nums = [0, 1, 3, 5, 7, 8, 9, 11, 13, 14, 15]
expected = ['0->1', '3', '5', '7->9', '11', '13->15']
self.assertEqual(expected, extract_range(nums))
if __name__ == '__main__':
unittest.main(exit=False)
Nov 29, 2020 [Medium] Implement Soundex
Question: Soundex is an algorithm used to categorize phonetically, such that two names that sound alike but are spelled differently have the same representation.
Soundex maps every name to a string consisting of one letter and three numbers, like M460.
One version of the algorithm is as follows:
- Remove consecutive consonants with the same sound (for example, change ck -> c).
- Keep the first letter. The remaining steps only apply to the rest of the string.
- Remove all vowels, including y, w, and h.
- Replace all consonants with the following digits:
- b, f, p, v → 1
- c, g, j, k, q, s, x, z → 2
- d, t → 3
- l → 4
- m, n → 5
- r → 6
- If you don’t have three numbers yet, append zeros until you do. Keep the first three numbers.
Using this scheme, Jackson and Jaxen both map to J250.
Solution: https://repl.it/@trsong/Implement-Soundex
import unittest
def soundex_code(s):
skip_letters = {'y', 'w', 'h'}
score_to_letter = {
'1': 'bfpv',
'2': 'cgjkqsxz',
'3': 'dt',
'4': 'l',
'5': 'mn',
'6': 'r'
}
letter_to_score = {ch: score for score, chs in score_to_letter.items() for ch in chs}
initial = s[0].upper()
res = [initial]
prev = None
for ch in s:
lower_case = ch.lower()
if lower_case in skip_letters:
continue
code = letter_to_score.get(lower_case, '0')
if prev is not None and code != '0' and code != prev:
res.append(code)
prev = code
if len(res) == 4:
break
res.extend('0000')
return ''.join(res[:4])
class SoundexCodeSpec(unittest.TestCase):
def test_example(self):
# J, 2 for the C, K ignored, S ignored, 5 for the N, 0 added
self.assertEqual('J250', soundex_code('Jackson'))
def test_example2(self):
self.assertEqual('J250', soundex_code('Jaxen'))
def test_name_with_double_letters(self):
# G, 3 for the T, 6 for the first R, second R ignored, 2 for the Z
self.assertEqual('G362', soundex_code('Gutierrez'))
def test_side_by_side_same_code(self):
# P, F ignored, 2 for the S, 3 for the T, 6 for the R
self.assertEqual('P236', soundex_code('Pfister'))
def test_side_by_side_same_code2(self):
# T, 5 for the M, 2 for the C, Z ignored, 2 for the K
self.assertEqual('T522', soundex_code('Tymczak'))
def test_append_zero_to_end(self):
self.assertEqual('L000', soundex_code('Lee'))
def test_discard_extra_letters(self):
# W, 2 for the S, 5 for the N, 2 for the G, remaining letters disregarded
self.assertEqual('W252', soundex_code('Washington'))
def test_separate_consonant_with_same_code(self):
self.assertEqual('A261', soundex_code('Ashcraft'))
def test_more_example(self):
self.assertEqual('K530', soundex_code('Knuth'))
def test_more_example2(self):
self.assertEqual('K530', soundex_code('Kant'))
def test_more_example3(self):
self.assertEqual('J612', soundex_code('Jarovski'))
def test_more_example4(self):
self.assertEqual('R252', soundex_code('Resnik'))
def test_more_example5(self):
self.assertEqual('R252', soundex_code('Reznick'))
def test_more_example6(self):
self.assertEqual('E460', soundex_code('Euler'))
def test_more_example7(self):
self.assertEqual('P362', soundex_code('Peterson'))
def test_more_example8(self):
self.assertEqual('J162', soundex_code('Jefferson'))
def test_more_example9(self):
self.assertEqual('T526', soundex_code('Tangrui'))
def test_more_example10(self):
self.assertEqual('S520', soundex_code('Song'))
def test_more_example11(self):
self.assertEqual('J520', soundex_code('Jing'))
def test_more_example12(self):
self.assertEqual('Z520', soundex_code('Zhang'))
if __name__ == '__main__':
unittest.main(exit=False)
Nov 28, 2020 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: Suppose after strip out extra whitespaces the string is "the sky is blue"
. We just need to do the following steps:
- reverse entire sentence.
"eulb si yks eht"
- reverse each word in that sentence.
"blue is sky the"
Solution: https://repl.it/@trsong/Reverse-words-in-a-string
import unittest
def reverse_word(s):
char_arr = remove_white_spaces(s)
reverse_section(char_arr, 0, len(char_arr) - 1)
reverse_each_word(char_arr)
return ''.join(char_arr)
def remove_white_spaces(s):
prev = ' '
res = []
for ch in s:
if prev == ch == ' ':
continue
res.append(ch)
prev = ch
if res and res[-1] == ' ':
res.pop()
return res
def reverse_each_word(arr):
i = 0
n = len(arr)
while i < n:
if arr[i] == ' ':
i += 1
continue
start = i
while i < n and arr[i] != ' ':
i += 1
reverse_section(arr, start, i-1)
def reverse_section(arr, start, end):
while start < end:
arr[start], arr[end] = arr[end], arr[start]
start += 1
end -= 1
class ReverseWordSpec(unittest.TestCase):
def test_example1(self):
s = "the sky is blue"
expected = "blue is sky the"
self.assertEqual(expected, reverse_word(s))
def test_example2(self):
s = " hello world! "
expected = "world! hello"
self.assertEqual(expected, reverse_word(s))
def test_example3(self):
s = "a good example"
expected = "example good a"
self.assertEqual(expected, reverse_word(s))
def test_mutliple_whitespaces(self):
s = " "
expected = ""
self.assertEqual(expected, reverse_word(s))
def test_mutliple_whitespaces2(self):
s = "the sky is blue"
expected = "blue is sky the"
self.assertEqual(expected, reverse_word(s))
def test_even_number_of_words(self):
s = " car cat"
expected = "cat car"
self.assertEqual(expected, reverse_word(s))
def test_even_number_of_words2(self):
s = "car cat "
expected = "cat car"
self.assertEqual(expected, reverse_word(s))
def test_no_whitespaces(self):
s = "asparagus"
expected = "asparagus"
self.assertEqual(expected, reverse_word(s))
if __name__ == '__main__':
unittest.main(exit=False)
Nov 27, 2020 [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/Verify-List-of-Direction-and-Position-Rules
import unittest
def direction_rule_validate(rules):
neighbors_by_direction = {
direction: {} for direction in [Direction.E, Direction.W, Direction.S, Direction.N]
}
for rule in rules:
start, directions, end = rule.split()
for direction in directions:
neighbors = neighbors_by_direction[direction]
if check_connectivity(neighbors, end, start):
return False
neighbors[start] = neighbors.get(start, set())
neighbors[start].add(end)
opposite_neighbors = neighbors_by_direction[Direction.get_opposite_direction(direction)]
opposite_neighbors[end] = neighbors.get(end, set())
opposite_neighbors[end].add(start)
return True
def check_connectivity(neighbors, start, end):
stack = [start]
visited = set()
while stack:
cur = stack.pop()
if cur == end:
return True
if cur in visited:
continue
visited.add(cur)
if cur not in neighbors:
continue
for child in neighbors[cur]:
if child not in visited:
stack.append(child)
return False
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
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)
Nov 26, 2020 [Hard] Inversion Pairs
Question: We can determine how “out of order” an array A is by counting the number of inversions it has. Two elements
A[i]
andA[j]
form an inversion ifA[i] > A[j]
buti < j
. That is, a smaller element appears after a larger element. Given an array, count the number of inversions it has. Do this faster thanO(N^2)
time. You may assume each element in the array is distinct.For example, a sorted list has zero inversions. The array
[2, 4, 1, 3, 5]
has three inversions:(2, 1)
,(4, 1)
, and(4, 3)
. The array[5, 4, 3, 2, 1]
has ten inversions: every distinct pair forms an inversion.
Trivial Solution:
def count_inversion_pairs_naive(nums):
inversions = 0
n = len(nums)
for i in xrange(n):
for j in xrange(i+1, n):
inversions += 1 if nums[i] > nums[j] else 0
return inversions
My thoughts: We can start from trivial solution and perform optimization after. In trivial solution basically, we try to count the total number of larger number on the left of smaller number. However, while solving this question, you need to ask yourself, is it necessary to iterate through all combination of different pairs and calculate result?
e.g. For input [5, 4, 3, 2, 1]
each pair is an inversion pair, once I know (5, 4)
will be a pair, then do I need to go over the remain (5, 3)
, (5, 2)
, (5, 1)
as 3,2,1 are all less than 4?
So there probably exits some tricks to allow us save some effort to not go over all possible combinations.
Did you notice the following properties?
count_inversion_pairs([5, 4, 3, 2, 1]) = count_inversion_pairs([5, 4]) + count_inversion_pairs([3, 2, 1]) + inversion_pairs_between([5, 4], [3, 2, 1])
inversion_pairs_between([5, 4], [3, 2, 1]) = inversion_pairs_between(sorted([5, 4]), sorted([3, 2, 1])) = inversion_pairs_between([4, 5], [1, 2, 3])
This is bascially modified version of merge sort. Consider we break [5, 4, 3, 2, 1]
into two almost equal parts: [5, 4]
and [3, 2, 1]
. Notice such break won’t affect inversion pairs, as whatever on the left remains on the left. However, inversion pairs between [5, 4]
and [3, 2, 1]
can be hard to count without doing it one-by-one.
If only we could sort them separately as sort won’t affect the inversion order between two lists. i.e. [4, 5]
and [1, 2, 3]
. Now let’s see if we can find the pattern, if 4 < 1
, then 5 should < 1
. And we also have 4 < 2
and 4 < 3
. We can simply skip all elem > than 4
on each iteration, i.e. we just need to calculate how many elem > 4 on each iteration. This gives us property 2.
And we can futher break [5, 4]
into [5]
and [4]
recursively. This gives us property 1.
Combine property 1 and 2 gives us the modified version of Merge-Sort.
Solution with Merge Sort: https://repl.it/@trsong/Count-Inversion-Pairs
import unittest
def count_inversion_pairs(nums):
count, _ = count_and_sort(nums)
return count
def count_and_sort(nums):
if len(nums) < 2:
return 0, nums
mid = len(nums) // 2
sub_res1, sorted1 = count_and_sort(nums[:mid])
sub_res2, sorted2 = count_and_sort(nums[mid:])
count_res, merged = count_and_merge(sorted1, sorted2)
return sub_res1 + sub_res2 + count_res, merged
def count_and_merge(nums1, nums2):
inversions = 0
merged = []
i = j = 0
len1, len2 = len(nums1), len(nums2)
while i < len1 and j < len2:
if nums1[i] <= nums2[j]:
merged.append(nums1[i])
i += 1
else:
# there are len1 - i numbers one the left array greater than right
inversions += len1 - i
merged.append(nums2[j])
j += 1
while i < len1:
merged.append(nums1[i])
i += 1
while j < len2:
merged.append(nums2[j])
j += 1
return inversions, merged
class CountInversionPairSpec(unittest.TestCase):
def test_example(self):
nums = [2, 4, 1, 3, 5]
expected = 3 # (2, 1), (4, 1), (4, 3)
self.assertEqual(expected, count_inversion_pairs(nums))
def test_example2(self):
nums = [5, 4, 3, 2, 1]
expected = 10 # (5, 4), (5, 3), ... (2, 1) = 4 + 3 + 2 + 1 = 10
self.assertEqual(expected, count_inversion_pairs(nums))
def test_empty_array(self):
self.assertEqual(0, count_inversion_pairs([]))
def test_one_elem_array(self):
self.assertEqual(0, count_inversion_pairs([42]))
def test_ascending_array(self):
nums = [1, 4, 6, 8, 9]
expected = 0
self.assertEqual(expected, count_inversion_pairs(nums))
def test_ascending_array2(self):
nums = [1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4]
expected = 0
self.assertEqual(expected, count_inversion_pairs(nums))
def test_increasing_decreasing_array(self):
nums = [1, 2, 3, 2, 5]
expected = 1 # (3, 2)
self.assertEqual(expected, count_inversion_pairs(nums))
def test_decreasing_increasing_array(self):
nums = [0, -1, -2, -2, 2, 3]
expected = 5 # (0, -1), (0, -2), (0, -2), (-1, -2), (-1, -2)
self.assertEqual(expected, count_inversion_pairs(nums))
def test_unique_value_array(self):
nums = [0, 0, 0]
expected = 0
self.assertEqual(expected, count_inversion_pairs(nums))
if __name__ == '__main__':
unittest.main(exit=False)
Nov 25, 2020 [Hard] Minimum Cost to Construct Pyramid with Stones
Question: You have
N
stones in a row, and would like to create from them a pyramid. This pyramid should be constructed such that the height of each stone increases by one until reaching the tallest stone, after which the heights decrease by one. In addition, the start and end stones of the pyramid should each be one stone high.You can change the height of any stone by paying a cost of
1
unit to lower its height by1
, as many times as necessary. Given this information, determine the lowest cost method to produce this pyramid.For example, given the stones
[1, 1, 3, 3, 2, 1]
, the optimal solution is to pay2
to create[0, 1, 2, 3, 2, 1]
.
My thoughts: To find min cost is equivalent to find max pyramid we can construct. With that being said, all we need is to figure out each position’s max possible height.
A position’s height can only be 1 greater than prvious one on the left and right position. We can scan from left and right to figure out the max height for each position.
After that, we need to find center location of pyramid. That is just the max height. And the total number of stones equals 1 + 2 + .. + n + ... + 1 = n * n
.
Therefore the min cost
is just sum of all stones - total stones of max pyramid
.
Solution: https://repl.it/@trsong/Minimum-Cost-to-Construct-Pyramid-with-Stones
import unittest
def min_cost_pyramid(stones):
n = len(stones)
left_trim_heights = [None] * n
right_trim_heights = [None] * n
left_trim_heights[0] = min(1, stones[0])
right_trim_heights[n - 1] = min(1, stones[n - 1])
for i in xrange(1, n):
left_trim_heights[i] = min(left_trim_heights[i - 1] + 1, stones[i])
right_trim_heights[n - i - 1] = min(right_trim_heights[n - i] + 1, stones[n - i - 1])
trim_heights = map(min, zip(left_trim_heights, right_trim_heights))
pyramid_height = max(trim_heights)
total_pyramid_stones = pyramid_height * pyramid_height # 1 + 2 + ... + 2 + 1
return sum(stones) - total_pyramid_stones
class MinCostPyramidSpec(unittest.TestCase):
def test_example(self):
stones = [1, 1, 3, 3, 2, 1]
expected = 2 # [0, 1, 2, 3, 2, 1]
self.assertEqual(expected, min_cost_pyramid(stones))
def test_small_pyramid(self):
stones = [1, 2, 1]
expected = 0
self.assertEqual(expected, min_cost_pyramid(stones))
def test_small_pyramid2(self):
stones = [1, 1, 1]
expected = 2 # [0, 1, 0]
self.assertEqual(expected, min_cost_pyramid(stones))
def test_almost_pyramid(self):
stones = [1, 2, 3, 4, 2, 1]
expected = 4 # [1, 2, 3, 2, 1, 0]
self.assertEqual(expected, min_cost_pyramid(stones))
def test_choice_between_different_pyramid(self):
stones = [1, 2, 1, 0, 0, 1, 2, 3, 2, 1, 0, 1, 0]
expected = 5 # [0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0]
self.assertEqual(expected, min_cost_pyramid(stones))
def test_build_from_flat_plane(self):
stones = [5, 5, 5, 5, 5]
expected = 16 # [1, 2, 3, 2, 1]
self.assertEqual(expected, min_cost_pyramid(stones))
def test_concave_array(self):
stones = [0, 0, 3, 2, 3, 0]
expected = 4 # [0, 0, 1, 2, 1, 0]
self.assertEqual(expected, min_cost_pyramid(stones))
def test_multiple_layer_platforms(self):
stones = [2, 2, 5, 5, 5, 5, 5, 5, 5, 5, 5, 9, 9, 9, 5, 5, 5, 5, 5, 5, 2, 2]
# [0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0]
expected = 61
self.assertEqual(expected, min_cost_pyramid(stones))
def test_multiple_layer_platforms2(self):
stones = [0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 2, 2, 1, 1, 1, 0]
expected = 16 # [0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, 0]
self.assertEqual(expected, min_cost_pyramid(stones))
def test_choose_between_two_pyramids(self):
stones = [1, 2, 3, 2, 1, 0, 0, 1, 6, 1, 0]
expected = 8 # [1, 2, 3, 2, 1, 0, 0, 0, 0, 0, 0]
self.assertEqual(expected, min_cost_pyramid(stones))
if __name__ == '__main__':
unittest.main(exit=False)
Nov 24, 2020 [Easy] Reconstruct a Jumbled Array
Question: The sequence
[0, 1, ..., N]
has been jumbled, and the only clue you have for its order is an array representing whether each number is larger or smaller than the last.Given this information, reconstruct an array that is consistent with it. For example, given
[None, +, +, -, +]
, you could return[1, 2, 3, 0, 4]
.
My thoughts: treat +
as +1
, +2
, +3
and -
as -1
, -2
from 0
you can generate an array with satisfied condition yet incorrect range. Then all we need to do is to shift to range from 0
to N
by minusing each element with global minimum value.
Solution: https://repl.it/@trsong/Reconstruct-a-Jumbled-Array
import unittest
def build_jumbled_array(clues):
res = [0] * len(clues)
upper = lower = 0
for i in xrange(1, len(clues)):
if clues[i] == '+':
res[i] = upper + 1
upper += 1
elif clues[i] == '-':
res[i] = lower - 1
lower -= 1
return map(lambda num: num - lower, res)
class BuildJumbledArraySpec(unittest.TestCase):
@staticmethod
def generate_clues(nums):
nums_signs = [None] * len(nums)
for i in xrange(1, len(nums)):
nums_signs[i] = '+' if nums[i] > nums[i-1] else '-'
return nums_signs
def validate_result(self, cludes, res):
res_set = set(res)
res_signs = BuildJumbledArraySpec.generate_clues(res)
msg = "Incorrect result %s. Expect %s but gives %s." % (res, cludes, res_signs)
self.assertEqual(len(cludes), len(res_set), msg)
self.assertEqual(0, min(res), msg)
self.assertEqual(len(cludes) - 1, max(res), msg)
self.assertEqual(cludes, res_signs, msg)
def test_example(self):
clues = [None, '+', '+', '-', '+']
# possible solution: [1, 2, 3, 0, 4]
res = build_jumbled_array(clues)
self.validate_result(clues, res)
def test_empty_array(self):
self.assertEqual([], build_jumbled_array([]))
def test_one_element_array(self):
self.assertEqual([0], build_jumbled_array([None]))
def test_two_elements_array(self):
self.assertEqual([1, 0], build_jumbled_array([None, '-']))
def test_ascending_array(self):
clues = [None, '+', '+', '+']
expected = [0, 1, 2, 3]
self.assertEqual(expected, build_jumbled_array(clues))
def test_descending_array(self):
clues = [None, '-', '-', '-', '-']
expected = [4, 3, 2, 1, 0]
self.assertEqual(expected, build_jumbled_array(clues))
def test_random_array(self):
clues = [None, '+', '-', '+', '-']
# possible solution: [1, 4, 2, 3, 0]
res = build_jumbled_array(clues)
self.validate_result(clues, res)
def test_random_array2(self):
clues = [None, '-', '+', '-', '-', '+']
# possible solution: [3, 1, 4, 2, 0, 5]
res = build_jumbled_array(clues)
self.validate_result(clues, res)
def test_random_array3(self):
clues = [None, '+', '-', '+', '-', '+', '+', '+']
# possible solution: [1, 7, 0, 6, 2, 3, 4, 5]
res = build_jumbled_array(clues)
self.validate_result(clues, res)
def test_random_array4(self):
clues = [None, '+', '+', '-', '+']
# possible solution: [1, 2, 3, 0, 4]
res = build_jumbled_array(clues)
self.validate_result(clues, res)
def test_random_array5(self):
clues = [None, '-', '-', '-', '+']
# possible solution: [3, 2, 1, 0, 4]
res = build_jumbled_array(clues)
self.validate_result(clues, res)
if __name__ == '__main__':
unittest.main(exit=False)
Nov 23, 2020 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/Identify-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
DIRECTIONS = [(-1, 0), (1, 0), (0, 1), (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 DIRECTIONS:
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)
Nov 22, 2020 LC 212 [Hard] Word Search II
Question: Given an m x n board of characters and a list of strings words, return all words on the board.
Each word must be constructed from letters of sequentially adjacent cells, where adjacent cells are horizontally or vertically neighboring. The same letter cell may not be used more than once in a word.
Example 1:
Input: words = ["oath","pea","eat","rain"], board = [
["o","a","a","n"],
["e","t","a","e"],
["i","h","k","r"],
["i","f","l","v"]]
Output: ["eat", "oath"]
Example 2:
Input: words = ["abcb"], board = [
["a","b"],
["c","d"]]
Output: []
Solution with Backtracking and Trie: https://repl.it/@trsong/Word-Search-II
import unittest
def search_word(board, words):
if not board or not board[0] or not words:
return []
trie = Trie()
for word in words:
trie.insert(word)
res = []
n, m = len(board), len(board[0])
for r in xrange(n):
for c in xrange(m):
backtrack(board, trie, (r, c), res)
return res
class Trie(object):
def __init__(self):
self.word = None
self.children = None
def insert(self, word):
p = self
for ch in word:
if not p.children:
p.children = {}
if ch not in p.children:
p.children[ch] = Trie()
p = p.children[ch]
p.word = word
DIRECTIONS = [(-1, 0), (1, 0), (0, 1), (0, -1)]
def backtrack(board, parent_node, pos, res):
r, c = pos
ch = board[r][c]
if not ch or not parent_node.children or not parent_node.children.get(ch, None):
return
node = parent_node.children[ch]
if node.word:
res.append(node.word)
node.word = None
board[r][c] = None
n, m = len(board), len(board[0])
for dr, dc in DIRECTIONS:
new_r, new_c = r + dr, c + dc
if 0 <= new_r < n and 0 <= new_c < m and board[new_r][new_c]:
backtrack(board, node, (new_r, new_c), res)
board[r][c] = ch
class SearchWordSpec(unittest.TestCase):
def assert_result(self, expected, res):
self.assertEqual(sorted(expected), sorted(res))
def test_example(self):
words = ['oath','pea','eat','rain']
board = [
['o','a','a','n'],
['e','t','a','e'],
['i','h','k','r'],
['i','f','l','v']]
expected = ['eat', 'oath']
self.assert_result(expected, search_word(board, words))
def test_example2(self):
words = ['abcb']
board = [
['a','b'],
['c','d']]
expected = []
self.assert_result(expected, search_word(board, words))
def test_unique_char(self):
words = ['a', 'aa', 'aaa']
board = [
['a','a'],
['a','a']]
expected = ['a', 'aa', 'aaa']
self.assert_result(expected, search_word(board, words))
def test_empty_grid(self):
self.assertEqual([], search_word([], ['a']))
def test_empty_empty_word(self):
self.assertEqual([], search_word(['a'], []))
def test_word_use_all_letters(self):
words = ['abcdef']
board = [
['a','b'],
['f','c'],
['e','d']]
expected = ['abcdef']
self.assert_result(expected, search_word(board, words))
if __name__ == '__main__':
unittest.main(exit=False)
Nov 21, 2020 [Hard] Find the Element That Appears Once While Others Occur 3 Times
Question: Given an array of integers where every integer occurs three times except for one integer, which only occurs once, find and return the non-duplicated integer.
For example, given
[6, 1, 3, 3, 3, 6, 6]
, return1
. Given[13, 19, 13, 13]
, return19
.Do this in
O(N)
time andO(1)
space.
My thoughts: An interger repeats 3 time, then each of its digit will repeat 3 times. If a digit repeat 1 more time on top of that, then that digit must be contributed by the unique number.
Solution: https://repl.it/@trsong/Find-the-Element-That-Appears-Once-While-Others-Occur-3-Time
import unittest
INT_SIZE = 32
def find_uniq_elem(nums):
res = 0
count = 0
for i in xrange(INT_SIZE):
count = 0
for num in nums:
if num & 1 << i:
count += 1
if count % 3 == 1:
res |= 1 << i
return res
class FindUniqElemSpec(unittest.TestCase):
def test_example(self):
self.assertEqual(1, find_uniq_elem([6, 1, 3, 3, 3, 6, 6]))
def test_example2(self):
self.assertEqual(19, find_uniq_elem([13, 19, 13, 13]))
def test_example3(self):
self.assertEqual(2, find_uniq_elem([12, 1, 12, 3, 12, 1, 1, 2, 3, 3]))
def test_example4(self):
self.assertEqual(20, find_uniq_elem([10, 20, 10, 30, 10, 30, 30]))
def test_ascending_array(self):
self.assertEqual(4, find_uniq_elem([1, 1, 1, 2, 2, 2, 3, 3, 3, 4]))
def test_descending_array(self):
self.assertEqual(2, find_uniq_elem([2, 1, 1, 1, 0, 0, 0]))
if __name__ == '__main__':
unittest.main(exit=False)
Nov 20, 2020 [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 1:
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/Determine-Word-Ordering-in-a-Different-Alphabetical-Order
import unittest
def is_sorted(words, order):
char_rank = {ch: rank for rank, ch in enumerate(order)}
prev = ""
for word in words:
is_smaller = False
for ch1, ch2 in zip(prev, word):
if char_rank[ch1] > char_rank[ch2]:
return False
if char_rank[ch1] < char_rank[ch2]:
is_smaller = True
break
if not is_smaller and len(prev) > len(word):
return False
prev = word
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)
Nov 19, 2020 [Easy] Count Visible Nodes in Binary Tree
Question: In a binary tree, if in the path from root to the node A, there is no node with greater value than A’s, this node A is visible. We need to count the number of visible nodes in a binary tree.
Example 1:
Input:
5
/ \
3 10
/ \ /
20 21 1
Output: 4
Explanation: There are 4 visible nodes: 5, 20, 21, and 10.
Example 2:
Input:
-10
\
-15
\
-1
Output: 2
Explanation: Visible nodes are -10 and -1.
Solution with DFS: https://repl.it/@trsong/Count-Visible-Nodes-in-Binary-Tree
import unittest
def count_visible_nodes(root):
if not root:
return 0
stack = [(root, float('-inf'))]
res = 0
while stack:
cur, prev_max = stack.pop()
if cur.val > prev_max:
res += 1
for child in [cur.left, cur.right]:
if not child:
continue
stack.append((child, max(prev_max, cur.val)))
return res
class TreeNode(object):
def __init__(self, val, left=None, right=None):
self.val = val
self.left = left
self.right = right
class CountVisibleNodeSpec(unittest.TestCase):
def test_example(self):
"""
5*
/ \
3 10*
/ \ /
20* 21* 1
"""
left_tree = TreeNode(3, TreeNode(20), TreeNode(21))
right_tree = TreeNode(10, TreeNode(1))
root = TreeNode(5, left_tree, right_tree)
self.assertEqual(4, count_visible_nodes(root))
def test_example2(self):
"""
-10*
\
-15
\
-1*
"""
root = TreeNode(-10, right=TreeNode(-15, TreeNode(-1)))
self.assertEqual(2, count_visible_nodes(root))
def test_empty_tree(self):
self.assertEqual(0, count_visible_nodes(None))
def test_full_tree(self):
"""
1*
/ \
2* 3*
/ \ / \
4* 5* 6* 7*
"""
left_tree = TreeNode(2, TreeNode(4), TreeNode(5))
right_tree = TreeNode(3, TreeNode(6), TreeNode(7))
root = TreeNode(1, left_tree, right_tree)
self.assertEqual(7, count_visible_nodes(root))
def test_one_node_tree(self):
self.assertEqual(1, count_visible_nodes(TreeNode(42)))
def test_complete_tree(self):
"""
10*
/ \
9 8
/ \ /
7 6 5
"""
left_tree = TreeNode(9, TreeNode(7), TreeNode(6))
right_tree = TreeNode(8, TreeNode(5))
root = TreeNode(10, left_tree, right_tree)
self.assertEqual(1, count_visible_nodes(root))
if __name__ == '__main__':
unittest.main(exit=False)
Nov 18, 2020 [Hard] Non-adjacent Subset Sum
Question: Given an array of size n with unique positive integers and a positive integer K, check if there exists a combination of elements in the array satisfying both of below constraints:
- The sum of all such elements is K
- None of those elements are adjacent in the original array
Example:
Input: K = 14, arr = [1, 9, 8, 3, 6, 7, 5, 11, 12, 4]
Output: [3, 7, 4]
Solution with DP: https://repl.it/@trsong/Non-adjacent-Subset-Sum
import unittest
def non_adj_subset_sum(nums, k):
if not nums:
return None
n = len(nums)
# Let dp[k][n] represents whether exists subset sum k for nums[:n]
# dp[k][n] = dp[k][n-1] or dp[k - nums[n-1]][n-2]
dp = [[False for _ in xrange(n + 1)] for _ in xrange(k + 1)]
for s in xrange(1, k+1):
for i in xrange(1, n+1):
if s == nums[i-1] or dp[s][i-1]:
dp[s][i] = True
continue
if i > 2 and s >= nums[i-1]:
dp[s][i] = dp[s - nums[i-1]][i-2]
if not dp[k][n]:
return None
res = []
for i in xrange(n, 0, -1):
if dp[k][i-1]:
continue
num = nums[i-1]
if k == num or i > 2 and dp[k - num][i - 2]:
res.append(num)
k -= num
return res[::-1]
class NonAdjSubsetSumSpec(unittest.TestCase):
def assert_result(self, k, nums, res):
self.assertEqual(set(), set(res) - set(nums))
self.assertEqual(k, sum(res))
def test_example(self):
k, nums = 14, [1, 9, 8, 3, 6, 7, 5, 11, 12, 4]
# Possible solution [3, 7, 4]
res = non_adj_subset_sum(nums, k)
self.assert_result(k, nums, res)
def test_multiple_solution(self):
k, nums = 12, [1, 2, 3, 4, 5, 6, 7]
# Possible solution [2, 4, 6]
res = non_adj_subset_sum(nums, k)
self.assert_result(k, nums, res)
def test_no_subset_satisfied(self):
k, nums = 100, [1, 2]
self.assertIsNone(non_adj_subset_sum(nums, k))
def test_no_subset_satisfied2(self):
k, nums = 3, [1, 2]
self.assertIsNone(non_adj_subset_sum(nums, k))
def test_should_not_pick_adjacent_elements(self):
k, nums = 3, [1, 2, 3]
expected = [3]
self.assertEqual(expected, non_adj_subset_sum(nums, k))
def test_should_not_pick_adjacent_elements2(self):
k, nums = 4, [1, 2, 3]
expected = [1, 3]
self.assertEqual(expected, non_adj_subset_sum(nums, k))
def test_pick_every_other_elements(self):
k, nums = 11, [1, 90, 2, 80, 3, 100, 5]
expected = [1, 2, 3, 5]
self.assertEqual(expected, non_adj_subset_sum(nums, k))
def test_pick_first_and_last(self):
k, nums = 3, [1, 10, 11, 7, 4, 12, 2]
expected = [1, 2]
self.assertEqual(expected, non_adj_subset_sum(nums, k))
def test_pick_every_three_elements(self):
k, nums = 6, [1, 100, 109, 2, 101, 110, 3]
expected = [1, 2, 3]
self.assertEqual(expected, non_adj_subset_sum(nums, k))
if __name__ == '__main__':
unittest.main(exit=False)
Nov 17, 2020 [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 with Postorder Traversal: https://repl.it/@trsong/Count-Total-Number-of-Uni-val-Subtrees
import unittest
class Result(object):
def __init__(self, res, is_unival):
self.res = res
self.is_unival = is_unival
def count_unival_subtrees(tree):
return count_unival_subtrees_recur(tree).res
def count_unival_subtrees_recur(tree):
if not tree:
return Result(0, True)
left_res = count_unival_subtrees_recur(tree.left)
right_res = count_unival_subtrees_recur(tree.right)
is_current_unival = left_res.is_unival and left_res.is_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
current_count = left_res.res + right_res.res + (1 if is_current_unival else 0)
return Result(current_count, is_current_unival)
class TreeNode(object):
def __init__(self, val, left=None, right=None):
self.val = val
self.left = left
self.right = right
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)
Nov 16, 2020 LC 1647 [Medium] Minimum Deletions to Make Character Frequencies Unique
Question: A string s is called good if there are no two different characters in s that have the same frequency.
Given a string s, return the minimum number of characters you need to delete to make s good.
The frequency of a character in a string is the number of times it appears in the string. For example, in the string “aab”, the frequency of ‘a’ is 2, while the frequency of ‘b’ is 1.
Example 1:
Input: s = "aab"
Output: 0
Explanation: s is already good.
Example 2:
Input: s = "aaabbbcc"
Output: 2
Explanation: You can delete two 'b's resulting in the good string "aaabcc".
Another way it to delete one 'b' and one 'c' resulting in the good string "aaabbc".
Example 3:
Input: s = "ceabaacb"
Output: 2
Explanation: You can delete both 'c's resulting in the good string "eabaab".
Note that we only care about characters that are still in the string at the end (i.e. frequency of 0 is ignored).
My thoughts: sort frequency in descending order, while iterate through all frequencies, keep track of biggest next frequency we can take. Then the min deletion for that letter is freq - biggestNextFreq
. Remember to reduce the biggest next freq by 1 for each step.
Greedy Solution: https://repl.it/@trsong/Minimum-Deletions-to-Make-Character-Frequencies-Unique
import unittest
CHAR_SIZE = 26
def min_deletions(s):
histogram = [0] * CHAR_SIZE
ord_a = ord('a')
for ch in s:
histogram[ord(ch) - ord_a] += 1
histogram.sort(reverse=True)
next_count = histogram[0]
res = 0
for count in histogram:
if count <= next_count:
next_count = count - 1
else:
# reduce count to next_count
res += count - next_count
next_count -= 1
next_count = max(0, next_count)
return res
class MinDeletionSpec(unittest.TestCase):
def test_example(self):
self.assertEqual(0, min_deletions("aab"))
def test_example2(self):
# remove 2b's
self.assertEqual(2, min_deletions("aaabbbcc"))
def test_example3(self):
# remove 2b's
self.assertEqual(2, min_deletions("ceabaacb"))
def test_empty_string(self):
self.assertEqual(0, min_deletions(""))
def test_string_with_same_char_freq(self):
s = 'a' * 100 + 'b' * 100 + 'c' * 2 + 'd' * 1
self.assertEqual(1, min_deletions(s))
def test_remove_all_other_string(self):
self.assertEqual(4, min_deletions("abcde"))
def test_collision_after_removing(self):
# remove 1b, 1c, 2d, 2e, 1f
s = 'a' * 10 + 'b' * 10 + 'c' * 9 + 'd' * 9 + 'e' * 8 + 'f' * 6
self.assertEqual(7, min_deletions(s))
def test_remove_all_of_certain_letters(self):
# remove 3b, 1f
s = 'a' * 3 + 'b' * 3 + 'c' * 2 + 'd' + 'f'
self.assertEqual(4, min_deletions(s))
if __name__ == '__main__':
unittest.main(exit=False)
Nov 15, 2020 [Medium] Number of Flips to Make Binary String
Question: You are given a string consisting of the letters
x
andy
, such asxyxxxyxyy
. In addition, you have an operation called flip, which changes a singlex
toy
or vice versa.Determine how many times you would need to apply this operation to ensure that all x’s come before all y’s. In the preceding example, it suffices to flip the second and sixth characters, so you should return 2.
My thoughts: Basically, the question is about finding a sweet cutting spot so that # flip on left plus # flip on right is minimized. We can simply scan through the array from left and right to allow constant time query for number of flip need on the left and right for a given spot. And the final answer is just the min of sum of left and right flips.
Solution with DP: https://repl.it/@trsong/Find-Number-of-Flips-to-Make-Binary-String
import unittest
def min_flip_to_make_binary(s):
if not s:
return 0
n = len(s)
left_y_count = 0
right_x_count = 0
left_accu = [0] * n
right_accu = [0] * n
for i in xrange(n):
left_accu[i] = left_y_count
left_y_count += 1 if s[i] == 'y' else 0
right_accu[n - 1 - i] = right_x_count
right_x_count += 1 if s[n - 1 - i] == 'x' else 0
res = float('inf')
for left_y, right_x in zip(left_accu, right_accu):
res = min(res, left_y + right_x)
return res
class MinFlipToMakeBinarySpec(unittest.TestCase):
def test_example(self):
self.assertEqual(2, min_flip_to_make_binary('xyxxxyxyy')) # xxxxxxxyy
def test_empty_string(self):
self.assertEqual(0, min_flip_to_make_binary(''))
def test_already_binary(self):
self.assertEqual(0, min_flip_to_make_binary('xxxxxxxyy'))
def test_flipped_string(self):
self.assertEqual(3, min_flip_to_make_binary('yyyxxx')) # yyyyyy
def test_flip_all_x(self):
self.assertEqual(1, min_flip_to_make_binary('yyx')) # yyy
def test_flip_all_y(self):
self.assertEqual(2, min_flip_to_make_binary('yyxxx')) # xxxxx
def test_flip_all_y2(self):
self.assertEqual(4, min_flip_to_make_binary('xyxxxyxyyxxx')) # xxxxxxxxxxxx
def test_flip_y(self):
self.assertEqual(2, min_flip_to_make_binary('xyxxxyxyyy')) # xxxxxxxyyy
def test_flip_x_and_y(self):
self.assertEqual(3, min_flip_to_make_binary('xyxxxyxyyx')) # xxxxxyyyyy
if __name__ == '__main__':
unittest.main(exit=False)
Nov 14, 2020 [Medium] Isolated Islands
Question: Given a matrix of 1s and 0s, return the number of “islands” in the matrix. A 1 represents land and 0 represents water, so an island is a group of 1s that are neighboring whose perimeter is surrounded by water.
For example, this matrix has 4 islands.
1 0 0 0 0
0 0 1 1 0
0 1 1 0 0
0 0 0 0 0
1 1 0 0 1
1 1 0 0 1
Solution with DFS: https://repl.it/@trsong/Count-Number-of-Isolated-Islands
import unittest
DIRECTIONS = [-1, 0, 1]
def calc_islands(area_map):
if not area_map or not area_map[0]:
return 0
n, m = len(area_map), len(area_map[0])
visited = set()
res = 0
for r in xrange(n):
for c in xrange(m):
if area_map[r][c] == 0 or (r, c) in visited:
continue
res += 1
dfs_island(area_map, (r, c), visited)
return res
def dfs_island(area_map, pos, visited):
n, m = len(area_map), len(area_map[0])
stack = [pos]
while stack:
cur_r, cur_c = stack.pop()
if (cur_r, cur_c) in visited:
continue
visited.add((cur_r, cur_c))
for dr in DIRECTIONS:
for dc in DIRECTIONS:
new_r, new_c = cur_r + dr, cur_c + dc
if (0 <= new_r < n and 0 <= new_c < m and
area_map[new_r][new_c] == 1 and
(new_r, new_c) not in visited):
stack.append((new_r, new_c))
class CalcIslandSpec(unittest.TestCase):
def test_sample_area_map(self):
self.assertEqual(4, calc_islands([
[1, 0, 0, 0, 0],
[0, 0, 1, 1, 0],
[0, 1, 1, 0, 0],
[0, 0, 0, 0, 0],
[1, 1, 0, 0, 1],
[1, 1, 0, 0, 1]
]))
def test_some_random_area_map(self):
self.assertEqual(5, calc_islands([
[1, 1, 0, 0, 0],
[0, 1, 0, 0, 1],
[1, 0, 0, 1, 1],
[0, 0, 0, 0, 0],
[1, 0, 1, 0, 1]
]))
def test_island_edge_of_map(self):
self.assertEqual(5, calc_islands([
[1, 0, 0, 1, 0],
[0, 1, 0, 1, 0],
[0, 0, 0, 0, 0],
[1, 0, 1, 0, 1],
[1, 0, 1, 0, 1]
]))
def test_huge_water(self):
self.assertEqual(0, calc_islands([
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0]
]))
def test_huge_island(self):
self.assertEqual(1, calc_islands([
[1, 0, 1, 0, 1],
[1, 0, 0, 1, 0],
[1, 1, 1, 0, 1]
]))
def test_non_square_island(self):
self.assertEqual(1, calc_islands([
[1],
[1],
[1]
]))
if __name__ == '__main__':
unittest.main(exit=False)
Nov 13, 2020 [Medium] Find Missing Positive
Question: Given an unsorted integer array, find the first missing positive integer.
Example 1:
Input: [1, 2, 0]
Output: 3
Example 2:
Input: [3, 4, -1, 1]
Output: 2
My thougths: Ideally each positive number should map to the same index as its value - 1
. So all we need to do is for each postion, use its value as index and swap with that element until we find correct number. Keep doing this and each postive should store in postion of its value - 1
. Now we just scan through the entire array until find the first missing number by checking each element’s value against index.
Soluion: https://repl.it/@trsong/Find-First-Missing-Positive
import unittest
def find_missing_positive(nums):
n = len(nums)
value_to_index = lambda value: value - 1
for i in xrange(n):
while 1 <= nums[i] <= n and value_to_index(nums[i]) != i:
target_index = value_to_index(nums[i])
if nums[i] == nums[target_index]:
break
nums[i], nums[target_index] = nums[target_index], nums[i]
for i, num in enumerate(nums):
if value_to_index(num) != i:
return i + 1
return n + 1
class FindMissingPositiveSpec(unittest.TestCase):
def test_example(self):
nums = [1, 2, 0]
expected = 3
self.assertEqual(expected, find_missing_positive(nums))
def test_example2(self):
nums = [3, 4, -1, 1]
expected = 2
self.assertEqual(expected, find_missing_positive(nums))
def test_empty_array(self):
nums = []
expected = 1
self.assertEqual(expected, find_missing_positive(nums))
def test_all_non_positives(self):
nums = [-1, 0, -1, -2, -1, -3, -4]
expected = 1
self.assertEqual(expected, find_missing_positive(nums))
def test_number_out_of_range(self):
nums = [101, 102, 103]
expected = 1
self.assertEqual(expected, find_missing_positive(nums))
def test_duplicated_numbers(self):
nums = [1, 1, 3, 3, 2, 2, 5]
expected = 4
self.assertEqual(expected, find_missing_positive(nums))
def test_missing_positive_falls_out_of_range(self):
nums = [5, 4, 3, 2, 1]
expected = 6
self.assertEqual(expected, find_missing_positive(nums))
def test_number_off_by_one_position(self):
nums = [0, 2, 3, 4, 7, 6, 1]
expected = 5
self.assertEqual(expected, find_missing_positive(nums))
def test_positive_and_negative_numbers(self):
nums = [-1, -3, -2, 0, 1, 2, 4, -4, 5, -6, 7]
expected = 3
self.assertEqual(expected, find_missing_positive(nums))
if __name__ == '__main__':
unittest.main(exit=False)
Nov 12, 2020 [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/Find-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])
dp = [[0 for _ in xrange(m)] for _ in xrange(n)]
for r in xrange(n):
for c in xrange(m):
left_max = dp[r-1][c] if r > 0 else 0
top_max = dp[r][c-1] if c > 0 else 0
dp[r][c] = grid[r][c] + max(left_max, top_max)
return dp[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)
Nov 11, 2020 LC 859 [Easy] Buddy Strings
Question: Given two strings A and B of lowercase letters, return true if and only if we can swap two letters in A so that the result equals B.
Example 1:
Input: A = "ab", B = "ba"
Output: true
Example 2:
Input: A = "ab", B = "ab"
Output: false
Example 3:
Input: A = "aa", B = "aa"
Output: true
Example 4:
Input: A = "aaaaaaabc", B = "aaaaaaacb"
Output: true
Example 5:
Input: A = "", B = "aa"
Output: false
Solution: https://repl.it/@trsong/Buddy-Strings
import unittest
def is_buddy_string(s1, s2):
if len(s1) != len(s2):
return False
unmatches = []
for c1, c2 in zip(s1, s2):
if c1 != c2:
unmatches.append((c1, c2))
if len(unmatches) > 2:
return False
if len(unmatches) == 2:
return unmatches[0] == unmatches[1][::-1]
elif len(unmatches) == 0:
return has_duplicates(s1)
else:
return False
def has_duplicates(s):
visited = set()
for c in s:
if c in visited:
return True
visited.add(c)
return False
class IsBuddyString(unittest.TestCase):
def test_example(self):
self.assertTrue(is_buddy_string('ab', 'ba'))
def test_example2(self):
self.assertFalse(is_buddy_string('ab', 'ab'))
def test_example3(self):
self.assertTrue(is_buddy_string('aa', 'aa'))
def test_example4(self):
self.assertTrue(is_buddy_string('aaaaaaabc', 'aaaaaaacb'))
def test_example5(self):
self.assertFalse(is_buddy_string('ab', 'aa'))
def test_empty_string(self):
self.assertFalse(is_buddy_string('', ''))
def test_single_char_string(self):
self.assertFalse(is_buddy_string('a', 'b'))
def test_same_string_without_duplicates(self):
self.assertFalse(is_buddy_string('abc', 'abc'))
def test_string_with_duplicates(self):
self.assertFalse(is_buddy_string('aba', 'abc'))
def test_different_length_string(self):
self.assertFalse(is_buddy_string('aa', 'aaa'))
def test_different_length_string2(self):
self.assertFalse(is_buddy_string('ab', 'baa'))
def test_different_length_string3(self):
self.assertFalse(is_buddy_string('ab', 'abba'))
def test_swap_failure(self):
self.assertFalse(is_buddy_string('abcaa', 'abcbb'))
if __name__ == '__main__':
unittest.main(exit=False)
Nov 10, 2020 [Medium] All Root to Leaf Paths in Binary Tree
Question: Given a binary tree, return all paths from the root to leaves.
Example:
Given the tree:
1
/ \
2 3
/ \
4 5
Return [[1, 2], [1, 3, 4], [1, 3, 5]]
Solution with Backtracking: https://repl.it/@trsong/Print-All-Root-to-Leaf-Paths-in-Binary-Tree
import unittest
def path_to_leaves(tree):
if not tree:
return []
res = []
backtrack(res, tree, [])
return res
def backtrack(res, current_node, path):
if not current_node.left and not current_node.right:
res.append(path + [current_node.val])
else:
for child in [current_node.left, current_node.right]:
if not child:
continue
path.append(current_node.val)
backtrack(res, child, path)
path.pop()
class TreeNode(object):
def __init__(self, val, left=None, right=None):
self.val = val
self.left = left
self.right= right
class PathToLeavesSpec(unittest.TestCase):
def test_empty_tree(self):
self.assertEqual([], path_to_leaves(None))
def test_one_level_tree(self):
self.assertEqual([[1]], path_to_leaves(TreeNode(1)))
def test_two_level_tree(self):
"""
1
/ \
2 3
"""
tree = TreeNode(1, TreeNode(2), TreeNode(3))
expected = [[1, 2], [1, 3]]
self.assertEqual(expected, path_to_leaves(tree))
def test_example(self):
"""
1
/ \
2 3
/ \
4 5
"""
n3 = TreeNode(3, TreeNode(4), TreeNode(5))
tree = TreeNode(1, TreeNode(2), n3)
expected = [[1, 2], [1, 3, 4], [1, 3, 5]]
self.assertEqual(expected, path_to_leaves(tree))
def test_complete_tree(self):
"""
1
/ \
2 3
/ \ /
4 5 6
"""
left_tree = TreeNode(2, TreeNode(4), TreeNode(5))
right_tree = TreeNode(3, TreeNode(6))
tree = TreeNode(1, left_tree, right_tree)
expected = [[1, 2, 4], [1, 2, 5], [1, 3, 6]]
self.assertEqual(expected, path_to_leaves(tree))
if __name__ == '__main__':
unittest.main(exit=False)
Nov 9, 2020 [Medium] Invert a Binary Tree
Question: You are given the root of a binary tree. Invert the binary tree in place. That is, all left children should become right children, and all right children should become left children.
Example:
Given the following tree:
a
/ \
b c
/ \ /
d e f
should become:
a
/ \
c b
\ / \
f e d
Solution: https://repl.it/@trsong/Invert-All-Nodes-in-Binary-Tree
import unittest
def invert_tree(root):
if root:
root.left, root.right = invert_tree(root.right), invert_tree(root.left)
return root
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
self.val == other.val and
self.left == other.left and
self.right == other.right)
def __repr__(self):
stack = [(self, 0)]
res = []
while stack:
node, depth = stack.pop()
res.append("\n" + "\t" * depth)
if not node:
res.append("* None")
continue
res.append("* " + str(node.val))
for child in [node.right, node.left]:
stack.append((child, depth+1))
return "\n" + "".join(res) + "\n"
class InvertTreeSpec(unittest.TestCase):
def test_empty_tree(self):
self.assertIsNone(invert_tree(None))
def test_heavy_left_tree(self):
"""
1
/
2
/
3
"""
tree = TreeNode(1, TreeNode(2, TreeNode(3)))
"""
1
\
2
\
3
"""
expected_tree = TreeNode(1, right=TreeNode(2, right=TreeNode(3)))
self.assertEqual(invert_tree(tree), expected_tree)
def test_heavy_right_tree(self):
"""
1
/ \
2 3
/
4
"""
tree = TreeNode(1, TreeNode(2), TreeNode(3, TreeNode(4)))
"""
1
/ \
3 2
\
4
"""
expected_tree = TreeNode(1, TreeNode(3, right=TreeNode(4)), TreeNode(2))
self.assertEqual(invert_tree(tree), expected_tree)
def test_sample_tree(self):
"""
1
/ \
2 3
/ \ /
4 5 6
"""
n2 = TreeNode(2, TreeNode(4), TreeNode(5))
n3 = TreeNode(3, TreeNode(6))
n1 = TreeNode(1, n2, n3)
"""
1
/ \
3 2
\ / \
6 5 4
"""
en2 = TreeNode(2, TreeNode(5), TreeNode(4))
en3 = TreeNode(3, right=TreeNode(6))
en1 = TreeNode(1, en3, en2)
self.assertEqual(invert_tree(n1), en1)
if __name__ == '__main__':
unittest.main(exit=False)
Nov 8, 2020 [Medium] Similar Websites
Question: You are given a list of (website, user) pairs that represent users visiting websites. Come up with a program that identifies the top k pairs of websites with the greatest similarity.
Note: The similarity metric bewtween two sets equals intersection / union.
Example:
Suppose k = 1, and the list of tuples is:
[('a', 1), ('a', 3), ('a', 5),
('b', 2), ('b', 6),
('c', 1), ('c', 2), ('c', 3), ('c', 4), ('c', 5),
('d', 4), ('d', 5), ('d', 6), ('d', 7),
('e', 1), ('e', 3), ('e', 5), ('e', 6)]
Then a reasonable similarity metric would most likely conclude that a and e are the most similar, so your program should return [('a', 'e')].
My thoughts: The similarity metric bewtween two sets equals intersection / union. So, the way to get top k similar website is first calculate the similarity score between any two websites and after that use a priority queue to mantain top k similarity pairs.
However, as duplicate entry might occur, we have to treat normal set to multiset: treat 3, 3, 3
as 3(first), 3(second), 3(third)
.
a: 1 2 3(first) 3(second) 3(third)
b: 1 2 3(first)
The similarty between (a, b) is 3/5
Solution with Priority Queue: https://repl.it/@trsong/Find-Similar-Websites
import unittest
from collections import defaultdict
from Queue import PriorityQueue
def top_similar_websites(website_log, k):
user_site_hits = defaultdict(lambda: defaultdict(int))
site_hits = defaultdict(int)
for site, user in website_log:
user_site_hits[user][site] += 1
site_hits[site] += 1
cross_site_hits = defaultdict(lambda: defaultdict(int))
for site_hits_per_user in user_site_hits.values():
for site1 in site_hits_per_user:
for site2 in site_hits_per_user:
cross_site_hits[site1][site2] += min(site_hits_per_user[site1], site_hits_per_user[site2])
min_heap = PriorityQueue()
sites = sorted(site_hits.keys())
for site1 in sites:
for site2 in sites:
if site2 == site1:
break
intersection = cross_site_hits[site1][site2]
total = site_hits[site1] + site_hits[site2]
union = total - intersection
similarity = float(intersection) / union
if min_heap.qsize() >= k and min_heap.queue[0][0] < similarity:
min_heap.get()
if min_heap.qsize() < k:
min_heap.put((similarity, (site1, site2)))
ascending_sites = [min_heap.get()[1] for _ in xrange(k)]
return ascending_sites[::-1]
class TopSimilarWebsiteSpec(unittest.TestCase):
def assert_result(self, expected, result):
# same length
self.assertEqual(len(expected), len(result))
for e, r in zip(expected, result):
# pair must be the same, order doesn't matter
self.assertEqual(set(e), set(r), "Expected %s but get %s" % (expected, result))
def test_example(self):
website_log = [
('a', 1), ('a', 3), ('a', 5),
('b', 2), ('b', 6),
('c', 1), ('c', 2), ('c', 3), ('c', 4), ('c', 5),
('d', 4), ('d', 5), ('d', 6), ('d', 7),
('e', 1), ('e', 3), ('e', 5), ('e', 6)]
# Similarity: (a,e)=3/4, (a,c)=3/5, (c, e)=1/2
expected = [('a', 'e'), ('a', 'c'), ('c', 'e')]
self.assert_result(expected, top_similar_websites(website_log, len(expected)))
def test_no_overlapping(self):
website_log = [('a', 1), ('b', 2)]
expected = [('a', 'b')]
self.assert_result(expected, top_similar_websites(website_log, len(expected)))
def test_should_return_correct_order(self):
website_log = [
('a', 1),
('b', 1), ('b', 2),
('c', 1), ('c', 2), ('c', 3),
('d', 1), ('d', 2), ('d', 3), ('d', 4),
('e', 1), ('e', 2), ('e', 3), ('e', 4), ('e', 5)]
# Similarity: (d,e)=4/5, (c,d)=3/4, (b,c)=2/3, (c,e)=3/5
expected = [('d', 'e'), ('c', 'd'), ('b', 'c'), ('c', 'e')]
self.assert_result(expected, top_similar_websites(website_log, len(expected)))
def test_duplicated_entries(self):
website_log = [
('a', 1), ('a', 1),
('b', 1),
('c', 1), ('c', 1), ('c', 2),
('d', 1), ('d', 3), ('d', 3), ('d', 4),
('e', 1), ('e', 1), ('e', 5), ('e', 6),
('f', 1), ('f', 7), ('f', 8), ('f', 8)
]
# Similarity: (a,c)=2/3
expected = [('a', 'c')]
self.assert_result(expected, top_similar_websites(website_log, len(expected)))
if __name__ == '__main__':
unittest.main(exit=False)
Nov 7, 2020 [Medium] Largest Square
Question: Given an N by M matrix consisting only of 1’s and 0’s, find the largest square matrix containing only 1’s and return its dimension size.
Example:
Given the following matrix:
[[1, 0, 0, 0],
[1, 1, 1, 1],
[1, 1, 1, 1],
[0, 1, 0, 0]]
Return 2. As the following 1s form the largest square matrix containing only 1s:
[1, 1],
[1, 1]
Solution with DP: https://repl.it/@trsong/Largest-Square
import unittest
def largest_square_dimension(grid):
if not grid or not grid[0]:
return 0
n, m = len(grid), len(grid[0])
# Let dp[r][c] represents max dimension of square matrix with bottom right corner at (r, c)
# dp[r][c] = min(dp[r-1][c], dp[r][c-1], dp[r-1][c-1]) + 1 if (r, c) is 1
# = 0 otherwise
dp = [[grid[r][c] for c in xrange(m)] for r in xrange(n)]
for r in xrange(1, n):
for c in xrange(1, m):
if grid[r][c] == 0:
continue
dp[r][c] = 1 + min(dp[r-1][c], dp[r][c-1], dp[r-1][c-1])
return max(map(max, dp))
class LargestSquareDimensionSpec(unittest.TestCase):
def test_example(self):
self.assertEqual(2, largest_square_dimension([
[1, 0, 0, 0],
[1, 1, 1, 1],
[1, 1, 1, 1],
[0, 1, 0, 0]
]))
def test_empty_grid(self):
self.assertEqual(0, largest_square_dimension([]))
def test_1d_grid(self):
self.assertEqual(1, largest_square_dimension([
[0, 0, 0, 0, 1, 0, 0]
]))
def test_1d_grid2(self):
self.assertEqual(0, largest_square_dimension([
[0],
[0],
[0]
]))
def test_dimond_shape(self):
self.assertEqual(3, largest_square_dimension([
[0, 0, 0, 0, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 0, 0, 0, 0],
[0, 0, 0, 1, 1, 1, 0, 0, 0],
[1, 1, 1, 1, 1, 1, 1, 1, 0],
[0, 0, 0, 1, 1, 1, 0, 0, 0],
[0, 0, 0, 0, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 0, 0, 0, 0]
]))
def test_dimond_shape2(self):
self.assertEqual(3, largest_square_dimension([
[0, 1, 0, 0, 1, 0, 0, 1, 0],
[0, 0, 1, 0, 1, 0, 1, 0, 0],
[0, 0, 0, 1, 1, 1, 0, 0, 0],
[1, 1, 1, 1, 1, 1, 1, 1, 0],
[0, 0, 0, 1, 1, 1, 0, 0, 0],
[0, 0, 1, 0, 1, 0, 1, 0, 0],
[0, 1, 0, 0, 1, 0, 0, 1, 0]
]))
def test_square_on_edge(self):
self.assertEqual(2, largest_square_dimension([
[0, 1, 0],
[1, 1, 0],
[1, 1, 0]
]))
def test_dense_matrix(self):
self.assertEqual(3, largest_square_dimension([
[0, 1, 1, 0, 1],
[1, 1, 0, 1, 0],
[0, 1, 1, 1, 0],
[1, 1, 1, 1, 0],
[1, 1, 1, 1, 1],
[0, 0, 0, 0, 0]
]))
if __name__ == '__main__':
unittest.main(exit=False)
Nov 6, 2020 [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
Solution: https://repl.it/@trsong/Find-the-M-thSmallest-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 not lst:
continue
it = iter(lst)
pq.put((it.next(), it))
while not pq.empty():
num, it = pq.get()
if m == 1:
return num
m -= 1
next_num = next(it, None)
if next_num is not None:
pq.put((next_num, it))
return None
class FindMSmallestSpec(unittest.TestCase):
def test_example1(self):
m, ksorted_list = 5, [
[1, 3],
[2, 4, 6],
[0, 9, 10, 11]
]
expected = 4
self.assertEqual(expected, find_m_smallest(ksorted_list, m))
def test_example2(self):
m, ksorted_list = 2, [
[1, 3, 20],
[2, 4, 6]
]
expected = 2
self.assertEqual(expected, find_m_smallest(ksorted_list, m))
def test_example3(self):
m, ksorted_list = 6, [
[1, 3, 20],
[2, 4, 6]
]
expected = 20
self.assertEqual(expected, find_m_smallest(ksorted_list, m))
def test_empty_sublist(self):
m, ksorted_list = 2, [
[1],
[],
[0, 2]
]
expected = 1
self.assertEqual(expected, find_m_smallest(ksorted_list, m))
def test_one_sublist(self):
m, ksorted_list = 5, [
[1, 2, 3, 4, 5],
]
expected = 5
self.assertEqual(expected, find_m_smallest(ksorted_list, m))
def test_target_out_of_boundary(self):
m, ksorted_list = 7, [
[1, 2, 3],
[4, 5, 6]
]
self.assertIsNone(find_m_smallest(ksorted_list, m))
if __name__ == '__main__':
unittest.main(exit=False)
Nov 5, 2020 [Easy] Maximum Subarray Sum
Question: You are given a one dimensional array that may contain both positive and negative integers, find the sum of contiguous subarray of numbers which has the largest sum.
For example, if the given array is
[-2, -5, 6, -2, -3, 1, 5, -6]
, then the maximum subarray sum is 7 as sum of[6, -2, -3, 1, 5]
equals 7Solve this problem with Divide and Conquer as well as DP separately.
Solution with Divide and Conquer: https://repl.it/@trsong/Maximum-Subarray-Sum-Divide-and-Conquer
def max_sub_array_sum(nums):
if not nums:
return 0
res = max_sub_array_sum_recur(nums, 0, len(nums) - 1)
return res.max
def max_sub_array_sum_recur(nums, lo, hi):
if lo == hi:
return Result(nums[lo])
mid = lo + (hi - lo) // 2
left_res = max_sub_array_sum_recur(nums, lo, mid)
right_res = max_sub_array_sum_recur(nums, mid+1, hi)
res = Result(0)
# From left to right, expand range
res.prefix = max(0, left_res.prefix, left_res.sum + right_res.prefix, left_res.sum + right_res.sum)
# From right to left, expand range
res.suffix = max(0, right_res.suffix, right_res.sum + left_res.suffix, right_res.sum + left_res.sum)
res.sum = left_res.sum + right_res.sum
res.max = max(res.prefix, res.suffix, left_res.suffix + right_res.prefix, left_res.max, right_res.max)
return res
class Result(object):
def __init__(self, res):
self.prefix = res
self.suffix = res
self.sum = res
self.max = res
Solution with DP: https://repl.it/@trsong/Maximum-Subarray-Sum-DP
import unittest
def max_sub_array_sum(nums):
n = len(nums)
# Let dp[i] represents max sub array sum ends at nums[i-1]
# dp[i] = max(0, dp[i-1] + nums[i-1])
dp = [0] * (n + 1)
res = 0
for i in xrange(1, n+1):
dp[i] = max(0, dp[i-1] + nums[i-1])
res = max(res, dp[i])
return res
class MaxSubArraySum(unittest.TestCase):
def test_empty_array(self):
self.assertEqual(0, max_sub_array_sum([]))
def test_ascending_array(self):
self.assertEqual(6, max_sub_array_sum([-3, -2, -1, 0, 1, 2, 3]))
def test_descending_array(self):
self.assertEqual(6, max_sub_array_sum([3, 2, 1, 0, -1]))
def test_example_array(self):
self.assertEqual(7, max_sub_array_sum([-2, -5, 6, -2, -3, 1, 5, -6]))
def test_negative_array(self):
self.assertEqual(0, max_sub_array_sum([-2, -1]))
def test_positive_array(self):
self.assertEqual(3, max_sub_array_sum([1, 2]))
def test_swing_array(self):
self.assertEqual(5, max_sub_array_sum([-3, 3, -2, 2, -5, 5]))
self.assertEqual(1, max_sub_array_sum([-1, 1, -1, 1, -1]))
self.assertEqual(2, max_sub_array_sum([-100, 1, -100, 2, -100]))
def test_converging_array(self):
self.assertEqual(4, max_sub_array_sum([-3, 3, -2, 2, 1, 0]))
def test_positive_negative_positive_array(self):
self.assertEqual(8, max_sub_array_sum([7, -1, -2, 3, 1]))
self.assertEqual(7, max_sub_array_sum([7, -1, -2, 0, 1, 1]))
def test_negative_positive_array(self):
self.assertEqual(3, max_sub_array_sum([-100, 1, 0, 2, -100]))
if __name__ == '__main__':
unittest.main(exit=False)
Nov 4, 2020 [Easy] Largest Path Sum from Root To Leaf
Question: Given a binary tree, find and return the largest path from root to leaf.
Example:
Input:
1
/ \
3 2
\ /
5 4
Output: [1, 3, 5]
Solution with DFS: https://repl.it/@trsong/Find-Largest-Path-Sum-from-Root-To-Leaf
import unittest
class TreeNode(object):
def __init__(self, val, left=None, right=None):
self.val = val
self.left = left
self.right = right
def largest_sum_path(root):
if not root:
return []
parent_lookup = {}
stack = [(root, 0)]
max_sum = float('-inf')
max_sum_node = root
while stack:
cur, prev_sum = stack.pop()
cur_sum = prev_sum + cur.val
if not cur.left and not cur.right and cur_sum > max_sum:
max_sum = cur_sum
max_sum_node = cur
else:
for child in [cur.left, cur.right]:
if not child:
continue
parent_lookup[child] = cur
stack.append((child, cur_sum))
res = []
node = max_sum_node
while node:
res.append(node.val)
node = parent_lookup.get(node, None)
return res[::-1]
class LargestSumPathSpec(unittest.TestCase):
def test_example(self):
"""
1
/ \
3 2
\ /
5 4
"""
left_tree = TreeNode(3, right=TreeNode(5))
right_tree = TreeNode(2, TreeNode(4))
root = TreeNode(1, left_tree, right_tree)
expected_path = [1, 3, 5]
self.assertEqual(expected_path, largest_sum_path(root))
def test_negative_nodes(self):
"""
10
/ \
-2 7
/ \
8 -4
"""
left_tree = TreeNode(-2, TreeNode(8), TreeNode(-4))
root = TreeNode(10, left_tree, TreeNode(7))
expected_path = [10, 7]
self.assertEqual(expected_path, largest_sum_path(root))
def test_empty_tree(self):
self.assertEqual([], largest_sum_path(None))
def test_heavy_right_tree(self):
"""
1
/ \
2 3
/ / \
8 4 5
/ \ \
6 7 9
"""
n5 = TreeNode(5, right=TreeNode(9))
n4 = TreeNode(4, TreeNode(6), TreeNode(7))
n3 = TreeNode(3, n4, n5)
n2 = TreeNode(2, TreeNode(8))
n1 = TreeNode(1, n2, n3)
expected_path = [1, 3, 5, 9]
self.assertEqual(expected_path, largest_sum_path(n1))
def test_all_paths_are_negative(self):
"""
-1
/ \
-2 -3
/ \ / \
-4 -5 -6 -7
"""
left_tree = TreeNode(-2, TreeNode(-4), TreeNode(-5))
right_tree = TreeNode(-3, TreeNode(-6), TreeNode(-7))
root = TreeNode(-1, left_tree, right_tree)
expected_path = [-1, -2, -4]
self.assertEqual(expected_path, largest_sum_path(root))
if __name__ == '__main__':
unittest.main(exit=False)
Nov 3, 2020 LC 121 [Easy] Best Time to Buy and Sell Stock
Question: You are given an array. Each element represents the price of a stock on that particular day. Calculate and return the maximum profit you can make from buying and selling that stock only once.
Example:
Input: [9, 11, 8, 5, 7, 10]
Output: 5
Explanation: Here, the optimal trade is to buy when the price is 5, and sell when it is 10, so the return value should be 5 (profit = 10 - 5 = 5).
Solution: https://repl.it/@trsong/Best-Time-to-Buy-and-Sell-Stock
import unittest
def max_profit(stock_data):
if not stock_data:
return 0
local_min = stock_data[0]
res = 0
for price in stock_data:
if price < local_min:
local_min = price
else:
res = max(res, price - local_min)
return res
class MaxProfitSpec(unittest.TestCase):
def test_blank_data(self):
self.assertEqual(max_profit([]), 0)
def test_1_day_data(self):
self.assertEqual(max_profit([9]), 0)
self.assertEqual(max_profit([-1]), 0)
def test_monotonically_increase(self):
self.assertEqual(max_profit([1, 2, 3]), 2)
self.assertEqual(max_profit([1, 1, 1, 2, 2, 3, 3, 3]), 2)
def test_monotonically_decrease(self):
self.assertEqual(max_profit([3, 2, 1]), 0)
self.assertEqual(max_profit([3, 3, 3, 2, 2, 1, 1, 1]), 0)
def test_raise_suddenly(self):
self.assertEqual(max_profit([3, 2, 1, 1, 2]), 1)
self.assertEqual(max_profit([3, 2, 1, 1, 9]), 8)
def test_drop_sharply(self):
self.assertEqual(max_profit([1, 3, 0]), 2)
self.assertEqual(max_profit([1, 3, -1]), 2)
def test_bear_market(self):
self.assertEqual(max_profit([10, 11, 5, 7, 1, 2]), 2)
self.assertEqual(max_profit([10, 11, 1, 4, 2, 7, 5]), 6)
def test_bull_market(self):
self.assertEqual(max_profit([1, 5, 3, 7, 2, 14, 10]), 13)
self.assertEqual(max_profit([5, 1, 11, 10, 12]), 11)
if __name__ == '__main__':
unittest.main(exit=False)
Nov 2, 2020 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.
Solution with DFS: https://repl.it/@trsong/Solve-Frog-Jump-Problem
import unittest
def can_cross(stones):
stone_set = set(stones)
visited = set()
stack = [(0, 0)]
goal = stones[-1]
while stack:
stone, step = stack.pop()
if stone == goal:
return True
visited.add((stone, step))
for delta in [-1, 0, 1]:
next_step = step + delta
next_stone = stone + next_step
if next_stone >= stone and next_stone in stone_set and (next_stone, next_step) not in visited:
stack.append((next_stone, next_step))
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_fast_then_slow(self):
self.assertTrue(can_cross([0, 1, 3, 6, 10, 13, 15, 16, 16]))
def test_fast_then_cooldown(self):
self.assertFalse(can_cross([0, 1, 3, 6, 10, 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)
Nov 1, 2020 [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 Divide-and-Conquer: https://repl.it/@trsong/Solve-the-Tower-of-Hanoi-Problem
import unittest
def hanoi_moves(n):
res = []
def hanoi_moves_recur(n, src, dst):
if n <= 0:
return
bridge = 3 - src - dst
# use the unused rod as bridge rod
# Step1: move n - 1 disks from src to bridge to allow last disk move to dst
hanoi_moves_recur(n - 1, src, bridge)
# Step2: move last disk from src to dst
res.append((src, dst))
# Step3: move n - 1 disks from bridge to dst
hanoi_moves_recur(n - 1, bridge, dst)
hanoi_moves_recur(n, 0, 2)
return res
class HanoiMoveSpec(unittest.TestCase):
def assert_hanoi_moves(self, n, moves):
game = HanoiGame(n)
# Turn on verbose for debugging
self.assertTrue(game.can_moves_finish_game(moves, verbose=False))
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)
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, verbose=False):
self.reset()
for step, action in enumerate(actions):
src, dst = action
if verbose:
self.display()
print "Step %d: %d -> %d" % (step, src, dst)
if not self.is_feasible_move(src, dst):
return False
else:
self.move(src, dst)
if verbose:
self.display()
return self.is_game_finished()
def display(self):
for plates in self.rods:
print "- %s" % str(plates)
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))
if __name__ == '__main__':
unittest.main(exit=False)