问题描述
给定字符串 s 和 t ,判断 s 是否为 t 的子序列。字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace"是"abcde"的一个子序列,而"aec"不是)。
进阶:如果有大量输入的 S,称作 S1, S2, … , Sk 其中 k >= 10亿,你需要依次检查它们是否为 T 的子序列。在这种情况下,你会怎样改变代码?
解析
简单题,两种方式,一种是双指针往后遍历,这种方式不会调用字符串的方法。另一种是利用Java字符串的indexOf方法来判断,利用第二个参数可以指定搜索的起始位置。indexOf的时间复杂度是O(n),n为搜索的子串长度,因此两种方式的搜索次数是一样的,即使存在代码:if(index == -1){return false;},实际上indexOf方法依然会搜索到字符串结束。
public boolean isSubsequence(String s, String t) {
int i = 0;
int j = 0;
while(i < s.length() && j < t.length()){
if(s.charAt(i) == t.charAt(j)){
i++;
}
j++;
}
return i == s.length();
}
public boolean isSubsequence(String s, String t) {
for(int i = 0, index = -1; i < s.length(); i++){
index = t.indexOf(s.charAt(i), index +1);
if(index == -1){
return false;
}
}
return true;
}

实际提交的情况来看,使用双指针的方式执行时间慢一点,但消耗内存小一点,实际运用上效率差别微乎其微。
进阶
当输入特别多的情况下,执行效率是非常关键的,因此不能像之前那样从头往后依次搜索,而在搜索算法中,二分搜索无疑是最快的搜索算法之一了,尤其是在搜索数据特别多的情况下。同时,可以对待搜索字符串进行预处理,为其中每个字符建立其映射。二分搜索算法可以使用Collections.binarySearch,参数分别是已排好序的列表和要搜索的元素。如果找到该元素,它返回元素的索引;如果没有找到,它会返回 -insertion_point - 1,其中 insertion_point 是元素若要保持列表有序应该被插入的位置。
Map<Character, List<Integer>> indexMap = new HashMap<>();
for (int i = 0; i < T.length(); i++) {
char c = T.charAt(i);
if (!indexMap.containsKey(c)) {
indexMap.put(c, new ArrayList<>());
}
indexMap.get(c).add(i);
}
public boolean isSubsequence(String S, Map<Character, List<Integer>> indexMap) {
int prevIndex = -1;
for (char c : S.toCharArray()) {
if (!indexMap.containsKey(c)) return false; // 不存在字符
List<Integer> indices = indexMap.get(c); // 字符出现的所有位置列表
// 二分搜索找到第一个大于 prevIndex 的位置
int pos = Collections.binarySearch(indices, prevIndex + 1);
// 如果小于0说明正好prevIndex + 1的位置并不是字符c
if (pos < 0) {
// 此时给返回的pos取负减一后是要插入的位置
pos = - pos - 1;
}
if (pos == indices.size()) {
// 如果要插入的位置大于列表长度说明字符c没这个位置
return false;
}
prevIndex = indices.get(pos);
}
return true;
}
二分查找的代码可能有些难理解,这里举个例子,假设字符串为ahcbagbdbcbcc,假设子串为abc,如果要查c,那么此时,应该是从a后面第一个b开始查询,也就是3+1=4的位置。c的位置列表对应的应该是[2, 9, 11, 12],通过二分查找查询4,此时是查询不到的,因此返回的是4要插入这个列表的位置,要插入2的后面,位置就是1(通过pos = - pos - 1;转化,实际上返回的是-2),然后```prevIndex = indices.get(pos)``更新此时的下标为9。那如果c在位置2后面就没有了,说明此时c对应的列表是[2],对于返回的1,返回值和列表长度相同,说明此时不满足,因此会返回false。
文章介绍了如何使用双指针和Java的indexOf方法判断字符串s是否为t的子序列,以及在大数据量下如何通过二分搜索和字符映射预处理提升搜索效率。
554

被折叠的 条评论
为什么被折叠?



