问题描述
问题分析
- 验证串
s
是否是字符串t
的子序列,有以下几种做法: 方法1: 贪心
用两个指针从前向后,若s[sIndex] == t[tIndex]
,说明在t
中已经找到一个,则两指针都后移,若s[sIndex] != t[tIndex]
,则将tIndex
后移,看t
的下一个元素能否和s[sIndex]
相匹配。方法2:动态规划
用dp[i][j]
来表示s[i ~ n] 是否是 t[j ~ n]的子序列,具体递推关系见实现方法三:二分查找 + 贪心(有点离线算法的意思)
该方法主要针对于 Follow up 的问题,如果s串有多个怎么办,可以先对t
串进行预处理,相当于一个离线算法。
设置一个list[]
数组,然后将根据字符,将相同字符串成一个list,list
中存的是该字符在t
串中的下标。这样list中一定是升序的。
然后遍历s
串,假设前一个字符在t
串中的索引为preIndex
,若当前字符ch
未在数组中出现过,则直接返回false,若出现过,再对相应list进行二分查找,查找刚好大于等于preIndex + 1
的元素是否存在,若存在,则更新preIndex
,继续检验下一字符。若不存在,直接返回false.
经验教训
- 利用二分查找的特点,实现贪心的思想
二分查找最终begin
会停在刚好大于等于target的位置:注意代码中二分查找的实现,LeetCode 300. Longest Increasing Subsequence(最长递增子序列) 也用到了二分查找这种特点
代码实现
- 贪心
public boolean isSubsequence(String s, String t) {
if (s == null || t == null) {
return false;
}
int sIndex = 0;
int tIndex = 0;
int sLen = s.length();
int tLen = t.length();
//贪心
while (sIndex < sLen && tIndex < tLen) {
if (s.charAt(sIndex) == t.charAt(tIndex)) {
++sIndex;
}
++tIndex;
}
return sIndex == sLen;
}
- 动态规划
public boolean isSubsequence(String s, String t) {
if (s == null || t == null) {
return false;
}
int sLen = s.length();
int tLen = t.length();
//dp[i][j]: 表示s[i ~ n] 是否是 t[j ~ n]的子序列
boolean[][] dp = new boolean[sLen + 1][tLen + 1];
for (int j = 0; j <= tLen; ++j) {
dp[sLen][j] = true;
}
for (int i = sLen - 1; i >= 0; --i) {
for (int j = tLen - 1; j >= 0; --j) {
if (s.charAt(i) == t.charAt(j)) {
dp[i][j] = dp[i + 1][j + 1];
}else {
dp[i][j] = dp[i][j + 1];
}
}
}
return dp[0][0];
}
- 二分查找 + 贪心
public boolean isSubsequence(String s, String t) {
if (s == null || t == null) {
return false;
}
List<Integer>[] list = new List[256];
//根据t串填list数组
int tLen = t.length();
for (int i = 0; i < tLen; ++i) {
char ch = t.charAt(i);
if (list[ch] == null) {
list[ch] = new ArrayList<Integer>();
}
//对于同一元素,存储索引
list[ch].add(i);
}
//preIndex用来存储s某一字符在t中的索引
int preIndex = -1;
int sLen = s.length();
//遍历s串
for (int i = 0; i < sLen; ++i) {
char ch = s.charAt(i);
if (list[ch] == null) {
return false;
}
//利用二分查找,返回list[ch]中大于等于 preIndex + 1 的 元素
int index = binarySearch(list[ch], preIndex + 1);
if (index == -1) {
return false;
}else {
//更新索引
preIndex = index;
}
}
return true;
}
//返回list中刚刚大于等于target的元素,“刚刚”的概念也可点贪心的意思
public int binarySearch(List<Integer> list, int target) {
int begin = 0;
int end = list.size() - 1;
while (begin <= end) {
int mid = begin + (end - begin) / 2;
if (list.get(mid) < target) {
begin = mid + 1;
}else {
end = mid - 1;
}
}
return begin == list.size() ? -1 : list.get(begin);
}