【每日算法】找出字符串中第一个匹配项的下标 LeetCode
1. 暴力匹配 + 优化
public int StrStr(string haystack, string needle)
{if (needle.Length == 0) return 0;if (haystack.Length < needle.Length) return -1;for (int i = 0; i < haystack.Length - needle.Length + 1; i++){if (haystack[i] == needle[0]){bool isMatch = true;for (int j = 1; j < needle.Length; j++){if (haystack[i + j] != needle[j]){isMatch = false;break;}}if (isMatch) return i;}}return -1;
}
逻辑分析
边界检查:
- 如果
needle
为空,直接返回0
。 - 如果
haystack
比needle
短,直接返回-1
。
- 如果
外层循环:
- 遍历
haystack
的每一个可能的起始位置i
(从0
到haystack.Length - needle.Length
)。
- 遍历
内层匹配:
- 如果
haystack[i]
和needle[0]
匹配,则进入内层循环:- 逐个比较
haystack[i + j]
和needle[j]
。 - 如果发现不匹配,标记
isMatch = false
并跳出内层循环。
- 逐个比较
- 如果所有字符都匹配,返回当前起始位置
i
。
- 如果
时间复杂度:
- 最坏情况下为
O(n * m)
(n
是haystack
长度,m
是needle
长度)。
- 最坏情况下为
2. 双指针方法
public int StrStr(string haystack, string needle)
{if (needle.Length == 0) return 0;if (haystack.Length < needle.Length) return -1;int n = haystack.Length;int m = needle.Length;for (int i = 0; i <= n - m; i++){int j = 0;while (j < m && haystack[i + j] == needle[j]){j++;}if (j == m) return i;}return -1;
}
逻辑分析
边界检查:
- 如果
needle
为空,直接返回0
。 - 如果
haystack
比needle
短,直接返回-1
。
- 如果
外层循环:
- 遍历
haystack
的每一个可能的起始位置i
。
- 遍历
双指针匹配:
- 使用指针
j
从0
开始,逐个比较haystack[i + j]
和needle[j]
。 - 如果字符匹配,
j
向后移动;否则跳出循环。 - 如果
j
移动到needle
的末尾(j == m
),说明匹配成功,返回i
。
- 使用指针
时间复杂度:
- 最坏情况下为
O(n * m)
,但实际运行中可能比你的代码更高效(因为减少了isMatch
变量的判断)。
- 最坏情况下为
3. 两种方法的对比
特性 | 你的代码 | 双指针方法 |
---|---|---|
外层循环 | 遍历所有可能的起始位置 i | 同左 |
内层匹配 | 使用 isMatch 标记 | 直接移动指针 j |
代码简洁性 | 稍显冗余(多一个变量) | 更简洁 |
性能 | 可能稍慢(多一次变量赋值) | 可能稍快 |
适用场景 | 适合初学者理解 | 适合追求简洁和效率的场景 |
4. 为什么双指针更高效?
减少变量操作:
- 你的代码中每次匹配需要操作
isMatch
变量,而双指针直接通过j
的移动来判断匹配。
- 你的代码中每次匹配需要操作
更直接的逻辑:
- 双指针方法通过
while
循环直接推进j
,逻辑更紧凑。
- 双指针方法通过
5. 示例说明
假设 haystack = "hello"
, needle = "ll"
:
暴力匹配 + 优化:
i = 2
时,haystack[2] = 'l'
匹配needle[0]
,进入内层循环:j = 1
:haystack[3] = 'l'
匹配needle[1]
,isMatch
保持true
。- 返回
i = 2
。
双指针方法:
i = 2
时,j
从0
开始:haystack[2] = 'l'
匹配needle[0]
,j++
。haystack[3] = 'l'
匹配needle[1]
,j++
。j == 2
,返回i = 2
。