Knuth-Morris-Pratt
Problems involving string pattern matching using the KMP algorithm, which achieves linear time complexity through preprocessing of the pattern.
Problems
Find String Roots
Problem Information
- Source: SPOJ
- Difficulty: Secret
- Time Limit: 1000ms
- Memory Limit: 512MB
Problem Statement
The Nth root of a string S is a string T such that T concatenated N times equals S. For example:
- S = “abcabcabcabc” has 4th root T = “abc”
- For N = 2, the 2nd root is “abcabc”
- For N = 1, any string S is its own 1st root
Find the maximum N such that the Nth root of S exists.
Input Format
- Multiple test cases, one per line
- Each line: a non-empty string S (≤ 10⁵ characters)
- Input ends with “*”
Output Format
For each test case, print the greatest N such that the Nth root exists.
Solution
Approach
Use KMP failure function. If the string has period P, then N = len(S) / P. The period P = len(S) - failure[len(S)-1]. N is valid only if len(S) is divisible by P.
Python Solution
def compute_failure(s):
"""KMP failure function"""
n = len(s)
fail = [0] * n
for i in range(1, n):
j = fail[i - 1]
while j > 0 and s[i] != s[j]:
j = fail[j - 1]
if s[i] == s[j]:
j += 1
fail[i] = j
return fail
def solve():
while True:
s = input().strip()
if s == '*':
break
n = len(s)
fail = compute_failure(s)
# Period of the string
period = n - fail[n - 1]
# Check if n is divisible by period
if n % period == 0:
print(n // period)
else:
print(1)
if __name__ == "__main__":
solve()
Alternative
def solve():
while True:
s = input().strip()
if s == '*':
break
n = len(s)
# Build KMP prefix function
pi = [0] * n
for i in range(1, n):
j = pi[i - 1]
while j > 0 and s[i] != s[j]:
j = pi[j - 1]
if s[i] == s[j]:
j += 1
pi[i] = j
# The smallest period is n - pi[n-1]
# If n is divisible by this period, that's our answer
smallest_period = n - pi[n - 1]
if n % smallest_period == 0:
print(n // smallest_period)
else:
print(1) # String is not periodic, only 1st root exists
if __name__ == "__main__":
solve()
One-liner
def kmp_period(s):
n = len(s)
pi = [0] * n
for i in range(1, n):
j = pi[i-1]
while j and s[i] != s[j]: j = pi[j-1]
pi[i] = j + (s[i] == s[j])
p = n - pi[-1]
return n // p if n % p == 0 else 1
while True:
s = input().strip()
if s == '*': break
print(kmp_period(s))
Complexity Analysis
-
Time Complexity: O( S ) per string -
Space Complexity: O( S ) for failure array
Key Insight
The KMP failure function reveals the string’s periodic structure. If fail[n-1] = k, then the first k characters match the last k characters, meaning the string has period (n-k). The maximum N is n/period if n is divisible by the period.
Gaint and Sifat
Problem Information
- Source: Codechef
- Difficulty: Secret
- Time Limit: 1000ms
- Memory Limit: 512MB
Problem Statement
Sifat needs to find how many times word s occurs as a substring in sentence S (after removing all spaces from S).
Input Format
- First line: T (1 ≤ T ≤ 30) - number of test cases
- Each test case: two lines
- First line: sentence S
- Second line: word s
Constraints
-
1 ≤ S ≤ 100000 -
1 ≤ s ≤ S
Output Format
For each case, print case number and count of occurrences of s in S (spaces removed).
Solution
Approach
- Remove all spaces from S
- Use KMP algorithm to count occurrences of pattern s in text S
Python Solution
def kmp_count(text, pattern):
"""Count occurrences of pattern in text using KMP"""
if not pattern:
return 0
# Build failure function
m = len(pattern)
fail = [0] * m
for i in range(1, m):
j = fail[i - 1]
while j > 0 and pattern[i] != pattern[j]:
j = fail[j - 1]
if pattern[i] == pattern[j]:
j += 1
fail[i] = j
# Search
count = 0
j = 0
for i in range(len(text)):
while j > 0 and text[i] != pattern[j]:
j = fail[j - 1]
if text[i] == pattern[j]:
j += 1
if j == m:
count += 1
j = fail[j - 1] # Continue searching for overlapping matches
return count
def solve():
t = int(input())
for case in range(1, t + 1):
S = input().replace(' ', '') # Remove all spaces
s = input().strip()
count = kmp_count(S, s)
print(f"Case {case}: {count}")
if __name__ == "__main__":
solve()
Alternative
def solve():
t = int(input())
for case in range(1, t + 1):
text = input().replace(' ', '')
pattern = input().strip()
# Build KMP prefix function
def build_lps(p):
lps = [0] * len(p)
length = 0
i = 1
while i < len(p):
if p[i] == p[length]:
length += 1
lps[i] = length
i += 1
elif length != 0:
length = lps[length - 1]
else:
lps[i] = 0
i += 1
return lps
lps = build_lps(pattern)
# KMP search
count = 0
i = j = 0
while i < len(text):
if text[i] == pattern[j]:
i += 1
j += 1
if j == len(pattern):
count += 1
j = lps[j - 1]
elif j != 0:
j = lps[j - 1]
else:
i += 1
print(f"Case {case}: {count}")
if __name__ == "__main__":
solve()
Complexity Analysis
-
Time Complexity: O( S + s ) per test case -
Space Complexity: O( s ) for failure function
Key Insight
After removing spaces from S, this becomes a standard string matching problem. KMP algorithm efficiently counts all (possibly overlapping) occurrences of the pattern in linear time.
MUH and Cube Walls
Problem Information
- Source: Codeforces
- Difficulty: Secret
- Time Limit: 2000ms
- Memory Limit: 512MB
Problem Statement
Bears have a wall of n towers with heights a₁, a₂, …, aₙ. Elephant Horace has a wall of w towers with heights b₁, b₂, …, bw.
Horace can “see an elephant” on a segment of w contiguous towers in the bears’ wall if the heights match as a sequence (allowing raising/lowering the entire wall uniformly).
Count segments where Horace can “see an elephant”.
Input Format
- First line: n and w (1 ≤ n, w ≤ 2×10⁵)
- Second line: n integers aᵢ (1 ≤ aᵢ ≤ 10⁹) - bears’ wall heights
- Third line: w integers bᵢ (1 ≤ bᵢ ≤ 10⁹) - elephant’s wall heights
Output Format
Print the number of segments where Horace can “see an elephant”.
Solution
Approach
Two walls match if their “shape” (differences between consecutive heights) is the same. Convert both walls to difference arrays and use KMP to count pattern matches.
Python Solution
def solve():
n, w = map(int, input().split())
a = list(map(int, input().split()))
b = list(map(int, input().split()))
# Edge case: single tower always matches
if w == 1:
print(n)
return
# Convert to difference arrays
diff_a = [a[i+1] - a[i] for i in range(n-1)]
diff_b = [b[i+1] - b[i] for i in range(w-1)]
# KMP to count occurrences of diff_b in diff_a
pattern = diff_b
text = diff_a
m = len(pattern)
if m == 0:
print(n)
return
# Build failure function
fail = [0] * m
for i in range(1, m):
j = fail[i-1]
while j > 0 and pattern[i] != pattern[j]:
j = fail[j-1]
if pattern[i] == pattern[j]:
j += 1
fail[i] = j
# Search
count = 0
j = 0
for i in range(len(text)):
while j > 0 and text[i] != pattern[j]:
j = fail[j-1]
if text[i] == pattern[j]:
j += 1
if j == m:
count += 1
j = fail[j-1]
print(count)
if __name__ == "__main__":
solve()
Alternative
def kmp_search(text, pattern):
if not pattern:
return len(text) + 1
# Build LPS array
m = len(pattern)
lps = [0] * m
length = 0
i = 1
while i < m:
if pattern[i] == pattern[length]:
length += 1
lps[i] = length
i += 1
elif length:
length = lps[length - 1]
else:
i += 1
# Search
count = 0
i = j = 0
while i < len(text):
if text[i] == pattern[j]:
i += 1
j += 1
if j == m:
count += 1
j = lps[j - 1]
elif j:
j = lps[j - 1]
else:
i += 1
return count
def solve():
n, w = map(int, input().split())
a = list(map(int, input().split()))
b = list(map(int, input().split()))
if w == 1:
print(n)
return
# Compute differences
da = [a[i+1] - a[i] for i in range(n-1)]
db = [b[i+1] - b[i] for i in range(w-1)]
print(kmp_search(da, db))
if __name__ == "__main__":
solve()
Complexity Analysis
- Time Complexity: O(N + W)
- Space Complexity: O(W) for failure function
Key Insight
Two sequences of heights have the same “shape” if their consecutive differences are identical. Converting both walls to difference arrays reduces this to exact pattern matching, solvable with KMP.
Maximum Profit
Problem Information
- Source: GeeksforGeeks
- Difficulty: Secret
- Time Limit: 1000ms
- Memory Limit: 1024MB
Problem Statement
Given stock prices for N days and at most K transactions allowed, find the maximum profit. A transaction is buying then selling. You cannot buy a new stock until the previous transaction is completed.
Input Format
- First line: T (number of test cases)
- For each test case:
- First line: K (max transactions)
- Second line: N (number of days)
- Third line: N prices
Constraints
- 1 ≤ T ≤ 100
- 0 < K ≤ 10
- 2 ≤ N ≤ 30
- 0 ≤ A[i] ≤ 1000
Output Format
Print maximum profit (0 if no profit possible).
Solution
Approach
Use DP with states: dp[t][d] = max profit using at most t transactions up to day d.
For each day, either:
- Don’t trade:
dp[t][d] = dp[t][d-1] - Complete a transaction: buy on day j, sell on day d
dp[t][d] = max(dp[t-1][j-1] + price[d] - price[j])for all j < d
Python Solution
def solve():
t = int(input())
for _ in range(t):
k = int(input())
n = int(input())
prices = list(map(int, input().split()))
if n <= 1 or k == 0:
print(0)
continue
# If k >= n/2, we can do unlimited transactions
if k >= n // 2:
profit = sum(max(0, prices[i+1] - prices[i]) for i in range(n-1))
print(profit)
continue
# DP approach
# dp[t][d] = max profit with at most t transactions ending on or before day d
dp = [[0] * n for _ in range(k + 1)]
for t in range(1, k + 1):
max_diff = -prices[0] # max of (dp[t-1][j] - prices[j])
for d in range(1, n):
# Don't sell on day d
dp[t][d] = dp[t][d-1]
# Sell on day d (bought on some earlier day j)
dp[t][d] = max(dp[t][d], prices[d] + max_diff)
# Update max_diff for future days
max_diff = max(max_diff, dp[t-1][d] - prices[d])
print(dp[k][n-1])
if __name__ == "__main__":
solve()
Alternative
def solve():
T = int(input())
for _ in range(T):
k = int(input())
n = int(input())
prices = list(map(int, input().split()))
if n <= 1:
print(0)
continue
# dp[i][j][0] = max profit at day i with j transactions, not holding
# dp[i][j][1] = max profit at day i with j transactions, holding
INF = float('inf')
dp = [[[-INF, -INF] for _ in range(k + 2)] for _ in range(n + 1)]
dp[0][0][0] = 0
for i in range(n):
for j in range(k + 1):
# Not holding stock
if dp[i][j][0] > -INF:
# Stay not holding
dp[i+1][j][0] = max(dp[i+1][j][0], dp[i][j][0])
# Buy stock
dp[i+1][j][1] = max(dp[i+1][j][1], dp[i][j][0] - prices[i])
# Holding stock
if dp[i][j][1] > -INF:
# Stay holding
dp[i+1][j][1] = max(dp[i+1][j][1], dp[i][j][1])
# Sell stock (complete transaction)
dp[i+1][j+1][0] = max(dp[i+1][j+1][0], dp[i][j][1] + prices[i])
ans = max(dp[n][j][0] for j in range(k + 1))
print(max(0, ans))
if __name__ == "__main__":
solve()
Complexity Analysis
- Time Complexity: O(N × K)
- Space Complexity: O(N × K)
Key Insight
This is the “Best Time to Buy and Sell Stock” problem with at most K transactions. The optimized DP tracks max_diff to avoid an O(N²) inner loop, reducing complexity from O(N²K) to O(NK).
Password
Problem Information
- Source: Codeforces
- Difficulty: Secret
- Time Limit: 2000ms
- Memory Limit: 512MB
Problem Statement
Given string s, find the longest substring t that is:
- A prefix of s
- A suffix of s
- Appears somewhere in the middle of s (not at the very beginning or end)
If no such substring exists, print “Just a legend”.
Input Format
- Single line: string s (length 1 to 10⁶), consisting of lowercase Latin letters
Output Format
Print the substring t, or “Just a legend” if it doesn’t exist.
Solution
Approach
Use KMP failure function:
- Find all prefix-suffix lengths using failure function
- Check which of these also appear in the middle
- Return the longest valid one
Python Solution
def solve():
s = input().strip()
n = len(s)
if n < 3:
print("Just a legend")
return
# Build KMP failure function
fail = [0] * n
for i in range(1, n):
j = fail[i - 1]
while j > 0 and s[i] != s[j]:
j = fail[j - 1]
if s[i] == s[j]:
j += 1
fail[i] = j
# Find all valid prefix-suffix lengths
prefix_suffix_lens = set()
length = fail[n - 1]
while length > 0:
prefix_suffix_lens.add(length)
length = fail[length - 1]
# Find lengths that appear in the middle (positions 1 to n-2)
middle_lens = set()
for i in range(n - 2): # Exclude last position
if fail[i] > 0:
middle_lens.add(fail[i])
# Find longest length that is both prefix-suffix AND appears in middle
valid_lens = prefix_suffix_lens & middle_lens
if not valid_lens:
print("Just a legend")
else:
max_len = max(valid_lens)
print(s[:max_len])
if __name__ == "__main__":
solve()
Alternative
def solve():
s = input().strip()
n = len(s)
# KMP failure function
pi = [0] * n
for i in range(1, n):
j = pi[i - 1]
while j > 0 and s[i] != s[j]:
j = pi[j - 1]
if s[i] == s[j]:
j += 1
pi[i] = j
# Count occurrences of each prefix (by its length)
cnt = [0] * (n + 1)
for i in range(n):
cnt[pi[i]] += 1
# Accumulate: prefix of length k includes prefix of length pi[k-1]
for i in range(n - 1, 0, -1):
cnt[pi[i - 1]] += cnt[i]
# Find valid prefix-suffix that appears >= 3 times
# (start, end, middle)
length = pi[n - 1]
while length > 0:
# cnt[length] >= 2 means it appears at least twice besides being suffix
# But we need it to appear in the middle
# Check if this prefix appears at position other than 0 and n-length
if cnt[length] >= 3:
print(s[:length])
return
# Or if it appears as a prefix of the suffix prefix
if cnt[length] >= 2 and pi[length - 1] > 0:
print(s[:pi[length - 1]])
return
length = pi[length - 1]
print("Just a legend")
if __name__ == "__main__":
solve()
Complexity Analysis
- Time Complexity: O(N)
- Space Complexity: O(N)
Key Insight
The KMP failure function gives us all prefix-suffix matches. We need to find one that also appears in the middle. Track which lengths appear in the middle (via fail[i] for i in [0, n-2]), then find the longest one that’s also a prefix-suffix.
Text Editor
Problem Information
- Source: Codeforces
- Difficulty: Secret
- Time Limit: 1000ms
- Memory Limit: 512MB
Problem Statement
Given text A, pattern B, and minimum count n, find the longest prefix of B that appears at least n times in A. If no such prefix exists, print “IMPOSSIBLE”.
Input Format
-
First line: text A (1 ≤ A ≤ 10⁵) -
Second line: pattern B (1 ≤ B ≤ A ) -
Third line: integer n (1 ≤ n ≤ A )
Output Format
Print the longest prefix of B that appears at least n times in A, or “IMPOSSIBLE”.
Solution
Approach
-
For each prefix of B (length 1, 2, …, B ), count occurrences in A - Find the longest prefix with count ≥ n
- Use KMP for efficient counting
Python Solution
def count_occurrences(text, pattern):
"""Count occurrences of pattern in text using KMP"""
if not pattern:
return len(text) + 1
# Build failure function for pattern
m = len(pattern)
fail = [0] * m
for i in range(1, m):
j = fail[i - 1]
while j > 0 and pattern[i] != pattern[j]:
j = fail[j - 1]
if pattern[i] == pattern[j]:
j += 1
fail[i] = j
# Search
count = 0
j = 0
for char in text:
while j > 0 and char != pattern[j]:
j = fail[j - 1]
if char == pattern[j]:
j += 1
if j == m:
count += 1
j = fail[j - 1]
return count
def solve():
A = input().strip()
B = input().strip()
n = int(input())
# Binary search on prefix length
# Or linear search from longest to shortest
result = ""
# Try each prefix length from longest to shortest
for length in range(len(B), 0, -1):
prefix = B[:length]
if count_occurrences(A, prefix) >= n:
result = prefix
break
if result:
print(result)
else:
print("IMPOSSIBLE")
if __name__ == "__main__":
solve()
Optimized
def solve():
A = input().strip()
B = input().strip()
n = int(input())
# Build KMP failure function for B
m = len(B)
fail = [0] * m
for i in range(1, m):
j = fail[i - 1]
while j > 0 and B[i] != B[j]:
j = fail[j - 1]
if B[i] == B[j]:
j += 1
fail[i] = j
# Count occurrences of each prefix length
# cnt[k] = number of times prefix of length k appears in A
cnt = [0] * (m + 1)
j = 0
for char in A:
while j > 0 and char != B[j]:
j = fail[j - 1]
if j < m and char == B[j]:
j += 1
# Prefix of length j matched here
cnt[j] += 1
# Propagate counts: if prefix of length k appears, so does prefix of length fail[k-1]
for k in range(m, 0, -1):
cnt[fail[k - 1]] += cnt[k]
# Find longest prefix with count >= n
for length in range(m, 0, -1):
if cnt[length] >= n:
print(B[:length])
return
print("IMPOSSIBLE")
if __name__ == "__main__":
solve()
Complexity Analysis
-
Time Complexity: O( A + B ) -
Space Complexity: O( B )
Key Insight
When matching pattern B against text A, at each position we’ve matched some prefix of B. Count how many times each prefix length is reached. Then propagate counts downward (longer prefix matches imply shorter prefix matches) to get total counts.
e-Coins
Problem Information
- Source: UVA
- Difficulty: Secret
- Time Limit: 3000ms
- Memory Limit: 512MB
Problem Statement
E-coins have two values: conventional value and InfoTechnological value. The e-modulus is calculated as:
sqrt(X*X + Y*Y) where X is sum of conventional values and Y is sum of InfoTechnological values.
Find the minimum number of e-coins needed to achieve a target e-modulus S.
Input Format
- Multiple test cases
- First line of each case: m (number of e-coin types) and S (target e-modulus)
- Next m lines: conventional and InfoTechnological values of each e-coin type
Output Format
For each case, print minimum coins needed, or “not possible” if impossible.
Solution
Approach
This is a 2D coin change problem. Use BFS or DP:
- State: (sum_conventional, sum_info)
- Target: find state where sqrt(x² + y²) = S, i.e., x² + y² = S²
- Minimize number of coins
Python Solution
from collections import deque
def solve():
import sys
input_data = sys.stdin.read().split('\n')
idx = 0
n = int(input_data[idx])
idx += 1
for _ in range(n):
parts = input_data[idx].split()
m, S = int(parts[0]), int(parts[1])
idx += 1
coins = []
for _ in range(m):
parts = input_data[idx].split()
coins.append((int(parts[0]), int(parts[1])))
idx += 1
target_sq = S * S
# BFS to find minimum coins
# dist[x][y] = minimum coins to reach sum (x, y)
INF = float('inf')
dist = [[INF] * (S + 1) for _ in range(S + 1)]
dist[0][0] = 0
queue = deque([(0, 0)])
while queue:
x, y = queue.popleft()
for cx, cy in coins:
nx, ny = x + cx, y + cy
if nx <= S and ny <= S and dist[nx][ny] == INF:
dist[nx][ny] = dist[x][y] + 1
if nx * nx + ny * ny == target_sq:
print(dist[nx][ny])
break
queue.append((nx, ny))
else:
continue
break
else:
# Check all valid endpoints
found = False
for x in range(S + 1):
for y in range(S + 1):
if x * x + y * y == target_sq and dist[x][y] != INF:
print(dist[x][y])
found = True
break
if found:
break
if not found:
print("not possible")
if __name__ == "__main__":
solve()
Alternative
def solve_case():
line = input().split()
m, S = int(line[0]), int(line[1])
coins = []
for _ in range(m):
parts = input().split()
coins.append((int(parts[0]), int(parts[1])))
target_sq = S * S
INF = float('inf')
# dp[x][y] = min coins to achieve sums (x, y)
dp = [[INF] * (S + 1) for _ in range(S + 1)]
dp[0][0] = 0
# Process like unbounded knapsack
for cx, cy in coins:
for x in range(S + 1):
for y in range(S + 1):
if dp[x][y] < INF:
nx, ny = x + cx, y + cy
if nx <= S and ny <= S:
dp[nx][ny] = min(dp[nx][ny], dp[x][y] + 1)
# Find minimum among valid targets
ans = INF
for x in range(S + 1):
for y in range(S + 1):
if x * x + y * y == target_sq:
ans = min(ans, dp[x][y])
if ans == INF:
print("not possible")
else:
print(ans)
def solve():
n = int(input())
for _ in range(n):
solve_case()
if __name__ == "__main__":
solve()
Complexity Analysis
- Time Complexity: O(M × S²) for DP
- Space Complexity: O(S²)
Key Insight
This is a 2D variant of the coin change problem. Instead of reaching a single target value, we need to reach any (x, y) pair where x² + y² = S². BFS finds the minimum number of coins efficiently.