Hard
题目描述
给定一个由 n 个顶点组成的无向树,顶点编号为 1 到 n。一只青蛙从顶点 1 开始跳跃。在一秒钟内,青蛙从当前顶点跳到另一个未访问的直接相连顶点。青蛙不能跳回到已访问过的顶点。如果青蛙可以跳到多个顶点,它会以相同的概率随机选择其中一个。否则,当青蛙无法跳到任何未访问的顶点时,它会永远停留在当前顶点。
无向树的边在数组 edges 中给出,其中 edges[i] = [ai, bi] 表示顶点 ai 和 bi 之间存在一条边。
返回在 t 秒后青蛙在顶点 target 上的概率。与实际答案相差不超过 10^-5 的答案将被接受。
示例 1:
输入: n = 7, edges = [[1,2],[1,3],[1,7],[2,4],[2,6],[3,5]], t = 2, target = 4
输出: 0.16666666666666666
解释: 青蛙从顶点 1 开始,第 1 秒后以 1/3 的概率跳到顶点 2,然后第 2 秒后以 1/2 的概率跳到顶点 4。因此青蛙在 2 秒后位于顶点 4 的概率是 1/3 * 1/2 = 1/6 = 0.16666666666666666。
示例 2:
输入: n = 7, edges = [[1,2],[1,3],[1,7],[2,4],[2,6],[3,5]], t = 1, target = 7
输出: 0.3333333333333333
解释: 青蛙从顶点 1 开始,第 1 秒后以 1/3 = 0.3333333333333333 的概率跳到顶点 7。
约束条件:
- 1 <= n <= 100
- edges.length == n - 1
- edges[i].length == 2
- 1 <= ai, bi <= n
- 1 <= t <= 50
- 1 <= target <= n
解题思路
这是一道概率动态规划问题,需要使用DFS来模拟青蛙的跳跃过程。
思路分析:
建图:首先根据edges数组构建邻接表表示的无向树。
DFS核心思想:使用深度优先搜索,参数包括当前顶点、剩余时间和到达当前顶点的概率。
关键判断:
- 如果当前时间正好等于t且当前顶点为target,返回概率
- 如果时间用完但不在target,返回0
- 如果当前顶点没有未访问的邻居,青蛙会停留,需要检查是否为target
概率计算:从当前顶点跳到每个未访问邻居的概率是 1/未访问邻居数量。
剪枝优化:
- 如果到达target的距离大于剩余时间,直接返回0
- 如果已经到达target但还有剩余时间,只有当target是叶节点时才能停留
算法步骤:
- 构建邻接表
- 从顶点1开始DFS,初始概率为1.0
- 对于每个顶点,计算其未访问的邻居数量
- 递归计算到达target的概率
代码实现
class Solution {
public:
double frogPosition(int n, vector<vector<int>>& edges, int t, int target) {
vector<vector<int>> graph(n + 1);
for (auto& edge : edges) {
graph[edge[0]].push_back(edge[1]);
graph[edge[1]].push_back(edge[0]);
}
vector<bool> visited(n + 1, false);
return dfs(graph, visited, 1, t, target, 1.0);
}
private:
double dfs(vector<vector<int>>& graph, vector<bool>& visited, int curr, int time, int target, double prob) {
visited[curr] = true;
int unvisited = 0;
for (int neighbor : graph[curr]) {
if (!visited[neighbor]) {
unvisited++;
}
}
if (time == 0) {
return curr == target ? prob : 0.0;
}
if (unvisited == 0) {
return curr == target ? prob : 0.0;
}
if (curr == target) {
return 0.0;
}
double result = 0.0;
for (int neighbor : graph[curr]) {
if (!visited[neighbor]) {
result += dfs(graph, visited, neighbor, time - 1, target, prob / unvisited);
}
}
visited[curr] = false;
return result;
}
};
class Solution:
def frogPosition(self, n: int, edges: List[List[int]], t: int, target: int) -> float:
from collections import defaultdict
graph = defaultdict(list)
for a, b in edges:
graph[a].append(b)
graph[b].append(a)
visited = [False] * (n + 1)
def dfs(curr, time, prob):
visited[curr] = True
unvisited_neighbors = [neighbor for neighbor in graph[curr] if not visited[neighbor]]
unvisited_count = len(unvisited_neighbors)
if time == 0:
visited[curr] = False
return prob if curr == target else 0.0
if unvisited_count == 0:
visited[curr] = False
return prob if curr == target else 0.0
if curr == target:
visited[curr] = False
return 0.0
result = 0.0
for neighbor in unvisited_neighbors:
result += dfs(neighbor, time - 1, prob / unvisited_count)
visited[curr] = False
return result
return dfs(1, t, 1.0)
public class Solution {
public double FrogPosition(int n, int[][] edges, int t, int target) {
List<int>[] graph = new List<int>[n + 1];
for (int i = 0; i <= n; i++) {
graph[i] = new List<int>();
}
foreach (var edge in edges) {
graph[edge[0]].Add(edge[1]);
graph[edge[1]].Add(edge[0]);
}
bool[] visited = new bool[n + 1];
return DFS(graph, visited, 1, t, target, 1.0);
}
private double DFS(List<int>[] graph, bool[] visited, int curr, int time, int target, double prob) {
visited[curr] = true;
int unvisited = 0;
foreach (int neighbor in graph[curr]) {
if (!visited[neighbor]) {
unvisited++;
}
}
if (time == 0) {
visited[curr] = false;
return curr == target ? prob : 0.0;
}
if (unvisited == 0) {
visited[curr] = false;
return curr == target ? prob : 0.0;
}
if (curr == target) {
visited[curr] = false;
return 0.0;
}
double result = 0.0;
foreach (int neighbor in graph[curr]) {
if (!visited[neighbor]) {
result += DFS(graph, visited, neighbor, time - 1, target, prob / unvisited);
}
}
visited[curr] = false;
return result;
}
}
var frogPosition = function(n, edges, t, target) {
const graph = Array(n + 1).fill(null).map(() => []);
for (const [a, b] of edges) {
graph[a].push(b);
graph[b].push(a);
}
const visited = new Set([1]);
const queue = [[1, 1.0, 0]]; // [vertex, probability, time]
while (queue.length > 0) {
const [vertex, prob, time] = queue.shift();
const unvisitedNeighbors = graph[vertex].filter(neighbor => !visited.has(neighbor));
if (vertex === target) {
if (time === t || unvisitedNeighbors.length === 0) {
return prob;
}
return 0;
}
if (time === t) continue;
if (unvisitedNeighbors.length === 0) {
if (vertex === target) return prob;
continue;
}
const nextProb = prob / unvisitedNeighbors.length;
for (const neighbor of unvisitedNeighbors) {
visited.add(neighbor);
queue.push([neighbor, nextProb, time + 1]);
}
}
return 0;
};
复杂度分析
| 复杂度类型 | 时间复杂度 | 空间复杂度 |
|---|---|---|
| 最优解法 | O(n) | O(n) |
说明:
- 时间复杂度: O(n),在最坏情况下需要遍历所有节点一次
- 空间复杂度: O(n),主要用于存储图的邻接表和递归调用栈
相关题目
- . Longest Special Path (Hard)