西维蜀黍的OJ Blog

🐒 Software engineer | 📷 Photographer | 👹 Urban explorer

[Interview] Transactional Key Value Store

Squarepoint Capital interview

Description

Part 1: Create a class for a simple key-value store which supports the following methods:

  • set(key, value) - add a new entry to the key-value store, or update it if it’s already there
  • get(key) - get the value for the given key. Raise a KeyError if the key is not there
  • delete(key) - delete the entry. Raise a KeyError if the key is not there

Example:

  ...


[Topic] Anagrams

解析

49. Group Anagrams

# hashmap + sorting - myself
#  - time: O(N * max_char log max_char)
#  - space: O(n)
def groupAnagrams(self, strs):
		m = {}
		for str in strs:
			sortedStr = "".join(sorted(str))
			if sortedStr in m:
				m[sortedStr].append(str)
			else:
				m[sortedStr] = [str]

		return m.values()
  

# hashmap - myself
#  - time: O(n * max_char)
#  - space: O(n)
	def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
		m = {}
		for str in strs:  # time: O(n)
			curSign = [0] * 26
			for char in str:  # time: O(max_char)
				curSign[ord(char) - ord("a")] += 1  # ord("a") means the ASCII of "a"

			# because Python cannot use list as the keys of a list
			if tuple(curSign) in m:
				m[tuple(curSign)].append(str)
			else:
				m[tuple(curSign)] = [str]
		return m.values()

ref

  ...


[Topic] BFS (Breadth-first Search)

BFS (Breadth-first Search)

BFS 的核心思想应该不难理解的,就是把一些问题抽象成图,从一个点开始,向四周开始扩散。一般来说,我们写 BFS 算法都是用「队列」这种数据结构,每次将一个节点周围的所有节点加入队列。

  ...


[Topic] Palindrome

A string is palindromic if it reads the same forward and backward.

  • Palindrome 问题通常都可以用双指针
  • 需要分别考虑奇数长度和偶数长度的情况

解析

5. Longest Palindromic Substring

  • 有palindromic sub-str 包含奇数和偶数长度两种情况
# burte force
#  - time: O(n^3) # n to check a sub-str being a palindrome or not, n^2 to chekc all possible sub-str
#  - space: O(1)
# - Get every possible substring (O(n^2)), and check whether it is a palindrome (O(n))
    def longestPalindrome(self, s):
        """
        :type s: str
        :rtype: str
        """
        res, resLen = "", 0

        for i in range(len(s)):
            for j in range(i, len(s)):
                l, r = i, j
                while l < r and s[l] == s[r]:
                    l += 1
                    r -= 1

                # l >= r 以判断 l 和 r 是否相遇了,如果相遇了,说明 s[i:j+1] 为 palindrome
                if l >= r and resLen < (j - i + 1):
                    res = s[i: j + 1]
                    resLen = j - i + 1
        return res
      
# 双指针
#  - time: O(n^2)
#  - space: O(1)  
    def longestPalindrome(self, s):
        """
        :type s: str
        :rtype: str
        """
        maxLen = 0
        res = ""
        for i in range(len(s)):
            # odd length, e.g., cbc in cbc
            l, r = i, i
            while l >= 0 and r < len(s) and s[l] == s[r]:
                curLen = r - l + 1  # 计算两指针包括的元素长度
                if curLen > maxLen:
                    maxLen = curLen
                    res = s[l:r + 1]
                l -= 1
                r += 1

            # even length, e.g., bb in cbbc
            l, r = i, i + 1
            while l >= 0 and r < len(s) and s[l] == s[r]:
                curLen = r - l + 1  # 计算两指针包括的元素长度
                if curLen > maxLen:
                    maxLen = curLen
                    res = s[l:r + 1]
                l -= 1
                r += 1
        return res
# ??  
# Manacher's Algorithm
#  - time: O(n)
#  - space: O(1)  
    def longestPalindrome(self, s: str) -> str:
        def manacher(s):
            t = '#' + '#'.join(s) + '#'
            n = len(t)
            p = [0] * n
            l, r = 0, 0
            for i in range(n):
                p[i] = min(r - i, p[l + (r - i)]) if i < r else 0
                while (i + p[i] + 1 < n and i - p[i] - 1 >= 0 
                       and t[i + p[i] + 1] == t[i - p[i] - 1]):
                    p[i] += 1
                if i + p[i] > r:
                    l, r = i - p[i], i + p[i]
            return p
        
        p = manacher(s)
        resLen, center_idx = max((v, i) for i, v in enumerate(p))
        resIdx = (center_idx - resLen) // 2
        return s[resIdx : resIdx + resLen]

ref

  ...


[Topic] Prefix Sum

题目

解析

238. Product of Array Except Self

  • 用hashmap记下乘积,先从左到右循环,再从右到左循环
# Approach 1 - more elegent
def productExceptSelf(self, nums: List[int]) -> List[int]:
		res = [1] * len(nums)
		for idx in range(1, len(nums)):
			res[idx] = res[idx - 1] * nums[idx - 1]

		temp = nums[-1]
		for idx in range(len(nums) - 2, -1, -1):
			res[idx] = res[idx] * temp
			temp = temp * nums[idx]

		return res

# Approach 2 - just a bit easier to understand
	def productExceptSelf(self, nums: List[int]) -> List[int]:
		res = [1] * len(nums)
		temp = nums[0]
		for idx in range(1, len(nums)):
			res[idx] = temp
			temp = temp * nums[idx]

		temp = nums[-1]
		for idx in range(len(nums) - 2, -1, -1):
			res[idx] = res[idx] * temp
			temp = temp * nums[idx]

		return res

Ref

  ...


[Topic] Sum

解析

1. Two Sum

# brute force
# - time: O(n2)
# - space: O(1)
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        for i in range(len(nums)):
            for j in range(i + 1, len(nums)):
                if nums[i] + nums[j] == target:
                    return [i, j]
        return []

# sort + 双指针
# - time: O(nlogn)
# - space: O(n)
   def twoSum(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: List[int]
        """
        A = []  # 记录排序前,元素 num 的原始索引(因为结果中需要返回原始索引)
        for i, num in enumerate(nums):
            A.append([num, i])

        A.sort()  # A 将按照每个二元组的第一个元素(即数值)从小到大排序
        i, j = 0, len(nums) - 1  # i 指向数组左端(最小值),j 指向数组右端(最大值)
        while i < j:
            cur = A[i][0] + A[j][0]
            if cur == target:
                return [A[i][1], A[j][1]]
            elif cur < target:
                i += 1
            else:
                j -= 1
        return []
      
# hashmap
# - time: O(n)
# - space: O(n)
    def twoSum(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: List[int]
        """
        prevMap = {}  # prevMap 中存储的都是可以被考虑的数字
        for i in range(len(nums)):
            num = nums[i]
            diff = target - num
            if diff in prevMap:
                return [i, prevMap[diff]]

            prevMap[num] = i

167. Two Sum II - Input Array Is Sorted

# brute force
#  - time: O(n^2)
#  - space: O(1)  
    def twoSum(self, numbers: List[int], target: int) -> List[int]:
        for i in range(len(numbers)):
            for j in range(i + 1, len(numbers)):
                if numbers[i] + numbers[j] == target:
                    return [i + 1, j + 1]
        return []
      
# 左右指针(双指针)
#  - time: O(n)
#  - space: O(1)  
    def twoSum(self, numbers, target):
        """
        :type numbers: List[int]
        :type target: int
        :rtype: List[int]
        """
        l, r = 0, len(numbers) - 1
        while l < r:
            cur_sum = numbers[l] + numbers[r]
            if cur_sum == target:
                return [l + 1, r + 1]
            elif cur_sum > target:
                r -= 1
            else:
                l += 1

ref

  ...


[Topic] Trapping Rain Water

解析

11 Container With Most Water

  • 面积公式 = min(h[l], h[r]) * (r - l)
  • 判断 height[l]height[r]的大小,如果 height[l] 小,l++,因为此时移动 r,并不会改变结果
    • 在每个状态下,无论长板或短板向中间收窄一格,都会导致水槽 底边宽度 -1 变短:
      • 若向内 移动短板 ,水槽的短板 min(h[i],h[j]) 可能变大,因此下个水槽的面积 可能增大 。
      • 若向内 移动长板 ,水槽的短板 min(h[i],h[j]) 不变或变小,因此下个水槽的面积 一定变小 。
# brute force
# - time: O(n2)
# - space: O(1)

# 双指针
# - time: O(n)
# - space: O(1)

	def maxArea(self, height: List[int]) -> int:
		l, r = 0, len(height) - 1
		max_water = 0
		while l < r:
			cur_water = (r - l) * min(height[l], height[r])
			max_water = max(max_water, cur_water)
			if height[l] < height[r]:
				l += 1
			else:
				r -= 1

		return max_water

ref

  ...


[Notes] Array

题目

双指针

在处理数组和链表相关问题时,双指针技巧是经常用到的,双指针技巧主要分为两类:左右指针和快慢指针。

所谓左右指针,就是两个指针相向而行或者相背而行;而所谓快慢指针,就是两个指针同向而行,一快一慢。

快慢指针

左右指针

  ...


[Notes] Sliding Window

滑动窗口算法

说起滑动窗口算法,很多读者都会头疼。这个算法技巧的思路非常简单,就是维护一个窗口,不断滑动,然后更新答案么。LeetCode 上有起码 10 道运用滑动窗口算法的题目,难度都是中等和困难。该算法的大致逻辑如下:

int left = 0, right = 0;

while (left < right && right < s.size()) {
    // 增大窗口
    window.add(s[right]);
    right++;
    
    while (window needs shrink) {
        // 缩小窗口
        window.remove(s[left]);
        left++;
    }
}

这个算法技巧的时间复杂度是 O(N),比字符串暴力算法要高效得多。

另外,虽然滑动窗口代码框架中有一个嵌套的 while 循环,但算法的时间复杂度依然是 O(N),其中 N 是输入字符串/数组的长度。

为什么呢?简单说,指针 left, right 不会回退(它们的值只增不减),所以字符串/数组中的每个元素都只会进入窗口一次,然后被移出窗口一次,不会说有某些元素多次进入和离开窗口,所以算法的时间复杂度就和字符串/数组的长度成正比。

  ...


[Notes] Topological Sort(拓扑排序)

  ...