05 August 2008
Given a string s and a string t, check if s is subsequence of t.
A subsequence of a string is a new string which is formed from the original string by deleting some (can be none) of the characters without disturbing the relative positions of the remaining characters. (ie,
is a subsequence of “abcde” while “aec” is not).1
"ace"
Follow up:
If there are lots of incoming S, say S1, S2, … , Sk where k >= 1B, and you want to check one by one to see if T has its subsequence. In this scenario, how would you change your code?
Credits:
Special thanks to @pbrother for adding this problem and creating all test cases.
Example 1:
1
2
Input: s = "abc", t = "ahbgdc"
Output: true
Example 2:
1
2
Input: s = "axc", t = "ahbgdc"
Output: false
Constraints:
1)双指针
假定当前需要匹配字符 c,而字符 c 在 t 中的位置以 x1 和 x2出现(x1 < x2 ),那么贪心取x 1 是最优解,因为 x2 后面能取到的字符,x1 也都能取到,并且通过x1 与 x2 之间的可选字符,更有希望能匹配成功
2)考虑前面的双指针的做法,可以注意到有大量的时间用于在 t 中找到下一个匹配字符。可以预处理出对于 t 的每一个位置,从该位置开始往后每一个字符第一次出现的位置。这样,就不用花时间逐个寻找下一个字符在后面与否。
可以使用动态规划的方法实现预处理:
状态定义- 令 f[i][j] 表示字符串 t 中从位置 i 开始往后字符 j 第一次出现的位置。
初始化 - n为较短s的长度,m为较长t的长度;假定下标从0开始,那么 f[i][j]中有0≤i≤m−1 ,对于边界状态 f[m-1][..],我们置 f[m][..] 为 m,让 f[m-1][..] 正常进行转移。这样对于某个字符j,如果 f[i][j]=m,则表示从位置 i 开始往后不存在字符 j。
状态转移方程 - 在进行状态转移时,如果 t 中位置 i 的字符就是 j,那么 f[i][j]=i,否则 j 出现在位置 i+1 开始往后,即 f[i][j]=f[i+1][j],因此要倒过来进行动态规划,从后往前枚举 i。
这样我们可以写出状态转移方程:
这样,我们可以利用一个数组来记录(空间换时间),每次花费 O(1) 时间就可以跳转到下一个位置(而不需要一个个对比字符寻找),直到位置变为 m 或 s 中的每一个字符都匹配成功。
考虑状态压缩 - 同时注意到,该解法中对 t 的处理与 s 无关,且预处理完成后,可以利用预处理数组的信息,线性地算出任意一个字符串 s 是否为 t 的子串。这样可以解决follow up问题,“如果有大量输入的 S,称作S1, S2, … , Sk 其中 k >= 10亿,你需要依次检查它们是否为 T 的子序列。在这种情况下,你会怎样改变代码?”
3) Binary Search也可以解决输入大量字符串的问题。
双指针
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
public boolean isSubsequence(String s, String t) {
if (s.isEmpty()) {
return true;
}
if (t.isEmpty()) {
return false;
}
int indexS = 0;
int indexT = 0;
while (indexS < s.length() && indexT < t.length()) {
if (s.charAt(indexS) == t.charAt(indexT)) {
indexS++;
indexT++;
} else {
indexT++;
}
}
return indexS == s.length();
}
}
DP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class Solution {
public boolean isSubsequence(String s, String t) {
int n = s.length(), m = t.length();
// 二维数组记录在t中每个位置,26个小写字母在当前或者之后出现的位置
// m + 1表示额外在最后多出来一个记录表示不存在
int[][] dp = new int[m + 1][26];
for (int i = 0; i < 26; i++) { // 在最后一个位置上初始化所有26个小写字母的位置为m(不存在)
dp[m][i] = m;
}
// 从后向前计算t中每个位置对应的26个小写字母的位置
for (int i = m - 1; i >= 0; i--) {
for (int j = 0; j < 26; j++) {
if (j + 'a' == t.charAt(i)) { //循环查找26个小写字符是否等于从后向前过程中t中的当前字符
dp[i][j] = i;
} else { // 当前字符不等于就让当前等于右边的状态
dp[i][j] = dp[i + 1][j];
}
}
}
// 开始查找s是否为t的子串
int add = 0; // 记录s中下一个字符的位置
for (int k = 0; k < n; k++) {
int index = s.charAt(k) - 'a';
if (dp[add][index] == m) { // 到了最右没找完s中字符
return false;
}
add = dp[add][index] + 1;
}
return true;
}
}