給定一個英文字串,找出第一個最長的不含重複字元的子字串。若有多個同長度,回傳最先出現者。
維護一個「目前無重複視窗」[start, i],掃描字串:
hash[256] 記錄每個 ASCII 字元最後出現的索引,初始 -1。s[i]:
hash[s[i]] < start:字元不在當前視窗內 → 視窗擴大,更新 hash[s[i]] = i。hash[s[i]] + 1,更新 hash,並檢查前一視窗長度是否為最大。
#include
#include
char* GetSubStr(const char* str) {
int hash[256];
for (int i = 0; i < 256; i++) hash[i] = -1;
int start = 0, maxStart = 0, maxEnd = 0;
int strLen = strlen(str);
for (int i = 0; i < strLen; i++) {
unsigned char c = str[i]; // 避免負索引
if (hash[c] < start) {
hash[c] = i; // 字元不在當前視窗,擴大視窗
} else {
// 字元在視窗內重複
int curLen = i - start;
if (curLen > maxEnd - maxStart) {
maxStart = start;
maxEnd = i;
}
start = hash[c] + 1; // 視窗起點跳過重複字元
hash[c] = i;
}
}
// 處理字串結尾的視窗
if (strLen - start > maxEnd - maxStart) {
maxStart = start;
maxEnd = strLen;
}
int maxLen = maxEnd - maxStart;
char* result = (char*)malloc(maxLen + 1);
memcpy(result, str + maxStart, maxLen);
result[maxLen] = '\0';
return result;
}
| 原版問題 | 修正 |
|---|---|
| 迴圈結束未檢查最後視窗 | 加入尾端檢查 if (strLen - start > ...) |
hash[str[i]] 可能負索引 | 轉 unsigned char 索引 |
| 記憶體洩漏風險 | 呼叫端須 free(),文件註明 |
"abcabcbb" → "abc" (長度 3)
"bbbbb" → "b" (長度 1)
"pwwkew" → "wke" (長度 3)
" " → " " (空白字元)
"dvdf" → "vdf" (長度 3, 非 "dvd")
void GetSubStrInPlace(const char* str, char* out, int* outLen) {
int hash[256]; /* ... 同邏輯 ... */
/* 直接寫入 out 緩衝區,回傳長度 */
}
核心洞見:「字元最後出現位置」是連結過去與未來的關鍵資訊。滑動視窗不需嵌套迴圈,單趟掃描即可維護無重複區間,是處理「子字串/子陣列滿足特定條件」問題的通用模板。