面试题 02.04. 分割链表
编写程序以 x 为基准分割链表,使得所有小于 x 的节点排在大于或等于 x 的节点之前。如果链表中包含 x,x 只需出现在小于 x 的元素之后(如下所示)。分割元素 x 只需处于“右半部分”即可,其不需要被置于左右两部分之间。
翻译:将小于x的数移到大于或者等于x的数之前
题解
先找到第一个大于或者等于x的节点(pos),然后遍历之后的节点,发现小于x的节点就移到(pos)之前。考察的操作:链表节点的删除和插入。
实际上直接插在头节点之前也行。整道题看起是不是很像快排的(partion)操作,说明能用链表实现快排 ><
class Solution {
public:
ListNode* partition(ListNode* head, int x) {
if (!head || !head->next) return head;
ListNode* root = new ListNode(-1);
root->next = head;
head = root;
while(head->next != NULL) {
if ((*(head->next)).val >= x) break;
head = head->next;
}
ListNode* start = head, *pre = head;
head = head->next; // 上面的while循环得到的是第一个大于或者等于x的节点 之前的节点
while(head != NULL) {
if ((*head).val < x) {
pre->next = head->next;
head->next = start->next;
start->next = head;
head = pre->next;
}
else {
pre = head;
head = head->next;
}
}
return root->next;
}
};
1367. 二叉树中的列表
给你一棵以 root 为根的二叉树和一个 head 为第一个节点的链表。
如果在二叉树中,存在一条一直向下的路径,且每个点的数值恰好一一对应以 head 为首的链表中每个节点的值,那么请你返回 True ,否则返回 False 。
一直向下的路径的意思是:从树中某个节点开始,一直连续向下的路径
题解
思路比较明显:枚举节点,然后判断以这个节点开头是否能和给的链表匹配成功。(真的暴力)
md,复杂度计算错了。尽管在一次匹配过程中可能要遍历(2^{len})个节点,但实际的树只有(n)个节点,所以时间复杂度(O(n*min(2^{len}, n)))。另外,我都不会写带返回值的DFS了,难受~~~
/*
不太喜欢这样的题,不过官方的code真的很nice,学习学习(https://leetcode-cn.com/problems/linked-list-in-binary-tree/solution/er-cha-shu-zhong-de-lie-biao-by-leetcode-solution/)
*/
class Solution {
public:
bool DFS(ListNode* head, TreeNode* root) {
if (head == NULL) return true;
if (root == NULL) return false;
if ((*head).val != (*root).val) return false;
return DFS(head->next, root->left) || DFS(head->next, root->right);
}
bool isSubPath(ListNode* head, TreeNode* root) {
if (head == NULL) return true;
if (root == NULL) return false;
return DFS(head, root) || isSubPath(head, root->left) || isSubPath(head, root->right);
}
};
面试题35. 复杂链表的复制(好题)
请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null。
题解
在每个节点的后面插入一个新的节点,更新random
指针后,取出奇数节点,就是复制后的链表。
想到用map实现也算是妙啊
class Solution {
public:
Node* copyRandomList(Node* head) {
if (head == NULL) return head;
map<Node*, Node*> mp; // 知道 map 和 unordered_map 的区别吗
Node* start = head;
while(start != NULL) {
mp[start] = new Node(start->val);
start = start->next;
}
start = head;
while(start != NULL) {
mp[start]->next = mp[start->next];
mp[start]->random = mp[start->random];
start = start->next;
}
return mp[head];
}
};
1171. 从链表中删去总和值为零的连续节点
给你一个链表的头节点 head,请你编写代码,反复删去链表中由 总和 值为 0 的连续节点组成的序列,直到不存在这样的序列为止。
删除完毕后,请你返回最终结果链表的头节点。
你可以返回任何满足题目要求的答案。
题解
常规思路是枚举区间,时间复杂度(O(n^2)).
连续区间的和可由前缀和相减得到,所以只需要判断当前位置的前缀和是否在该位置以前出现过,出现,则说明这两个位置之间的区间和为0。因为是链表,故用map存一下拥有相同前缀和的节点。时间复杂度大概是(O(nlog_2(n)))
加一个限制条件:最终序列的长度要最短。怎么破?
class Solution {
public:
ListNode* removeZeroSumSublists(ListNode* head) {
ListNode* root = new ListNode(-1);
root->next = head;
ListNode* h = root;
map<int, ListNode*> mp;
mp[0] = root;
int sum = 0, temp_sum = 0;
while(h->next != NULL) {
h = h->next;
sum += h->val;
if (mp.find(sum) != mp.end()) {
ListNode* temp = mp[sum]->next;
mp[sum]->next = h->next;
temp_sum = sum;
while(temp != h) {
temp_sum += temp->val;
mp.erase(temp_sum); // 删除和为0的整个区间,避免干扰(红黑树的删除操作要调整节点,能不用就不用)
temp = temp->next;
}
}
else mp[sum] = h;
}
return root->next;
}
};
1019. 链表中的下一个更大节点
给出一个以头节点 head 作为第一个节点的链表。链表中的节点分别编号为:node_1, node_2, node_3, ... 。
每个节点都可能有下一个更大值(next larger value):对于 node_i,如果其 next_larger(node_i) 是 node_j.val,那么就有 j > i 且 node_j.val > node_i.val,而 j 是可能的选项中最小的那个。如果不存在这样的 j,那么下一个更大值为 0 。
返回整数答案数组 answer,其中 answer[i] = next_larger(node_{i+1}) 。
题解
单调栈,从后向前遍历,如果当前元素小于栈顶则压入,否则,就弹出栈顶元素。
说哈复杂度,每个数都只入栈一次,所以(n)次循环中,总的入栈出栈次数最大可能是(2n),故复杂度(O(n))
class Solution {
public:
vector<int> nextLargerNodes(ListNode* head) {
vector<int> num;
while(head != NULL) num.push_back(head->val), head = head->next;
int n = (int)num.size();
stack<int> st;
st.push(0x3f3f3f3f);
//vector<int> ans(n, 0); 少用一个vector,空间减少了,时间倒增加了几十ms
for (int i = n - 1; i >= 0; --i) {
while(num[i] >= st.top()) st.pop();
int t = num[i];
num[i] = st.top();
st.push(t);
}
for (int &x: num) if (x == 0x3f3f3f3f) x = 0;
return num;
}
};
面试题 02.03. 删除中间节点
实现一种算法,删除单向链表中间的某个节点(除了第一个和最后一个节点,不一定是中间节点),假定你只能访问该节点。
题解
刷多了思维有点僵化,md,调换值就可以了
/* 如果是面试,多半就over了 */
class Solution {
public:
void deleteNode(ListNode* node) {
if (node == NULL) return;
while(node->next != NULL) {
node->val = node->next->val;
if (node->next->next == NULL) {
node->next = NULL;
break;
}
node = node->next;
}
}
};
实际上,将下一个节点的值赋给当前节点,然后删除下一个节点
class Solution {
public:
void deleteNode(ListNode* node) {
node->val = node->next->val;
node->next = node->next->next;
}
};