一天一大 leet(判断子序列)难度:简单-Day20200727

img

题目:

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

你可以认为 s 和 t 中仅包含英文小写字母。字符串 t 可能会很长(长度 ~= 500,000),而 s 是个短字符串(长度 <=100)。

字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。
(例如,"ace"是"abcde"的一个子序列,而"aec"不是)。

示例:

  1. 示例 1
s = "abc", t = "ahbgdc"
返回 true.
  1. 示例 2
s = "axc", t = "ahbgdc"
返回 false.

后续挑战 :
如果有大量输入的 S,称作 S1, S2, … , Sk 其中 k >= 10 亿,你需要依次检查它们是否为 T 的子序列。在这种情况下,你会怎样改变代码?

抛砖引玉

img

思路

  • 遍历 s,按索引取出 s 中的单个字符
  • 在 t 中查询这个字符的位置,然后删除这个字符及其之前的字符
  • 如果删除后 s 未遍历的字符比 t 上则不满足
  • 如果变量完成都匹配则返回 true
/**
 * @param {string} s
 * @param {string} t
 * @return {boolean}
 */
var isSubsequence = function (s, t) {
  let slen = s.length,
    tlen = t.length
  if (slen > tlen) return false
  for (let i = 0; i < slen; i++) {
    let index = t.indexOf(s[i])
    if (index === -1) return false
    t = t.substring(index + 1)
    if (slen - i - 1 > t.length) return false
  }
  return true
}
  • 上面每取出一个字符都需要在 t 中 indexof 查询,
  • indexOf 的边界是通过 substring 截取字符串完成,

换种思路不具体操作字符串 s,而是通过索引来限制字符串查找范围

  • index 默认从 0 开始
  • s[i],不等于 t[index],则接着查询 index+1 位置,其中 index 小于 t.length,i>=index
  • s[i]无论匹配结果如果,i 向后移动式查找范围缩小 index+1
  • 如果 index === t.length 则说明 s===t,
  • 如果 index>t.length 则 index 再查询时有字符未匹配到超出限制范围
/**
 * @param {string} s
 * @param {string} t
 * @return {boolean}
 */
var isSubsequence = function (s, t) {
  let index = 0
  for (let i = 0; i < s.length; i++) {
    while (index < t.length && s[i] !== t[index]) {
      index++
    }
    index++
  }
  return index <= t.length
}

双指针

  • 上面是通过一个索引限制 t 的查询范围
  • 更直观些,可以声明两个变量 s->i,t->j,分别表示两个字符串指针
  • 匹配成功 i 递增,匹配下一个字符
  • 当前位未匹配 j 递增,继续尝试匹配
  • 边界:
    • i 小于 s.length
    • j 小于 t.length
  • 触发边界条件终止时,t 变量完则说明 t 中字符全匹配了,不然返回 false
/**
 * @param {string} s
 * @param {string} t
 * @return {boolean}
 */
var isSubsequence = function (s, t) {
  let slen = s.length,
    tlen = t.length,
    i = 0,
    j = 0
  while (i < slen && j < tlen) {
    if (s.charAt(i) == t.charAt(j)) {
      i++
    }
    j++
  }
  return i == slen
}

动态规划

  • 设 t 长 tlen
  • 声明一个 tlen*26 的矩阵 dp
  • 矩阵中记录 t 中每个字符第一次出现所在的坐标点
-ahbgdc-
0066
122266
3444446
45555556
56
6333366
71166
6
266
  • 在生成 s 的矩阵时,因为无法预期第一次出现 t[i]的位置,则倒序查询默认填充 tlen(表示不存在):
    • dp[i][j],在 a-z 中,等于的字符,则将 t 中索引存放到 dp[i][j]中
    • dp[i][j],不等于的字符,则该位置不是 t[i]出现位置,其值沿用本行已计算的值 dp[i+1][j]
  • 遍历 s,每一个字符对应 dp 一行
    • 如果该行存放的位置为边界则说明匹配,t 中未查询到该字符
    • 位置未越界则,继续查询后一个 s 中的字符,s 的其实位置+1
/**
 * @param {string} s
 * @param {string} t
 * @return {boolean}
 */
var isSubsequence = function (s, t) {
  let slen = s.length,
    tlen = t.length,
    dp = Array.from({ length: tlen + 1 }, () => Array(26))

  // 填充边界值
  for (let i = 0; i < 26; i++) {
    dp[tlen][i] = tlen
  }

  for (let i = tlen - 1; i >= 0; i--) {
    // 生成dp记录每个字符第一次出现位置
    for (let j = 0; j < 26; j++) {
      if (t.charAt(i) === String.fromCharCode(j + 97)) {
        dp[i][j] = i
      } else {
        dp[i][j] = dp[i + 1][j]
      }
    }
  }
  // 查询字符s[i],的t字符起始位置
  let index = 0
  for (let i = 0; i < slen; i++) {
    // 遇到边界说明未匹配到
    if (dp[index][s.charAt(i).charCodeAt() - 97] === tlen) {
      return false
    }
    // 满足条件更新t起始位置
    index = dp[index][s.charAt(i).charCodeAt() - 97] + 1
  }

  return true
}

正则

  • s = “abc”
  • t = “ahbgdc”
  • 转换成正则表达式:a[a-Z]*b[a-Z]*c[a-Z]*
  • 用时去匹配 t,查询 t 中是否包含满足该顺序字符
/**
 * @param {string} s
 * @param {string} t
 * @return {boolean}
 */
var isSubsequence = function (s, t) {
  return new RegExp(s.split('').join('[a-z]*')).test(t)
}

博客: 小书童博客

每天的每日一题,写的题解会同步更新到公众号一天一大 lee 栏目
欢迎关注留言

公号: 坑人的小书童

坑人的小书童

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
探险家小扣的行动轨迹,都将保存在记录仪。expeditions[i] 表示小扣第 i 次探险记录,用一个字符串数组表示。其的每个「营地」由大小写字母组成,通过子串 -> 连接。例:"Leet->code->Campsite",表示到访了 "Leet"、"code"、"Campsite" 三个营地。expeditions[0] 包含了初始小扣已知的所有营地;对于之后的第 i 次探险(即 expeditions[i] 且 i > 0),如果记录包含了之前均没出现的营地,则表示小扣 新发现 的营地。 请你找出小扣发现新营地最多且索引最小的那次探险,并返回对应的记录索引。如果所有探险记录都没有发现新的营地,返回 -1。注意: 大小写不同的营地视为不同的营地; 营地的名称长度均大于 0。用python实现。给你几个例子:示例 1: 输入:expeditions = ["leet->code","leet->code->Campsite->Leet","leet->code->leet->courier"] 输出:1 解释: 初始已知的所有营地为 "leet" 和 "code" 第 1 次,到访了 "leet"、"code"、"Campsite"、"Leet",新发现营地 2 处:"Campsite"、"Leet" 第 2 次,到访了 "leet"、"code"、"courier",新发现营地 1 处:"courier" 第 1 次探险发现的新营地数量最多,因此返回 1。示例 2: 输入:expeditions = ["Alice->Dex","","Dex"] 输出:-1 解释: 初始已知的所有营地为 "Alice" 和 "Dex" 第 1 次,未到访任何营地; 第 2 次,到访了 "Dex",未新发现营地; 因为两次探险均未发现新的营地,返回 -1
04-23
我的理解是,你想让我编写一段 Python 代码,实现一个算法,用于在给定的探险记录找到发现新营地最多且索引最小的探险记录。 那么首先,我需要解析题目的输入输出要求。 输入要求: - expeditions:一个字符串数组,表示小扣第一次到访的所有营地,和之后的每次探险记录(一个记录就是一个字符串,其营地通过子串 -> 连接)。 输出要求: - 如果所有的探险记录都没有发现新的营地,则返回 -1; - 否则,返回发现新营地最多且索引最小的探险记录的索引。注:探险记录的索引从 0 开始计数。 接下来,我可以考虑如何编写代码来解决这个问题。 我的思路是,首先用一个集合记录所有已知的营地名称,然后遍历每个探险记录,依次将其的营地名称加入到集合,并判断是否有新的营地被发现。 如果某个探险记录发现了新的营地,那么我就记录下发现了多少个新营地,以及这个探险记录的索引。最后,遍历所有的探险记录,找到发现新营地最多且索引最小的记录。 代码如下: ```python def find_new_camp(expeditions): known_camps = set() # 已知的营地集合 max_new_camps = 0 # 最多的新营地数量 max_new_camps_index = -1 # 最多新营地数量的探险记录的索引 for i in range(len(expeditions)): sub_camps = expeditions[i].split("->") # 提取探险记录所有的营地名称 num_new_camps = 0 # 记录这个探险记录发现的新营地数量 for camp in sub_camps: if camp not in known_camps: # 如果这个营地是新的,就记录一下 known_camps.add(camp) num_new_camps += 1 if num_new_camps > max_new_camps: # 如果发现了更多的新营地,就更新最大值和索引 max_new_camps = num_new_camps max_new_camps_index = i return max_new_camps_index if max_new_camps > 0 else -1 ``` 我使用了两个变量 `max_new_camps` 和 `max_new_camps_index` 来记录已经遍历过的探险记录,发现了最多的新营地数量,以及具有最多新营地数量的探险记录的索引。然后我遍历所有的探险记录,统计其发现的新营地数量,如果大于当前的最大值,就更新 `max_new_camps` 和 `max_new_camps_index` 变量。最后,如果发现了新的营地,就返回 `max_new_camps_index`。如果所有的探险记录都没有发现新营地,就返回 -1。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值