如何在Python中更高效地找到字符串中最早出现且只出现一次的字符?

编程语言 2026-07-10

我在尝试写一个Python函数,返回字符串中第一个不重复的字符。 例如:

  • "leetcode""l"
  • "loveleetcode""v"
  • "aabb"None

我希望解法高效(最好是O(n) 时间复杂度)。

下面是我尝试过的:

def first_unique_char(s):
    for c in s:
        if s.count(c) == 1:
            return c
    return None

它能工作,但我觉得对于很长的字符串效率不高。 能否帮我优化这个并解释为什么改进后的版本更好?

解决方案

在你的代码中,你是逐个字符地查看(这是一个O(n) 的操作,其中n 是输入字符串的长度),然后再统计该字符在字符串中出现的次数。为了得到这个计数,你还需要再次查看每个字符(也是一个O(n) 的操作)。

因此,算法的时间复杂度是O(n**2),因为我们对同一个操作执行了n 次。

在下面的代码中,我们逐个遍历每个字符,并递增我们已经看到的该字符的出现次数。为此我们使用 collections.Counter 类,它是 dict 的一个子类。这是一个O(n) 的操作,其中n 是实参 s 的大小。

然后逐一查看每个字符及其计数,直到找到一个计数为1 的字符并返回该字符。如果找不到这样的字符,我们返回 None。这同样也是一个O(n) 操作。因此我们连续执行了两个O(n) 操作,即O(n) + O(n),总体仍然是O(n)。

from collections import Counter


def first_unique_char(s):
    counts = Counter(s);
    for ch, count in counts.items():
        if count == 1:
            return ch
    return None

for s in (
    'leetcodee',
    'loveleetcode',
    'aabb'
):
    print(repr(s), '->', repr(first_unique_char(s)))

输出:

leetcodee' -> 'l'
'loveleetcode' -> 'v'
'aabb' -> None

更新:适用于Python 3.7及以下版本

有人指出,键的插入顺序只有在Python 3.7及以上版本中才会得到保证。如果你使用的版本比它更早,那么:

from collections import OrderedDict


def first_unique_char(s):
    counts = OrderedDict();
    for ch in s:
        count = counts.get(ch, 0)
        count += 1
        counts[ch] = count

    for ch, count in counts.items():
        if count == 1:
            return ch
    return None

for s in (
    'leetcodee',
    'loveleetcode',
    'aabb'
):
    print(repr(s), '->', repr(first_unique_char(s)))

对于上述实现,对输入字符串中的每个字符,执行两次字典查找:一次用于获取当前计数(可能不存在),一次用于更新自增计数。这里有另一种实现,使用 setdefault 方法和一个 OrderedDict 实例,将每个输入字符的查找次数降至一次,但代价是分配新的 Counter 实例。不过,我们能够将类 Counter 的实例化次数控制在n + 1,其中n 是字符串s 中不同字符的数量。这在长字符串且重复字符很多的情况下应该比之前的算法更快。

from collections import OrderedDict


def first_unique_char(s):
    class Counter:
        def __init__(self):
            self.count = 0

    counts = OrderedDict();
    unused_counter = Counter()

    for ch in s:
        char_counter = counts.setdefault(ch, unused_counter)
        char_counter.count += 1
        if char_counter is unused_counter:
            unused_counter = Counter()

    for ch, counter in counts.items():
        if counter.count == 1:
            return ch
    return None

for s in (
    'leetcodee',
    'loveleetcode',
    'aabb'
):
    print(repr(s), '->', repr(first_unique_char(s)))
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章