从相邻元素对还原数组 图解题解
这道题到底在问什么
- 输入
- adjacentPairs = [[2,1],[3,4],[3,2]]
- 输出
- [1,2,3,4] (相邻关系:1-2、2-3、3-4)
- 输入
- adjacentPairs = [[4,-2],[1,4],[-3,1]]
- 输出
- [-3,1,4,-2] (含负数,反过来 [-2,4,1,-3] 也算对)
最优解:一步一步想明白
- 3记牢三件事:相邻对就是边、度为 1 的是端点、走链时跳过来时的邻居。下面一帧帧套它。
- 4先把出现过的 5 个数摆成 5 个点:15、40、27、33、58。此刻它们之间一条边都没有,右边的邻接表也是空的。接下来把 adjacentPairs 里的每一对,都连成一条无向边。
- 5看第 0 对相邻元素 [27, 40],它说明 27 和 40 在原数组里紧挨着。先把这两个点点亮,准备在它们之间连一条无向边。
- 6连上 27 和 40 这条边。因为是无向的,两边都要记:27 的邻居里加上 40、40 的邻居里加上 27。看右边面板,27 现在的度是 1、40 的度是 1。继续下一对。
- 7看第 1 对相邻元素 [33, 58],它说明 33 和 58 在原数组里紧挨着。先把这两个点点亮,准备在它们之间连一条无向边。
- 8连上 33 和 58 这条边。因为是无向的,两边都要记:33 的邻居里加上 58、58 的邻居里加上 33。看右边面板,33 现在的度是 1、58 的度是 1。继续下一对。
- 9看第 2 对相邻元素 [15, 40],它说明 15 和 40 在原数组里紧挨着。先把这两个点点亮,准备在它们之间连一条无向边。
- 10连上 15 和 40 这条边。因为是无向的,两边都要记:15 的邻居里加上 40、40 的邻居里加上 15。看右边面板,15 现在的度是 1、40 的度是 2。继续下一对。
- 11看第 3 对相邻元素 [27, 33],它说明 27 和 33 在原数组里紧挨着。先把这两个点点亮,准备在它们之间连一条无向边。
- 12连上 27 和 33 这条边。因为是无向的,两边都要记:27 的邻居里加上 33、33 的邻居里加上 27。看右边面板,27 现在的度是 2、33 的度是 2。四条边全连完,图就建好了。
- 13图建好了,扫一遍每个点的度。度为 2 的 40、27、33 都是被夹在中间的元素,而度为 1 的只有 15 和 58 这两个点,它们各自只有一个邻居,正是这条链的两个端头。
- 14从哪一端起步都能得到合法答案。参考解统一从较小的那个端点出发,也就是 15。把它标成起点,接下来沿着边一个点一个点往前走,同时把走过的值填进还原数组。
- 15从起点 15 看起。它是端点,邻居只有 40 一个,方向没有悬念,下一步就走向 40。
- 16把起点 15 填进还原数组的第 0 位。目前 nums 是 [15]。
- 17走到 40。它有两个邻居:27 和 15。其中 15 是刚才来的那个,跳过它;另一个 27 就是要去的下一站,把这条边点亮。
- 18把 40 接到还原数组第 1 位。灰色的问号是还没填的位置,继续往后走。
- 19走到 27。它有两个邻居:40 和 33。其中 40 是刚才来的那个,跳过它;另一个 33 就是要去的下一站,把这条边点亮。
- 20把 27 接到还原数组第 2 位。灰色的问号是还没填的位置,继续往后走。
- 21走到 33。它有两个邻居:58 和 27。其中 27 是刚才来的那个,跳过它;另一个 58 就是要去的下一站,把这条边点亮。
- 22把 33 接到还原数组第 3 位。灰色的问号是还没填的位置,继续往后走。
- 23走到 58。它是另一个端点,唯一的邻居 33 正是上一步来的地方,没有新的可走,链到此为止,全部还原完成。
- 24把 58 接到还原数组第 4 位。它是链尾,数组填满了。
- 25整条链走完,还原数组是 [15, 40, 27, 33, 58]。你可以核对一下:相邻的 15-40、40-27、27-33、33-58 恰好就是题目给的四对,一个不多一个不少,还原正确。反着写成 [58, 33, 27, 40, 15] 也是合法答案。
⚠️ 容易写错的地方
✗ 错:想着给相邻对排序、拼接来还原
✓ 对:把相邻对当无向边建图
相邻对顺序被打乱、左右也不定,直接排序拼不出链;建成图后「链」的结构一目了然
✗ 错:从任意点或中间点开始走
✓ 对:必须从度为 1 的端点出发
中间点有两个邻居、两个方向都能走,无法一次定序;端点只有一个方向,能一次走通整条链
✗ 错:走中间点时忘了记来时的邻居
✓ 对:带着 prev,每步跳过 prev 取另一个邻居
中间点有两个邻居,不排除 prev 就会原地折返、走不出去
✗ 错:用数组下标当图的键
✓ 对:用哈希表建图
值可能是负数、可到正负十万,拿值当下标会越界,必须用哈希表映射邻居
完整代码(Python / C++ / Java)
Python
from __future__ import annotations
from typing import *
from collections import *
from functools import *
from itertools import *
from math import *
from heapq import *
from bisect import *
from string import *
from operator import *
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
class Solution:
def restoreArray(self, adjacentPairs: List[List[int]]) -> List[int]:
g = defaultdict(list)
for a, b in adjacentPairs:
g[a].append(b)
g[b].append(a)
start = min(x for x, ns in g.items() if len(ns) == 1)
ans, prev, cur = [], None, start
while cur is not None:
ans.append(cur)
nxt = None
for v in g[cur]:
if v != prev:
nxt = v
break
prev, cur = cur, nxt
return ansC++
#include <algorithm>
#include <array>
#include <cctype>
#include <climits>
#include <cmath>
#include <deque>
#include <functional>
#include <iostream>
#include <limits>
#include <map>
#include <numeric>
#include <optional>
#include <queue>
#include <set>
#include <sstream>
#include <stack>
#include <string>
#include <tuple>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
using namespace std;
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode() : val(0), left(nullptr), right(nullptr) {}
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
};
struct ListNode {
int val;
ListNode *next;
ListNode() : val(0), next(nullptr) {}
ListNode(int x) : val(x), next(nullptr) {}
ListNode(int x, ListNode *next) : val(x), next(next) {}
};
class Solution {
public:
vector<int> restoreArray(vector<vector<int>>& adjacentPairs) {
unordered_map<int, vector<int>> g;
for (auto& e : adjacentPairs) {
g[e[0]].push_back(e[1]);
g[e[1]].push_back(e[0]);
}
int start = INT_MAX;
for (auto& [x, ns] : g) if (ns.size() == 1) start = min(start, x);
vector<int> ans;
int prev = INT_MIN, cur = start;
while (true) {
ans.push_back(cur);
int nxt = INT_MIN;
for (int v : g[cur]) if (v != prev) { nxt = v; break; }
if (nxt == INT_MIN) break;
prev = cur;
cur = nxt;
}
return ans;
}
};Java
import java.util.*;
class TreeNode { int val; TreeNode left; TreeNode right; TreeNode(){} TreeNode(int val){this.val=val;} TreeNode(int val, TreeNode left, TreeNode right){this.val=val;this.left=left;this.right=right;} }
class ListNode { int val; ListNode next; ListNode(){} ListNode(int val){this.val=val;} ListNode(int val, ListNode next){this.val=val;this.next=next;} }
class Solution {
public int[] restoreArray(int[][] adjacentPairs) {
Map<Integer, List<Integer>> g = new HashMap<>();
for (int[] e : adjacentPairs) {
g.computeIfAbsent(e[0], k -> new ArrayList<>()).add(e[1]);
g.computeIfAbsent(e[1], k -> new ArrayList<>()).add(e[0]);
}
int start = Integer.MAX_VALUE;
for (Map.Entry<Integer, List<Integer>> e : g.entrySet()) if (e.getValue().size() == 1) start = Math.min(start, e.getKey());
int[] ans = new int[g.size()];
int prev = Integer.MIN_VALUE, cur = start;
for (int i = 0; i < ans.length; i++) {
ans[i] = cur;
int next = Integer.MIN_VALUE;
for (int v : g.get(cur)) if (v != prev) { next = v; break; }
prev = cur;
cur = next;
}
return ans;
}
}复杂度
时间
O(n)
建图遍历 n 减 1 个相邻对是 O(n);扫一遍找度为 1 的端点是 O(n);沿链走每个点恰好访问一次也是 O(n),合起来线性
空间
O(n)
邻接表要为每个值存它的邻居,总条目数是边数的两倍即 2 乘 n 减 1,峰值与 n 同阶;输出数组也是 O(n)
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 从相邻元素对还原数组 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
为什么两个端点的度数一定是 1,中间元素一定是 2?+
因为原数组是线性排列。最左边的元素右边有一个邻居、左边没有,最右边的元素同理,所以首尾各只贡献一条边、度为 1。中间的每个元素左右各有一个邻居,度为 2。这正是能用「度为 1」精准定位两端的原因。
能不能不真的建图?+
可以换个说法但本质一样。你可以用一个哈希表直接记每个值的相邻值集合,这其实就是邻接表;找出只出现一个相邻值的两个键当端点,再顺着相邻关系走。所谓建图,落地就是这张「值到邻居」的哈希表,绕不开它。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄声线把 从相邻元素对还原数组 一步步讲透(全站 0 节语音精讲,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。