之前在CSDN写过一篇 链表与快慢指针 的笔记(判断链表是否有环、找到环的入口、反转链表):
请编写一个函数,使其可以删除某个链表中给定的(非末尾)节点,你将只被给定要求被删除的节点。
分析:之前有同学去哈深面试也问过类似的问题。没有给我们链表的起点,只给我们了一个要删的节点,跟我们以前遇到的情况不太一样,我们之前要删除一个节点的方法是要有其前一个节点的位置,然后将其前一个节点的next连向要删节点的下一个,然后delete掉要删的节点即可。这道题的处理方法是先把当前节点的值用下一个节点的值覆盖了,然后我们删除下一个节点即可。
吐槽:谁能想到还可以用替身攻击,还可以把下一个的复制到当前节点,然后把下一个节点的删掉,神奇!!!
class Solution {
public:
void deleteNode(ListNode* node) {
ListNode* old_node_next = node->next;
node->val = old_node_next->val;
node->next = old_node_next->next;
delete old_node_next;
}
};
给定一个链表,每个节点包含一个额外增加的随机指针,该指针可以指向链表中的任何节点或空节点。
要求:返回这个链表的深拷贝,必须返回给定头的拷贝作为对克隆列表的引用。
其实,,,完全没看懂这个题。。。
解答:使用递归的解法,写起来相当的简洁,还是需要一个 HashMap 来建立原链表结点和拷贝链表结点之间的映射。在递归函数中,首先判空,若为空,则返回空指针。然后就是去 HashMap 中查找是否已经在拷贝链表中存在了该结点,是的话直接返回。否则新建一个拷贝结点 res,然后建立原结点和该拷贝结点之间的映射,然后就是要给拷贝结点的 next 和 random 指针赋值了,直接分别调用递归函数即可,参见代码如下:
/*
// Definition for a Node.
class Node {
public:
int val;
Node* next;
Node* random;
Node() {}
Node(int _val, Node* _next, Node* _random) {
val = _val;
next = _next;
random = _random;
}
};
*/
class Solution {
public:
Node* copyRandomList(Node* head) {
unordered_map m;
return helper(head, m);
}
Node* helper(Node* node, unordered_map& m) {
if (!node) return nullptr;
if (m.count(node)) return m[node];
Node *res = new Node(node->val, nullptr, nullptr);
m[node] = res;
res->next = helper(node->next, m);
res->random = helper(node->random, m);
return res;
}
};
编写一个程序,找到两个单链表相交的起始节点。
要求:如果两个链表没有交点,返回 null.在返回结果后,两个链表仍须保持原有的结构。可假定整个链表结构中没有循环。程序尽量满足 O(n) 时间复杂度,且仅用 O(1) 内存。
这个题真的是easy等级!!!然而我真的没写出来,哭。一开始想的是反转链表,这样就可以从头开始比较,结果题目要求链表必须保持原有的结构。
解析:方法一:分别遍历两个链表,得到分别对应的长度。然后求长度的差值,把较长的那个链表向后移动这个差值的个数,然后一一比较即可
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
if (!headA || !headB) return NULL;
int lenA = getLength(headA), lenB = getLength(headB);
if (lenA < lenB) {
for (int i = 0; i < lenB - lenA; ++i) headB = headB->next;
} else {
for (int i = 0; i < lenA - lenB; ++i) headA = headA->next;
}
while (headA && headB && headA != headB) {
headA = headA->next;
headB = headB->next;
}
return (headA && headB) ? headA : NULL;
}
int getLength(ListNode* head) {
int cnt = 0;
while (head) {
++cnt;
head = head->next;
}
return cnt;
}
};
方法二:虽然题目中强调了链表中不存在环,但是我们可以用环的思想来做,我们让两条链表分别从各自的开头开始往后遍历,当其中一条遍历到末尾时,我们跳到另一个条链表的开头继续遍历。两个指针最终会相等,而且只有两种情况,一种情况是在交点处相遇,另一种情况是在各自的末尾的空节点处相等。为什么一定会相等呢,因为两个指针走过的路程相同,是两个链表的长度之和,所以一定会相等。
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
if (!headA || !headB) return NULL;
ListNode *a = headA, *b = headB;
while (a != b) {
a = a ? a->next : headB;
b = b ? b->next : headA;
}
return a;
}
};
在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序。
分析:看到时间复杂度O(nlgn)最先想到的就是把链表的数据存到vector,用sort进行排序,然后把排好序的数据重新建成一个链表…很明显这个方法效率太低.
归并排序:归并排序的核心其实是分治法 Divide and Conquer,就是将链表从中间断开,分成两部分,左右两边再分别调用排序的递归函数 sortList(),得到各自有序的链表后,再进行 merge(),这样整体就是有序的了。
class Solution {
public:
ListNode* sortList(ListNode* head) {
if (!head || !head->next) return head;
ListNode *slow = head, *fast = head, *pre = head;
while (fast && fast->next) {
pre = slow;
slow = slow->next;
fast = fast->next->next;
}
pre->next = NULL;
return merge(sortList(head), sortList(slow));
}
ListNode* merge(ListNode* l1, ListNode* l2) {
if (!l1) return l2;
if (!l2) return l1;
if (l1->val < l2->val) {
l1->next = merge(l1->next, l2);
return l1;
} else {
l2->next = merge(l1, l2->next);
return l2;
}
}
};
补充几个其他类型算法题:
给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。
不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。
链接:https://leetcode-cn.com/problems/remove-duplicates-from-sorted-array
方法一:理解无碍,但是比较慢
nums.erase(unique(nums.begin(),nums.end()),nums.end());
return nums.size();
方法二:leetcode官网给的答案是用双指针做:数组完成排序后,我们可以放置两个指针i和j,其中i是慢指针,而j是快指针。只要nums[i]==nums[j],我们就增加j以跳过重复项。当我们遇到 nums[j]!=nums[i]时,跳过重复项的运行已经结束,因此我们必须把它nums[j]的值复制到nums[i+1]。然后递增i,接着我们将再次重复相同的过程,直到j到达数组的末尾为止。
class Solution {
public:
int removeDuplicates(vector& nums) {
int n=nums.size();
if(n<=1)return n;
int i=0,j=1;
while(j