Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,27 @@ edit_url: https://github.com/doocs/leetcode/edit/main/solution/3700-3799/3778.Mi

<!-- solution:start -->

### 方法一
### 方法一:Dijkstra 算法

题目实际上等价于从节点 $0$ 到节点 $n-1$ 寻找一条路径,可以有一次机会将经过的某条边的权重视为 $0$,使得路径权重和最小。

我们首先将 $\textit{edges}$ 转化为邻接表 $\textit{g}$,其中 $\textit{g}[u]$ 存储所有与节点 $u$ 相连的边 $(v, w)$,表示节点 $u$ 与节点 $v$ 之间有一条权重为 $w$ 的边。

接下来,我们使用 Dijkstra 算法来寻找最短路径。我们定义一个二维数组 $\textit{dist}$,其中 $\textit{dist}[u][0]$ 表示从节点 $0$ 到节点 $u$ 的路径权重和的最小值,且没有使用将某条边权重视为 $0$ 的机会;$\textit{dist}[u][1]$ 表示从节点 $0$ 到节点 $u$ 的路径权重和的最小值,且已经使用了将某条边权重视为 $0$ 的机会。

我们使用一个优先队列 $\textit{pq}$ 来存储待处理的节点,初始时将 $(0, 0, 0)$ 入队,表示从节点 $0$ 出发,当前路径权重和为 $0$,且没有使用机会。

在每次迭代中,我们从优先队列中取出路径权重和最小的节点 $(\textit{cur}, u, \textit{used})$。如果当前路径权重和 $\textit{cur}$ 大于 $\textit{dist}[u][\textit{used}]$,则跳过该节点。

如果当前节点 $u$ 是节点 $n-1$ 且已经使用了机会 $\textit{used} = 1$,则返回当前路径权重和 $\textit{cur}$。

对于节点 $u$ 的每一条边 $(v, w)$,我们计算不使用机会的情况下到达节点 $v$ 的路径权重和 $\textit{nxt} = \textit{cur} + w$。如果 $\textit{nxt} < \textit{dist}[v][\textit{used}]$,则更新 $\textit{dist}[v][\textit{used}]$ 并将 $(\textit{nxt}, v, \textit{used})$ 入队。

如果当前还没有使用机会 $\textit{used} = 0$,则计算使用机会的情况下到达节点 $v$ 的路径权重和 $\textit{nxt} = \textit{cur}$。如果 $\textit{nxt} < \textit{dist}[v][1]$,则更新 $\textit{dist}[v][1]$ 并将 $(\textit{nxt}, v, 1)$ 入队。

遍历结束后,返回 $\textit{dist}[n-1][1]$ 即为答案。

时间复杂度 $O(m \times \log n)$,空间复杂度 $O(n + m)$,其中 $n$ 和 $m$ 分别是节点数和边数。

<!-- tabs:start -->

Expand Down Expand Up @@ -360,6 +380,66 @@ function minCostExcludingMax(n: number, edges: number[][]): number {
}
```

#### Rust

```rust
use std::cmp::Reverse;
use std::collections::BinaryHeap;

impl Solution {
pub fn min_cost_excluding_max(n: i32, edges: Vec<Vec<i32>>) -> i64 {
let n = n as usize;

let mut g: Vec<Vec<(usize, i64)>> = vec![Vec::new(); n];
for e in edges {
let u = e[0] as usize;
let v = e[1] as usize;
let w = e[2] as i64;
g[u].push((v, w));
g[v].push((u, w));
}

let inf: i64 = i64::MAX / 4;
let mut dist = vec![[inf; 2]; n];
dist[0][0] = 0;

// (cur_cost, node, used)
let mut pq = BinaryHeap::new();
pq.push(Reverse((0_i64, 0_usize, 0_usize)));

while let Some(Reverse((cur, u, used))) = pq.pop() {
if cur > dist[u][used] {
continue;
}

if u == n - 1 && used == 1 {
return cur;
}

for &(v, w) in &g[u] {
// normal edge
let nxt = cur + w;
if nxt < dist[v][used] {
dist[v][used] = nxt;
pq.push(Reverse((nxt, v, used)));
}

// skip max edge (only once)
if used == 0 {
let nxt = cur;
if nxt < dist[v][1] {
dist[v][1] = nxt;
pq.push(Reverse((nxt, v, 1)));
}
}
}
}

dist[n - 1][1]
}
}
```

<!-- tabs:end -->

<!-- solution:end -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,27 @@ edit_url: https://github.com/doocs/leetcode/edit/main/solution/3700-3799/3778.Mi

<!-- solution:start -->

### Solution 1
### Solution 1: Dijkstra's Algorithm

The problem is essentially equivalent to finding a path from node $0$ to node $n-1$, where we have one opportunity to treat the weight of a traversed edge as $0$, in order to minimize the sum of path weights.

We first convert $\textit{edges}$ into an adjacency list $\textit{g}$, where $\textit{g}[u]$ stores all edges $(v, w)$ connected to node $u$, indicating that there is an edge with weight $w$ between node $u$ and node $v$.

Next, we use Dijkstra's algorithm to find the shortest path. We define a 2D array $\textit{dist}$, where $\textit{dist}[u][0]$ represents the minimum sum of path weights from node $0$ to node $u$ without using the opportunity to treat an edge weight as $0$; $\textit{dist}[u][1]$ represents the minimum sum of path weights from node $0$ to node $u$ having already used the opportunity to treat an edge weight as $0$.

We use a priority queue $\textit{pq}$ to store pending nodes. Initially, we enqueue $(0, 0, 0)$, indicating that we start from node $0$, with a current path weight sum of $0$, and haven't used the opportunity.

In each iteration, we dequeue the node $(\textit{cur}, u, \textit{used})$ with the minimum path weight sum from the priority queue. If the current path weight sum $\textit{cur}$ is greater than $\textit{dist}[u][\textit{used}]$, we skip this node.

If the current node $u$ is node $n-1$ and we have already used the opportunity $\textit{used} = 1$, we return the current path weight sum $\textit{cur}$.

For each edge $(v, w)$ of node $u$, we calculate the path weight sum to reach node $v$ without using the opportunity: $\textit{nxt} = \textit{cur} + w$. If $\textit{nxt} < \textit{dist}[v][\textit{used}]$, we update $\textit{dist}[v][\textit{used}]$ and enqueue $(\textit{nxt}, v, \textit{used})$.

If we haven't used the opportunity yet $\textit{used} = 0$, we calculate the path weight sum to reach node $v$ when using the opportunity: $\textit{nxt} = \textit{cur}$. If $\textit{nxt} < \textit{dist}[v][1]$, we update $\textit{dist}[v][1]$ and enqueue $(\textit{nxt}, v, 1)$.

After the traversal ends, we return $\textit{dist}[n-1][1]$ as the answer.

The time complexity is $O(m \times \log n)$, and the space complexity is $O(n + m)$, where $n$ and $m$ are the number of nodes and edges, respectively.

<!-- tabs:start -->

Expand Down Expand Up @@ -358,6 +378,66 @@ function minCostExcludingMax(n: number, edges: number[][]): number {
}
```

#### Rust

```rust
use std::cmp::Reverse;
use std::collections::BinaryHeap;

impl Solution {
pub fn min_cost_excluding_max(n: i32, edges: Vec<Vec<i32>>) -> i64 {
let n = n as usize;

let mut g: Vec<Vec<(usize, i64)>> = vec![Vec::new(); n];
for e in edges {
let u = e[0] as usize;
let v = e[1] as usize;
let w = e[2] as i64;
g[u].push((v, w));
g[v].push((u, w));
}

let inf: i64 = i64::MAX / 4;
let mut dist = vec![[inf; 2]; n];
dist[0][0] = 0;

// (cur_cost, node, used)
let mut pq = BinaryHeap::new();
pq.push(Reverse((0_i64, 0_usize, 0_usize)));

while let Some(Reverse((cur, u, used))) = pq.pop() {
if cur > dist[u][used] {
continue;
}

if u == n - 1 && used == 1 {
return cur;
}

for &(v, w) in &g[u] {
// normal edge
let nxt = cur + w;
if nxt < dist[v][used] {
dist[v][used] = nxt;
pq.push(Reverse((nxt, v, used)));
}

// skip max edge (only once)
if used == 0 {
let nxt = cur;
if nxt < dist[v][1] {
dist[v][1] = nxt;
pq.push(Reverse((nxt, v, 1)));
}
}
}
}

dist[n - 1][1]
}
}
```

<!-- tabs:end -->

<!-- solution:end -->
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
use std::cmp::Reverse;
use std::collections::BinaryHeap;

impl Solution {
pub fn min_cost_excluding_max(n: i32, edges: Vec<Vec<i32>>) -> i64 {
let n = n as usize;

let mut g: Vec<Vec<(usize, i64)>> = vec![Vec::new(); n];
for e in edges {
let u = e[0] as usize;
let v = e[1] as usize;
let w = e[2] as i64;
g[u].push((v, w));
g[v].push((u, w));
}

let inf: i64 = i64::MAX / 4;
let mut dist = vec![[inf; 2]; n];
dist[0][0] = 0;

// (cur_cost, node, used)
let mut pq = BinaryHeap::new();
pq.push(Reverse((0_i64, 0_usize, 0_usize)));

while let Some(Reverse((cur, u, used))) = pq.pop() {
if cur > dist[u][used] {
continue;
}

if u == n - 1 && used == 1 {
return cur;
}

for &(v, w) in &g[u] {
// normal edge
let nxt = cur + w;
if nxt < dist[v][used] {
dist[v][used] = nxt;
pq.push(Reverse((nxt, v, used)));
}

// skip max edge (only once)
if used == 0 {
let nxt = cur;
if nxt < dist[v][1] {
dist[v][1] = nxt;
pq.push(Reverse((nxt, v, 1)));
}
}
}
}

dist[n - 1][1]
}
}