給定父字符串 strFather 和子字符串 strSub,統計 strSub 在 strFather 中不重疊出現的次數。例如:「ababab」中「ab」出現 3 次,「aaa」中「aa」出現 1 次 (不重疊)。
public class SubstringCounter {
/**
* 統計子串非重疊出現次數
* @param father 父字符串
* @param sub 子字符串
* @return 出現次數
*/
public static int countOccurrences(String father, String sub) {
if (father == null || sub == null || sub.isEmpty()) {
return 0;
}
int count = 0;
int subLen = sub.length();
int fatherLen = father.length();
int i = 0;
// 只需遍歷到 fatherLen - subLen,之後剩餘長度不足匹配
while (i <= fatherLen - subLen) {
// 提取當前窗口子串
String window = father.substring(i, i + subLen);
if (sub.equals(window)) {
count++;
i += subLen; // 命中:索引跳過整個子串長度 (不重疊)
} else {
i++; // 未命中:索引後移一位
}
}
return count;
}
// 測試
public static void main(String[] args) {
String str = "abeefddfadeeabfkkeeabeeabeerfab";
System.out.println("子串 'ab' 出現次數: " + countOccurrences(str, "ab")); // 4
System.out.println("子串 'ee' 出現次數: " + countOccurrences(str, "ee")); // 4
System.out.println("子串 'f' 出現次數: " + countOccurrences(str, "f")); // 6
// 邊界測試
System.out.println("'aaa' 中 'aa': " + countOccurrences("aaa", "aa")); // 1 (不重疊)
System.out.println("'aaaa' 中 'aa': " + countOccurrences("aaaa", "aa")); // 2
System.out.println("空子串: " + countOccurrences("abc", "")); // 0
System.out.println("null 父串: " + countOccurrences(null, "a")); // 0
}
}
| 指標 | 複雜度 | 說明 |
|---|---|---|
| 時間複雜度 | O(n * m) | n=父串長度, m=子串長度。substring+equals 為 O(m) |
| 空間複雜度 | O(m) | 每次 substring 創建新字符串對象 |
| 最優情況 | O(n/m) | 全匹配時索引大步跳躍 |
| 最差情況 | O(n * m) | 如 "aaaaaaaa" 匹配 "aaa" |
// 替換 substring + equals
if (father.regionMatches(i, sub, 0, subLen)) {
count++;
i += subLen;
} else {
i++;
}
regionMatches 直接比較字符數組區間,無新對象分配,性能顯著提升。
對於超長文本 (GB 級) 或高頻調用,建議使用標準庫或成熟庫:
// Apache Commons Lang
int count = StringUtils.countMatches(father, sub);
// 或 Java 9+ Stream (但會統計重疊)
long count = father.chars()... // 不推薦用於非重疊統計
import java.util.regex.*;
public static int countByRegex(String father, String sub) {
String pattern = Pattern.quote(sub); // 自動轉義 . * ? 等元字符
Matcher matcher = Pattern.compile(pattern).matcher(father);
int count = 0;
while (matcher.find()) count++;
return count;
}
正則預編譯後匹配速度極快,且天然支持不重疊語義。
| 輸入 | 預期 | 陷阱說明 |
|---|---|---|
| father="aaa", sub="aa" | 1 | 重疊邊界:索引跳躍邏輯決定是否重疊 |
| sub="" 或 null | 0 | 防守性編程:空串無意義,避免死循環 |
| sub 包含正則元字符 | 按字面匹配 | 手寫算法無影響,正則需 Pattern.quote |
| 極長字符串 (10MB+) | 正常返回 | 避免遞歸/大量對象創建導致 OOM |
regionMatches 版本,無依賴、高性能、語義清晰。Apache Commons Lang 的 StringUtils.countMatches。Pattern.quote + Matcher.find()。