# 718\. Maximum Length of Repeated Subarray similar: https://hackmd.io/@y56/BJXStesvY?type=view ## DP M * N If matched here, here = previous + 1 ## binary search + rolling bash ### binary search https://hackmd.io/@y56/SJ3z5fBq5 "has common sublist of length L" ===> "has common sublist of length x" for all x <= L, Let True/T be has_commom_with(x) At least one True. Set "has common sublist of length 0" always to be True. This means can't find any common Do binary search to find right most True TF....FF TTF...FF TTT...TT ### Rolling hashing list[i : j] into sum of a_n * base^n for n in the length list[i + 1 : j + 1], remove the highest order digit, multiply by base, and then add the new comer Try to reused the overlapping part ## sliding window Time: O((L1+L2) * L1) try all different alignment, totally (L1 + L2) possibilities; In each alignment, find longest matched sublist ``` 22222222222222222222 1111111111 1111111111 1111111111 ... ``` ``` 1111111111 22222222222222222222 22222222222222222222 22222222222222222222 ..... ``` ```python= """ DP """ class Solution: def findLength(self, nums1: List[int], nums2: List[int]) -> int: if not nums1 or not nums1: return 0 # DP: build a grid, when letter matched, mark as T # Try to find len of the longest consecutive diagonal T's """ 123456789 0 1T 2 T 0 1T 0 3 T 4 T 5 T """ # DP ver 2: init all as 0, if matched here, here = upper_left_val + 1 # if upper-left is outside, treat that as a 0 # try to find max num in grid len1 = len(nums1) len2 = len(nums2) dp = [[0] * len2 for _ in range(len1)] # dp[i][j] is for nums1[i] vs nums2[j] seen_max = 0 for i in range(len1): for j in range(len2): upperleft = 0 if 0 <= i - 1 < len1 and 0 <= j - 1 < len2: upperleft = dp[i - 1][j - 1] if nums1[i] == nums2[j]: dp[i][j] = upperleft + 1 seen_max = max(seen_max, dp[i][j]) return seen_max ################################################################################### """ binary search + rolling hash """ # L1 = len(nums1) # L2 = len(nums2) # WOLG, L1 <= L2 # given length L # do binary search to find largest L # so, time: O(log L1) # for each search # (L1 - L + 1) lists in nums1; call (L1 - L + 1) as m1 # (L2 - L + 1) lists in nums2; call (L2 - L + 1) as m2 # m1 <= m2 # Note: hashing a list needs time linear to len of that list # m1 times putting into set_1; casting list to tuple: O(L); # assuming put to a set in more expensive than query # m2 times to query if in set_1; casting list to tuple: O(L); # time: O(m1*L + m2*L) ie O((m1+m2)*L) ie O((L1+L2)*L1) # total time: O((L1+L2) * L1 * log L1) # can do rolling hash => O((L1+L2) * log L1) class RollingHashset: def __init__(self, nums1, length): self.base = 113 self.prime = 10 ** 9 + 7 self.nums1 = nums1 self.length = length self.dict = defaultdict(list) for hash_value, start_index in self.__rolling_hashed(nums1): # Use open Hashing (Separate chaining). self.dict[hash_value].append(start_index) def __rolling_hashed(self, nums): length = self.length result = [] hash_value = 0 highest_power = self.base ** (length - 1) % self.prime for i, num in enumerate(nums): # the previous is already full-length, starting to remove oldest if i >= length: hash_value -= (nums[i - length] * highest_power) % self.prime # raise power, and add new number hash_value = (hash_value * self.base + num) % self.prime # collect it if reaching enough length if i >= self.length - 1: # also record starting index of sublist result.append((hash_value, i - length + 1)) return result def has_any_hit_by(self, nums2): nums1 = self.nums1 for hash_value, start_index_2 in self.__rolling_hashed(nums2): if hash_value in self.dict: # check if truly matched, or just collision for start_index_1 in self.dict[hash_value]: # can use pointers here to avoid list-slicing initiate new lists sub_nums1 = nums1[start_index_1 : start_index_1 + self.length] sub_nums2 = nums2[start_index_2 : start_index_2 + self.length] if sub_nums1 == sub_nums2: return True return False class Solution: def findLength(self, nums1: List[int], nums2: List[int]) -> int: if len(nums1) > len(nums2): nums1, nums2 = nums2, nums1 L1 = len(nums1) L2 = len(nums2) def has_common(length): if length == 0: return True # my handmade hashset to receive query if X from nums2 is in nums1 rolling_hashset = RollingHashset(nums1, length) return rolling_hashset.has_any_hit_by(nums2) # find rightmost True, guaranteed at least one True at leftmost left, right = 0, L1 while left < right: mid = (left + right) // 2 + 1 if has_common(mid): left = mid else: right = mid - 1 return left ''' Sliding window ''' class Solution: def findLength(self, nums1: List[int], nums2: List[int]) -> int: def helper(nums_i, nums_j): seen_max = 0 for i in range(len(nums_i)): # Align nums_i[i] and nums_j[0] j = 0 count = 0 while i < len(nums_i) and j < len(nums_j): if nums_i[i] == nums_j[j]: count += 1 else: count = 0 seen_max = max(seen_max, count) i += 1 j += 1 return seen_max return max(helper(nums1, nums2), helper(nums2, nums1)) ```