1923. Longest Common Subpath
Problem Description
You have a country with n
cities numbered from 0
to n - 1
. Every pair of cities in this country is connected by a road.
There are m
friends (numbered from 0
to m - 1
) traveling through the country. Each friend follows a specific path through the cities, represented as an array of city numbers in the order they visit them. A friend can visit the same city multiple times in their journey, but the same city won't appear consecutively in their path.
Given:
- An integer
n
(number of cities) - A 2D array
paths
wherepaths[i]
represents the travel path of thei-th
friend
Your task is to find the length of the longest common subpath that appears in every friend's path. If no common subpath exists among all friends, return 0
.
A subpath is defined as a contiguous sequence of cities within a path. For example, if a friend's path is [1, 2, 3, 4]
, then [2, 3]
is a subpath, but [1, 3]
is not (since it's not contiguous).
The solution uses binary search combined with rolling hash to efficiently find the longest common subpath. The algorithm:
- Binary searches on the possible length of the common subpath (from 0 to the minimum path length)
- For each candidate length
k
, uses rolling hash to check if there exists a subpath of lengthk
that appears in all friends' paths - The rolling hash technique allows checking all subpaths of a given length in linear time by computing hash values incrementally
- Returns the maximum length where a common subpath exists among all friends
Intuition
The key insight is recognizing that if a common subpath of length k
exists among all friends' paths, then any common subpath of length less than k
also exists. This monotonic property makes binary search applicable.
Think about it this way: if all friends share the subpath [2, 3, 4]
(length 3), then they definitely also share [2, 3]
(length 2) and [3, 4]
(length 2). This means we can binary search on the length of the common subpath.
The challenge then becomes: given a specific length k
, how do we efficiently check if there's a common subpath of that length?
A naive approach would be to extract all subpaths of length k
from each friend's path and find their intersection. However, comparing subpaths directly would be expensive - for each subpath comparison, we'd need O(k)
time to check if two sequences are equal.
This is where rolling hash comes in. Instead of comparing subpaths element by element, we compute a hash value for each subpath. The hash function is designed so that:
- Equal subpaths always produce the same hash value
- We can compute the hash of adjacent subpaths incrementally in
O(1)
time
The rolling hash formula used is: for a subpath [a, b, c]
, the hash is a * base^2 + b * base^1 + c * base^0
. When we slide the window to get the next subpath, we can update the hash value by removing the contribution of the leftmost element and adding the new rightmost element.
To check if a subpath of length k
is common to all friends:
- For each friend's path, compute hashes of all subpaths of length
k
- Track which unique subpath hashes each friend has seen (using a set to avoid counting the same subpath multiple times per friend)
- Count how many friends have seen each unique hash
- If any hash appears in all
m
friends' paths, then a common subpath of lengthk
exists
The binary search narrows down the maximum possible length, ultimately finding the longest common subpath shared by all friends.
Learn more about Binary Search patterns.
Solution Approach
The solution implements a binary search combined with rolling hash to find the longest common subpath efficiently.
Step 1: Preprocessing - Computing Hash Arrays
First, we precompute powers of the base for the rolling hash:
base = 133331
mod = 2**64 + 1
p = [0] * (mx + 1)
p[0] = 1
for i in range(1, len(p)):
p[i] = p[i - 1] * base % mod
Then, for each friend's path, we compute a prefix hash array:
for path in paths:
h = [0] * (len(path) + 1)
for i, x in enumerate(path, 1):
h[i] = h[i - 1] * base % mod + x
hh.append(h)
The prefix hash allows us to compute the hash of any subpath [i, j]
in O(1)
time using the formula:
hash(i, j) = (h[j] - h[i-1] * p[j-i+1]) % mod
Step 2: Binary Search on Length
We binary search on the possible length of the common subpath:
l, r = 0, min(len(path) for path in paths)
while l < r:
mid = (l + r + 1) >> 1
if check(mid):
l = mid
else:
r = mid - 1
The search range is from 0
to the length of the shortest path (since the common subpath can't be longer than any individual path).
Step 3: Checking if Length k is Valid
The check(k)
function determines if there exists a common subpath of length k
:
def check(k: int) -> bool:
cnt = Counter()
for h in hh:
vis = set()
for i in range(1, len(h) - k + 1):
j = i + k - 1
x = (h[j] - h[i - 1] * p[j - i + 1]) % mod
if x not in vis:
vis.add(x)
cnt[x] += 1
return max(cnt.values()) == m
For each friend's path:
- We iterate through all possible starting positions for a subpath of length
k
- Calculate the hash value of each subpath using the rolling hash formula
- Use a set
vis
to track unique subpath hashes for this friend (avoiding duplicate counts) - Increment the global counter
cnt
for each unique hash
If any hash value appears exactly m
times (once for each friend), then all friends share that subpath.
Time Complexity:
- Preprocessing:
O(total_path_length)
to compute all prefix hashes - Each
check(k)
call:O(total_path_length)
to examine all subpaths - Binary search makes
O(log(min_path_length))
calls tocheck
- Overall:
O(total_path_length * log(min_path_length))
Space Complexity:
O(total_path_length)
for storing the prefix hash arraysO(number_of_unique_subpaths)
for the hash counter and visited sets
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small example to illustrate the solution approach.
Input:
n = 5
(cities numbered 0-4)paths = [[0,1,2,3,4], [2,3,4], [4,0,1,2,3]]
- We have 3 friends with their respective paths
Step 1: Preprocessing - Computing Hash Arrays
First, we compute the prefix hash arrays for each path. Let's use base = 10
for simplicity (actual solution uses 133331):
For path [0,1,2,3,4]
:
h[0] = 0
h[1] = 0*10 + 0 = 0
h[2] = 0*10 + 1 = 1
h[3] = 1*10 + 2 = 12
h[4] = 12*10 + 3 = 123
h[5] = 123*10 + 4 = 1234
Similarly compute for [2,3,4]
and [4,0,1,2,3]
.
Step 2: Binary Search on Length
The minimum path length is 3 (friend 1's path), so we binary search between 0 and 3.
First iteration: l=0, r=3, mid=2
Check if there's a common subpath of length 2.
Step 3: Checking Length 2
For each friend's path, extract all subpaths of length 2 and compute their hashes:
Friend 0's path [0,1,2,3,4]
:
[0,1]
: hash = 0*10 + 1 = 1[1,2]
: hash = 1*10 + 2 = 12[2,3]
: hash = 2*10 + 3 = 23[3,4]
: hash = 3*10 + 4 = 34
Friend 1's path [2,3,4]
:
[2,3]
: hash = 2*10 + 3 = 23[3,4]
: hash = 3*10 + 4 = 34
Friend 2's path [4,0,1,2,3]
:
[4,0]
: hash = 4*10 + 0 = 40[0,1]
: hash = 0*10 + 1 = 1[1,2]
: hash = 1*10 + 2 = 12[2,3]
: hash = 2*10 + 3 = 23
Count occurrences:
- Hash 1: appears in 2 friends (0,2)
- Hash 12: appears in 2 friends (0,2)
- Hash 23: appears in 3 friends (0,1,2) ✓
- Hash 34: appears in 2 friends (0,1)
- Hash 40: appears in 1 friend (2)
Since hash 23 (representing subpath [2,3]
) appears in all 3 friends' paths, check(2)
returns true
.
Binary search continues: l=2, r=3, mid=3
Checking Length 3:
Friend 0's path [0,1,2,3,4]
:
[0,1,2]
: hash = 12[1,2,3]
: hash = 123[2,3,4]
: hash = 234
Friend 1's path [2,3,4]
:
[2,3,4]
: hash = 234
Friend 2's path [4,0,1,2,3]
:
[4,0,1]
: hash = 401[0,1,2]
: hash = 12[1,2,3]
: hash = 123
Count occurrences:
- Hash 12: appears in 2 friends (0,2)
- Hash 123: appears in 2 friends (0,2)
- Hash 234: appears in 2 friends (0,1)
- Hash 401: appears in 1 friend (2)
No hash appears in all 3 friends' paths, so check(3)
returns false
.
Binary search updates: r=2
Since l=2
and r=2
, the search terminates.
Result: The longest common subpath has length 2, which corresponds to [2,3]
that all friends visit in their journeys.
Solution Implementation
1class Solution:
2 def longestCommonSubpath(self, n: int, paths: List[List[int]]) -> int:
3 def has_common_subpath_of_length(length: int) -> bool:
4 """
5 Check if there exists a common subpath of given length across all paths.
6 Uses rolling hash to efficiently compare subpaths.
7 """
8 # Count occurrences of each hash value across all paths
9 hash_count = Counter()
10
11 for path_hash in path_hashes:
12 # Track unique hash values seen in current path to avoid double counting
13 seen_hashes = set()
14
15 # Check all subpaths of given length in current path
16 for start_idx in range(1, len(path_hash) - length + 1):
17 end_idx = start_idx + length - 1
18
19 # Calculate rolling hash for subpath [start_idx, end_idx]
20 # Formula: (hash[end] - hash[start-1] * base^length) % mod
21 subpath_hash = (path_hash[end_idx] - path_hash[start_idx - 1] * base_powers[end_idx - start_idx + 1]) % MOD
22
23 # Only count each unique hash once per path
24 if subpath_hash not in seen_hashes:
25 seen_hashes.add(subpath_hash)
26 hash_count[subpath_hash] += 1
27
28 # A common subpath exists if any hash appears in all paths
29 return max(hash_count.values()) == num_paths
30
31 # Initialize constants and variables
32 num_paths = len(paths)
33 max_path_length = max(len(path) for path in paths)
34
35 # Hash parameters (large prime base and modulo for collision resistance)
36 BASE = 133331
37 MOD = 2**64 + 1
38
39 # Precompute powers of base for rolling hash calculation
40 base_powers = [0] * (max_path_length + 1)
41 base_powers[0] = 1
42 for i in range(1, len(base_powers)):
43 base_powers[i] = base_powers[i - 1] * BASE % MOD
44
45 # Compute prefix hash arrays for all paths
46 path_hashes = []
47 for path in paths:
48 path_length = len(path)
49 # prefix_hash[i] stores hash of path[0:i]
50 prefix_hash = [0] * (path_length + 1)
51
52 for i, city in enumerate(path, 1):
53 # Rolling hash: hash[i] = hash[i-1] * base + current_element
54 prefix_hash[i] = (prefix_hash[i - 1] * BASE % MOD + city) % MOD
55
56 path_hashes.append(prefix_hash)
57
58 # Binary search for the maximum length of common subpath
59 left, right = 0, min(len(path) for path in paths)
60
61 while left < right:
62 # Use ceiling division to bias towards larger values
63 mid = (left + right + 1) >> 1
64
65 if has_common_subpath_of_length(mid):
66 # If common subpath of length mid exists, try larger lengths
67 left = mid
68 else:
69 # If no common subpath of length mid, try smaller lengths
70 right = mid - 1
71
72 return left
73
1class Solution {
2 // Constants and data structures
3 private static final int MAX_LENGTH = 100010;
4 private static final long HASH_BASE = 133331L;
5
6 private long[] prefixHash = new long[MAX_LENGTH]; // Stores prefix hash values
7 private long[] basePowers = new long[MAX_LENGTH]; // Stores powers of hash base
8 private int[][] paths;
9
10 // Map to count occurrences of each hash value across all paths
11 private Map<Long, Integer> hashCount = new HashMap<>();
12 // Map to track which path index last contributed to a hash value
13 private Map<Long, Integer> lastPathIndex = new HashMap<>();
14
15 public int longestCommonSubpath(int n, int[][] paths) {
16 // Binary search bounds initialization
17 int left = 0;
18 int right = MAX_LENGTH;
19
20 // Set right bound to minimum path length
21 for (int[] path : paths) {
22 right = Math.min(right, path.length);
23 }
24
25 this.paths = paths;
26
27 // Binary search for the maximum common subpath length
28 while (left < right) {
29 int mid = (left + right + 1) >> 1; // Equivalent to (left + right + 1) / 2
30
31 if (hasCommonSubpathOfLength(mid)) {
32 left = mid; // Can find common subpath of length mid, try longer
33 } else {
34 right = mid - 1; // Cannot find, try shorter
35 }
36 }
37
38 return left;
39 }
40
41 /**
42 * Checks if there exists a common subpath of given length in all paths
43 * @param targetLength the length of subpath to check
44 * @return true if a common subpath of targetLength exists in all paths
45 */
46 private boolean hasCommonSubpathOfLength(int targetLength) {
47 // Clear maps for new check
48 hashCount.clear();
49 lastPathIndex.clear();
50
51 // Initialize base power
52 basePowers[0] = 1;
53
54 // Process each path
55 for (int pathIndex = 0; pathIndex < paths.length; pathIndex++) {
56 int pathLength = paths[pathIndex].length;
57
58 // Build prefix hash and base powers for current path
59 for (int i = 1; i <= pathLength; i++) {
60 basePowers[i] = basePowers[i - 1] * HASH_BASE;
61 prefixHash[i] = prefixHash[i - 1] * HASH_BASE + paths[pathIndex][i - 1];
62 }
63
64 // Check all subpaths of targetLength in current path
65 for (int endPos = targetLength; endPos <= pathLength; endPos++) {
66 // Calculate hash for subpath from (endPos - targetLength + 1) to endPos
67 long hashValue = getSubpathHash(endPos - targetLength + 1, endPos);
68
69 // Only count this hash once per path to avoid duplicates
70 if (!lastPathIndex.containsKey(hashValue) || lastPathIndex.get(hashValue) != pathIndex) {
71 lastPathIndex.put(hashValue, pathIndex);
72 hashCount.put(hashValue, hashCount.getOrDefault(hashValue, 0) + 1);
73 }
74 }
75 }
76
77 // Find the maximum count among all hash values
78 int maxOccurrences = 0;
79 for (int count : hashCount.values()) {
80 maxOccurrences = Math.max(maxOccurrences, count);
81 }
82
83 // Check if any subpath appears in all paths
84 return maxOccurrences == paths.length;
85 }
86
87 /**
88 * Calculates the rolling hash value for a subpath
89 * @param startPos starting position (1-indexed)
90 * @param endPos ending position (1-indexed, inclusive)
91 * @return hash value of the subpath
92 */
93 private long getSubpathHash(int startPos, int endPos) {
94 // Using rolling hash formula: hash[l,r] = hash[r] - hash[l-1] * base^(r-l+1)
95 return prefixHash[endPos] - prefixHash[startPos - 1] * basePowers[endPos - startPos + 1];
96 }
97}
98
1class Solution {
2private:
3 // Constants and data structures
4 static constexpr int MAX_LENGTH = 100010;
5 static constexpr long long HASH_BASE = 133331LL;
6
7 long long prefix_hash[MAX_LENGTH]; // Stores prefix hash values
8 long long base_powers[MAX_LENGTH]; // Stores powers of hash base
9 vector<vector<int>> paths;
10
11 // Map to count occurrences of each hash value across all paths
12 unordered_map<long long, int> hash_count;
13 // Map to track which path index last contributed to a hash value
14 unordered_map<long long, int> last_path_index;
15
16public:
17 int longestCommonSubpath(int n, vector<vector<int>>& paths) {
18 // Binary search bounds initialization
19 int left = 0;
20 int right = MAX_LENGTH;
21
22 // Set right bound to minimum path length
23 for (const auto& path : paths) {
24 right = min(right, static_cast<int>(path.size()));
25 }
26
27 this->paths = paths;
28
29 // Binary search for the maximum common subpath length
30 while (left < right) {
31 int mid = (left + right + 1) >> 1; // Equivalent to (left + right + 1) / 2
32
33 if (hasCommonSubpathOfLength(mid)) {
34 left = mid; // Can find common subpath of length mid, try longer
35 } else {
36 right = mid - 1; // Cannot find, try shorter
37 }
38 }
39
40 return left;
41 }
42
43private:
44 /**
45 * Checks if there exists a common subpath of given length in all paths
46 * @param target_length the length of subpath to check
47 * @return true if a common subpath of target_length exists in all paths
48 */
49 bool hasCommonSubpathOfLength(int target_length) {
50 // Clear maps for new check
51 hash_count.clear();
52 last_path_index.clear();
53
54 // Initialize base power
55 base_powers[0] = 1;
56
57 // Process each path
58 for (int path_index = 0; path_index < paths.size(); path_index++) {
59 int path_length = paths[path_index].size();
60
61 // Build prefix hash and base powers for current path
62 for (int i = 1; i <= path_length; i++) {
63 base_powers[i] = base_powers[i - 1] * HASH_BASE;
64 prefix_hash[i] = prefix_hash[i - 1] * HASH_BASE + paths[path_index][i - 1];
65 }
66
67 // Check all subpaths of target_length in current path
68 for (int end_pos = target_length; end_pos <= path_length; end_pos++) {
69 // Calculate hash for subpath from (end_pos - target_length + 1) to end_pos
70 long long hash_value = getSubpathHash(end_pos - target_length + 1, end_pos);
71
72 // Only count this hash once per path to avoid duplicates
73 if (last_path_index.find(hash_value) == last_path_index.end() ||
74 last_path_index[hash_value] != path_index) {
75 last_path_index[hash_value] = path_index;
76 hash_count[hash_value]++;
77 }
78 }
79 }
80
81 // Find the maximum count among all hash values
82 int max_occurrences = 0;
83 for (const auto& [hash_val, count] : hash_count) {
84 max_occurrences = max(max_occurrences, count);
85 }
86
87 // Check if any subpath appears in all paths
88 return max_occurrences == paths.size();
89 }
90
91 /**
92 * Calculates the rolling hash value for a subpath
93 * @param start_pos starting position (1-indexed)
94 * @param end_pos ending position (1-indexed, inclusive)
95 * @return hash value of the subpath
96 */
97 long long getSubpathHash(int start_pos, int end_pos) {
98 // Using rolling hash formula: hash[l,r] = hash[r] - hash[l-1] * base^(r-l+1)
99 return prefix_hash[end_pos] - prefix_hash[start_pos - 1] * base_powers[end_pos - start_pos + 1];
100 }
101};
102
1// Constants and data structures
2const MAX_LENGTH = 100010;
3const HASH_BASE = 133331n; // Using BigInt for large number calculations
4
5let prefixHash: bigint[] = new Array(MAX_LENGTH); // Stores prefix hash values
6let basePowers: bigint[] = new Array(MAX_LENGTH); // Stores powers of hash base
7let paths: number[][];
8
9// Map to count occurrences of each hash value across all paths
10let hashCount: Map<bigint, number> = new Map();
11// Map to track which path index last contributed to a hash value
12let lastPathIndex: Map<bigint, number> = new Map();
13
14function longestCommonSubpath(n: number, pathsInput: number[][]): number {
15 // Binary search bounds initialization
16 let left = 0;
17 let right = MAX_LENGTH;
18
19 // Set right bound to minimum path length
20 for (const path of pathsInput) {
21 right = Math.min(right, path.length);
22 }
23
24 paths = pathsInput;
25
26 // Binary search for the maximum common subpath length
27 while (left < right) {
28 const mid = (left + right + 1) >> 1; // Equivalent to Math.floor((left + right + 1) / 2)
29
30 if (hasCommonSubpathOfLength(mid)) {
31 left = mid; // Can find common subpath of length mid, try longer
32 } else {
33 right = mid - 1; // Cannot find, try shorter
34 }
35 }
36
37 return left;
38}
39
40/**
41 * Checks if there exists a common subpath of given length in all paths
42 * @param targetLength - The length of subpath to check
43 * @returns True if a common subpath of targetLength exists in all paths
44 */
45function hasCommonSubpathOfLength(targetLength: number): boolean {
46 // Clear maps for new check
47 hashCount.clear();
48 lastPathIndex.clear();
49
50 // Initialize base power
51 basePowers[0] = 1n;
52
53 // Process each path
54 for (let pathIndex = 0; pathIndex < paths.length; pathIndex++) {
55 const pathLength = paths[pathIndex].length;
56
57 // Build prefix hash and base powers for current path
58 for (let i = 1; i <= pathLength; i++) {
59 basePowers[i] = basePowers[i - 1] * HASH_BASE;
60 prefixHash[i] = prefixHash[i - 1] * HASH_BASE + BigInt(paths[pathIndex][i - 1]);
61 }
62
63 // Check all subpaths of targetLength in current path
64 for (let endPos = targetLength; endPos <= pathLength; endPos++) {
65 // Calculate hash for subpath from (endPos - targetLength + 1) to endPos
66 const hashValue = getSubpathHash(endPos - targetLength + 1, endPos);
67
68 // Only count this hash once per path to avoid duplicates
69 if (!lastPathIndex.has(hashValue) || lastPathIndex.get(hashValue) !== pathIndex) {
70 lastPathIndex.set(hashValue, pathIndex);
71 hashCount.set(hashValue, (hashCount.get(hashValue) || 0) + 1);
72 }
73 }
74 }
75
76 // Find the maximum count among all hash values
77 let maxOccurrences = 0;
78 for (const count of hashCount.values()) {
79 maxOccurrences = Math.max(maxOccurrences, count);
80 }
81
82 // Check if any subpath appears in all paths
83 return maxOccurrences === paths.length;
84}
85
86/**
87 * Calculates the rolling hash value for a subpath
88 * @param startPos - Starting position (1-indexed)
89 * @param endPos - Ending position (1-indexed, inclusive)
90 * @returns Hash value of the subpath
91 */
92function getSubpathHash(startPos: number, endPos: number): bigint {
93 // Using rolling hash formula: hash[l,r] = hash[r] - hash[l-1] * base^(r-l+1)
94 return prefixHash[endPos] - prefixHash[startPos - 1] * basePowers[endPos - startPos + 1];
95}
96
Time and Space Complexity
Time Complexity: O(S * log(min_len))
where S
is the total sum of all path lengths and min_len
is the length of the shortest path.
The analysis breaks down as follows:
- Binary search runs for
O(log(min_len))
iterations - For each binary search iteration, we call the
check(k)
function - Inside
check(k)
:- We iterate through all
m
paths - For each path of length
len(path)
, we compute rolling hashes for all subpaths of lengthk
, which takesO(len(path) - k + 1)
iterations - Each hash computation is
O(1)
due to the precomputed prefix hashes - Total work in
check(k)
isO(sum(len(path) - k + 1))
for all paths, which isO(S)
- We iterate through all
- Preprocessing step computes prefix hashes for all paths in
O(S)
time - Overall:
O(S) + O(S * log(min_len)) = O(S * log(min_len))
Space Complexity: O(S + mx)
where S
is the total sum of all path lengths and mx
is the length of the longest path.
The space breakdown:
- Array
p
stores powers of base up tomx + 1
:O(mx)
- Array
hh
stores prefix hashes for all paths:O(S)
total space - Inside
check(k)
:cnt
Counter can store at mostO(S)
unique hash valuesvis
set for each path stores at mostO(len(path))
hashes
- Overall:
O(S + mx)
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Hash Collision Issues with Poor Hash Function Choice
Pitfall: Using a small modulo or inappropriate base value can lead to hash collisions, where different subpaths produce the same hash value. This causes the algorithm to incorrectly identify non-matching subpaths as identical.
Example scenario:
- Using
MOD = 10^9 + 7
(a common choice) might cause collisions when dealing with large datasets - Using a small base like
31
or256
increases collision probability
Solution:
- Use a large prime modulo like
2^64 + 1
or2^61 - 1
- Choose a large random prime base (e.g.,
133331
) - Consider using double hashing with two different hash functions for extra safety:
def has_common_subpath_with_double_hash(length: int) -> bool:
BASE1, MOD1 = 133331, 2**64 + 1
BASE2, MOD2 = 100003, 2**61 - 1
hash_count = Counter()
for i, path in enumerate(paths):
seen_hashes = set()
for start in range(len(path) - length + 1):
# Compute two independent hashes
hash1 = compute_hash(path, start, length, BASE1, MOD1)
hash2 = compute_hash(path, start, length, BASE2, MOD2)
combined_hash = (hash1, hash2) # Use tuple as key
if combined_hash not in seen_hashes:
seen_hashes.add(combined_hash)
hash_count[combined_hash] += 1
return max(hash_count.values()) == num_paths
2. Incorrect Handling of Duplicate Subpaths Within a Single Path
Pitfall: Counting the same subpath multiple times within a single friend's path, leading to incorrect results when checking if a subpath appears in all paths.
Example: If friend 0's path is [1, 2, 3, 1, 2]
and we're checking for subpath [1, 2]
, it appears twice. Without proper deduplication, the counter might show this subpath appearing "2 times" when we only have 1 friend.
Solution: The code correctly handles this by using a seen_hashes
set for each path to ensure each unique subpath is counted only once per friend:
seen_hashes = set() # Reset for each friend's path
for start_idx in range(1, len(path_hash) - length + 1):
# ... calculate subpath_hash ...
if subpath_hash not in seen_hashes: # Only count once per path
seen_hashes.add(subpath_hash)
hash_count[subpath_hash] += 1
3. Off-by-One Errors in Index Calculations
Pitfall: Confusion between 0-based and 1-based indexing when computing prefix hashes and extracting subpath hashes.
Common mistakes:
- Using wrong range boundaries:
range(0, len(path_hash) - length)
instead ofrange(1, len(path_hash) - length + 1)
- Incorrect power calculation: using
base_powers[length]
instead ofbase_powers[end_idx - start_idx + 1]
Solution: Be consistent with indexing convention. The code uses 1-based indexing for prefix hashes:
# Prefix hash array is 1-indexed: prefix_hash[i] = hash of path[0:i]
for i, city in enumerate(path, 1): # Start from index 1
prefix_hash[i] = (prefix_hash[i - 1] * BASE + city) % MOD
# When extracting subpath hash, adjust indices accordingly
for start_idx in range(1, len(path_hash) - length + 1): # 1-based start
end_idx = start_idx + length - 1
# Use the correct power: end_idx - start_idx + 1 = length
subpath_hash = (path_hash[end_idx] - path_hash[start_idx - 1] * base_powers[length]) % MOD
4. Integer Overflow in Languages Without Arbitrary Precision
Pitfall: In languages like C++ or Java, multiplying large numbers can cause integer overflow before the modulo operation is applied.
Solution for other languages:
// C++ solution using unsigned long long and careful modulo
unsigned long long computeHash(int start, int length) {
// Use multiplication with modulo at each step
unsigned long long hash = (prefixHash[start + length] -
(prefixHash[start] * power[length] % MOD) + MOD) % MOD;
return hash;
}
In Python, this isn't an issue due to arbitrary precision integers, but it's important to be aware of when porting the solution.
Which of the tree traversal order can be used to obtain elements in a binary search tree in sorted order?
Recommended Readings
Binary Search Speedrun For each of the Speedrun questions you will be given a binary search related problem and a corresponding multiple choice question The multiple choice questions are related to the techniques and template s introduced in the binary search section It's recommended that you have gone through at
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!