leetcode
leetcode 1701 ~ 1750
最长公共子路径

最长公共子路径

难度:

标签:

题目描述

代码结果

运行时间: 1443 ms, 内存: 92.1 MB


/*
 * Solution Approach with Java Stream:
 * 1. Use binary search to find the maximum length of the common subpath.
 * 2. For each possible length, check if there is a common subpath by maintaining a set of hash values.
 * 3. Use streams to perform set operations for each path.
 */

import java.util.*;
import java.util.stream.Collectors;

public class SolutionStream {
    public int longestCommonSubpath(int n, int[][] paths) {
        int left = 1, right = Arrays.stream(paths).mapToInt(p -> p.length).min().getAsInt();

        long mod = (1L << 61) - 1;
        long base = 1000000007;

        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (hasCommonSubpath(paths, mid, mod, base)) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
        return right;
    }

    private boolean hasCommonSubpath(int[][] paths, int length, long mod, long base) {
        Set<Long> commonHashes = null;

        for (int[] path : paths) {
            Set<Long> currentHashes = new HashSet<>();
            long hash = 0, power = 1;

            for (int i = 0; i < length; i++) {
                hash = (hash * base + path[i]) % mod;
                power = (power * base) % mod;
            }
            currentHashes.add(hash);

            for (int i = length; i < path.length; i++) {
                hash = (hash * base + path[i] - path[i - length] * power % mod + mod) % mod;
                currentHashes.add(hash);
            }

            if (commonHashes == null) {
                commonHashes = currentHashes;
            } else {
                commonHashes.retainAll(currentHashes);
                if (commonHashes.isEmpty()) return false;
            }
        }
        return !commonHashes.isEmpty();
    }
}

解释

方法:

该题解采用后缀数组和LCP数组结合滑动窗口的方法来寻找最长公共子路径。首先,构建一个由所有路径组成的超级字符串,每个路径之间用一个特殊的分隔符隔开。然后对这个超级字符串构建后缀数组,这样就可以在O(n log n)的时间内找到所有后缀的排序。接下来,使用后缀数组计算出最长公共前缀(LCP)数组。利用LCP数组,通过滑动窗口技术来维护所有路径在当前窗口内的最小LCP值,确保窗口内包含所有路径至少一次。最后,窗口的最小LCP值即为我们要找的最长公共子路径的长度。

时间复杂度:

O(n log n)

空间复杂度:

O(n)

代码细节讲解

🦆
在构建后缀数组的过程中,你如何处理每个路径之间的特殊分隔符,以确保这些分隔符不会与城市编号混淆?
在构建后缀数组的过程中,为了避免城市编号与特殊分隔符混淆,我们对城市编号进行了偏移处理,将每个城市编号加上路径的数量 m。这样,所有城市编号都会大于任何一个分隔符编号,分隔符的编号为 m-i(其中 i 是路径的索引)。通过这种方式,我们确保了城市编号与分隔符的值不会重叠,从而在后缀数组和LCP数组的计算中正确区分不同的路径。
🦆
算法中提到了使用滑动窗口来维护路径的最小LCP值,但具体如何初始化窗口,并确保窗口中始终包含所有路径至少一次?
滑动窗口在算法中用于确保每个窗口内包含所有路径至少一次。窗口初始化时,开始和结束指针都设为数组的起始位置。随着结束指针向右移动,我们增加对应后缀起始位置的路径计数,并将当前LCP值与窗口的最小值进行比较,如果有必要,更新窗口的最小值。如果发现某路径的计数超过1,开始指针向右移动,直到每个路径在窗口中至少出现一次。通过这种方式,我们可以找到同时涵盖所有路径的最小LCP区间,即为我们要找的最长公共子路径的长度。
🦆
为什么在计算LCP数组时需要在字符串末尾添加一个特殊字符(-1)?这对于结果有什么具体影响?
在计算LCP数组时,在字符串末尾添加一个特殊字符(-1)是为了确保最后一个字符是全局最小的,这样可以避免在进行LCP计算时越界。因为比较时我们会检查当前后缀与前一个后缀的共同前缀,添加-1确保了在字符串尾部的比较会立即停止,因为没有其他字符会比-1更小。这样,我们能够正确地终止比较过程,避免错误地扩展LCP值。
🦆
在使用后缀数组和LCP数组寻找最长公共子路径时,你如何确保不会将不同路径中的重复城市作为公共子路径的一部分?
在使用后缀数组和LCP数组寻找最长公共子路径时,我们通过在每个路径末尾添加唯一的分隔符来区分不同路径。这些分隔符在计算过程中保证了任何找到的公共子序列不会跨越多个路径。此外,通过滑动窗口技术确保每个窗口至少包含每个路径一次,我们可以保证找到的最长公共子路径是所有路径共有的,而不是由于路径内部的重复城市引起的误匹配。

相关问题