class Solution {
public:
int longestCommonSubpath(int n, vector<vector<int>>& paths) {
}
};
1923. 最长公共子路径
一个国家由 n
个编号为 0
到 n - 1
的城市组成。在这个国家里,每两个 城市之间都有一条道路连接。
总共有 m
个编号为 0
到 m - 1
的朋友想在这个国家旅游。他们每一个人的路径都会包含一些城市。每条路径都由一个整数数组表示,每个整数数组表示一个朋友按顺序访问过的城市序列。同一个城市在一条路径中可能 重复 出现,但同一个城市在一条路径中不会连续出现。
给你一个整数 n
和二维数组 paths
,其中 paths[i]
是一个整数数组,表示第 i
个朋友走过的路径,请你返回 每一个 朋友都走过的 最长公共子路径 的长度,如果不存在公共子路径,请你返回 0
。
一个 子路径 指的是一条路径中连续的城市序列。
示例 1:
输入:n = 5, paths = [[0,1,2,3,4], [2,3,4], [4,0,1,2,3]] 输出:2 解释:最长公共子路径为 [2,3] 。
示例 2:
输入:n = 3, paths = [[0],[1],[2]] 输出:0 解释:三条路径没有公共子路径。
示例 3:
输入:n = 5, paths = [[0,1,2,3,4], [4,3,2,1,0]] 输出:1 解释:最长公共子路径为 [0],[1],[2],[3] 和 [4] 。它们长度都为 1 。
提示:
1 <= n <= 105
m == paths.length
2 <= m <= 105
sum(paths[i].length) <= 105
0 <= paths[i][j] < n
paths[i]
中同一个城市不会连续重复出现。原站题解
python3 解法, 执行用时: 1248 ms, 内存消耗: 40.1 MB, 提交时间: 2023-08-31 09:35:40
class Solution: def longestCommonSubpath(self, n: int, paths: List[List[int]]) -> int: # 根据正文部分的分析,我们选择的 mod 需要远大于 10^10 # Python 直接选择 10^9+7 * 10^9+9 作为模数即可 # 乘积约为 10^18,远大于 10^10 mod = (10**9 + 7) * (10**9 + 9) # 本题中数组元素的范围为 [0, 10^5] # 因此我们在 [10^6, 10^7] 的范围内随机选取进制 base base = random.randint(10**6, 10**7) m = len(paths) # 确定二分查找的上下界 left, right, ans = 1, len(min(paths, key=lambda p: len(p))), 0 while left <= right: length = (left + right) // 2 mult = pow(base, length, mod) s = set() check = True for i in range(m): hashvalue = 0 # 计算首个长度为 len 的子数组的哈希值 for j in range(length): hashvalue = (hashvalue * base + paths[i][j]) % mod t = set() # 如果我们遍历的是第 0 个数组,或者上一个数组的哈希表中包含该哈希值 # 我们才会将哈希值加入当前数组的哈希表中 if i == 0 or hashvalue in s: t.add(hashvalue) # 递推计算后续子数组的哈希值 for j in range(length, len(paths[i])): hashvalue = (hashvalue * base - paths[i][j - length] * mult + paths[i][j]) % mod if i == 0 or hashvalue in s: t.add(hashvalue) if not t: check = False break s = t if check: ans = length left = length + 1 else: right = length - 1 return ans
golang 解法, 执行用时: 212 ms, 内存消耗: 23.8 MB, 提交时间: 2023-08-31 09:35:26
func longestCommonSubpath(_ int, paths [][]int) (ans int) { a := []int{} minPathLen := int(1e9) // 二分右边界 for _, p := range paths { minPathLen = min(minPathLen, len(p)) a = append(a, 1e9) // 用一个不存在 paths 中的数拼接所有路径 a = append(a, p...) } n, m := len(a), len(paths) // 标记每个元素属于哪条路径 ids := make([]int, n) id := -1 for i, v := range a { if v == 1e9 { id++ ids[i] = m } else { ids[i] = id } } // 构建 a 的后缀数组和高度数组 s := make([]byte, 0, n*4) for _, v := range a { s = append(s, byte(v>>24), byte(v>>16), byte(v>>8), byte(v)) } // Go 自带后缀数组,由 SA-IS 算法实现,复杂度 O(n) _sa := *(*[]int32)(unsafe.Pointer(reflect.ValueOf(suffixarray.New(s)).Elem().FieldByName("sa").Field(0).UnsafeAddr())) sa := make([]int32, 0, n) for _, v := range _sa { if v&3 == 0 { sa = append(sa, v>>2) } } rank := make([]int, n) for i := range rank { rank[sa[i]] = i } height := make([]int, n) h := 0 for i, rk := range rank { if h > 0 { h-- } if rk > 0 { for j := int(sa[rk-1]); i+h < n && j+h < n && a[i+h] == a[j+h]; h++ { } } height[rk] = h } // 二分求答案 return sort.Search(minPathLen, func(limit int) bool { limit++ vis := make([]int, m) for i := 1; i < n; i++ { if height[i] < limit { continue } cnt := 0 for st := i; i < n && height[i] >= limit; i++ { // 检查 sa[i] 和 sa[i-1] if j := ids[sa[i]]; j < m && vis[j] != st { vis[j] = st cnt++ } if j := ids[sa[i-1]]; j < m && vis[j] != st { vis[j] = st cnt++ } } if cnt == m { return false } } return true }) } func min(a, b int) int { if a < b { return a }; return b }