Welcome to Subscribe On Youtube
Formatted question description: https://leetcode.ca/all/1923.html
1923. Longest Common Subpath
Level
Hard
Description
There is a country of n
cities numbered from 0
to n - 1
. In this country, there is a road connecting every pair of cities.
There are m
friends numbered from 0
to m - 1
who are traveling through the country. Each one of them will take a path consisting of some cities. Each path is represented by an integer array that contains the visited cities in order. The path may contain a city more than once, but the same city will not be listed consecutively.
Given an integer n
and a 2D integer array paths
where paths[i]
is an integer array representing the path of the i-th
friend, return the length of the longest common subpath that is shared by every friend’s path, or 0
if there is no common subpath at all.
A subpath of a path is a contiguous sequence of cities within that path.
Example 1:
Input: n = 5, paths = [[0,1,2,3,4],
[2,3,4],
[4,0,1,2,3]]
Output: 2
Explanation: The longest common subpath is [2,3].
Example 2:
Input: n = 3, paths = [[0],[1],[2]]
Output: 0
Explanation: There is no common subpath shared by the three paths.
Example 3:
Input: n = 5, paths = [[0,1,2,3,4],
[4,3,2,1,0]]
Output: 1
Explanation: The possible longest common subpaths are [0], [1], [2], [3], and [4]. All have a length of 1.
Constraints:
1 <= n <= 10^5
m == paths.length
2 <= m <= 10^5
sum(paths[i].length) <= 10^5
0 <= paths[i][j] < n
- The same city is not listed multiple times consecutively in
paths[i]
.
Solution
Use binary search and rolling hash. The minimum possible length is 0 and the maximum possible length is the shortest length among all paths. Each time, check whether it is possible to have a common subpath of the selected length.
-
class Solution { static final long BASE = 100001, MODULO = 100000000007L; public int longestCommonSubpath(int n, int[][] paths) { int minLength = Integer.MAX_VALUE; for (int[] path : paths) minLength = Math.min(minLength, path.length); long[] pow = new long[minLength + 1]; pow[0] = 1; for (int i = 1; i <= minLength; i++) pow[i] = pow[i - 1] * BASE % MODULO; int low = 0, high = minLength; while (low < high) { int mid = (high - low + 1) / 2 + low; if (check(paths, mid, pow)) low = mid; else high = mid - 1; } return low; } public boolean check(int[][] paths, int mid, long[] pow) { int m = paths.length; Set<Long> set = rollingHash(paths[0], mid, pow); for (int i = 1; i < m; i++) { set.retainAll(rollingHash(paths[i], mid, pow)); if (set.isEmpty()) return false; } return true; } public Set<Long> rollingHash(int[] path, int mid, long[] pow) { Set<Long> set = new HashSet<Long>(); long hash = 0; int length = path.length; for (int i = 0; i < mid; i++) hash = (hash * BASE + path[i]) % MODULO; set.add(hash); for (int prev = 0, curr = mid; curr < length; prev++, curr++) { hash = (hash * BASE % MODULO - path[prev] * pow[mid] % MODULO + path[curr]) % MODULO; if (hash < 0) hash += MODULO; set.add(hash); } return set; } } ############ class Solution { int N = 100010; long[] h = new long[N]; long[] p = new long[N]; private int[][] paths; Map<Long, Integer> cnt = new HashMap<>(); Map<Long, Integer> inner = new HashMap<>(); public int longestCommonSubpath(int n, int[][] paths) { int left = 0, right = N; for (int[] path : paths) { right = Math.min(right, path.length); } this.paths = paths; while (left < right) { int mid = (left + right + 1) >> 1; if (check(mid)) { left = mid; } else { right = mid - 1; } } return left; } private boolean check(int mid) { cnt.clear(); inner.clear(); p[0] = 1; for (int j = 0; j < paths.length; ++j) { int n = paths[j].length; for (int i = 1; i <= n; ++i) { p[i] = p[i - 1] * 133331; h[i] = h[i - 1] * 133331 + paths[j][i - 1]; } for (int i = mid; i <= n; ++i) { long val = get(i - mid + 1, i); if (!inner.containsKey(val) || inner.get(val) != j) { inner.put(val, j); cnt.put(val, cnt.getOrDefault(val, 0) + 1); } } } int max = 0; for (int val : cnt.values()) { max = Math.max(max, val); } return max == paths.length; } private long get(int l, int r) { return h[r] - h[l - 1] * p[r - l + 1]; } }
-
// OJ: https://leetcode.com/problems/longest-common-subpath/ // Time: O(N + logM * P) where P is the total length of all paths. // Space: O(M) class Solution { bool valid(vector<vector<int>> &A, int len) { unordered_set<unsigned long long> s, tmp; for (int i = 0; i < A.size() && (i == 0 || s.size()); ++i) { unsigned long long d = 1099511628211, h = 0, p = 1; tmp.clear(); swap(s, tmp); for (int j = 0; j < A[i].size(); ++j) { h = h * d + A[i][j]; if (j < len) p *= d; else h -= A[i][j - len] * p; if (j >= len - 1 && (i == 0 || tmp.count(h))) s.insert(h); } } return s.size(); } public: int longestCommonSubpath(int n, vector<vector<int>>& A) { int L = 0, R = min_element(begin(A), end(A), [](auto &a, auto &b) { return a.size() < b.size(); })->size(); while (L < R) { int M = (L + R + 1) / 2; if (valid(A, M)) L = M; else R = M - 1; } return L; } };
-
class Solution: def longestCommonSubpath(self, n: int, paths: List[List[int]]) -> int: def get(l, r, h): return (h[r] - h[l - 1] * p[r - l + 1]) % mod def check(l): cnt = Counter() for k, path in enumerate(paths): vis = set() for i in range(len(path) - l + 1): j = i + l - 1 x = get(i + 1, j + 1, hh[k]) if x not in vis: vis.add(x) cnt[x] += 1 return max(cnt.values()) == len(paths) base = 133331 mod = 2**64 + 1 p = [0] * 100010 p[0] = 1 for i in range(1, len(p)): p[i] = (p[i - 1] * base) % mod hh = [] for path in paths: h = [0] * (len(path) + 10) for j, c in enumerate(path): h[j + 1] = (h[j] * base) % mod + c hh.append(h) left, right = 0, min(len(path) for path in paths) while left < right: mid = (left + right + 1) >> 1 if check(mid): left = mid else: right = mid - 1 return left