动态规划之判断子序列

75 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第30天,点击查看活动详情

动态规划(Dynamic Programming)是一种分阶段求解决策问题的数学思想,它通过把原问题分解为简单的子问题来解决复杂问题。

判断子序列

给定字符串 s 和 t ,判断 s 是否为 t 的子序列。

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

示例 1:

输入: s = "abc", t = "ahbgdc"

输出: true

示例 2:

输入: s = "axc", t = "ahbgdc"

输出: false

双指针

设置双指针 i , j 分别指向字符串 s , t 的首个字符,遍历字符串 t :

  • 当 s[i] == t[j] 时,代表匹配成功,此时同时 i++ , j++ ; 进而,若 i 已走过 s 尾部,代表 s 是 t 的子序列,此时应提前返回 true ;
  • 当 s[i] != t[j] 时,代表匹配失败,此时仅 j++ ; 若遍历完字符串 t 后,字符串 s 仍未遍历完,代表 s 不是 t 的子序列,此时返回 false 。

代码如下:

fun isSubsequence(s: String, t: String): Boolean {
    if (s.isEmpty()) return true
    var i = 0
    var j = 0
    while (j < t.length) {
        if (s[i] == t[j]) {
            if (++i == s.length) return true
        }
        j++
    }
    return false
}

复杂度分析

  • 时间复杂度 O(N)
  • 空间复杂度 O(1)

动态规划

定义dp数组dp[i][j]表示长度为 i ,末尾项为 s[i-1] 的子字符串,与长度为 j,末尾项为 t[j-1] 的子字符串,二者的相同子序列长度。 递推公式如下:

  • 如果 s[i−1]!=t[j−1], 有 dp[i][j]=dp[i][j−1]
  • 如果 s[i−1]==t[j−1] , 有 dp[i][j]=dp[i−1][j−1]+1

如果 i==0 || j==0,则二者没有公共部分,dp[i][j]=0 最后判断dp[i][j] 的最大值是否和s的大小一样长。

代码如下:

fun isSubsequence(s: String, t: String): Boolean {
    val l1 = s.length
    val l2 = t.length
    val dp = Array(l1 + 1) { IntArray(l2 + 1) }
    for (i in 1..l1) {
        for (j in 1..l2) {
            if (s[i - 1] == t[j - 1]) {
                dp[i][j] = dp[i - 1][j - 1] + 1
            } else {
                dp[i][j] = dp[i][j - 1]
            }
        }
    }
    return dp[l1][l2] == l1
}