LeetCode探索(七):387_字符串中的第一个唯一字符

460 阅读2分钟

这是我参与11月更文挑战的第 14 天,活动详情查看:2021最后一次更文挑战

前言

在数据结构和算法中,栈和队列是一对好兄弟。栈的机制是后进先出,可以理解为一个杯子,后面放入杯子中的要先取出来。而队列的机制是先进先出,可以理解为一个队伍,排在前面的人会优先进行接待。栈和队列在编程中有着广泛的应用,其语法在这里就不赘述了,读者有兴趣的话可以去查阅相关的资料。这里要介绍的是如何借助队列的思想去解决一道LeetCode题目。

队列

题目

给定一个字符串,找到它的第一个不重复的字符,并返回它的索引。如果不存在,则返回 -1。

示例:

s = "leetcode"
返回 0

s = "loveleetcode"
返回 2

提示:你可以假定该字符串只包含小写字母。

思考

对于这道题,首先可以想到的是遍历字符串,对于字符串的每个字符,再遍历一次查询是否有重复的字符。这样可以解决我们的问题,但是,该解法的时间复杂度较差,是O(n^2),显然不够优雅!

那么可以怎么改进我们的算法呢?答案有很多,其中之一,我们可以借助队列的思想去实现。

首先,我们定义一个哈希表存储字符对应的索引,如果字符第二次出现,则将索引定义了-1。然后,定义一个队列(这里使用数组),在遍历字符串的同时,存入所有满足条件的字符及其对应的索引。遍历结束时,队列的第一个元素也就是索引最小的满足条件的字符。这样,就解决了我们的问题了。

下面是具体的代码。

解答

队列

var firstUniqChar = function(s) {
  const position = new Map() // 哈希表
  const q = [] // 队列
  for (let [i, ch] of Array.from(s).entries()) {
    if (!position.has(ch)) {
      position.set(ch, i) // 哈希表中字符ch对应的值设置为索引i
      q.push([s[i], i]) // 队列中存入[字符、索引]
    } else {
      position.set(ch, -1) // 哈希表中字符ch对应的值设置为-1
      while (q.length && position.get(q[0][0]) === -1) { // 延迟删除,删除队列中不满足的元素
        q.shift() // 移除数组的第一个元素:相当于队列的先进先出
      }
    }
  }
  return q.length ? q[0][1] : -1
}

复杂度分析:

  • 时间复杂度:O(n),其中 n 是字符串 s 的长度。

  • 空间复杂度:O(|Σ|),在本题中 s 只包含小写字母,因此 |Σ| ≤ 26。

延迟删除:在队列中,即使有一些字符出现了超过一次,但只要它不位于队首,就不会对答案造成影响,我们也就可以不用去删除它。

参考