12/30/2020

[LeetCode] 380. Insert Delete GetRandom O (1)

Problem : https://leetcode.com/problems/insert-delete-getrandom-o1/ 

The crux is using the last element of the list to replace the element needs to be removed to meet the requirement of O(1) deleting operation.


import random

class RandomizedSet:

    def __init__(self):
        """
        Initialize your data structure here.
        """
        
        self.dict = {}
        self.list = []
        

    def insert(self, val: int) -> bool:
        """
        Inserts a value to the set. Returns true if the set did not already contain the specified element.
        """
        
        res = val not in self.dict
        
        if res:
            self.dict[val] = len(self.list)
            self.list.append(val)
        return res

    def remove(self, val: int) -> bool:
        """
        Removes a value from the set. Returns true if the set contained the specified element.
        """
        res = val in self.dict
        if res:
            # use the last element to replace the removed element
            index = self.dict[val]
            del self.dict[val]
            
            if index != len(self.list) - 1:
                self.list[index] = self.list[-1]
        
                # update element index and popup the last element from list
                self.dict[self.list[index]] = index
                
            self.list.pop()
        return res
        

    def getRandom(self) -> int:
        """
        Get a random element from the set.
        """
        
        pick = random.randint(0, len(self.list) - 1)
        
        return self.list[pick]
        


# Your RandomizedSet object will be instantiated and called as such:
# obj = RandomizedSet()
# param_1 = obj.insert(val)
# param_2 = obj.remove(val)
# param_3 = obj.getRandom()

[LeetCode] 378. Kth Smallest Element in a Sorted Matrix

Problem : https://leetcode.com/problems/kth-smallest-element-in-a-sorted-matrix/

This problem equals to merge N sorted lists and find the kth element from the merged list.

Use heap to find small element from N sorted list.

Time complexity = O(K * Log N ), N = len(matrix)


class Solution:
    def kthSmallest(self, matrix: List[List[int]], k: int) -> int:
        heap = []
        
        for i in range(len(matrix)):
            heapq.heappush(heap, (matrix[i][0], i, 0))
        
        result = 0
        for _ in range(k):
            result, y, x = heapq.heappop(heap)
            
            x += 1
            if x < len(matrix[y]):
                heapq.heappush(heap, (matrix[y][x], y, x))
        
        return result

[LeetCode] 377. Combination Sum IV

 Problem : https://leetcode.com/problems/combination-sum-iv/

This quiz is similar to "Coin Change". 

Assume taking nums[i], find the combination for sum = target - nums[i].


Time complexity = O ( M * N ). M = target, N = len(nums)

Top-down solution:


class Solution:
    def combinationSum4(self, nums: List[int], target: int) -> int:
        N = len(nums)
        nums.sort()
        
        @lru_cache(maxsize = None)
        def helper(remain):
            if remain == 0: return 1
            
            result = 0
            
            for i in range(N):
                if nums[i] <= remain:
                    result += helper(remain - nums[i])
                else:
                    break
            
            return result

        return helper(target)

Bottom-up solution:


class Solution:
    def combinationSum4(self, nums: List[int], target: int) -> int:
        dp = [0] * (target + 1)
        dp[0] = 1
        
        nums.sort()
        
        for combo in range(1, target+1):
            for n in nums:
                if n > combo:
                    break
                    
                dp[combo] += dp[combo - n]
        
        return dp[target]

Follow-up question:

To allow negative number in the given array, we need to limit the ouput combination length. Otherwise, it won't be able to stop the searching.

[LeetCode] 376. Wiggle Subsequence

 Problem : https://leetcode.com/problems/wiggle-subsequence/


Intuitive DP solution:

dp[i][0] = length of the longest up-wiggle sequence ends by i-th number

dp[i][1] = length of the longest down-wiggle sequence ends by i-th number


Update dp[i][0] and dp[i][1] by comparing nums[i] with all numbers before it.

Time complexity = O ( N ** 2 )


class Solution:
    def wiggleMaxLength(self, nums: List[int]) -> int:
        N = len(nums)
        
        if N < 2: return N
        
        dp = [[0, 0] for _ in range(N)]
        dp[0][0] = dp[0][1] = 1
        
        result = 0
        
        for i in range(N):
            for j in range(i):
                if nums[i] > nums[j]:
                    dp[i][0] = max(dp[i][0], dp[j][1] + 1)
                elif nums[i] < nums[j]:
                    dp[i][1] = max(dp[i][1], dp[j][0] + 1)
            
            result = max(result, dp[i][0], dp[i][1])
        
        return result

Linear DP solution:

up[i] = length of the longest up-wiggle sequence in section nums[0:i+1].

down[i] = length of the longest down-wiggle sequence in section nums[0:i+1]

Be aware, the longest up / down wiggle sequence is not necessary ends by nums[i].


For every nums[i], there could be 3 cases:


When nums[i] == nums[i-1]: 

nums[i] cannot extend either previous up-wiggle or down-wiggle sequence,

up[i] = up[i-1], down[i] = down[i-1]


When nums[i] < nums[i-1]:

nums[i] can be appended to previous down-wiggle sequence  and create a longer up-wiggle sequence, up[i] = down[i-1] + 1

nums[i] cannot be appended to previous up-wiggle sequence to create a longer down-wiggle sequence.

So, length of the longest down-wiggle sequence in section nums[0:i+1] does not change.

down[i] = down[i-1]


When nums[i] > nums[i-1]:

nums[i] can be appended to previous up-wiggle sequence and create a longer down-wiggle sequence,

down[i] = up[i-1] + 1

nums[i] cannot be appended to previous down-wiggle sequence to create a longer up-wiggle sequence.

So, length of the longest up-wiggle sequence in section nums[0:i+1] does not change.

up[i] = up[i-1]


Time Complexity = O ( N )


class Solution:
    def wiggleMaxLength(self, nums: List[int]) -> int:
        N = len(nums)
        
        if N < 2: return N
        
        up = [0] * N
        down = [0] * N
        
        up[0] = down[0] = 1
        
        for i in range(1, N):
            if nums[i] > nums[i-1]:
                up[i] = down[i-1] + 1
                down[i] = down[i-1]
            elif nums[i] < nums[i-1]:
                down[i] = up[i-1] + 1
                up[i] = up[i-1]
            else:
                up[i] = up[i-1]
                down[i] = down[i-1]
        
        return max(up[-1], down[-1])

Because for i-th number, it only compares the previous number to update the up / down wiggle sequence of section nums[0:i+1].

It only needs 2 variables to the save the length of up-wiggle and down-wiggle sequence of section nums[0:i]


class Solution:
    def wiggleMaxLength(self, nums: List[int]) -> int:
        N = len(nums)
        
        if N < 2: return N

        up = down = 1
        
        for i in range(1, N):
            if nums[i] > nums[i-1]:
                up = down + 1
            elif nums[i] < nums[i-1]:
                down = up + 1

        return max(up, down)

[LeetCode] 375. Guess Number Higher or Lower II

 Problem: https://leetcode.com/problems/guess-number-higher-or-lower-ii/

Need to find the minimal cost among all worst scenarios ( wrong guesses ).

For a sorted section [low, high]:

If the pivot < low + (high - low) // 2, the worst scenario cost of left section always less than the cost of right section.  Because numbers in section are in ascending order and the left section has less numbers than right sections.

It is safe to start the pivot from the middle position ( low + (high - low) // 2 ).


class Solution:
    def getMoneyAmount(self, n: int) -> int:
        
        @lru_cache(maxsize = None)
        def helper(low, high):
            if low >= high:
                return 0
            
            result = 2**31 - 1 # MAX_INT
            for i in range(low + (high - low)//2, high+1):
                # cost of picking number i (wrong guess) + worst scenario one left / right sections
                cost = i + max(helper(low,i-1), helper(i+1,high))
                # minimal cost among all worst scenarios
                result = min(result, cost)
            
            return result
        
        return helper(1, n)

12/29/2020

[LeetCode] 374. Guess Number Higher or Lower

 Problem: https://leetcode.com/problems/guess-number-higher-or-lower/

Use binary search to locate the target number.

We can optimize the process by narrowing the window down as small as possible before start the binary search.

Time Complexity = O ( Log N )


# The guess API is already defined for you.
# @param num, your guess
# @return -1 if my number is lower, 1 if my number is higher, otherwise return 0
# def guess(num: int) -> int:

class Solution:
    def guessNumber(self, n: int) -> int:
        left, right = n, 1
        
        while guess(left) == -1:
            # left = left // 2
            left >>= 1 
        
        while guess(right) == 1:
            # right = right * 2
            right <<= 1 
        
        while left <= right:
            # mid = left + (right - left) // 2
            mid = left + ((right - left) >> 1)
            
            tmp = guess(mid)
            if tmp == 0: return mid
            if tmp == 1: left = mid + 1
            if tmp == -1: right = mid - 1
        
        return left

[LeetCode] 373. Find K Pairs with Smallest Sums

 Problem: https://leetcode.com/problems/find-k-pairs-with-smallest-sums/

Assume len(nums1) = M and len(nums2) = N.

nums1 and nums2 can form M sorted list and each list contains N elements.

This problem equals to merge M sorted list and output the first k elements.

The first element of each list is ( nums1[i],  nums2[0] ).  

Because nums1 is sorted list,  ( nums1[i],  nums2[0] ) is the smallest sum on each list.

To find the smallest sum among all list (global smallest sum), we put the smallest sum on each list into the heap.

Then popup the global smallest sum.   Then expend the heap with the next smallest sum of the list from where the current global smallest sum comes.


Time Complexity = O ( K * Log N ).  N = len(nums1)



class Solution:
    def kSmallestPairs(self, nums1: List[int], nums2: List[int], k: int) -> List[List[int]]:
        """
                1 ,    2 ,    3
            1  (1,1)  (1,2)  (1,3)
            
            1  (1,1)  (1,2)  (1,3)
            
            2  (2,1)  (2,2)  (2,3)
            
        
        """
        
        if k == 0 or not nums1 or not nums2: return []
        
        heap = []
        result = []
        
        # add the first element of all lines into heap
        for y in range(len(nums1)):
            heapq.heappush(heap, (nums1[y] + nums2[0], y, 0))
        
        while len(result) < k and heap:
            # pop current minimum sum from heap
            _, y, x = heapq.heappop(heap)
            result.append([nums1[y], nums2[x]])
            
            # push next sum combination into heap
            x += 1
            if x < len(nums2):
                heapq.heappush(heap, (nums1[y] + nums2[x], y, x))
        
        return result

[LeetCode] 372. Super Pow

 Problem: https://leetcode.com/problems/super-pow/


class Solution:
    def superPow(self, a: int, b: List[int]) -> int:
        """
        
        a = Ak + B
        b = Ck + D
        
        a * b = (Ak + B) * (Ck + D) = ACk + ADk + BCk + BD
        
        (a * b) % k = (ACk + ADk + BCk + BD) % k = BD % k
        
        a % k = B
        b % k = D
        
        (a * b) % k = (a % k) * (b % k) % k
        
        (a ** b) % k = (a * a * a .. a) % k 
                     = (a % k) * (a % k) ... * (a % k) % k
                     = (a % k) ** b % k
                     
        (a % k) % k =  a % k
        
        for a = 2, b = [1, 0]
        
        result = (2 ** (1 * 10 + 0)) % 1337 
               = (2 ** (1*10) * 2 ** 0) % 1337
               = (2 ** 1 ** 10 % 1337) * (2 ** 0  % 1337) % 1337
               = ((2 % 1337) ** 10 % 1337) * ((2 % 1337) ** 0 % 1337) % 1337
        """
        
        if not b: return 1
        
        e = b.pop()
        
        return self.myPow(self.superPow(a, b), 10) * self.myPow(a, e) % 1337 
    
    
    def myPow(self, base, exp):
        result = 1
        base = base % 1337
        
        for i in range(exp):
            result = (result * base) % 1337
            
        return result

12/27/2020

[LeetCode] 371. Sum of Two Integers

 Problem: https://leetcode.com/problems/sum-of-two-integers/


This quiz is a bit of easier to solve with C language.



class Solution {
public:
    int getSum(int a, int b) {
        int c = 0;
        int carry = 0;
        
        for (int i = 0; i < 32; i ++) {
            int aa = (a >> i) & 1;
            int bb = (b >> i) & 1;
            int cc = aa ^ bb ^ carry;
                    
            carry = (aa + bb + carry) / 2;
            c = c | (cc << i);
        }
        
        return c;
    }
};

The Python solution.


class Solution:
    def getSum(self, a: int, b: int) -> int:
        c = 0
        carry = 0    
        
        for i in range(32):
            x = (a >> i) & 1
            y = (b >> i) & 1
           
            z = x ^ y ^ carry
            
            carry = x & y or x & carry or y & carry
            
            c = (z << i) ^ c
        
        return (c ^ 0x80000000) - 0x80000000

12/24/2020

[LeetCode] 368. Largest Divisible Subset

 Problem : https://leetcode.com/problems/largest-divisible-subset/

For a valid subset in ascending order [A, B, C, D], if the largest number D can be divided by the Number e, then the subset [A, B, C, D, e] is still a valid subset.

Time Complexity = O ( N ** 2)



class Solution:
    def largestDivisibleSubset(self, nums: List[int]) -> List[int]:
        N = len(nums)
        
        # edge cases when nums is empty or singleton list
        if N <= 1: return nums
        
        # sort the input nums first to form an ascending list
        nums.sort()
        
        # dp[i] : valid subset ends by i-th num
        # dp[i][0] = predecessor of i-th num
        # dp[i][1] = max length of the valid subset ends by i-th num
        dp = [[-1,0] for _ in range(N)]
        
        subsetLen = 0
        subsetEnd = 0
        
        for i in range(N):
            for j in range(i):
                if nums[i] % nums[j] == 0:
                    if dp[i][1] < dp[j][1] + 1:
                        dp[i][1] = dp[j][1] + 1
                        dp[i][0] = j
                    
                        if dp[i][1] > subsetLen:
                            subsetEnd = i
                            subsetLen = dp[i][1]
        
        result = []
        
        # iterate back to gather all elements of the valid subset
        while subsetEnd != -1:
            result.append(nums[subsetEnd])
            subsetEnd = dp[subsetEnd][0]
        
        return result[::-1]
        

11/25/2020

[LeetCode] 367. Valid Perfect Square

 Problem : https://leetcode.com/problems/valid-perfect-square/

Generally, for number N, it's square root is in the range of [1, N)

Use binary search to find the square root.


class Solution:
    def isPerfectSquare(self, num: int) -> bool:
        
        left, right = 1, num
        
        while left <= right:
            
            mid = left + (right - left) // 2
            
            if mid * mid == num:
                return True
            
            if mid * mid > num:
                right = mid - 1
            else:
                left = mid + 1
        
        
        return False

[LeetCode] 365. Water and Jug Problem

 Problem : https://leetcode.com/problems/water-and-jug-problem/

This problem equals to consider there is a large container, use the 2 jugs to pour water into the container or take water out of the container, and end up with z liters water in the container.

a * x + b * y = z

Positive a / b means pour water into the container.

Negative a / b means take water out of the container.


According to Bézout's identity :  a * x + b * y =  GCD( x, y )

GCD : greatest common divisor of x and y.


So, we can get the needed liters of water when GCD( x, y ) % z == 0.


class Solution:
    def canMeasureWater(self, x: int, y: int, z: int) -> bool:
        
        def gcd(a, b):
            if a != 0:
                return gcd(b%a, a)
            else:
                return b

        if x > y:
            x, y = y, x
            
        
        return z == 0 or z <= (x + y) and y > 0 and z % gcd(x, y) == 0

11/23/2020

[LeetCode] 363. Max Sum of Rectangle No Larger Than K

 Problem : https://leetcode.com/problems/max-sum-of-rectangle-no-larger-than-k/


class Solution:
    def maxSumSubmatrix(self, matrix: List[List[int]], k: int) -> int:
        if not matrix or not matrix[0]: return 0
        
        ROW = len(matrix)
        COLUMN = len(matrix[0])
        result = -2 ** 31
        
        for left in range(COLUMN):
            rowSums = [0] * ROW
            
            for right in range(left, COLUMN):
                
                for i in range(ROW):
                    rowSums[i] += matrix[i][right]
                    
                # find the max subarry no more than k
                dp = [0]
                total = 0

                for rsum in rowSums:
                    total += rsum

                    i = bisect.bisect_left(dp, total - k)
                    if i < len(dp): 
                        result = max(result, total - dp[i])

                    bisect.insort(dp, total)
            
        return result

11/18/2020

[LeetCode] 357. Count Numbers with Unique Digits

 Problem : https://leetcode.com/problems/count-numbers-with-unique-digits/

The input number N means the maximum number of digits we can have for the wanted numbers.

Let F(N) = valid number of unique digits combination for N digits

F(0) = 0. Wanted number = 0. Total number = 1

F(1) = 1. Wanted number = [0 ... 9].   Total number = 10

F(2) =  9 * 9 + F (1) = 91.   The first digit has 9 options from 1 to 9. The second digit has 9 options from 0 to 9 ( excluding the digit used by the first digits)

F(3) = 9 * 9 * 8 + F(2) + F(1)


class Solution:
    def countNumbersWithUniqueDigits(self, n: int) -> int:
        if n == 0:
            return 1
        
        if n == 1:
            return 10
        
        result = 9
        digits = 9
        
        # calculate valid combiations of N digits
        for _ in range(1,n):
            result *= digits
            digits -= 1
        
        return result + self.countNumbersWithUniqueDigits(n-1)

11/17/2020

[LeetCode] 335. Design Twitter

 Problem : https://leetcode.com/problems/design-twitter/

- Tweet be posted later may have smaller tweetId. So we need to save the time when the tweet be post.

- Should not allow user to follow themselves.

- Should not allow user to unfollow not-followed user.


from operator import itemgetter

class Twitter:

    def __init__(self):
        """
        Initialize your data structure here.
        """
        self.timer = 1
        self.follows = defaultdict(set)
        self.tweets = defaultdict(list)

    def postTweet(self, userId: int, tweetId: int) -> None:
        """
        Compose a new tweet.
        """
        self.tweets[userId].append((self.timer, tweetId))
        self.timer += 1
        

    def getNewsFeed(self, userId: int) -> List[int]:
        """
        Retrieve the 10 most recent tweet ids in the user's news feed. Each item in the news feed must be posted by users who the user followed or by the user herself. Tweets must be ordered from most recent to least recent.
        """
        
        allTweets = self.tweets[userId][:]
        
        for f in self.follows[userId]:
            allTweets.extend(self.tweets[f])
            
        
        allTweets.sort(reverse = True, key = itemgetter(0))
        allTweets = allTweets if len(allTweets) <= 10 else allTweets[:10]
        
        return [tweetId for _, tweetId in allTweets]
                

    def follow(self, followerId: int, followeeId: int) -> None:
        """
        Follower follows a followee. If the operation is invalid, it should be a no-op.
        """
        
        if followerId != followeeId:
            self.follows[followerId].add(followeeId)
        

    def unfollow(self, followerId: int, followeeId: int) -> None:
        """
        Follower unfollows a followee. If the operation is invalid, it should be a no-op.
        """
        
        if followerId != followeeId and followeeId in self.follows[followerId]:
            self.follows[followerId].remove(followeeId)
        


# Your Twitter object will be instantiated and called as such:
# obj = Twitter()
# obj.postTweet(userId,tweetId)
# param_2 = obj.getNewsFeed(userId)
# obj.follow(followerId,followeeId)
# obj.unfollow(followerId,followeeId)

11/15/2020

[LeetCode] 354. Russian Doll Envelopes

 Problem : https://leetcode.com/problems/russian-doll-envelopes/

This problem equals to find the length of longest increasing subsequence on 2 dimensions.

1. Sort the array. Ascending on width and descend on height for same width.

Because [3,3] cannot be contained by [3, 4], we need to put [3,4] in front of [3,3]. Otherwise,  "[3,3], [3,4]" will be considered as a valid increasing subsequence.


2.  Find the longest increasing subsequence base on height.

Since width of envelop has been sorted in increasing order, we only need to consider height.

Time Complexity = O ( N Log N )


from functools import cmp_to_key

class Solution:
    def maxEnvelopes(self, envelopes: List[List[int]]) -> int:
        
        def lowerBound(dp, n):
            left, right = 0, len(dp)
            
            while left < right:
                mid = left + (right - left) // 2
                
                if dp[mid] == n:
                    right = mid
                elif dp[mid] > n:
                    right = mid
                elif dp[mid] < n:
                    left = mid + 1
            
            return left
        
        def compare(a, b):
            return a[0] - b[0] if a[0] != b[0] else b[1] - a[1]
        
        envelopes.sort(key=cmp_to_key(compare))
        
        dp = []
        
        for _, h  in envelopes:
            i = lowerBound(dp, h)
            
            if i == len(dp):
                dp.append(h)
            else:
                dp[i] = h
        
        return len(dp)

11/13/2020

[LeetCode] 352. Data Stream as Disjoint Intervals

 Problem : https://leetcode.com/problems/data-stream-as-disjoint-intervals/

Use binary search to find the insertion point. Then merge the previous and next intervals.


class SummaryRanges:

    def __init__(self):
        """
        Initialize your data structure here.
        """
        
        self.intervals = []

    def addNum(self, val: int) -> None:
        
        left, right = 0, len(self.intervals)
        
        while left < right:
            mid = left + (right -left) // 2
            
            tmp = self.intervals[mid]
            
            if tmp[0] <= val <= tmp[1]:
                return
            
            if tmp[0] > val:
                right = mid
            elif tmp[1] < val:
                left = mid + 1
        
        self.intervals.insert(left, [val, val])
        
        cur = self.intervals[left]
        pre = self.intervals[left-1] if left -1 >= 0 else None
        nxt = self.intervals[left+1] if left + 1 < len(self.intervals) else None
        
        if pre and pre[1] + 1 == cur[0]:
            pre[1] = cur[0]
            cur = pre
            self.intervals.pop(left)
            left -= 1
        
        if nxt and cur[1] + 1 == nxt[0]:
            cur[1] = nxt[1]
            self.intervals.pop(left + 1)
            

    def getIntervals(self) -> List[List[int]]:
            return self.intervals

[LeetCode] 350. Intersection of Two Arrays II

Problem : https://leetcode.com/problems/intersection-of-two-arrays-ii/

2 pointers solution:  


class Solution:
    def intersect(self, nums1: List[int], nums2: List[int]) -> List[int]:
        nums1.sort()
        nums2.sort()
        
        result = []
        i = j = 0
        
        while i < len(nums1) and j < len(nums2):
            if nums1[i] < nums2[j]:
                i += 1
            elif nums1[i] > nums2[j]:
                j += 1
            else:
                result.append(nums1[i])
                i += 1
                j += 1
        
        return result

When elements are stored on disk, use external sort algorithm to sort items first. Then use this 2 pointers approach to find the intersection.

Hash table based solutin:


class Solution:
    def intersect(self, nums1: List[int], nums2: List[int]) -> List[int]:
        counter1 = Counter(nums1)
        counter2 = Counter(nums2)
        
        result = []
        
        for k, v1 in counter1.items():
            v2 = counter2.get(k, 0)
            
            if min(v1, v2):
                result += [k] * min(v1, v2)
        
        return result

Edited on 09/17/2021. Update a simpler hash table based solution.

[LeetCode] 349. Intersection of Two Arrays

 Problem : https://leetcode.com/problems/intersection-of-two-arrays/

Binary search solution: 


class Solution:
    def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
        
        def bsearch(nums, target):
            left, right = 0, len(nums)
            
            while left < right:
                mid = left + (right - left) // 2
                if nums[mid] == target:
                    return True
                if nums[mid] < target:
                    left = mid + 1
                else:
                    right = mid
            
            return False
        
        result = []
        
        if len(nums1) > len(nums2) : nums1, nums2 = nums2, nums1
        
        nums1.sort()
        nums2.sort()
        
        for i, n in enumerate(nums1):
            if i - 1 >= 0 and nums1[i] == nums1[i-1]:
                continue
            
            if bsearch(nums2, n):
                result.append(n)
            
        
        return result

2 pointers solution:


class Solution:
    def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
        nums1.sort()
        nums2.sort()
        
        result = []
        
        i = j = 0
        
        while i < len(nums1) and j < len(nums2):
            if nums1[i] < nums2[j]:
                i += 1
            elif nums1[i] > nums2[j]:
                j += 1
            else:
                if not result or result[-1] != nums1[i]:
                    result.append(nums1[i])
                i += 1
                j += 1
        
        return result

Hash table based solution:


class Solution:
    def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
        seen = defaultdict(lambda : [0, 0])
        
        for n in nums1:
            seen[n][0] += 1
        
        for n in nums2:
            seen[n][1] += 1
            
        
        return filter(lambda n : seen[n][0] >= 1 and seen[n][1] >= 1, seen.keys())

11/11/2020

[LeetCode] 347. Top K Frequent Elements

 Problem : https://leetcode.com/problems/top-k-frequent-elements/

Use hash map to save count of each elements. 

Then use heap to get first k element .

Time Complexity = O ( N Log k )


class Solution:
    def topKFrequent(self, nums: List[int], k: int) -> List[int]:
        if k > len(nums): return nums
        
        count = Counter(nums)
        
        return heapq.nlargest(k, count.keys(), key=count.get)

[LeetCode] 345. Reverse Vowels of a String

 Problem : https://leetcode.com/problems/reverse-vowels-of-a-string/


class Solution:
    def reverseVowels(self, s: str) -> str:
        vowels = set(['a', 'A', 'e', 'E', 'i', 'I', 'o', 'O', 'u', 'U'])
        result = [w for w in s]
        
        i, j = 0, len(s)-1
        
        while i <= j:
            if result[i] not in vowels:
                i += 1
            elif result[j] not in vowels:
                j -= 1
            else:
                result[i], result[j] = s[j], s[i]
                i += 1
                j -= 1
           
        return "".join(result)

[LeetCode] 344. Reverse String

 Problem : https://leetcode.com/problems/reverse-string/

An uncommon 2 pointers approach.



class Solution:
    def reverseString(self, s: List[str]) -> None:
        """
        Do not return anything, modify s in-place instead.
        """
        N = len(s)
        for i in range(N//2):
            j = N - i - 1
            s[i], s[j] = s[j], s[i]

[LeetCode] 343. Integer Break

 Problem : https://leetcode.com/problems/integer-break/

For each number i,  it can be broken into at least  j,  (i-j).  j = [1 ... i -1]

Let DP[i] = maximum product of number i when i be broken into at least 2 integers.

DP[i] = Max( j * (i-j),  j * DP[i-j] ),    j = [ 1 ... i - 1 ]


class Solution:
    def integerBreak(self, n: int) -> int:
        dp = [1] * (n+1)
        
        for i in range(3, n+1):
            for j in range(1, i):
                dp[i] = max(dp[i], max(j* (i-j),  j *dp[i-j]))
        
        return dp[n]

Another memorization approach:


class Solution {
    int[] memo;
    
    public int integerBreak(int n) {
        memo = new int[n+1];
        memo[1] = 1;
        
        return helper(n);
    }
    
    int helper(int n) {
        if (memo[n] != 0) {
            return memo[n];
        }
        
        for (int x = 1; x <= n / 2; x++) {
            
            // i.e. For number 3,  helper(3) = 1 * 2 = 2. 
            // 3 > helper(3).  we need to use 3 instead of helper(3) to get the larger product.
            memo[n] = Math.max(memo[n], Math.max(x, helper(x)) * Math.max(n-x, helper(n-x)));
        } 
        
        return memo[n];
    }
}

Edited on 11/26/2021. Update the top-down solution.

11/08/2020

[LeetCode] 342. Power of Four

 Problem : https://leetcode.com/problems/power-of-four/

A recursive solution :


class Solution:
    def isPowerOfFour(self, num: int) -> bool:
        if num == 0: return False
        return num == 1 or num % 4 == 0 and self.isPowerOfFour(num // 4)

A math solution:

If N is power of 4, Log(N, base=4) is an integer.


class Solution:
    def isPowerOfFour(self, num: int) -> bool:
        return num > 0 and log(num, 4) % 1 == 0

[LeetCode] 341. Flatten Nested List Iterator

 Problem : https://leetcode.com/problems/flatten-nested-list-iterator/

Use stack to cache the expanded lists.

Must filter out the empty nested lists in hasNext() method.


# """
# This is the interface that allows for creating nested lists.
# You should not implement it, or speculate about its implementation
# """
#class NestedInteger:
#    def isInteger(self) -> bool:
#        """
#        @return True if this NestedInteger holds a single integer, rather than a nested list.
#        """
#
#    def getInteger(self) -> int:
#        """
#        @return the single integer that this NestedInteger holds, if it holds a single integer
#        Return None if this NestedInteger holds a nested list
#        """
#
#    def getList(self) -> [NestedInteger]:
#        """
#        @return the nested list that this NestedInteger holds, if it holds a nested list
#        Return None if this NestedInteger holds a single integer
#        """

class NestedIterator:
    def __init__(self, nestedList: [NestedInteger]):
        self.stack = []
        for n in reversed(nestedList):
            self.stack.append(n)
    
    def next(self) -> int:
        return self.stack.pop().getInteger()
        
    def hasNext(self) -> bool:
        while self.stack and not self.stack[-1].isInteger():
            # expand non-empty nested list
            nestedList = self.stack.pop().getList()
            if nestedList:
                for n in reversed(nestedList):
                    self.stack.append(n)

        return self.stack
         

# Your NestedIterator object will be instantiated and called as such:
# i, v = NestedIterator(nestedList), []
# while i.hasNext(): v.append(i.next())

Another approach of using stack. It's similar to how compiler maintains call-stack.


# """
# This is the interface that allows for creating nested lists.
# You should not implement it, or speculate about its implementation
# """
#class NestedInteger:
#    def isInteger(self) -> bool:
#        """
#        @return True if this NestedInteger holds a single integer, rather than a nested list.
#        """
#
#    def getInteger(self) -> int:
#        """
#        @return the single integer that this NestedInteger holds, if it holds a single integer
#        Return None if this NestedInteger holds a nested list
#        """
#
#    def getList(self) -> [NestedInteger]:
#        """
#        @return the nested list that this NestedInteger holds, if it holds a nested list
#        Return None if this NestedInteger holds a single integer
#        """

class NestedIterator:
    def __init__(self, nestedList: [NestedInteger]):
        self.stack = [[nestedList, 0]]
    
    def next(self) -> int:
        nlist, nindex = self.stack[-1]
        
        self.stack[-1][1] += 1
        return nlist[nindex].getInteger()
        
    
    def hasNext(self) -> bool:     
        while self.stack:
            nlist, nindex = self.stack[-1]

            if nindex == len(nlist):
                self.stack.pop()
                continue

            if nlist[nindex].isInteger():
                break

            self.stack[-1][1] += 1
            childList = nlist[nindex].getList()
            if childList:
                self.stack.append([childList, 0])
        
        return self.stack
         

# Your NestedIterator object will be instantiated and called as such:
# i, v = NestedIterator(nestedList), []
# while i.hasNext(): v.append(i.next())

Edited on 04/13/2021. Add the second stack based approach.

[LeetCode] 338. Counting Bits

 Problem : https://leetcode.com/problems/counting-bits/

A naive solution:

Time complexity = O (n * sizeof(integer))  


class Solution:
    def countBits(self, num: int) -> List[int]:
        def helper(n):
            result = 0
            while n:
                result += n & 1
                n >>= 1
            
            return result
        
        return [helper(n) for n in range(num+1)]

A DP solution:


class Solution:
    def countBits(self, num: int) -> List[int]:
        result = [0] * (num + 1)
        if num >= 1:
            result[1] = 1
            
            for i in range(2, num+1):
                if i & 1 == 0:
                    # if i is even number
                    result[i] = result[i//2]
                else:
                    # if i is odd number
                    result[i] = result[i//2] + 1
        
        return result

[LeetCode] 337. House Robber III

 Problem : https://leetcode.com/problems/house-robber-iii/

Each level of the tree has 2 state :   safe or  not-safe.

If current level is safe to rob, thief can choose rob current level then next level is not safe to rob. Or skip current level, then next level is safe to rob.

Use memorization approach to find the answer.


# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def rob(self, root: TreeNode) -> int:
        
        @lru_cache(maxsize = None)
        def helper(node, safe):
            result = 0
            
            if node:
                if safe:
                    result = node.val + helper(node.left, not safe) + helper(node.right, not safe)
                    result = max(result, helper(node.left, safe) + helper(node.right, safe))
                else:
                    result = helper(node.left, not safe) + helper(node.right, not safe)
                
            return result
        
        return max(helper(root, True), helper(root, False))

[LeetCode] 336. Palindrome Pairs

 Problem : https://leetcode.com/problems/palindrome-pairs/

There are 3 cases can form a palindrome string.

1)  ABC + CBA

2)  CBA + [A-Palindrome-String] ABC 

3)  ABC[A-Palindrome-String]  + CBA   


class Solution:
    def palindromePairs(self, words: List[str]) -> List[List[int]]:
        
        def isPalindrome(w, left, right):
            while left < right:
                if w[left] != w[right]: return False
                left += 1
                right -= 1
            
            return True
        
        
        result = []
        seen = { k : v for v, k in enumerate(words) }
        lens = sorted({len(w) for w in words})
        
        for i, w in enumerate(words):
            # rw = reversed 'w'
            rw = w[::-1]
            
            # case abc -- cba
            if rw in seen and seen[rw] != i:
                result.append([i, seen[rw]])
                
            l = 0
            while lens[l] < len(rw):
                # case cba  -- ddabc                
                if isPalindrome(rw, 0, len(rw) - lens[l] - 1) and \
                   rw[len(w) - lens[l]:] in seen:
                    
                    result.append([i, seen[rw[len(w) - lens[l]:]]])
                # case abcdd -- cba
                if isPalindrome(rw, lens[l], len(rw)-1) and \
                   rw[:lens[l]] in seen:
                    result.append([seen[rw[:lens[l]]], i])
                
                l += 1
        
        return result

Use Trie to find possible suffix.


class TrieNode:
    def __init__(self, val):
        self.val = val
        self.index = -1
        self.nodes = {}


class Trie:
    def __init__(self):
        self.root = TrieNode('')
    
    def add(self, index, word):
        if not word:
            self.root.index = index
        else:
            p = self.root
            
            for w in word:
                if w in p.nodes:
                    p = p.nodes[w]
                else:
                    node = TrieNode(w)
                    p.nodes[w] = node
                    p = node
                    
            p.index = index
        
def isPalindrome(word):
    if not word: return True
    
    left, right = 0, len(word)-1
    
    while left < right and word[left] == word[right]:
        left += 1
        right -=1 
    
    return left >= right

class Solution:
    def palindromePairs(self, words: List[str]) -> List[List[int]]:
        trie = Trie()
        
        for index, word in enumerate(words):
            trie.add(index, word[::-1])
        
        result = []
        
        for index, word in enumerate(words):
            i = 0
            p = trie.root
                        
            # dfs
            stack = [(p, "")]
            
            while stack:
                p, suffix = stack.pop()
                
                if p.index != -1 and p.index != index and isPalindrome(word[i:] + suffix):
                    result.append((index, p.index))
                
                if not suffix and i < len(word) and word[i] in p.nodes:
                    stack.append((p.nodes[word[i]], ""))
                    i += 1
                else:
                    for n in p.nodes.values():
                        stack.append((n, n.val + suffix))
        return result

Edited on 06/13/2021. Add the Trie based solution.

11/07/2020

[LeetCode] 335. Self Crossing

 Problem : https://leetcode.com/problems/self-crossing/

There are only 3 cases can lead self-crossing.


class Solution:
    def isSelfCrossing(self, x: List[int]) -> bool:
        N = len(x)
        
        for i in range(N):
            # 4th line >= 2nd line and 1st line >= 3rd line
            if i >= 3 and x[i] >= x[i-2] and x[i-3] >= x[i-1]:
                return True
            
            # 2nd line == 4th line and 5th line >= 3rd line - 1st line
            if i >= 4 and x[i-1] == x[i-3] and x[i] >= x[i-2] - x[i-4]:
                return True
            
            # 4th line >= 2nd line and 
            # 3rd line >= 5th line and 
            # 5th line >= 3rd line - 1st line
            # and 6th line >= 4th line - 2nd line
            
            if i >= 5 and x[i-2] >= x[i-4] and \
               x[i-3] >= x[i-1] and \
               x[i-1] >= x[i-3] - x[i-5] and \
               x[i] >= x[i-2] - x[i-4]:
                return True
        
        return False

10/26/2020

[LeetCode] 334. Increasing Triplet Subsequence

Problem : https://leetcode.com/problems/increasing-triplet-subsequence/

Use 2 caches to save the minimal number from left and the maximal number from right of each position.

Return true when minimal_from_left < nums[i] < maximal_from_right.

Time Complexity = O ( N )

Space Complexity = O ( 2 * N )


class Solution:
    def increasingTriplet(self, nums: List[int]) -> bool:
        if len(nums) < 3:
            return False
        
        mi = [0] * len(nums)
        mx = [0] * len(nums)
        
        mi_so_far = nums[0]
        for i in range(len(nums)):
            mi_so_far = min(mi_so_far, nums[i])
            mi[i] = mi_so_far
        
        mx_so_far = nums[-1]
        for i in reversed(range(len(nums))):
            mx_so_far = max(mx_so_far, nums[i])
            mx[i] = mx_so_far
        
        for i in range(len(nums)):
            if mi[i] < nums[i] < mx[i]:
                return True
        
        return False

Space Complexity O(1) Solution:

Use 2 pointers m1, m2 to save number with smaller value.

It finds the increasing triplet when find a number larger than both m1 and m2.

 
class Solution:
    def increasingTriplet(self, nums: List[int]) -> bool:
        if len(nums) < 3:
            return False
        
        m1 = m2 = 2 ** 31 - 1 #MAX_INT
        
        for n in nums:
            if m1 >= n : 
                m1 = n
            elif m2 >= n :
                # find m2 > m1
                m2 = n
            else:
                # find m3 > m2 > m1
                return True
        
        return False

[LeetCode] 332. Reconstruct Itinerary

 Problem : https://leetcode.com/problems/reconstruct-itinerary/

Consider each city is a vertex in a directed graph, then every ticket is an edge between 2 vertices.

Use one hash table to track all available tickets. ( Notice : There could be duplicated tickets between 2 cities )

Then use DFS to find the possible path between city "JFK" to the ending city.

DFS ends when number of city = total number of tickets  + 1


class Solution:
    def findItinerary(self, tickets: List[List[str]]) -> List[str]:
        graph = defaultdict(list)
        allTickets = defaultdict(int)
        
        for src, dst in tickets:
            graph[src].append(dst)
            allTickets[(src,dst)] += 1
        
        for src in graph.keys():
            graph[src].sort()
        
        
        def dfs(src, path):
            if len(path) == len(tickets) + 1:
                return path
            
            for dst in graph[src]:
                if allTickets[(src,dst)] > 0:
                    allTickets[(src,dst)] -= 1
                    
                    tmp = dfs(dst, path + [dst])
                    if tmp:
                        return tmp
                    
                    allTickets[(src,dst)] += 1
                    
            return None
        
    
        return dfs("JFK", ["JFK"])

[LeetCode] 331. Verify Preorder Serialization of a Binary Tree

 Problem : https://leetcode.com/problems/verify-preorder-serialization-of-a-binary-tree/

Re-create node list by splitting the input string with comma.

Then iterate the node list with pre-order traversal process.

Validate the tree node during traversal.


class Solution:
    def isValidSerialization(self, preorder: str) -> bool:
        nodes = preorder.split(',')
        
        if not nodes : return True
        
        def helper(i):
            if i >= len(nodes):
                return (i, False)
            
            if nodes[i] == '#':
                return (i, True)
            
            i, result = helper(i+1)
            if not result:
                return (i, False)
            
            i, result = helper(i+1)
            if not result:
                return (i, False)
            
            return (i, True)
        
        i, result = helper(0)
        
        return i == len(nodes) - 1 and result

[LeetCode] 329. Longest Increasing Path in a Matrix

 Problem : https://leetcode.com/problems/longest-increasing-path-in-a-matrix/


class Solution:
    def longestIncreasingPath(self, matrix: List[List[int]]) -> int:
        ROW = len(matrix)
        COLUMN = len(matrix[0])
        
        diff = ((0,1), (0,-1), (1,0), (-1,0))
        
        @cache
        def helper(y, x):
            """
            Longest increasing path starts from matrix[y][x]
            """
            result = 1
            for dx, dy in diff:
                nx = x + dx
                ny = y + dy
                if 0 <= nx < COLUMN and 0 <= ny < ROW and matrix[y][x] < matrix[ny][nx]:
                    # increasing sequence can only be built with one direction
                    # no need to mark the visited positions
                    result = max(result, 1 + helper(ny, nx))
            
            return result
        
        result = 1
        for y in range(ROW):
            for x in range(COLUMN):
                result = max(result, helper(y,x))
        return result

Edited on 04/10/2021. Use @cache annotation.

10/22/2020

[LeetCode] 328. Odd Even Linked List

 Problem : https://leetcode.com/problems/odd-even-linked-list/

Use dummy list to save odd and even node separately:


/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode oddEvenList(ListNode head) {
        ListNode odd = new ListNode();
        ListNode even = new ListNode();
        
        ListNode p = head;
        ListNode po = odd;
        ListNode pe = even;
        
        int count = 1;
        
        while (p != null) {
            if (count++ % 2 == 1) {
                po.next = p;
                po = po.next;
            } else {
                pe.next = p;
                pe = pe.next;
            }
            
            p = p.next;
        }
        
        po.next = even.next;
        pe.next = null;
        
        return odd.next;
    }
}

Edited on 12/01/2021. Replaced with iterative solution.

10/21/2020

[LeetCode] 327. Count of Range Sum

 Problem : https://leetcode.com/problems/count-of-range-sum/

Time Complexity = O ( N ** 2 ). Time Limit Exceeded.


class Solution:
    def countRangeSum(self, nums: List[int], lower: int, upper: int) -> int:
        N = len(nums)
        
        sums = [0] * (N+1)
        
        for i in range(N):
            sums[i+1] = sums[i] + nums[i]
        
        result = 0
        for i in range(N+1):
            for j in range(i+1, N+1):
                if lower <= sums[j] - sums[i] <= upper:
                    result += 1
        
        return result

Count and merge sort solution:


class Solution:
    def countRangeSum(self, nums: List[int], lower: int, upper: int) -> int:
        
        def countAndMergeSort(sums, start, end):
            if end - start <= 1: return 0
            mid = start + (end - start) // 2
            
            count = countAndMergeSort(sums, start, mid) + countAndMergeSort(sums, mid, end)
            
            j = k = t = mid
            cache = [0] * (end - start)
            r = 0
            
            for i in range(start, mid):
                while k < end and sums[k] - sums[i] < lower: 
                    k += 1
                while j < end and sums[j] - sums[i] <= upper: 
                    j += 1
                while t < end and sums[t] < sums[i]:
                    cache[r] = sums[t]
                    r += 1
                    t += 1
    
                cache[r] = sums[i]
                r += 1
            
                count += j - k
            
            r = 0
            for i in range(start, t):
                sums[i] = cache[r]
                r += 1
            
            return count
            
        
        
        
        N = len(nums)
        
        sums = [0] * (N+1)    
        for i in range(N):
            sums[i+1] = sums[i] + nums[i]
        
    
        return countAndMergeSort(sums, 0, len(sums))
                
       

[LeetCode] 326. Power of Three

 Problem : https://leetcode.com/problems/power-of-three/

A naive solution:


class Solution:
    def isPowerOfThree(self, n: int) -> bool:
        if n <= 0:
            return False
        
        if n == 1:
            return True
        
        return n % 3 == 0 and self.isPowerOfThree(n // 3)


A math solution:


class Solution:
    def isPowerOfThree(self, n: int) -> bool:
        return n > 0 and 3 ** (math.log2(n) // math.log2(3)) == n

Edited on 05/04/2021. Update the math solution.

[LeetCode] 324. Wiggle Sort II

 Problem : https://leetcode.com/problems/wiggle-sort-ii/


class Solution:
    def wiggleSort(self, nums: List[int]) -> None:
        """
        Do not return anything, modify nums in-place instead.
        """
        
        tmp = sorted(nums)
        N = len(tmp)
        
        i, j = ((N + 1) // 2) - 1, len(tmp) - 1
        k = 0
        
        while k < N:
            nums[k] = tmp[i]
            k += 1
            i -= 1
            
            if k == len(tmp):
                break
                
            nums[k] = tmp[j]
            k += 1
            j -= 1

[LeetCode] 322. Coin Change

Problem : https://leetcode.com/problems/coin-change/

Time Complexity = O ( M * N ),   M = amount,  N = len(coins).

Top-down Solution :


class Solution:
    def coinChange(self, coins: List[int], amount: int) -> int:
        
        MAX_INT = 2 ** 31 - 1
        N = len(coins)
        coins.sort()
        
        @lru_cache(maxsize = None)
        def helper(amount):
            """
            Fewest number of coins needed
            to make up this amount.
            """
            if amount == 0:
                return 0
            
            result = MAX_INT
            
            for i in range(N):
                if coins[i] > amount:
                    break
                
                tmp = helper(amount - coins[i])
                if tmp != -1:
                    result = min(result, tmp + 1)
            
            return result if result != MAX_INT else -1
        
        
        return helper(amount)

Bottom-up Solution:


class Solution:
    def coinChange(self, coins: List[int], amount: int) -> int:
        # dp[n] = Fewest number of coins needed to make up amount n
        dp = [amount+1] * (amount + 1)
        dp[0] = 0
        
        coins.sort()
        
        for n in range(1, amount+1):
            for coin in coins:
                if coin > n:
                    break
                
                dp[n] = min(dp[n], dp[n-coin] + 1)
        
        return dp[-1] if dp[-1] != amount + 1 else -1

[LeetCode] 321. Create Maximum Number

 Problem : https://leetcode.com/problems/create-maximum-number/


class Solution:
    def maxNumber(self, nums1: List[int], nums2: List[int], k: int) -> List[int]:
        
        def monotoneDecreasingSequence(nums, size):
            if size <= 0: return []
            
            dropCount = len(nums) - size
            
            result = []
            
            for n in nums:
                while result and result[-1] < n and dropCount > 0:
                    result.pop()
                    dropCount -= 1
                    
                result.append(n)
            
            return result[:size]
    
    
        def merge(n1, n2):
            i = j = 0
            result = []
            while i < len(n1) or j < len(n2):
                if i < len(n1) and j < len(n2):
                    # important! compare the rest numbers in lexical 
                    if n1[i:] > n2[j:]:
                        result.append(n1[i])
                        i += 1
                    else:
                        result.append(n2[j])
                        j += 1
                elif i < len(n1):
                    result.append(n1[i])
                    i += 1
                else:
                    result.append(n2[j])
                    j += 1
            
            return result
        
        result = []
        for i in range(max(0, k - len(nums2)), min(k, len(nums1)) +1):
            mds1 = monotoneDecreasingSequence(nums1, i)
            mds2 = monotoneDecreasingSequence(nums2, k - i)
            merged = merge(mds1, mds2)
            
            result = max(result, merged)
        
        return result

10/18/2020

[LeetCode] 319. Bulb Switcher

Problem : https://leetcode.com/problems/bulb-switcher/

A naive solution ( Time Limit Exceeded ).  Use bits to represent bulbs.


class Solution:
    def bulbSwitch(self, n: int) -> int:
        if n <= 0:
            return 0
        
        # turn on all bulbs
        bits = [0xFFFFFFFF] * (n // 32 + 1)
        
        for i in range(1, n):
            for j in range(i, n, i+1):
                # toggle bulbs
                bits[j//32] ^= 1 << (j%32)
        
        # count "ON" bulbs
        result = 0
        for i in range(n):
            if bits[i//32] & (1 << (i%32)):
                result += 1
        
        return result

Math solution:

Since a bulb is "OFF" initially,  a bulb ends up with "ON" state if it is switched an odd number of times.

Bulb X is switched in round Y if and only if  Y divides X ( X % Y == 0 ). 

So bulb X ends up with "ON" if and only if it has odd number of divisor.

Divisors come in pairs.  When X = 36, its divisors are (1, 36), (2, 18), (3,12), (4,9), (6,6)

So Bulb 36 will be toggled in round 1, 2, 3, 4, 6, 9, 12, 18, 36.  So Bulb 36 will be "ON" in the end.

So only square numbers have odd number of divisors.

The problem equals to count the square number between 1 to n 


class Solution:
    def bulbSwitch(self, n: int) -> int:
        result = 1
        
        while result * result <= n:
            result += 1
        
        return result - 1

10/17/2020

[LeetCode] 318. Maximum Product of Word Lengths

 Problem : https://leetcode.com/problems/maximum-product-of-word-lengths/

Calculate hash key base on the letters.  Then wordA and wordB have common letter if keyA & keyB != 0

Time Complexity = O ( N ** 2 )


class Solution {
    public int maxProduct(String[] words) {
        final int[] keys = Arrays.stream(words)
            .mapToInt(this::keyOf)
            .toArray();
        
        return IntStream.range(0, words.length)
            .map(i -> maxProductStartsWithWord(i, words, keys))
            .max()
            .orElse(0);
    }
    
    int maxProductStartsWithWord(int i, String[] words, int[] keys) {
        return IntStream.range(i+1, words.length)
                    .filter(j -> (keys[i] & keys[j]) == 0)
                    .map(j -> words[i].length() * words[j].length())
                    .max()
                    .orElse(0);
    }
    
    int keyOf(String word) {
        return word.chars()
            .reduce(0, (key, w) -> key | (1 << (w - 'a')));
    }
}

Edited on 05/31/2022. Replace with Java functional programming style solution.

[LeetCode] 316. Remove Duplicate Letters

 Problem : https://leetcode.com/problems/remove-duplicate-letters/

To form the smallest string in lexicographical order, we need to put the remaining smallest letters in front.

Time Complexity = O ( N )


class Solution:
    def removeDuplicateLetters(self, s: str) -> str:
        seen = {}
        used = defaultdict(bool)
        
        # remember the last position the letter be seen
        for i, w in enumerate(s):
            seen[w] = i
        
        result = []
        
        for i, w in enumerate(s):
            if used[w]:
                continue
            
            # replace the selected letter
            # if the letter is larger than current one
            # and we still have this letter in the back
            while result and ord(result[-1]) > ord(w) and seen[result[-1]] > i:
                used[result[-1]] = False
                result.pop()
                
            result.append(w)
            used[w] = True
        
        return ''.join(result)

[LeetCode] 315. Count of Smaller Numbers After Self

 Problem : https://leetcode.com/problems/count-of-smaller-numbers-after-self/

The original array is unsorted. Iterate the input array from the rear and use insert sort to create a sorted sequence.  The insert position indicates the number of smaller elements.

Time Complexity = O ( N * Log N  + N * N ).   Inserting number to array is O(N) time complexity operation.


class Solution:
    def countSmaller(self, nums: List[int]) -> List[int]: 
        def lowerBound(n, dp):
            """
            the first poistion on the left where dp[i] <= n
            """
            left, right = 0, len(dp)
            
            while left < right:
                mid = left + (right - left) // 2
                
                if dp[mid] == n:
                    right = mid
                elif dp[mid] < n:
                    left = mid + 1
                elif dp[mid] > n:
                    right = mid
            
            return right
        
        result = [0] * len(nums)
        dp = []
        
        for i in reversed(range(len(nums))):
            n = nums[i]
            # insert sort
            j = lowerBound(n, dp)
            dp.insert(j, n)
            
            result[i] = j
            
        return result

Use Python build-in lib:


class Solution:
    def countSmaller(self, nums: List[int]) -> List[int]:
        dp = []
        result = [0] * len(nums)
        
        for i in reversed(range(len(nums))):
            
            lowerBound = bisect.bisect_left(dp, nums[i])
            
            result[i] = lowerBound
            
            bisect.insort_left(dp, nums[i])
        
        return result

Use segment tree:


class SegmentTree:
    def __init__(self, m):
        # M = number of leafs of the segement tree
        self.M = m
        
        # Because segement tree is a completed binary tree.
        # If number of leafs = M, then number of internal nodes = M - 1.
        # Because tree[0] will not be used, the total number of nodes = 2 * M
        self.tree = [0] * (2 * self.M)
        
    def update(self, n, delta):
        """
        update counter of number on index n
        """
        
        # number n leaf node index = n + M
        n += self.M
        self.tree[n] += delta
        
        n //= 2
        while n > 0:
            # left child node index = 2 * n
            # right child node index = 2 * n
            self.tree[n] = self.tree[2*n] + self.tree[2*n+1]
            n //= 2
    
    def query(self, left, right):
        """
        query accumulative count between range [left, right).
        the 'right' is excluded.
        """
        
        result = 0
        
        left += self.M
        right += self.M
        
        while left < right:
            if left & 1 == 1:
                # on left side, the left child node is not in the needed range.
                result += self.tree[left]
                left += 1
            
            left //= 2
            
            if right & 1 == 1:
                # on right side, the right child node is not in the needed range
                right -= 1
                result += self.tree[right]
            
            right //= 2
        
        return result

class Solution:
    def countSmaller(self, nums: List[int]) -> List[int]:
        if not nums: return []
        
        mi = min(nums)
        mx = max(nums) + 1
        m = mx - mi + 1
        segmentTree = SegmentTree(m)
        
        # query in segement tree for number of smaller elements to the right of nums[i]
        result = [0] * len(nums)
        for i in reversed(range(len(nums))):
            n = nums[i] - mi
            
            result[i] = segmentTree.query(0, n)
            segmentTree.update(n, 1)
        
        return result

Use fenwick tree:


class FenwickTree:
    def __init__(self, m):
        self.tree = [0] * m
    
    def update(self, n, delta):
        while n < len(self.tree):
            self.tree[n] += delta
            n += self.lowbit(n)
    
    def query(self, n):
        result = 0
        
        while n > 0 :
            result += self.tree[n]
            n -= self.lowbit(n)
        
        return result
            
    
    def lowbit(self, n):
        return n & (~(n-1))
        
class Solution:
    def countSmaller(self, nums: List[int]) -> List[int]:
        if not nums: return []
        
        mi = min(nums)
        mx = max(nums) + 1
        m = mx - mi + 1
        
        fenwickTree = FenwickTree(m)
        
        result = [0] * len(nums)
        
        for i in reversed(range(len(nums))):
            # fenwick tree node index starts with 1
            n = nums[i] - mi + 1
            
            result[i] = fenwickTree.query(n-1)
            fenwickTree.update(n, 1)
        
        return result

Use merge sort approach.

Split the nums array into left and right parts.

Sort the right part first, then for each number on the left part, use binary-search to find count of number less than it. (lower-bound)


class Solution:
    def countSmaller(self, nums: List[int]) -> List[int]:
        if not nums: return []
        
        result = [0] * len(nums)
        
        def mergeSort(left, right):
            if left + 1 == right: 
                return 

            mid = left + (right - left) // 2

            # sort right side
            mergeSort(mid, right)

            # for each number on left side, find
            # how many number is smaller than it on right side.
            for i in range(left, mid):
                lowerBound = bisect.bisect_left(nums, nums[i], mid, right)
                # count of smaller numbers = lowerBound - mid
                result[i] += lowerBound - mid
            
            # sort left side
            mergeSort(left, mid)
            
            # merge the sorted left and right side
            tmp = []
            i = left
            j = mid

            while i < mid and j < right:
                if nums[i] < nums[j]:
                    tmp.append(nums[i])
                    i += 1
                else:
                    tmp.append(nums[j])
                    j += 1

            while i < mid:
                tmp.append(nums[i])
                i += 1

            while j < right:
                tmp.append(nums[j])
                j += 1

            for i in range(right - left):
                nums[left+i] = tmp[i]
        
        # merge sort starts
        mergeSort(0, len(nums))
        
        return result

[LeetCode] 313. Super Ugly Number

Problem : https://leetcode.com/problems/super-ugly-number/

Ugly Number A = Ugly Number B * Prime Number

Consider each prime number is assigned to a list. Then the problem becomes merge multiple ugly number list. We need to generate ugly number on each list on the fly.

class Solution:
    def nthSuperUglyNumber(self, n: int, primes: List[int]) -> int:
        idx = [0] * len(primes)
        dp = [2**31 - 1] * n
        dp[0] = 1
        
        for i in range(1, n):
            # next ugly number = the minimal number generated by one prime
            # multiple the previous ulgy number
            for j, p in enumerate(primes):
                dp[i] = min(dp[i], p * dp[idx[j]])
                
            for j, p in enumerate(primes):
                # there could be multiple primes
                # generate current minimal number
                # increase idx for all of them
                if dp[i] ==  p * dp[idx[j]]:
                    idx[j] += 1
       
        return dp[-1]

10/16/2020

[LeetCode] 312. Burst Ballons

 Problem : https://leetcode.com/problems/burst-balloons/

Time Complexity = O ( N ** 3 ) 


class Solution:
    def maxCoins(self, nums: List[int]) -> int:
        nums = [1] + nums + [1]
        
        @lru_cache(maxsize = None)
        def dp(left, right):
            """
            maximumal coins can get for section section(left : right)
            left and right is excluded from this section
            """
            
            if left + 1 == right:
                # not include left and right
                return 0
            
            result = 0
            for i in range(left+1, right):
                # Balllon list = nums[left] , section(left, i), nums[i], section(i, right), nums[right]
                # tmpResult = nums[left] * nums[i] * nums[right] + max coins of section(left,i) + max coins of section(i, right)
                tmp = nums[left] * nums[i] * nums[right]
                tmp += dp(left, i)
                tmp += dp(i, right)
                result = max(result,  tmp)
            
            return result
        
        
        return dp(0, len(nums) - 1)

10/13/2020

[LeetCode] 310. Minimum Height Trees

 Problem : https://leetcode.com/problems/minimum-height-trees/

The entire process is like peeling an onion.  Keep removing vectors only has 1 edge until the graph has less than 2 vectors left. Return the remaining vectors as the root of MHTs.


class Solution {
    public List<Integer> findMinHeightTrees(int n, int[][] edges) {   
        int[] degree = new int[n];
        Map<Integer, List<Integer>> graph = new HashMap();
        
        // construct the graph
        for (int i = 0; i < edges.length; i++) {
            int a = edges[i][0];
            int b = edges[i][1];
            
            if (!graph.containsKey(a)) {
                graph.put(a, new ArrayList<Integer>());
            }
            
            if (!graph.containsKey(b)) {
                graph.put(b, new ArrayList<Integer>());
            }
            
            graph.get(a).add(b);
            graph.get(b).add(a);
            
            degree[a] += 1;
            degree[b] += 1;
        }
        
        // start to peel the onion
        Queue<Integer> queue = new LinkedList();
        boolean[] deleted = new boolean[n];
        int remains = n;
        
        for (int i = 0; i < n; i++) {
            // put all nodes on outline to the queue
            if (degree[i] <= 1) {
                queue.offer(i);
            }
        }
        
        // peel the onion until less than 2 nodes are left
        // a MHT can have 2 possible roots in maximum
        while (remains > 2) {
            int size = queue.size();
            // use for-loop to ensure peel all nodes on current layer
            for (int i = 0; i < size; i++) {
                // peel the node off the tree
                int a = queue.poll();
                deleted[a] = true;
                degree[a] -= 1;
                remains -= 1;

                for (int b : graph.get(a)) {
                    if (deleted[b]) {
                        continue;
                    }

                    // put its peer to queue if the peer node is on the next layer
                    degree[b] -= 1;
                    if (degree[b] == 1) {
                        queue.offer(b);
                    }
                }
            }
        }
        
        // nodes on the last layer are MHT's root 
        return new ArrayList<Integer>(queue);
    }
}

Edited on 12/16/2021. Replace with Java solution.

[LeetCode] 309. Best Time to Buy and Sell Stock with Cooldown

 Problem : https://leetcode.com/problems/best-time-to-buy-and-sell-stock-with-cooldown/

Each day has either 3 state :  Buy, Sell, CoolDown

On day i:

If it is in "buy" state, then it either buy stock on the last day, or it buy stock on day i ( cool down on last day ) .

If it is in "sell" state, then it either sell stock on the last day, or it sell stock on day i.

If it is in "cooldown" state, then it either cool down on last day, or it sell stock on last day.

Helper(i, state) returns the maximum profit on day i  in  x state.

To get the maximum profit on the last day,  it returns the profit can be made on the last day in "sell" state.

Top-Down Solution:


class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        buy, sell, cooldown = 0, 1, 2
        
        @lru_cache(maxsize = None)
        def helper(i, state):
            if i < 0:
                return 0
            
            if i == 0 and state == sell:  
                return 0
            
            if i == 0 and state == buy:
                return -1 * prices[0]
            
            if i == 0 and state == cooldown:
                return 0
            
            if state == buy:
                # max(buy-on-last-day,  buy-on-day-i)
                return max(helper(i-1, buy), -1 * prices[i] + helper(i-1, cooldown))
            
            if state == sell:
                # max(sell-on-last-day, sell-on-day-i)
                return max(helper(i-1, sell), prices[i] + helper(i-1, buy))
            
            if state == cooldown:
                # max(continue-cool-down, sell-on-last-day)
                return max(helper(i-1, cooldown), helper(i-1, sell)
    
        return helper(len(prices)-1, sell)

Bottom-Up Solution:


class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        
        if not prices:
            return 0
        
        buy = [0] * len(prices)
        sell = [0] * len(prices)
        cooldown = [0] * len(prices)
        
        buy[0] = -prices[0]
        
        for i in range(1, len(prices)):
            buy[i] = max(buy[i-1], -1 * prices[i] + cooldown[i-1])
            sell[i] = max(sell[i-1], buy[i-1] + prices[i])
            cooldown[i] = max(cooldown[i-1], sell[i-1]) 
            
        return sell[-1]

10/11/2020

[LeetCode] 307. Range Sum Query - Mutable

 Problem : https://leetcode.com/problems/range-sum-query-mutable/

Cache accumulative sum of each element and diffs introduced by each update.


class NumArray:

    def __init__(self, nums: List[int]):
        self.nums = nums
        self.sums = [0] * (len(nums) + 1)
        for i in range(len(nums)):
            self.sums[i+1] = self.sums[i] + self.nums[i]
        
        self.diff = {}
        
    def update(self, i: int, val: int) -> None:
        self.diff[i] = val - self.nums[i]
        
    def sumRange(self, i: int, j: int) -> int:
        result = self.sums[j+1] - self.sums[i]
        
        #print(self.nums, self.diff)
        
        for k in self.diff.keys():
            if i <=  k  <= j :
                result += self.diff[k]
        
        return result
        


# Your NumArray object will be instantiated and called as such:
# obj = NumArray(nums)
# obj.update(i,val)
# param_2 = obj.sumRange(i,j)

[LeetCode] 306. Additive Number

 Problem : https://leetcode.com/problems/additive-number/

Use DFS approach to verify the sequence.

Time Complexity = O ( N ** 3 )


class Solution:
    def isAdditiveNumber(self, num: str) -> bool:
        def dfs(start, pre, target):
            if start == len(num):
                return True
            
            if num[start] == '0':
                if target == 0:
                    return dfs(start + 1, 0, 0)
                else:
                    return False
            
            for i in range(start+1, len(num) + 1):
                if int(num[start:i]) > target:
                    return False
                
                
                if int(num[start:i]) == target:
                    return dfs(i, target, pre + target)
            
            return False
        
        
        for i in range(1, len(num) - 1):
            for j in range(i+1, len(num)):
                if i > 1 and num[0] == '0':
                    break
                
                a = int(num[:i])
                
                if j - i > 1 and num[i] == '0':
                    continue
                    
                b = int(num[i:j])
                if dfs(j, b, a + b):
                    return True
        
        return False

[LeetCode] 304. Range Sum Query 2D - Immutable

 Problem : https://leetcode.com/problems/range-sum-query-2d-immutable/

Calculate accumulative sum in the region row by row.


class NumMatrix:

    def __init__(self, matrix: List[List[int]]):
        self.row = len(matrix)
        self.column = len(matrix[0]) if self.row > 0 else 0
        
        if self.row != 0 and self.column != 0:
            self.nums = [[0] * (self.column + 1) for _ in range(self.row)]
            
            for y in range(self.row):
                for x in range(self.column):
                    self.nums[y][x+1] = self.nums[y][x] + matrix[y][x]
            
        else:
            self.nums = None
        

    def sumRegion(self, row1: int, col1: int, row2: int, col2: int) -> int:
        result = 0
        
        if 0 <= row1 < self.row and 0 <= col1 < self.column and \
           0 <= row2 < self.row and 0 <= col2 < self.column:
            for y in range(row1, row2+1):
                result += self.nums[y][col2+1] - self.nums[y][col1]
        
        
        return result
        
        


# Your NumMatrix object will be instantiated and called as such:
# obj = NumMatrix(matrix)
# param_1 = obj.sumRegion(row1,col1,row2,col2)

Use Binary Index Tree:


class BIT:
    def __init__(self, data):
        self.N = len(data)
        self.bit = [0] * (self.N + 1)
        
        for i in range(self.N):
            self.update(i+1, data[i])
    
    def update(self, index, delta): 
        while index <= self.N:
            self.bit[index] += delta
            index += self.lowbit(index)
    
    def query(self, index):
        sums = 0
        
        while index > 0:
            sums += self.bit[index]
            index -= self.lowbit(index)
        
        return sums
        
    
    def lowbit(self, index):
        return index & (-index)
    

class NumMatrix:

    def __init__(self, matrix: List[List[int]]):
        self.bits = [BIT(row) for row in matrix]
        

    def sumRegion(self, row1: int, col1: int, row2: int, col2: int) -> int:
        sums = 0
        
        for i in range(row1, row2+1):
            sums += self.bits[i].query(col2+1) - self.bits[i].query(col1)
        
        return sums


# Your NumMatrix object will be instantiated and called as such:
# obj = NumMatrix(matrix)
# param_1 = obj.sumRegion(row1,col1,row2,col2)

Edited on 02/27/2021. Use BIT based approach.

[LeetCode] 303. Range Sum Query - Immutable

 Problem : https://leetcode.com/problems/range-sum-query-immutable/

Let  Sums[i] = nums[0] + nums[1] + ...  nums[i - 1],

then SumRange(i, j) = Sums[j+1] - Sums[i]


class NumArray:

    def __init__(self, nums: List[int]):
        self.sums = [0] * (len(nums) + 1)
        
        for i in range(len(nums)):
            self.sums[i+1] = self.sums[i] + nums[i]
        
    def sumRange(self, i: int, j: int) -> int:
        return self.sums[j+1] - self.sums[i]
        


# Your NumArray object will be instantiated and called as such:
# obj = NumArray(nums)
# param_1 = obj.sumRange(i,j)

[LeetCode] 301. Remove Invalid Parentheses

 Problem : https://leetcode.com/problems/remove-invalid-parentheses/

Iterating the input string from left right,  for each bracket, we have 2 options. Either keep this bracket or remove this bracket from the final expression. In the end, check the expression is valid by verify if opening bracket == closing bracket. Use removedCount to track how many bracket be removed from the final expression.  Only save the expression with minimal removal count.

Time Complexity = O ( 2 ** N ).     ( As for each character we can choose either keep or remove it from the final expression. )


class Solution:
    def removeInvalidParentheses(self, s: str) -> List[str]:
        self.result = set()
        self.minRemoved = 2 ** 31 - 1
        
        def helper(index, leftCount, rightCount, expression, removedCount):
            
            if index == len(s):
                if leftCount == rightCount:
                    if removedCount < self.minRemoved:
                        self.result = set([expression])
                        self.minRemoved = removedCount
                    elif removedCount == self.minRemoved:
                        self.result.add(expression)
            else:
                if s[index] not in '()':
                    helper(index + 1, leftCount, rightCount, expression + s[index], removedCount)
                else:
                    # remove this bracket
                    helper(index + 1, leftCount, rightCount, expression, removedCount + 1)
                    
                    # keep this bracket
                    if s[index] == '(':
                        helper(index + 1, leftCount + 1, rightCount, expression + s[index], removedCount)
                    elif rightCount < leftCount:
                        # only keep closing bracket when right < left
                        helper(index + 1, leftCount, rightCount + 1, expression + s[index], removedCount)
        
        
        helper(0, 0, 0, '', 0)
        return self.result

Limited Backtracking:

This algorithm is faster because it only remove the misplaced parentheses.


class Solution:
    def removeInvalidParentheses(self, s: str) -> List[str]:
        misplacedLeft = misplacedRight = 0
        
        # find all misplaced parentheses 
        for i in range(len(s)):
            if s[i] == '(':
                misplacedLeft += 1
            elif s[i] == ')':
                if misplacedLeft > 0:
                    misplacedLeft -= 1
                else:
                    misplacedRight += 1
        
        # note: use hash set to avoid duplicate result
        result = set()
        
        def helper(left, right, leftToRemove, rightToRemove, index, tmp):
            if index == len(s):
                if leftToRemove == 0 and rightToRemove == 0:
                    # tmp is valid because all misplaced parentheses have been removed
                    result.add(tmp)
            else:
                if s[index] == '(':
                    if leftToRemove > 0:
                        # remove one misplaced 'left' parenthesis
                        helper(left, right, leftToRemove - 1, rightToRemove, index + 1, tmp)

                    # keep the 'left' parenthesis
                    # note: increase the 'left' counter
                    helper(left + 1, right, leftToRemove, rightToRemove, index + 1, tmp + '(')
                elif s[index] == ')':
                    if rightToRemove > 0:
                        # remove one misplaced 'right' parenthesis
                        helper(left, right, leftToRemove, rightToRemove - 1, index + 1, tmp)

                    if left > right:
                        # keep the 'right' parenthesis if it can still maintain a valid string
                        # note : increase the 'right' counter
                        helper(left, right + 1, leftToRemove, rightToRemove, index + 1, tmp + ')')
                else:
                    # keep all letters
                    helper(left, right, leftToRemove, rightToRemove, index + 1, tmp + s[index])
        
        helper(0, 0, misplacedLeft, misplacedRight, 0, '')
        return result

10/08/2020

[LeetCode] 300. Longest Increasing Subsequence

 Problem : https://leetcode.com/problems/longest-increasing-subsequence/

DP Solution

Let DP[i] = longest increasing sequence end at i, then DP[i] = DP[j] + 1 if Nums[i] > Nums[j]

Time Complexity = O ( N ** 2 )


class Solution:
    def lengthOfLIS(self, nums: List[int]) -> int:
        if not nums:
            return 0
        
        N = len(nums)
        dp = [1] * N
        
        for i in range(1, N):
            for j in reversed(range(i)):
                if nums[i] > nums[j]:
                    dp[i] = max(dp[i], 1 + dp[j])
        
        return max(dp)

DP + Binary Search Solution

DP is a cache to store the increasing sequence.

Iterate the input array and pick number from left to right to insert into DP.

lowerBound method returns the position of the newly added number should be inserted into DP to maintain an increasing sequence.  Replace existing number if the returned position does not expand DP. Otherwise, append the new number to DP.

The final length of DP is the length of the longest increasing subsequence.  However, DP is not the longest increasing subsequence.


Time Complexity = O ( N * LogN )


class Solution:
    def lengthOfLIS(self, nums: List[int]) -> int:
        
        def lowerBound(nums, n):
            """
            return the first position from left where nums[i] <= n
            return 0 if all items in nums > n
            return len(nums) if all items in nums < n
            """
            
            left, right = 0, len(nums)
            
            while left < right:
                mid = left + (right - left) // 2
                
                if nums[mid] == n:
                    right = mid
                elif nums[mid] < n:
                    left = mid + 1
                elif nums[mid] > n:
                    right = mid
                
            return left
        
        
        dp = []
        
        for n in nums:    
            i = lowerBound(dp, n)
            if i == len(dp):
                dp.append(n)
            else:
                dp[i] = n
        
        return len(dp)

Use built-in binary-search lib:


class Solution:
    def lengthOfLIS(self, nums: List[int]) -> int:
        dp = []
        
        for n in nums:
            i = bisect.bisect_left(dp, n)
            if i == len(dp):
                dp.append(n)
            else:
                dp[i] = n
        
        return len(dp)

Edited on 02/06/2021. Add implementation with built-in binary search lib.

[LeetCode] 299. Bulls and Cows

 Problem : https://leetcode.com/problems/bulls-and-cows/

An intuitive solution based on hash table.


class Solution:
    def getHint(self, secret: str, guess: str) -> str:
        counterS = Counter(secret)
        
        bulls = 0
        for i, g in enumerate(guess):
            if g == secret[i]:
                bulls += 1
                counterS[g] -= 1
        
        cows = 0
        for i, g in enumerate(guess):
             if g != secret[i] and g in counterS and counterS[g] > 0:
                cows += 1
                counterS[g] -= 1
        
        return "{}A{}B".format(bulls, cows)

[LeetCode] 297. Serialize and Deserialize Binary Tree

 Problem : https://leetcode.com/problems/serialize-and-deserialize-binary-tree/

Use BFS to serialize and deserialize the tree.


# Definition for a binary tree node.
# class TreeNode(object):
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Codec:

    def serialize(self, root):
        """Encodes a tree to a single string.
        
        :type root: TreeNode
        :rtype: str
        """
        output = []
        
        queue = deque([root])
        
        while queue:
            tmp = deque()
            
            while queue:
                node = queue.popleft()
                if not node:
                    output.append('#')
                else:
                    output.append(str(node.val))
                    tmp.append(node.left)
                    tmp.append(node.right)
                    
            queue = tmp
        
        return ','.join(output) if output[0] != '#' else ''
        

    def deserialize(self, data):
        """Decodes your encoded data to tree.
        
        :type data: str
        :rtype: TreeNode
        """
        
        if not data:
            return None
        
        data = data.split(',')
        
        root = TreeNode(int(data[0]))
        i = 1
        
        queue = deque([root])
        
        while i < len(data):
            tmp = deque()
            
            while queue:
                node = queue.popleft()
                
                if data[i] == '#':
                    node.left = None
                else:
                    node.left = TreeNode(int(data[i]))
                    tmp.append(node.left)
                
                i += 1
                
                if data[i] == '#':
                    node.right = None
                else:
                    node.right = TreeNode(int(data[i]))
                    tmp.append(node.right)
                
                i += 1
            
            queue = tmp
        
        return root
                    
        
        

# Your Codec object will be instantiated and called as such:
# ser = Codec()
# deser = Codec()
# ans = deser.deserialize(ser.serialize(root))

[LeetCode] 295. Find Median from Data Stream

 Problem : https://leetcode.com/problems/find-median-from-data-stream/

Use insert sort to keep the data cache in ascending order.

Then median = ( nums[len(nums) // 2 ] + nums[(len(nums) - 1) // 2] ) / 2

Time Complexity = O ( Log N ) +  O ( N ) ≈ O ( N )


class MedianFinder:

    def __init__(self):
        """
        initialize your data structure here.
        """
        self.nums = []
        

    def addNum(self, num: int) -> None:
        
        def lowerBoundary(n):
            left, right = 0, len(self.nums)
            
            while left < right:
                mid = left + (right - left) // 2
                
                if self.nums[mid] == n:
                    right = mid
                elif self.nums[mid] < n:
                    left = mid + 1
                elif self.nums[mid] > n:
                    right = mid
            
            return left if left - 1 >= 0 else 0
            
        # insert sort   
        self.nums.insert(lowerBoundary(num), num)
        

    def findMedian(self) -> float:
        
        l = len(self.nums)
        
        return (self.nums[l//2] + self.nums[(l-1)//2]) / 2.0
        


# Your MedianFinder object will be instantiated and called as such:
# obj = MedianFinder()
# obj.addNum(num)
# param_2 = obj.findMedian()

10/07/2020

[LeetCode] 292. Nim Game

 Problem : https://leetcode.com/problems/nim-game/

Let player A goes first.  When there is 4 stones,  player A will always lose.  Because no matter how many stone player A removes, player B can always remove the reset stones.

Because player A and B play optimally.  Both of they will try to remove stones and left 4 * N stones to its opponent.  So player A always lost, if total number of stones can be dived by 4.


class Solution:
    def canWinNim(self, n: int) -> bool:
        
        return n % 4 != 0

[LeetCode] 290. Word Pattern

 Problem : https://leetcode.com/problems/word-pattern/

Use 2 hash maps to save the mapping relationship.


class Solution:
    def wordPattern(self, pattern: str, str: str) -> bool:
        word = str.split(' ')
        
        w2p = defaultdict(lambda : '')
        p2w = defaultdict(lambda : '')
        
        if len(word) != len(pattern):
            return False
        
        for i in range(len(pattern)):
            if pattern[i] not in p2w and word[i] not in w2p:
                p2w[pattern[i]] = word[i]
                w2p[word[i]] = pattern[i]
            
            elif p2w[pattern[i]] != word[i] or w2p[word[i]] != pattern[i]:
                return False
        
        
        return True

[LeetCode] 289. Game of Life

 Problem : https://leetcode.com/problems/game-of-life/

Use bit mask to save the next-state on high-order bits.

Time Complexity = O ( M * N )

Space Complexity = O ( 1 )


class Solution:
    def gameOfLife(self, board: List[List[int]]) -> None:
        """
        Do not return anything, modify board in-place instead.
        """
        
        ROW = len(board)
        if ROW == 0: return 
        
        COLUMN = len(board[0])
        if COLUMN == 0: return
        
        def alive(y,x):
            return 0 <= y < ROW and 0 <= x < COLUMN and board[y][x] & 1 == 1
        
        def nextState(y,x):
            aliveCount = 0
            
            for dy in [-1, 0, 1]:
                for dx in [-1, 0, 1]:
                    if dy == 0 and dx == 0:
                        continue
                    
                    if alive(y+dy, x+dx):
                        aliveCount += 1
            
            """
            Any live cell with fewer than two live neighbors dies as if caused by under-population.
            Any live cell with two or three live neighbors lives on to the next generation.
            Any live cell with more than three live neighbors dies, as if by over-population.
            Any dead cell with exactly three live neighbors becomes a live cell, as if by reproduction.
            """
            
            # dead
            nextState = 0
            
            if board[y][x] == 1:
                if aliveCount < 2:
                    # dead
                    pass
                elif 2 <= aliveCount <= 3:
                    # alive
                    nextState = 1 << 3
                else:
                    # dead
                    pass
            else:
                if aliveCount == 3:
                    # alive
                    nextState = 1 << 3
                else:
                    # dead
                    pass
            
            board[y][x] |= nextState
                    
        
        def normalizeState(y,x):
            board[y][x] = 1 if board[y][x] & (1<<3) else 0
        
        for y in range(ROW):
            for x in range(COLUMN):
                nextState(y, x)
        
        for y in range(ROW):
            for x in range(COLUMN):
                normalizeState(y, x)