Off-by-one on range boundaries
Wrong move: Loop endpoints miss first/last candidate.
Usually fails on: Fails on minimal arrays and exact-boundary answers.
Fix: Re-derive loops from inclusive/exclusive ranges before coding.
Break down a hard problem into reliable checkpoints, edge-case handling, and complexity trade-offs.
You are given two strings s and pattern.
A string x is called almost equal to y if you can change at most one character in x to make it identical to y.
Return the smallest starting index of a substring in s that is almost equal to pattern. If no such index exists, return -1.
Example 1:
Input: s = "abcdefg", pattern = "bcdffg"
Output: 1
Explanation:
The substring s[1..6] == "bcdefg" can be converted to "bcdffg" by changing s[4] to "f".
Example 2:
Input: s = "ababbababa", pattern = "bacaba"
Output: 4
Explanation:
The substring s[4..9] == "bababa" can be converted to "bacaba" by changing s[6] to "c".
Example 3:
Input: s = "abcd", pattern = "dba"
Output: -1
Example 4:
Input: s = "dde", pattern = "d"
Output: 0
Constraints:
1 <= pattern.length < s.length <= 105s and pattern consist only of lowercase English letters.k consecutive characters can be changed?Problem summary: You are given two strings s and pattern. A string x is called almost equal to y if you can change at most one character in x to make it identical to y. Return the smallest starting index of a substring in s that is almost equal to pattern. If no such index exists, return -1. A substring is a contiguous non-empty sequence of characters within a string.
Start with the most direct exhaustive search. That gives a correctness anchor before optimizing.
Pattern signal: String Matching
"abcdefg" "bcdffg"
"ababbababa" "bacaba"
"abcd" "dba"
check-whether-two-strings-are-almost-equivalent)count-almost-equal-pairs-ii)Source-backed implementations are provided below for direct study and interview prep.
// Accepted solution for LeetCode #3303: Find the Occurrence of First Almost Equal Substring
class Solution {
public int minStartingIndex(String s, String pattern) {
int[] z1 = zFunction(new StringBuilder(pattern).append(s).toString());
int[] z2 = zFunction(new StringBuilder(pattern)
.reverse() //
.append(new StringBuilder(s).reverse())
.toString());
// Match s[i..i + len(pattern) - 1] with `pattern` from both the prefix and
// the suffix.
for (int i = 0; i <= s.length() - pattern.length(); ++i)
if (z1[pattern.length() + i] + z2[s.length() - i] >= pattern.length() - 1)
return i;
return -1;
}
// Returns the z array, where z[i] is the length of the longest prefix of
// s[i..n) which is also a prefix of s.
//
// https://cp-algorithms.com/string/z-function.html#implementation
private int[] zFunction(final String s) {
final int n = s.length();
int[] z = new int[n];
int l = 0;
int r = 0;
for (int i = 1; i < n; ++i) {
if (i < r)
z[i] = Math.min(r - i, z[i - l]);
while (i + z[i] < n && s.charAt(z[i]) == s.charAt(i + z[i]))
++z[i];
if (i + z[i] > r) {
l = i;
r = i + z[i];
}
}
return z;
}
private String reversed(final String s) {
return new StringBuilder(s).reverse().toString();
}
}
// Accepted solution for LeetCode #3303: Find the Occurrence of First Almost Equal Substring
package main
import "slices"
// https://space.bilibili.com/206214
func calcZ(s string) []int {
n := len(s)
z := make([]int, n)
boxL, boxR := 0, 0 // z-box 左右边界
for i := 1; i < n; i++ {
if i <= boxR {
z[i] = min(z[i-boxL], boxR-i+1)
}
for i+z[i] < n && s[z[i]] == s[i+z[i]] {
boxL, boxR = i, i+z[i]
z[i]++
}
}
return z
}
func rev(s string) string {
t := []byte(s)
slices.Reverse(t)
return string(t)
}
func minStartingIndex(s, pattern string) int {
preZ := calcZ(pattern + s)
sufZ := calcZ(rev(pattern) + rev(s))
slices.Reverse(sufZ) // 也可以不反转,下面写 sufZ[len(sufZ)-i]
m := len(pattern)
for i := m; i <= len(s); i++ {
if preZ[i]+sufZ[i-1] >= m-1 {
return i - m
}
}
return -1
}
# Accepted solution for LeetCode #3303: Find the Occurrence of First Almost Equal Substring
class Solution:
def minStartingIndex(self, s: str, pattern: str) -> int:
z1 = self._zFunction(pattern + s)
z2 = self._zFunction(pattern[::-1] + s[::-1])
# Match s[i..i + len(pattern) - 1] with `pattern` from both the prefix and
# the suffix.
for i in range(len(s) - len(pattern) + 1):
if z1[len(pattern) + i] + z2[len(s) - i] >= len(pattern) - 1:
return i
return -1
def _zFunction(self, s: str) -> list[int]:
"""
Returns the z array, where z[i] is the length of the longest prefix of
s[i..n) which is also a prefix of s.
https://cp-algorithms.com/string/z-function.html#implementation
"""
n = len(s)
z = [0] * n
l = 0
r = 0
for i in range(1, n):
if i < r:
z[i] = min(r - i, z[i - l])
while i + z[i] < n and s[z[i]] == s[i + z[i]]:
z[i] += 1
if i + z[i] > r:
l = i
r = i + z[i]
return z
// Accepted solution for LeetCode #3303: Find the Occurrence of First Almost Equal Substring
// Rust example auto-generated from java reference.
// Replace the signature and local types with the exact LeetCode harness for this problem.
impl Solution {
pub fn rust_example() {
// Port the logic from the reference block below.
}
}
// Reference (java):
// // Accepted solution for LeetCode #3303: Find the Occurrence of First Almost Equal Substring
// class Solution {
// public int minStartingIndex(String s, String pattern) {
// int[] z1 = zFunction(new StringBuilder(pattern).append(s).toString());
// int[] z2 = zFunction(new StringBuilder(pattern)
// .reverse() //
// .append(new StringBuilder(s).reverse())
// .toString());
//
// // Match s[i..i + len(pattern) - 1] with `pattern` from both the prefix and
// // the suffix.
// for (int i = 0; i <= s.length() - pattern.length(); ++i)
// if (z1[pattern.length() + i] + z2[s.length() - i] >= pattern.length() - 1)
// return i;
//
// return -1;
// }
//
// // Returns the z array, where z[i] is the length of the longest prefix of
// // s[i..n) which is also a prefix of s.
// //
// // https://cp-algorithms.com/string/z-function.html#implementation
// private int[] zFunction(final String s) {
// final int n = s.length();
// int[] z = new int[n];
// int l = 0;
// int r = 0;
// for (int i = 1; i < n; ++i) {
// if (i < r)
// z[i] = Math.min(r - i, z[i - l]);
// while (i + z[i] < n && s.charAt(z[i]) == s.charAt(i + z[i]))
// ++z[i];
// if (i + z[i] > r) {
// l = i;
// r = i + z[i];
// }
// }
// return z;
// }
//
// private String reversed(final String s) {
// return new StringBuilder(s).reverse().toString();
// }
// }
// Accepted solution for LeetCode #3303: Find the Occurrence of First Almost Equal Substring
// Auto-generated TypeScript example from java.
function exampleSolution(): void {
}
// Reference (java):
// // Accepted solution for LeetCode #3303: Find the Occurrence of First Almost Equal Substring
// class Solution {
// public int minStartingIndex(String s, String pattern) {
// int[] z1 = zFunction(new StringBuilder(pattern).append(s).toString());
// int[] z2 = zFunction(new StringBuilder(pattern)
// .reverse() //
// .append(new StringBuilder(s).reverse())
// .toString());
//
// // Match s[i..i + len(pattern) - 1] with `pattern` from both the prefix and
// // the suffix.
// for (int i = 0; i <= s.length() - pattern.length(); ++i)
// if (z1[pattern.length() + i] + z2[s.length() - i] >= pattern.length() - 1)
// return i;
//
// return -1;
// }
//
// // Returns the z array, where z[i] is the length of the longest prefix of
// // s[i..n) which is also a prefix of s.
// //
// // https://cp-algorithms.com/string/z-function.html#implementation
// private int[] zFunction(final String s) {
// final int n = s.length();
// int[] z = new int[n];
// int l = 0;
// int r = 0;
// for (int i = 1; i < n; ++i) {
// if (i < r)
// z[i] = Math.min(r - i, z[i - l]);
// while (i + z[i] < n && s.charAt(z[i]) == s.charAt(i + z[i]))
// ++z[i];
// if (i + z[i] > r) {
// l = i;
// r = i + z[i];
// }
// }
// return z;
// }
//
// private String reversed(final String s) {
// return new StringBuilder(s).reverse().toString();
// }
// }
Use this to step through a reusable interview workflow for this problem.
At each of the n starting positions in the text, compare up to m characters with the pattern. If a mismatch occurs, shift by one and restart. Worst case (e.g., searching "aab" in "aaaa...a") checks m characters at nearly every position: O(n × m).
KMP and Z-algorithm preprocess the pattern in O(m) to build a failure/Z-array, then scan the text in O(n) — never backtracking. Total: O(n + m). Rabin-Karp uses rolling hashes for O(n + m) expected time. All beat the O(n × m) brute force of checking every position.
Review these before coding to avoid predictable interview regressions.
Wrong move: Loop endpoints miss first/last candidate.
Usually fails on: Fails on minimal arrays and exact-boundary answers.
Fix: Re-derive loops from inclusive/exclusive ranges before coding.