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]