Skip to content

Commit ff0bb30

Browse files
committed
✨feat: add 1129、1669、1798、1976、393、396
1 parent ec686c5 commit ff0bb30

6 files changed

+415
-13
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
### 题目描述
2+
3+
这是 LeetCode 上的 **[1129. 颜色交替的最短路径]()** ,难度为 **中等**
4+
5+
Tag : 「BFS」、「最短路」
6+
7+
8+
9+
在一个有向图中,节点分别标记为 `0, 1, ..., n-1`。图中每条边为红色或者蓝色,且存在自环或平行边。
10+
11+
`red_edges` 中的每一个 `[i, j]` 对表示从节点 `i` 到节点 `j` 的红色有向边。类似地,`blue_edges` 中的每一个 `[i, j]` 对表示从节点 `i` 到节点 `j` 的蓝色有向边。
12+
13+
返回长度为 `n` 的数组 `answer`,其中 `answer[X]` 是从节点 `0` 到节点 `X` 的红色边和蓝色边交替出现的最短路径的长度。如果不存在这样的路径,那么 `answer[x] = -1`
14+
15+
示例 1:
16+
```
17+
输入:n = 3, red_edges = [[0,1],[1,2]], blue_edges = []
18+
19+
输出:[0,1,-1]
20+
```
21+
示例 2:
22+
```
23+
输入:n = 3, red_edges = [[0,1]], blue_edges = [[2,1]]
24+
25+
输出:[0,1,-1]
26+
```
27+
示例 3:
28+
```
29+
输入:n = 3, red_edges = [[1,0]], blue_edges = [[2,1]]
30+
31+
输出:[0,-1,-1]
32+
```
33+
示例 4:
34+
```
35+
输入:n = 3, red_edges = [[0,1]], blue_edges = [[1,2]]
36+
37+
输出:[0,1,2]
38+
```
39+
示例 5:
40+
```
41+
输入:n = 3, red_edges = [[0,1],[0,2]], blue_edges = [[1,0]]
42+
43+
输出:[0,1,1]
44+
```
45+
46+
提示:
47+
* `1 <= n <= 100`
48+
* `red_edges.length <= 400`
49+
* `blue_edges.length <= 400`
50+
* `red_edges[i].length = blue_edges[i].length = 2`
51+
* `0 <= red_edges[i][j], blue_edges[i][j] < n`
52+
53+
---
54+
55+
### 朴素 BFS
56+
57+
为了方便,将 `redEdges` 记为 `rs`,将 `blueEdges` 记为 `bs`
58+
59+
使用两数组进行建图,利用点数和边数关系(稀疏图),使用「邻接表」方式进行建图。
60+
61+
将所有红色有向边权值记为 `1`,将所有蓝色有向边权值记为 `-1`。注意这里的权重仅表示该边的颜色,并非代表经过该边的真实成本。
62+
63+
也正是经过所有边的成本均相同,对于原问题「从 `0` 号节点进行出发,求到所有点的颜色交替的最短路径」,我们容易想到使用 `BFS` 进行求解。
64+
65+
`BFS` 过程中将三元组 $(point, last, step)$ 进行入队:
66+
67+
* `point` 代表当前所在点编号
68+
* `last` 代表到达 `point` 所使用的边的颜色,默认为 `0` 代表不需要经过任何边到达起点
69+
* `step` 代表到达 `point` 所使用的步数
70+
71+
利用数据范围不大(点数为 $100$,边数为 $800$),可以直接对每个点进行一次独立的 `BFS` 来求最短路。由于图存在重边和自环,因此在求解最短路的过程需要对边进行去重。
72+
73+
代码:
74+
```Java
75+
class Solution {
76+
static int N = 110, M = 810, idx = 0;
77+
static int[] he = new int[N], e = new int[M], ne = new int[M], w = new int[M];
78+
void add(int a, int b, int c) {
79+
e[idx] = b;
80+
w[idx] = c;
81+
ne[idx] = he[a];
82+
he[a] = idx++;
83+
}
84+
public int[] shortestAlternatingPaths(int n, int[][] rs, int[][] bs) {
85+
idx = 0;
86+
Arrays.fill(he, -1);
87+
for (int[] e : rs) add(e[0], e[1], 1);
88+
for (int[] e : bs) add(e[0], e[1], -1);
89+
int[] ans = new int[n];
90+
boolean[] vis = new boolean[rs.length + bs.length];
91+
out:for (int k = 1; k < n; k++) {
92+
ans[k] = -1;
93+
Arrays.fill(vis, false);
94+
Deque<int[]> d = new ArrayDeque<>();
95+
d.addLast(new int[]{0, 0, 0}); // point, last, step
96+
while (!d.isEmpty()) {
97+
int[] info = d.pollFirst();
98+
int p = info[0], last = info[1], step = info[2];
99+
for (int i = he[p]; i != -1; i = ne[i]) {
100+
int j = e[i], c = w[i];
101+
if (vis[i]) continue;
102+
if (c + last == 0 || last == 0) {
103+
if (j == k) {
104+
ans[k] = step + 1;
105+
continue out;
106+
} else {
107+
d.addLast(new int[]{j, c, step + 1});
108+
vis[i] = true;
109+
}
110+
}
111+
}
112+
}
113+
}
114+
return ans;
115+
}
116+
}
117+
```
118+
* 时间复杂度:将点数记为 `n` ,边数记为 `m`。建图复杂度为 $O(m)$;构造答案复杂度为 $O(n \times (n + m))$。整体复杂度为 $O(n \times (n + m))$
119+
* 空间复杂度:$O(n + m)$
120+
121+
---
122+
123+
### 优化
124+
125+
实际上,我们并没有对每个点进行独立 `BFS` 的必要。
126+
127+
为了获取所有从节点 `0` 出发的最短路,可直接从节点 `0` 进行出发(对边进行去重),所有能够从节点 `0` 沿交替路径到达的节点必然都会被访问到,且节点首次被访问到时必然是最短路径。
128+
129+
因此我们只需要初始化所有的 `ans[i] = -1`,并从节点 `0` 开始进行一次 `BFS` 即可。
130+
131+
代码:
132+
```Java
133+
class Solution {
134+
static int N = 110, M = 810, idx = 0;
135+
static int[] he = new int[N], e = new int[M], ne = new int[M], w = new int[M];
136+
void add(int a, int b, int c) {
137+
e[idx] = b;
138+
w[idx] = c;
139+
ne[idx] = he[a];
140+
he[a] = idx++;
141+
}
142+
public int[] shortestAlternatingPaths(int n, int[][] rs, int[][] bs) {
143+
idx = 0;
144+
Arrays.fill(he, -1);
145+
for (int[] e : rs) add(e[0], e[1], 1);
146+
for (int[] e : bs) add(e[0], e[1], -1);
147+
int[] ans = new int[n];
148+
Arrays.fill(ans, -1);
149+
ans[0] = 0;
150+
boolean[] vis = new boolean[rs.length + bs.length];
151+
Arrays.fill(vis, false);
152+
Deque<int[]> d = new ArrayDeque<>();
153+
d.addLast(new int[]{0, 0, 0}); // point, last, step
154+
while (!d.isEmpty()) {
155+
int[] info = d.pollFirst();
156+
int p = info[0], last = info[1], step = info[2];
157+
for (int i = he[p]; i != -1; i = ne[i]) {
158+
if (vis[i]) continue;
159+
int j = e[i], c = w[i];
160+
if (c + last == 0 || last == 0) {
161+
ans[j] = ans[j] == -1 ? step + 1 : Math.min(ans[j], step + 1);
162+
d.addLast(new int[]{j, c, step + 1});
163+
vis[i] = true;
164+
}
165+
}
166+
}
167+
return ans;
168+
}
169+
}
170+
```
171+
* 时间复杂度:将点数记为 `n` ,边数记为 `m`。建图复杂度为 $O(m)$;构造答案复杂度为 $O(n + m)$。整体复杂度为 $O(n + m)$
172+
* 空间复杂度:$O(n + m)$
173+
174+
---
175+
176+
### 最后
177+
178+
这是我们「刷穿 LeetCode」系列文章的第 `No.1224` 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。
179+
180+
在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。
181+
182+
为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode
183+
184+
在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。
185+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
### 题目描述
2+
3+
这是 LeetCode 上的 **[1669. 合并两个链表](/)** ,难度为 **中等**
4+
5+
Tag : 「链表」、「模拟」
6+
7+
8+
9+
10+
给你两个链表 `list1` 和 `list2`,它们包含的元素分别为 `n` 个和 `m` 个。
11+
12+
请你将 `list1` 中下标从 `a``b` 的全部节点都删除,并将 `list2` 接在被删除节点的位置。
13+
14+
下图中蓝色边和节点展示了操作后的结果:
15+
16+
![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/11/28/fig1.png)
17+
18+
请你返回结果链表的头指针。
19+
20+
21+
22+
示例 1:
23+
![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/11/28/merge_linked_list_ex1.png)
24+
25+
```
26+
输入:list1 = [0,1,2,3,4,5], a = 3, b = 4, list2 = [1000000,1000001,1000002]
27+
28+
输出:[0,1,2,1000000,1000001,1000002,5]
29+
30+
解释:我们删除 list1 中下标为 3 和 4 的两个节点,并将 list2 接在该位置。上图中蓝色的边和节点为答案链表。
31+
```
32+
示例 2:
33+
![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/11/28/merge_linked_list_ex2.png)
34+
```
35+
输入:list1 = [0,1,2,3,4,5,6], a = 2, b = 5, list2 = [1000000,1000001,1000002,1000003,1000004]
36+
37+
输出:[0,1,1000000,1000001,1000002,1000003,1000004,6]
38+
39+
解释:上图中蓝色的边和节点为答案链表。
40+
```
41+
42+
提示:
43+
* $3 <= list1.length <= 10^4$
44+
* $1 <= a <= b < list1.length - 1$
45+
* $1 <= list2.length <= 10^4$
46+
47+
---
48+
49+
### 模拟
50+
51+
根据题意进行模拟即可。
52+
53+
使用两个变量 `A``B` 分别指向 `list1` 中两个断联的位置,分别将 `A` 指向 `list2` 的开头,将 `list2``next` 指针指向 `B`
54+
55+
56+
Java 代码:
57+
```Java
58+
class Solution {
59+
public ListNode mergeInBetween(ListNode list1, int a, int b, ListNode list2) {
60+
ListNode ans = list1;
61+
ListNode A = null, B = null;
62+
while (--a > 0 && --b > 0) list1 = list1.next;
63+
A = list1;
64+
while (b-- > 0) list1 = list1.next;
65+
B = list1;
66+
A.next = list2;
67+
while (list2.next != null) list2 = list2.next;
68+
list2.next = B.next;
69+
return ans;
70+
}
71+
}
72+
```
73+
TypeScript 代码:
74+
```TypeScript
75+
function mergeInBetween(list1: ListNode | null, a: number, b: number, list2: ListNode | null): ListNode | null {
76+
const ans = list1
77+
let A = null, B = null
78+
while (--a > 0 && --b > 0) list1 = list1.next
79+
A = list1
80+
while (b-- > 0) list1 = list1.next
81+
B = list1
82+
A.next = list2
83+
while (list2.next != null) list2 = list2.next
84+
list2.next = B.next
85+
return ans
86+
}
87+
```
88+
* 时间复杂度:$O(n + m)$
89+
* 空间复杂度:$O(1)$
90+
91+
---
92+
93+
### 最后
94+
95+
这是我们「刷穿 LeetCode」系列文章的第 `No.1699` 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。
96+
97+
在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。
98+
99+
为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode
100+
101+
在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。
102+

0 commit comments

Comments
 (0)