排名 203。最后一题属实陌生啦,只能想起知识点,完全想不起构造的方法啦。
思路:暴力枚举,哈希去重
时间复杂度: O ( n 3 ) \mathcal{O}(n^3) O(n3)
空间复杂度: O ( d 3 ) \mathcal{O}(d^3) O(d3), d d d 为 digits
的取值范围。
三层嵌套的 for
循环枚举组成数字的 d i g i t s i digits_i digitsi, d i g i t s j digits_j digitsj, d i g i t s k digits_k digitsk,枚举过程中需限制 i i i, j j j, k k k 互不相等,并可借助 unordered_map
对组合数字去重。
class Solution {
public:
vector<int> findEvenNumbers(vector<int>& ds) {
// 先对排序,保证构造出来的数字升序。
sort(ds.begin(), ds.end());
int n = ds.size();
// mark,去重用的哈希表
unordered_set<int> mark;
// anw 保存答案
vector<int> anw;
// 开始枚举
for (int i = 0; i < n; i++) {
// 去除前导零
if (ds[i] == 0) continue;
for (int j = 0; j < n; j++) {
if (i == j) continue;
for (int k = 0; k < n; k++) {
// 判断偶数
if (k == i || k == j || ds[k]%2 == 1) continue;
int val = ds[i]*100 + ds[j]*10 + ds[k];
// 去重
if (mark.insert(val).second) {
anw.emplace_back(val);
}
}
}
}
return anw;
}
};
思路:快慢指针,虚拟头节点
时间复杂度: O ( n ) O(n) O(n)
空间复杂度: O ( 1 ) O(1) O(1)
因为 head
也可能被删除,所以先定义一个 dummy
节点,其 next
指向 head
。
再定义两个指针:
ListNode *slow = &dummy;
ListNode *fast = head;
然后,slow
每次走一步,fast
每次走两步,这样当 fast
无法再走时,slow
恰巧指向待删除节点的前一个节点。
/**
* Definition for singly-linked list.
* 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:
ListNode* deleteMiddle(ListNode* head) {
ListNode dummy(0, head);
ListNode *fast = head, *slow = &dummy;
while(fast != nullptr && fast->next != nullptr) {
fast = fast->next->next;
slow = slow->next;
}
// 删除中间节点
slow->next = slow->next->next;
return dummy.next;
}
};
思路:深度优先遍历,删除公共前缀
时间复杂度: O ( n ) \mathcal{O}(n) O(n)
空间复杂度: O ( n ) \mathcal{O}(n) O(n)
首先,从根节点开始进行两次深度优先遍历,分别构造出:
root
到 start
的路径,记为 r2s
。root
到 dest
的路径,记为 r2d
。由于 start
和 dest
的最近公共祖先肯不是根节点,一次需找出 r2s
和 r2d
的最长公共前缀并删除。
记删除后的路径分别为 r2s'
和 r2d'
。那么最短路径可描述为:从 start
出发,先走 r2s'.size()
次 U
,此时到达了 start
和 dest
的最近公共祖先,然后再按 r2d'
行走即可到达 dest
。
/**
* Definition for a binary tree node.
* 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) {}
* };
*/
class Solution {
public:
// 找到 root -> goal 的路径,并存储在 path 中
// 特别的,为了便于实现,将路径逆序存储在 path 中
bool get(TreeNode *root, int goal, vector<char> &path) {
if (root == nullptr) {
return false;
}
if (root->val == goal) {
return true;
}
if (get(root->left, goal, path)) {
path.emplace_back('L');
return true;
} else if (get(root->right, goal, path)) {
path.emplace_back('R');
return true;
}
return false;
}
string getDirections(TreeNode* root, int startValue, int destValue) {
vector<char> r2s;
get(root, startValue, r2s);
reverse(r2s.begin(), r2s.end());
vector<char> r2d;
get(root, destValue, r2d);
reverse(r2d.begin(), r2d.end());
// 找出公共前缀的长度
int common = 0;
for (; common < r2d.size() && common < r2s.size() && r2d[common] == r2s[common]; common++) {
}
// 构造答案
return std::string(r2s.size()-common, 'U') + std::string(r2d.begin()+common, r2d.end());
}
};
思路:欧拉路
时间复杂度: O ( n ) \mathcal{O}(n) O(n)
空间复杂度: O ( n ) \mathcal{O}(n) O(n)
首先构图,将数字作为点,将数对作为边。那么问题转换为:找出一条路径,包含每条边一次且仅一次。这就是典型的欧拉路啦。不过太久没写了,比赛时死活没想起构造步骤,还是太弱了♂️
如果存在欧拉路,则所有点的出度和入度满足下列限制之一:
因为题目保证答案必然存在,所以可认为构造的图必然满足上述限制。
于是,可先找到点 v v v 作为起点,如果不存在 v v v 则说明存在欧拉回路,则可任选一点作为起点。不妨设起点为 s s s。
设有一维数组 p a t h path path 用以记录欧拉路。从 s s s 出发开始深度优先遍历,每经过一条边就将其删除。在遍历过程中,如果点 t t t 没有出边了,则将其追加至 p a t h path path 中。在遍历结束后, p a t h path path 中保存的即为反向的欧拉路。
因此,在遍历结束后,可逆序遍历 p a t h path path,构造出答案。详细实现可见注释。
class Solution {
public:
void dfs(int root, vector<int> &path, unordered_map<int, vector<int>> &edges) {
// 遍历到了 root 点。
auto &edge = edges[root];
// 依次深度遍历 root 的出边指向的点
while (!edge.empty()) {
// 为了借助 vector::pop_back() 实现删除,这里倒着遍历
// 先取出 edge.back()
auto e = edge.back();
// 删除这条边
edge.pop_back();
// 开始深度优先遍历 edge.back()
dfs(e, path, edges);
}
// 执行到这时,root 的出边必然都删除了,因此将其追加至 path
path.push_back(root);
}
vector<vector<int>> validArrangement(vector<vector<int>>& pairs) {
// 边表
unordered_map<int, vector<int>> edges;
// 记录每个数字的出入度
unordered_map<int, int> in, out;
for (const auto &pair : pairs) {
// 将 pair 作为有向边, pair[0] → pair[1]
edges[pair[0]].push_back(pair[1]);
// 更新出入度
in[pair[1]]++;
out[pair[0]]++;
}
// 选择起始点,先随机选择一个
int start = in.begin()->first;
for(const auto &p : out) {
// 找到了出度 - 入度 = 1 的点,则此点必须为起点
if (p.second - in[p.first] == 1) {
start = p.first;
break;
}
}
// path 保存逆序的欧拉路
std::vector<int> path;
// 开始深度优先遍历
dfs(start, path, edges);
vector<vector<int>> anw;
// 构造答案,path 中相邻的两个点,必然对应一个数对
for (int i = path.size()-1; i >= 1; i--) {
anw.emplace_back(vector<int>{
path[i], path[i-1]});
}
return anw;
}
};