题目
某年 百度的面试题。
一串首尾相连的珠子有 m 个,共有 N 种颜色(N <= 10)。设计一个算法,取出其中一段,要求包含所有 N 种颜色,并且长度最短。
思路
使用一个额外的数组 flags 存储颜色计数。若遍历的当前序列中包含了第 i 种颜色,则将数组中第 i 号元素值加 1。
使用双指针遍历原珠子数组 beads ,分别表示最短包含 N 种颜色珠子的最短子串的首尾。
- 两个指针,一开始都指向头结点。
- 指针一往后移动,在范围内,当包含了所有颜色的时候,停下来。
- 指针二往后移动,在范围内,移动的过程中判断是否包含了所有颜色,不包含的时候停下来。
- 计算此时两指针之间的距离,如果小于上一个最小记录,则更新为该距离。
- 重复以上动作直到指针一越界停止。
- 对于颜色数组,开始时数组所有元素置为0。当指针一移动时,将指针一指向的元素下标对应 flags 中元素值加一;指针二移动时,将指针二指向的元素下标对应 flags 中元素值减一。(这一段有点绕口,对照着下面的代码理解就知道了)
时间复杂度为 O(m),空间复杂度为 O(N)。
python代码
def isFull(flags): # 如果序列中包含了所有颜色,则每个颜色索引对应的值都会大于1,那么乘积一定不会为0
result = 1
for i in flags:
result *= i
return False if result == 0 else True
def getShortestBeads(beads, flags):
ap = bp = 0
least = len(beads)
while bp < len(beads):
# bp 一直往后移动,直到 flags 中所有颜色都至少有一个
while not isFull(flags) and bp < len(beads):
flags[beads[bp]] += 1 # beads[bp]颜色号加一
bp += 1
# ap 一直往后移动,同时判断是否包含了所有颜色
while isFull(flags) and ap < len(beads):
flags[beads[ap]] -= 1 # beads[ap]颜色号减一
ap += 1
# 更新最短序列长度
if bp <= len(beads):
count = bp - ap + 1
if count < least:
least = count
else:
return least
return least
if __name__ == '__main__':
beads = [1, 2, 2, 1, 1, 3, 0, 0, 2, 2, 3, 2, 3]
flags = [0] * 4 # 分别对应结果序列中各颜色的数量
print(getShortestBeads(beads, flags)) # 输出结果为 5
print(getShortestBeads([2, 1, 0, 1, 1, 2, 1, 0, 1, 1], [0, 0, 0])) # 输出结果为 3
结合代码理解分析过程,说白了就是用两个指针 ap 和 bp 来代表结果序列。bp 往后一直遍历珠子数组,使得 flags 颜色数组中每个颜色都至少有一个。然后 ap 往后一直遍历珠子数组,尝试着去尽可能减少当前结果序列中的总珠子个数,同时还得满足当前结果序列包含了所有颜色。
借用 isFull 函数来判断是否包含了所有颜色,可太妙了!敬佩大佬们!