题目描述
C语言中的strstr
函数用于在字符串haystack
中查找第一次出现字符串needle
的位置,如果未找到则返回NULL
。现在要求实现一个增强的strstr
函数,该函数可以使用带可选段的字符串来模糊查询。可选段使用[]
标识,表示该位置可以是可选段中的任意一个字符即可满足匹配条件。例如,"a[bc]"
可以匹配"ab"
或"ac"
。
输入描述
输入包含两个字符串,分别是源字符串和目标字符串,以空格分隔。
输出描述
输出一个整数,表示匹配子字符串在源字符串中的起始位置(从0开始计数)。如果没有匹配,则输出-1。
注意事项
- 源字符串中不包含
[]
。 - 目标字符串中的
[]
成对出现,且不会嵌套。 - 输入的字符串长度在[1,100]之间。
解题思路
- 解析目标字符串:将目标字符串解析为模式数组,其中普通字符直接保存,可选字符用集合(如Python中的
set
)表示。 - 在源字符串中查找匹配:遍历源字符串,对每个位置尝试匹配模式数组。如果完全匹配,返回当前位置;如果不匹配,继续下一个位置。
- 返回结果:如果遍历完源字符串仍未找到匹配,返回-1。
示例
输入:abcd b[cd]
输出:1
解释:在源字符串"abcd"
中,"bc"
子字符串的起始位置是1,而"b[cd]"
可以匹配"bc"
或"bd"
。
参考代码(java)
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class EnhancedStrstr {
/**
* 增强的字符串匹配函数
*
* @param haystack 要搜索的字符串
* @param needle 要查找的模式字符串
* @return 模式字符串在搜索字符串中的首次出现的起始索引;如果搜索字符串不包含模式字符串,则返回 -1
*
* 函数说明:
* 本函数尝试在搜索字符串(haystack)中找到模式字符串(needle)的首次出现。
* 它将模式字符串解析为字符和字符集列表,以便高效匹配。
* 然后遍历搜索字符串,将解析后的模式与搜索字符串的子串进行比较。
* 如果找到匹配项,则返回匹配项的起始索引;如果遍历整个字符串后未找到匹配项,则返回 -1。
*/
public static int enhancedStrstr(String haystack, String needle) {
// 将 'needle' 字符串解析为字符和字符集列表
List<Object> pattern = parsePattern(needle);
// 在 'haystack' 中查找匹配项
for (int i = 0; i <= haystack.length() - patternLength(pattern); i++) {
if (matchesPattern(haystack, i, pattern)) {
// 如果找到匹配项,返回当前索引
return i;
}
}
// 如果未找到匹配项,返回 -1
return -1;
}
/**
* 解析指定字符串中的模式,将普通字符和方括号内的字符选项分别存储为列表项。
*
* @param needle 待解析的字符串,包含方括号表示的字符选项。
* @return 返回一个List,其中包含字符和字符选项的Set。
* @throws IllegalArgumentException 如果字符串格式不正确,例如方括号未闭合或嵌套。
*/
private static List<Object> parsePattern(String needle) {
List<Object> pattern = new ArrayList<>();
StringBuilder currentOption = null;
// 遍历输入字符串中的每个字符
for (char c : needle.toCharArray()) {
// 处理方括号内选项的开始
if (c == '[') {
// 如果当前已有未处理的选项,说明嵌套使用了方括号,抛出异常
if (currentOption != null) {
throw new IllegalArgumentException("Invalid needle string: Nested [] not allowed");
}
// 开始新的选项构建
currentOption = new StringBuilder();
}
// 处理方括号内选项的结束
else if (c == ']') {
// 如果当前没有正在构建的选项,说明方括号配对不正确,抛出异常
if (currentOption == null) {
throw new IllegalArgumentException("Invalid needle string: Unmatched ]");
}
// 将当前选项字符串转换为字符集合并添加到模式中
Set<Character> options = new HashSet<>();
for (char optionChar : currentOption.toString().toCharArray()) {
options.add(optionChar);
}
pattern.add(options);
currentOption = null;
}
// 处理普通字符或当前选项内的字符
else {
// 根据当前选项构建状态决定是追加到选项中还是作为普通字符添加到模式中
if (currentOption != null) {
currentOption.append(c);
} else {
pattern.add(c);
}
}
}
// 检查是否有未闭合的方括号,如果有,抛出异常
if (currentOption != null) {
throw new IllegalArgumentException("Invalid needle string: Unclosed []");
}
return pattern;
}
/**
* 计算匹配模式的长度
* 匹配模式可以包含字符和字符集,其中字符集是一个集合,匹配时可以是集合中的任何一个字符
* 此方法用于确定模式的总长度,字符集只占用一个位置,无论其包含多少个字符
*
* @param pattern 匹配模式,可以包含Character类型的字符和Set<Character>类型的字符集
* @return 返回模式的总长度
*/
private static int patternLength(List<Object> pattern) {
// 初始化长度计数器
int length = 0;
// 遍历匹配模式中的每个元素
for (Object p : pattern) {
// 如果元素是字符,长度增加1
if (p instanceof Character) {
length++;
} else if (p instanceof Set) {
// 如果元素是字符集,也视为一个整体,长度增加1
// 字符集只占用一个位置,但匹配时可以是集合中的任何一个字符
length++;
}
}
// 返回计算得到的模式长度
return length;
}
/**
* 使用指定模式匹配从字符串的特定位置开始的子字符串是否符合该模式
*
* @param haystack 被搜索的字符串
* @param startIndex 开始匹配的位置索引
* @param pattern 包含字符和选项集的模式列表
* @return 如果模式匹配成功则返回true,否则返回false
*/
private static boolean matchesPattern(String haystack, int startIndex, List<Object> pattern) {
// 遍历模式中的每个元素,尝试与字符串中的字符匹配
for (int j = 0; j < pattern.size(); j++) {
Object p = pattern.get(j);
// 检查剩余的字符串长度是否足以完成模式的匹配
if (startIndex + j >= haystack.length()) {
return false; // haystack剩余长度不足以匹配整个模式
}
char haystackChar = haystack.charAt(startIndex + j);
// 根据模式元素的类型,进行字符精确匹配或选项集匹配
if (p instanceof Character) {
// 如果模式元素是字符类型,比较字符是否完全相同
if (haystackChar != (char) p) {
return false;
}
} else if (p instanceof Set) {
Set<Character> options = (Set<Character>) p;
// 如果模式元素是选项集类型,检查字符是否属于选项集
if (!options.contains(haystackChar)) {
return false;
}
}
}
return true; // 所有字符都匹配
}
public static void main(String[] args) {
String haystack = "abcd";
String needle = "b[cd]";
System.out.println(enhancedStrstr(haystack, needle)); // 应该输出 1
}
}
运行示例解析
输入
String haystack = "abcd";
String needle = "b[cd]";
解析过程
-
解析
needle
字符串:needle
字符串"b[cd]"
被parsePattern
方法解析为一个包含两个元素的列表pattern
。- 第一个元素是字符
'b'
,直接作为列表中的一个元素。 - 第二个元素是一个
Set<Character>
,包含字符'c'
和'd'
,因为它们在字符集[cd]
中。
-
计算模式长度:
patternLength(pattern)
方法计算并返回2
,因为虽然字符集[cd]
包含两个字符,但在模式匹配中它只占用一个位置。
-
在
haystack
中查找匹配项:enhancedStrstr
方法中的循环从haystack
的索引0
开始,尝试找到与pattern
匹配的子串。- 当
startIndex
为0
时,haystack
的子串"a"
不与pattern
的第一个元素'b'
匹配,因此继续下一个迭代。 - 当
startIndex
为1
时,haystack
的子串"b"
与pattern
的第一个元素'b'
匹配。 - 接着检查
haystack
的下一个字符'c'
,它位于pattern
的第二个元素(字符集[cd]
)中,因此也匹配。 - 由于整个
pattern
都匹配了,enhancedStrstr
方法返回startIndex
,即1
。
输出
1
这个输出表示在 haystack
字符串 "abcd"
中,从索引 1
开始的位置找到了与 needle
字符串 "b[cd]"
相匹配的子串。注意,这里的匹配是基于 needle
中定义的字符和字符集进行的,即使字符集 [cd]
可以匹配 haystack
中的 'c'
或 'd'
,但由于 needle
的第一个字符是 'b'
,且它必须紧接在 'c'
或 'd'
前面,所以只有在 "b"
后面紧跟着 'c'
或 'd'
时才认为匹配成功。在这个例子中,"bcd"
满足了这一条件,尽管 needle
使用字符集 [cd]
来表示 'c'
或 'd'
的任意性。