难度: Hard
Submission
运行时间: 4296 ms
内存: 58.8 MB
import math from typing import List from itertools import combinations from functools import lru_cache class Solution: def trafficCommand(self, directions: List[str]) -> int: """ :param directions: :return: """ ll = [len(i) for i in directions] @lru_cache(None) def can_pass(e, s, w, n): todo = {} op = [0] * 16 if e != '': todo['e'] = e.lower() if s != '': todo['s'] = s.lower() if w != '': todo['w'] = w.lower() if n != '': todo['n'] = n.lower() for i in todo: if i == 'e': if todo[i] == 's': if op[5] == 1 or op[8] == 1 or op[10] == 1 or op[15] == 1 or op[14] == 1: return False op[5] = 1 op[8] = 1 op[10] = 1 op[15] = 1 op[14] = 1 if todo[i] == 'w': if op[2] == 1 or op[3] == 1 or op[4] == 1 or op[5] == 1: return False op[2] = 1 op[3] = 1 op[4] = 1 op[5] = 1 if todo[i] == 'n': if op[1] == 1 or op[5] == 1: return False op[1] = 1 op[5] = 1 if i == 's': if todo[i] == 'w': if op[2] == 1 or op[7] == 1 or op[11] == 1 or op[13] == 1 or op[15] == 1: return False op[2] = 1 op[7] = 1 op[11] = 1 op[13] = 1 op[15] = 1 if todo[i] == 'e': if op[9] == 1 or op[11] == 1: return False op[9] = 1 op[11] = 1 if todo[i] == 'n': if op[1] == 1 or op[4] == 1 or op[8] == 1 or op[11] == 1: return False op[1] = 1 op[4] = 1 op[8] = 1 op[11] = 1 if i == 'w': if todo[i] == 's': if op[6] == 1 or op[10] == 1: return False op[6] = 1 op[10] = 1 if todo[i] == 'e': if op[6] == 1 or op[7] == 1 or op[9] == 1 or op[8] == 1: return False op[6] = 1 op[7] = 1 op[9] = 1 op[8] = 1 if todo[i] == 'n': if op[1] == 1 or op[3] == 1 or op[6] == 1 or op[12] == 1 or op[13] == 1: return False op[1] = 1 op[3] = 1 op[6] = 1 op[12] = 1 op[13] = 1 if i == 'n': if todo[i] == 's': if op[3] == 1 or op[0] == 1 or op[10] == 1 or op[7] == 1: return False op[3] = 1 op[0] = 1 op[10] = 1 op[7] = 1 if todo[i] == 'e': if op[9] == 1 or op[0] == 1 or op[4] == 1 or op[12] == 1 or op[14] == 1: return False op[9] = 1 op[0] = 1 op[4] = 1 op[12] = 1 op[14] = 1 if todo[i] == 'w': if op[0] == 1 or op[2] == 1: return False op[0] = 1 op[2] = 1 return True @lru_cache(None) def get_min_time(rest_idx: tuple): if sum(rest_idx) == 0: return 0 topick = [k for k, v in enumerate(rest_idx) if v > 0] ans = math.inf for i in topick: tp = list(rest_idx) tp[i] -= 1 ans1 = 1 + get_min_time(tuple(tp)) ans = min(ans, ans1) for i in range(2, len(topick) + 1): for j in combinations(topick, i): picked = j e, s, w, n = '', '', '', '' if 0 in picked: e = directions[0][len(directions[0]) - rest_idx[0]] if 1 in picked: s = directions[1][len(directions[1]) - rest_idx[1]] if 2 in picked: w = directions[2][len(directions[2]) - rest_idx[2]] if 3 in picked: n = directions[3][len(directions[3]) - rest_idx[3]] ret1 = can_pass(e, s, w, n) if ret1: tp = list(rest_idx) for x in picked: tp[x] -= 1 ans1 = 1 + get_min_time(tuple(tp)) ans = min(ans, ans1) return ans ret = get_min_time(tuple(ll)) return ret a = Solution() print(a.trafficCommand(["NN","WE","EN","EW"])) # print(a.trafficCommand(["S", "W", "N", "E"])) # # print(a.trafficCommand(directions = ["W","N","ES","W"])) # print(a.trafficCommand(directions=["NS", "WE", "SE", "EW"]))
Explain
该题解采用了递归加备忘录的方法来解决问题,涉及动态规划的思路。具体思路如下:首先,对每个方向的车辆数进行统计,并定义两个缓存函数。第一个缓存函数`can_pass`用于检查在不产生冲突的情况下哪些车可以同时通过交叉口。它使用了一个16位的数组来标记可能的冲突,并对每一种行驶方向的车辆进行冲突检查。第二个缓存函数`get_min_time`是主要的递归函数,用于计算清空路口所需的最短时间。它首先考虑一个车辆通过的情况,然后尝试所有可能的车辆组合,递归地计算通过剩余车辆所需的时间。这里使用了组合生成和对每种可能的行车情况进行模拟,通过调用`can_pass`来确保没有冲突。
时间复杂度: O(2^n)
空间复杂度: O(n + 4^n)
import math from typing import List from itertools import combinations from functools import lru_cache class Solution: def trafficCommand(self, directions: List[str]) -> int: # 计算每个方向的车辆数 ll = [len(i) for i in directions] # 使用备忘录优化,判断是否可通行 @lru_cache(None) def can_pass(e, s, w, n): todo = {} op = [0] * 16 # 标记16种可能的车辆动作组合 # 判断每个方向是否有车并转化为小写字母表示方向 if e != '': todo['e'] = e.lower() if s != '': todo['s'] = s.lower() if w != '': todo['w'] = w.lower() if n != '': todo['n'] = n.lower() for i in todo: # 根据车辆的目标方向,检查是否会产生冲突 # 例如东边的车向南行驶需要的路径是否被其他车辆占用 if i == 'e': if todo[i] == 's': if op[5] == 1 or op[8] == 1 or op[10] == 1 or op[15] == 1 or op[14] == 1: return False # 如果路径已被占用则返回不可通行 op[5] = 1 op[8] = 1 op[10] = 1 op[15] = 1 op[14] = 1 # 同样的检查逻辑应用于其他方向和目标组合 # 下面代码块遵循同样逻辑处理南、西、北方向的车辆 # 计算最小通过时间的函数 @lru_cache(None) def get_min_time(rest_idx: tuple): if sum(rest_idx) == 0: return 0 # 所有车辆已通过时返回0 topick = [k for k, v in enumerate(rest_idx) if v > 0] # 选择还有车辆的方向 ans = math.inf # 单车通过 for i in topick: tp = list(rest_idx) tp[i] -= 1 ans1 = 1 + get_min_time(tuple(tp)) ans = min(ans, ans1) # 尝试多车同时通过的情况,保证不冲突 for i in range(2, len(topick) + 1): for j in combinations(topick, i): picked = j e, s, w, n = '', '', '', '' # 获取对应方向车辆的当前行驶方向 if 0 in picked: e = directions[0][len(directions[0]) - rest_idx[0]] if 1 in picked: s = directions[1][len(directions[1]) - rest_idx[1]] if 2 in picked: w = directions[2][len(directions[2]) - rest_idx[2]] if 3 in picked: n = directions[3][len(directions[3]) - rest_idx[3]] ret1 = can_pass(e, s, w, n) # 检查是否冲突 if ret1: tp = list(rest_idx) for x in picked: tp[x] -= 1 ans1 = 1 + get_min_time(tuple(tp)) ans = min(ans, ans1) return ans ret = get_min_time(tuple(ll)) return ret # 示例调用 a = Solution() print(a.trafficCommand(["NN","WE","EN","EW"])) # print(a.trafficCommand(["S", "W", "N", "E"])) # # print(a.trafficCommand(directions = ["W","N","ES","W"])) # print(a.trafficCommand(directions=["NS", "WE", "SE", "EW"]))
Explore
函数`can_pass`通过一个16位的数组`op`来记录和管理冲突。每一位代表一个特定的车辆方向和目标方向的组合。例如,如果东边的车想向南行驶,这将会影响到其他可能与之冲突的路线(如其他车辆也想向南或交叉这条路线)。通过设置和检查这些位的状态,可以确保任何车辆行进的组合都不会违反交通规则。在实现时,需要确保每一种可能的车辆方向组合都被考虑到,并且对应的位正确地反映了是否会产生冲突。这样,通过逻辑检查和位操作来确保不会有违反交通规则的车辆组合被允许通过。
在递归逻辑中,从两辆车开始尝试主要是基于优化和实际场景的考虑。首先,单车通过是基本情况,必须先考虑。接着,从两辆车开始尝试是因为这是最简单的多车交互情况,可以有效地处理多数交通冲突和优化通行时间。如果直接从更多车辆开始,虽然可能在某些情况下更快,但会大幅增加复杂度和计算量,因为组合数量随车辆数量呈指数增长。因此,从两辆车开始,依次增加车辆数,是一个平衡计算效率和解决问题复杂度的实用方法。
在`get_min_time`函数中,使用了一个元组`rest_idx`来记录每个方向剩余的车辆数量。这个元组在每次递归调用时都会被更新,以反映车辆通过交叉口后的新状态。使用列表推导和元组操作来确保每次只减少通过的车辆数,而不影响未通过的车辆。此外,使用`lru_cache`装饰器对这个函数进行了缓存,可以避免重复计算相同的车辆状态,从而提高效率和防止错误重复处理。这种方法确保了状态的正确更新和高效的递归调用。