Skip to content

好的,我们来解答LeetCode上的问题392:“判断子序列”。

一、问题描述

给定字符串 s 和 t,判断 s 是否为 t 的子序列。 字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace"是"abcde"的一个子序列,而"aec"不是)。 进阶: 如果有大量输入的 S,称作 S1, S2, ... , Sk 其中 k >= 10亿,你需要依次检查它们是否为 T 的子序列。在这种情况下,你会怎样改变代码?

二、方案一:双指针法

1、思路

我们可以使用双指针的方法来解决这个问题。一个指针指向字符串 s,另一个指针指向字符串 t。我们遍历字符串 t,如果当前字符与 s 的指针指向的字符相同,则 s 的指针向前移动。如果 s 的指针能够遍历完整个 s,则说明 s 是 t 的子序列。

2、代码实现

go
func isSubsequence(s string, t string) bool {
    i, j := 0, 0
    for i < len(s) && j < len(t) {
        if s[i] == t[j] {
            i++
        }
        j++
    }
    return i == len(s)
}

3、复杂度分析

  • 时间复杂度:O(n),其中 n 是字符串 t 的长度。我们只需要遍历一次字符串 t。
  • 空间复杂度:O(1),我们只需要常数级别的额外空间。

三、方案二:动态规划

1、思路

对于进阶问题,如果有大量输入的 S,我们可以使用动态规划的方法来优化。我们可以预处理字符串 t,创建一个二维数组 dp,其中 dp[i][j] 表示字符串 t 中从位置 i 开始,长度为 j 的子串中,字符 t[j] 的位置。这样,对于每个输入的 S,我们可以快速地判断它是否为 T 的子序列。

2、代码实现

go
func isSubsequence(s string, t string) bool {
	if s == "" {
		return true
	}
	m, n := len(s), len(t)
	dp := make([][]bool, m+1)
	for i := 0; i <= m; i++ {
		dp[i] = make([]bool, n+1)
	}
	dp[0][0] = true

	for i := 1; i <= m; i++ {
		for j := 1; j <= n; j++ {
			if s[i-1] == t[j-1] {
				dp[i][j] = dp[i-1][j-1] || i == 1
			} else {
				dp[i][j] = dp[i][j-1]
			}
		}
	}

	return dp[m][n]
}

3、复杂度分析

  • 时间复杂度:O(m),其中 m 是字符串 s 的长度。我们只需要遍历一次字符串 s。
  • 空间复杂度:O(1),我们只需要常数级别的额外空间。

四、总结

对于一般情况,双指针法是一个简单且高效的方法。对于有大量输入的情况,动态规划方法可以进一步优化时间复杂度。

木川工作室 (微信:mcmc2024)