我正在Leetcode(500键盘行(上进行编程练习,在讨论中遇到了set.issubset((。与我自己的答案进行比较后,这里有一个有趣的发现:
1(
def checkWord(self, word):
r1 = 'qwertyuiop'
r2 = 'asdfghjkl'
r3 = 'zxcvbnm'
row = 0
for idx, ch in enumerate(word):
if idx == 0:
row = 1 if ch in r1 else 2 if ch in r2 else 3
continue
coming_row = 1 if ch in r1 else 2 if ch in r2 else 3
if row != coming_row:
return False
return True
如果我提交这个,我会得到 40 毫秒的运行时间
2(
def checkWord(self, word):
r1 = 'qwertyuiop'
r2 = 'asdfghjkl'
r3 = 'zxcvbnm'
return set(word).issubset(r1) or set(word).issubset(r2) or set(word).issubset(r3)
如果我使用 issubset((,我会得到 36ms 的运行时间,这超过了所有提交的 94.87%。
3(所以我想,嗯,这件作品有什么区别?根据我在这个主题上找到的一个问题 Python 的复杂度是 issubset((,方法 2( 的时间复杂度应该是 O(len(word((,我相信这应该与这篇文章相同:
def checkWord(self, word):
r1 = set('qwertyuiop')
r2 = set('asdfghjkl')
r3 = set('zxcvbnm')
row = 0
for idx, ch in enumerate(word):
if idx == 0:
row = 1 if ch in r1 else 2 if ch in r2 else 3
continue
coming_row = 1 if ch in r1 else 2 if ch in r2 else 3
if row != coming_row:
return False
return True
但是,它的运行时间为 32 毫秒,并且击败了所有 py3 提交的 100%......为什么 issubset(( 比这慢?谢谢!
请注意,checkWord
函数的第一个版本不会检查ch
值是否在r3
集中,因此您可以避免一次检查。
此外,在 checkword
的第二个版本中,您将创建一个包含 word
参数字符的集合三次。为什么不在检查"issubset"之前存储集合?你的代码将是这样的:
def checkWord3(word):
r1 = 'qwertyuiop'
r2 = 'asdfghjkl'
r3 = 'zxcvbnm'
s1 = set(word)
return s1.issubset(r1) or s1.issubset(r2) or s1.issubset(r3)
这是使用正则表达式的另一种解决方案:
r1 = re.compile("^[qwertyuiop]+$")
r2 = re.compile("^[asdfghjkl]+$")
r3 = re.compile("^[zxcvbnm]+$")
r1.match(word) or r2.match(word) or r3.match(word)
我已经测试了以下方法:
import random
import string
def checkWord1(word):
r1 = 'qwertyuiop'
r2 = 'asdfghjkl'
r3 = 'zxcvbnm'
row = 0
for idx, ch in enumerate(word):
if idx == 0:
row = 1 if ch in r1 else 2 if ch in r2 else 3
continue
coming_row = 1 if ch in r1 else 2 if ch in r2 else 3
if row != coming_row:
return False
return True
def checkWord2(word):
r1 = 'qwertyuiop'
r2 = 'asdfghjkl'
r3 = 'zxcvbnm'
return set(word).issubset(r1) or set(word).issubset(r2) or set(word).issubset(r3)
def checkWord3(word):
r1 = 'qwertyuiop'
r2 = 'asdfghjkl'
r3 = 'zxcvbnm'
r = set(word)
return r.issubset(r1) or r.issubset(r2) or r.issubset(r3)
def checkWord4(word):
r1 = set('qwertyuiop')
r2 = set('asdfghjkl')
r3 = set('zxcvbnm')
row = 0
for idx, ch in enumerate(word):
if idx == 0:
row = 1 if ch in r1 else 2 if ch in r2 else 3
continue
coming_row = 1 if ch in r1 else 2 if ch in r2 else 3
if row != coming_row:
return False
return True
并测量执行时间:
word = ''.join(random.choice(string.ascii_lowercase) for _ in range(random.randint(0, 9)))
print("One:")
%timeit -n 10000 checkWord1(word)
print("Two:")
%timeit -n 10000 checkWord2(word)
print("Three:")
%timeit -n 10000 checkWord3(word)
print("Four:")
%timeit -n 10000 checkWord4(word)
获取结果:
One:
475 ns ± 88.1 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
Two:
708 ns ± 117 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
Three:
552 ns ± 19.6 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
Four:
1.19 µs ± 10.8 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
尽管使用不同的随机生成的字符串,但结果是相似的。
一个最快,三个比两个更快更稳定,四个最差。
我认为为什么一个人是最快的原因是
issubset(object)
实际上隐式地将object
转换为set
。这是昂贵的。这种比较是不公平的。假设我们有一个词
"qsdfwe"
,在方法一中,它将检查是否在r1
中,在下面的循环中,如果发现它不是r1
的子集,它也不可能是其他的子集。
所以我实现了一个更快的方法,我认为我们可以只关注由第一个字符决定的一行,因为这三行是排他性的,所以我们不再检查其他行。
def checkWord5(word):
r = ['qwertyuiop', 'asdfghjkl', 'zxcvbnm']
first_char = word[0]
row = r[0] if first_char in r[0] else r[1] if first_char in r[1] else r[2]
for ch in word[1:]:
if ch not in row:
return False
return True
获取结果:
One:
728 ns ± 236 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
Two:
2.08 µs ± 55.8 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
Three:
1.43 µs ± 60 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
Four:
1.45 µs ± 7.74 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
Five:
374 ns ± 7.01 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)