N 个加油站组成一个环形,给定两个长度都是 N 的非负数组 oil 和 dis(N > 1),oil[i] 表示第 i 个加油站存的油可以跑多少千米,dis[i] 代表第 i 个加油站到环中下一个加油站相隔多少千米。
假设你有一辆邮箱足够大的车,初始时车里没有油。如果车从第 i 个加油站出发,最终可以回到这个加油站,那么第 i 个加油站就算良好出发点,否则就不算。请返回长度为 N 的 boolean 数组 res,res[i] 代表第 i 个加油站是不是良好出发点。
解法一:暴力算法
分析:
如果下图绿色节点顺利走完一圈,它就是良好出发点。
其实我们可以将 oil - dis 得到纯能数组。那么原问题就转化为:在纯能数组组成的环上,从起始点出发,累加纯能数组上的数值,累加和始终不为负数,那么起始点就是良好起点。
时间复杂度: O ( N 2 ) O(N^2) O(N2)
空间复杂度: O ( 1 ) O(1) O(1)
def stations(dis, oil):
if not dis or not oil or len(dis) < 2 or len(dis) != len(oil): return
res = [False] * len(dis)
n = len(dis)
# 起始点(大于 0)
init = -1
# 生成纯能数组
for i in range(n):
dis[i] = oil[i] - dis[i]
if dis[i] > 0: init = i
if init == -1: return res
# 尝试以每个加油站为出发点
for i in range(n):
res[i] = circular(dis, i) >= 0
return res
# 以 i 为出发点,尝试走完一圈。如果累计和为 0 ,退出
def circular(dis, i):
if dis[i] < 0: return dis[i]
n = len(dis)
sum_value = dis[i]
j = i + 1
while j % n != i:
sum_value += dis[j % n]
if sum_value < 0: break
j += 1
return sum_value
解法二:滑动窗口
连通区(窗口)伸缩规则:使用 [ start, end ) 表示连通区,前闭后开。
start 初始值为 init(初始点:在纯能数组中选择一个值大于 0 的数据作为初始点)
end = next_index( init , n)
如果 end 能扩展连通区:rest >= 0,就逆时针走,一直走到 rest 小于或者走到 start 位置;否则就顺时针移动 start,将所需的油累计在 need 变量上,在 end 扩展时使用 need,使用完毕后将 need 还原成 0。
跑完一圈后会出现两种情况:
情况一:没找到良好出发点
当走完一圈,如果 start 对应加油站不是一个良好出发点,就表明所有加油站不都不是良好出发点。
如下图:连通区是:【G,H,I,A,B】,start 在 G 位置,不是一个良好出发点,那么【H,I,A,B】 也不是良好出发点。因为在连通区内 G 是可以到达【H,I,A,B】任何位置。G 带着剩余油(rest>=0) 到达【H,I,A,B】都没走通,那么以【H,I,A,B】为起始点(rest==0) 更不可能走完一圈。
连通图的 start 没有扩展到【F,E,D,C】说明,【F,E,D,C】无法达到 G 位置,所以【F,E,D,C】也不是良好出发点。
综上所述所此种情况下,所有的加油站都不是良好出发点。
情况二:找到了一个良好出发点(start)
当走完一圈,如果 start 对应加油站是一个良好出发点(rest >= 0),我们需要寻找其他良好出发点。
如下图:连通区是:【G,H,I,A,B,C,D,E,F】,start 所在的 G 位置是一个良好出发点,那么从 start 上一个加油站 start1 出发到一路追溯到 init ,任何一个能到 G 的加油站都是良好出发点。
时间复杂度: O ( N ) O(N) O(N)
空间复杂度: O ( 1 ) O(1) O(1)
def stations2(dis, oil):
if not dis or not oil or len(dis) < 2 or len(dis) != len(oil): return
n = len(dis)
# 起始点(大于 0)
init = -1
# 生成纯能数组
for i in range(n):
dis[i] = oil[i] - dis[i]
if dis[i] > 0: init = i
return [False] * len(dis) if init < 0 else enlarge_area(dis, init)
def enlarge_area(dis, init):
n = len(dis)
res = [False] * n
# 连通区起始点
start = init
# 连通区终点
end = next_index(init, n)
# 突破 start 需要油量
need = 0
# 剩余油量
rest = 0
# 以 init 为起始点,跑一圈
while True:
# 连通区 start 扩展(如果 end 无法突破,就扩展 start,所需的油都累计在 need 中)
if dis[start] < need:
# 如果 dis[start] 为负数,need 值增加
# 如果 dis[start] 为正数,need 值减少
need -= dis[start]
else:
# 将 need 的累积的油计算到 rest
rest += dis[start] - need
# 重置 need
need = 0
# end 连续突破
while rest >= 0 and end != start:
rest += dis[end]
end = next_index(end, n)
# 如果 end 连续突破后,rest 还有剩余,说明是 end == start 的条件跳出循环的,已经跑了一圈了。
# 跑过一圈后,rest >= 0 油有剩余,说明以 start 是良好起始点
# 跑过一圈后,rest < 0 油没有剩余,说明没有一个良好起始点,直接返回
if rest >= 0:
res[start] = True
# 寻找其他的良好起始点
# 所有能正常能达到 start 的加油站都是良好起始点
# 所有从 start 上一个节点开始一路向上寻找能正常穿过 start 的加油站,并将对应 res 设置为 True
connect_good(dis, last_index(start, n), init, res)
# 已经跑了一圈了,其他良好起始点也寻找完毕,任务完成,跳出。
break
start = last_index(start, n)
if start == init or start == last_index(end, n):
break
return res
def connect_good(dis, start, init, res):
need = 0
n = len(dis)
while start != init:
# 如果当前节点 start 无法穿越,用 need 记录所需要油,继续向上寻找
if dis[start] < need:
need -= dis[start]
else:
# 成功穿越
res[start] = True
need = 0
start = last_index(start, n)
# 数组需要循环访问,需要在两个端点做特殊处理
# 获取 index 前一个索引
def last_index(index, size):
return size - 1 if index == 0 else index - 1
# 获取 index 后一个索引
def next_index(index, size):
return 0 if index == size - 1 else index + 1
对数器
import random
def check():
for _ in range(100):
n = int(random.random() * 5) + 1
oil = [int(random.random() * 5) + 1 for _ in range(n)]
dis = [int(random.random() * 5) + 1 for _ in range(n)]
res = stations(dis[:], oil[:])
res2 = stations2(dis[:], oil[:])
if res != res2:
print("ERROR", "res=", res, "res2=", res2, "oil=", oil, "dis=", dis)
print("Nice")
check()
本文由mdnice多平台发布