如果给定的字符串是s,计算复杂度显然至少为 O(s.size)(因为我们需要检查整个字符串以确认给定字符在字符串中恰好出现一次)。因此,我们可能会寻找一种具有相同计算复杂度的方法,最好是使用相对高效的 Ruby 内置方法并且易于理解和测试的方法。
假设给定的字符串如下:
s = "TgHEtGgh"
假设不考虑大小写,第一个只出现一次的字符是"E"。
作为第一步,我们可能希望计算字符串中每个字符的频率,小写和大写字符被同等对待1个:
sdn = s.downcase
#=> "tghetggh"
enum = sdn.each_char
#=> #<Enumerator: "tghetggh":each_char>
h = enum.tally
#=> {"t"=>2, "g"=>3, "h"=>2, "e"=>1}
这使用方法String#downcase、String#each_char 和Enumerable#tally。其中每一种方法的计算复杂度都是O(s.size),所以h的计算复杂度是一样的。作为奖励,这些方法中的每一个都是用 C 语言实现的。
当然,我们可以链接这些方法:
h = s.downcase.each_char.tally
#=> {"t"=>2, "g"=>3, "h"=>2, "e"=>1}
我们现在可以简单地遍历字符串 s 的字符,直到找到一个字符 c,h[c.downcase] == 1 是 true。
s.each_char.find { |c| h[c.downcase] == 1 }
#=> "E"
参见Enumerable#find。
为了使最后一步的计算复杂度为 O(s.size),计算 h[c.downcase] 的计算复杂度必须等于 1。事实上,哈希键查找的计算复杂度略大于1,但从实用的角度来看,我们可以假设它等于1。
1. 请注意,通过先写arr = sdn.chars #=> ["t", "g", "h", "e", "t", "g", "g", "h"],然后写h = arr.tally,我们可以获得相同的结果。这样做的缺点是,与 String#each_char 不同,String#chars 创建一个临时数组,消耗内存,但在这种情况下,使用 each_char 节省的内存可能是最小的。