难度: Hard
Submission
运行时间: 812 ms
内存: 17.0 MB
class Solution: def guardCastle(self, grid: List[str]) -> int: # 0 = S, 1 = C, 2 = P INF = 1000000000 N = len(grid) M = len(grid[0]) ans = INF for P in ('C', 'S'): last_endpoint = None # 00, 01, 10, 11 dp = [0, 0, 0, 0] for j in range(M): current = grid[0][j], grid[1][j] current = tuple(P if c == 'P' else c for c in current) if 'S' in current: if 'C' in current: dp[3] = INF break endpoint = 0 target = 'S' elif 'C' in current: endpoint = 1 target = 'C' else: endpoint = None target = None if current == (target, target): if last_endpoint == 1 - endpoint: dp = [INF, INF, INF, dp[0]] else: dp = [INF, INF, INF, dp[3]] elif current == (target, '#'): if last_endpoint == 1 - endpoint: dp = [INF, INF, dp[1], dp[1]] else: dp = [INF, INF, dp[3], dp[3]] elif current == ('#', target): if last_endpoint == 1 - endpoint: dp = [INF, dp[2], INF, dp[2]] else: dp = [INF, dp[3], INF, dp[3]] elif current == (target, '.'): if last_endpoint == 1 - endpoint: dp = [INF, INF, dp[1] + 1, dp[0]] dp[3] = min(dp[2], dp[3]) else: dp = [INF, INF, dp[3] + 1, dp[3]] elif current == ('.', target): if last_endpoint == 1 - endpoint: dp = [INF, dp[2] + 1, INF, dp[0]] dp[3] = min(dp[1], dp[3]) else: dp = [INF, dp[3] + 1, INF, dp[3]] elif current == ('.', '.'): dp[0] = min(dp[0], dp[1] + 1, dp[2] + 1, dp[3] + 2) dp[1] = dp[2] = min(dp[0], dp[3] + 1) elif current == ('.', '#'): dp[0] = dp[1] = min(dp[1], dp[3] + 1) dp[2] = dp[3] elif current == ('#', '.'): dp[0] = dp[2] = min(dp[2], dp[3] + 1) dp[1] = dp[3] else: dp[0] = dp[1] = dp[2] = dp[3] if endpoint is not None: last_endpoint = endpoint ans = min(ans, dp[3]) if ans >= INF: return -1 else: return ans
Explain
该题解采用了动态规划的方法来求解怎样以最少的障碍物阻止恶魔从出生点到达城堡。考虑两种可能的出发点(城堡C和出生点S),每次遍历地图列,更新动态规划表格(dp数组)来存储到当前列为止的最小障碍物数量。dp数组有四种状态,分别代表不同的行连接方式(两行直接连接,或者通过传送P点连接等)。在遍历中,根据当前列的情况更新dp数组,最终dp数组的最小值即为所需的最少障碍物数量。如果无法阻挡,则返回-1。
时间复杂度: O(M)
空间复杂度: O(1)
class Solution: def guardCastle(self, grid: List[str]) -> int: INF = int(1e9) # 定义一个足够大的数表示无法到达的情况 N = len(grid) M = len(grid[0]) ans = INF # 初始化答案为无穷大 for P in ('C', 'S'): # 分别以城堡C和出生点S作为起点 last_endpoint = None dp = [0, 0, 0, 0] # 初始化dp数组,表示不同的连接状态 for j in range(M): # 遍历每一列 current = (grid[0][j], grid[1][j]) current = tuple(P if c == 'P' else c for c in current) # 将P点视为当前起点 if 'S' in current: if 'C' in current: dp[3] = INF # 如果S和C在同一位置,设置障碍无效 break endpoint = 0 target = 'S' elif 'C' in current: endpoint = 1 target = 'C' else: endpoint = None target = None # 根据当前列的状态更新dp数组 if current == (target, target): if last_endpoint == 1 - endpoint: dp = [INF, INF, INF, dp[0]] else: dp = [INF, INF, INF, dp[3]] # 更多状态的更新逻辑省略... if endpoint is not None: last_endpoint = endpoint ans = min(ans, dp[3]) # 更新全局最小值 if ans >= INF: return -1 # 如果答案仍为INF,返回-1 else: return ans
Explore
在动态规划解法中,dp数组是用来存储不同连接状态下的最小障碍物数量。这个数组有四个状态,每个状态对应不同的行连接方式,例如两行可能直接连接,或者通过特定的点(比如传送点P)连接。每次迭代时,根据地图的当前列的字符('S', 'C', 或 'P')来更新dp数组。具体如何更新取决于当前列的字符组合和前一列的状态。例如,如果当前列的两个位置都是起点('S'或'C'),则可能需要设置障碍物或考虑不同的连接方式,从而更新dp数组中的值。如果某一列同时出现'S'和'C',则当前dp状态会设置为无穷大(INF),因为在同一位置设置障碍物无效。
将P点视为当前起点是一个处理策略,旨在简化动态规划的状态转移。在此题解中,P点代表传送点,可以视为柔性地连接到起点'S'或目标点'C'。这意味着当遇到P点时,可以根据需要将其视为'S'或'C',从而影响dp数组的更新。这种处理使得动态规划可以在遇到P点时更灵活地调整策略,例如,如果P点被视作起点S,那么dp数组会更新为考虑从P点开始的路径;如果P点被视作目标点C,那么更新会考虑阻止到达P点的路径。
动态规划的目标是通过合理设置障碍物确保恶魔无法到达城堡。解法通过迭代每一列,并根据每列的具体情况(例如是否含有出生点S、城堡C或传送点P),更新dp数组来尝试阻挡所有可能的路径。如果任意列显示S和C在同一列或通过P点可以直接或间接连接,则该列的dp状态会设置为无穷大(INF),表示此路径无法被阻断。题解中通过考虑从S和C两个不同的起点开始的策略,尽可能覆盖所有路径。如果最终dp数组的最小值仍然是无穷大,表明存在至少一条路径不能被阻断,返回-1表示失败。