这道题目要求我们判断字符串str1的任意排列组合是否出现在字符串str2中,如果存在则返回第一个匹配位置的起始索引,否则返回-1。这是一个典型的字符串匹配问题,但不同于普通的子串查找,它需要考虑字符排列组合的可能性。
假设str1="abc",那么它的有效排列组合包括"abc"、"acb"、"bac"、"bca"、"cab"、"cba"共6种。我们需要在str2中查找这些排列组合中的任意一个是否作为连续子串出现。
输入格式:
输出要求:
需要特别注意以下边界情况:
最直观的解法是:
但这种解法时间复杂度为O(n!×m),其中n是str1长度,m是str2长度。当n较大时(如n>10),计算量会爆炸式增长。
更高效的解法是使用滑动窗口结合字符频率统计:
这种方法的时间复杂度为O(n + m×k),其中k是字符集大小(通常为256),空间复杂度为O(k)。
选择滑动窗口算法的主要原因是:
java复制import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
// 输入处理
String str1 = sc.next();
String str2 = sc.next();
// 边界检查
if(str1.length() > str2.length()) {
System.out.println("-1");
return;
}
// 频率统计
int[] count1 = new int[256];
for(char c : str1.toCharArray()) count1[c]++;
// 滑动窗口
int[] windowCount = new int[256];
for(int i=0; i<str2.length(); i++) {
// 更新窗口频率
windowCount[str2.charAt(i)]++;
// 移除窗口左侧字符
if(i >= str1.length()) {
windowCount[str2.charAt(i-str1.length())]--;
}
// 频率比较
if(i >= str1.length()-1 && Arrays.equals(count1, windowCount)) {
System.out.println(i-str1.length()+1);
return;
}
}
System.out.println("-1");
}
}
go复制package main
import "fmt"
func main() {
var str1, str2 string
fmt.Scan(&str1, &str2)
len1, len2 := len(str1), len(str2)
if len1 > len2 {
fmt.Println("-1")
return
}
// 频率统计
count1 := [256]int{}
for i:=0; i<len1; i++ {
count1[str1[i]]++
}
// 滑动窗口
windowCount := [256]int{}
for i:=0; i<len2; i++ {
windowCount[str2[i]]++
if i >= len1 {
windowCount[str2[i-len1]]--
}
// 频率比较
if i >= len1-1 && windowCount == count1 {
fmt.Println(i-len1+1)
return
}
}
fmt.Println("-1")
}
可以维护一个matchCount变量,记录当前匹配的字符数,避免每次全量比较:
java复制int matchCount = 0;
for(int j=0; j<256; j++) {
if(windowCount[j] == count1[j]) matchCount++;
}
// 更新窗口时动态维护matchCount
对于全Unicode支持,需要使用HashMap代替固定数组:
java复制Map<Character, Integer> count1 = new HashMap<>();
for(char c : str1.toCharArray()) {
count1.put(c, count1.getOrDefault(c, 0)+1);
}
如果需要同时匹配多个str1的模式,可以:
这种字符串排列匹配算法在以下场景中有实际应用:
在实际开发中,理解这些基础算法的原理和应用场景,能够帮助我们在面对复杂字符串处理问题时快速找到解决方案。