删除单链表的重复节点
链接:面试题 02.01. 移除重复节点 力扣
题目描述:编写代码,移除未排序链表中的重复节点。保留最开始出现的节点。
示例:
输入:[1, 2, 3, 3, 2, 1]
输出:[1, 2, 3]
解题思路:
思路一:定义两个指针current和p来逐个遍历链表,current元素依次和p比较,直到p为NULL为止,current向后移动一个。
思路二:利用标记数组,标记当前值是否出现过。
代码实现
方法一:
struct ListNode* removeDuplicateNodes(struct ListNode* head){
if(head == NULL) return NULL;
struct ListNode* current = head;
while(current){
struct ListNode* p = current;
while(p->next){
if(p->next->val == current->val){
p->next = p->next->next;
}else{
p = p->next;
}
}
current = current->next;
}
return head;
}
方法二:
struct ListNode* removeDuplicateNodes(struct ListNode* head){
if(head == NULL || head->next == NULL) return head;
//题目描述val的值在0~20000之间,标记是否出现过
int index[200001] = {0};
index[head->val] = 1;
struct ListNode* pre = head, *q = head->next;
while(q){
if(!index[q->val]){
index[q->val] = 1;
pre = q;
q = q->next;
}else{
pre->next = q->next;
q = pre->next;
}
}
return head;
}
如何找出链表的倒数第K个元素?
链接:剑指 Offer 22. 链表中倒数第k个节点 力扣
题目描述:
输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。
例如,一个链表有 6
个节点,从头节点开始,它们的值依次是 1、2、3、4、5、6
。这个链表的倒数第 3
个节点是值为 4
的节点。
示例:
给定一个链表: 1->2->3->4->5, 和 k = 2.
返回链表 4->5
解题思路:
思路:快慢指针,先让快指针走k步,然后两个指针同步走,当快指针走到头时,慢指针就是链表倒数第k个节点。
代码实现
struct ListNode* getKthFromEnd(struct ListNode* head, int k){
struct ListNode* slow = head;
struct ListNode* fast = head;
while(--k){
fast = fast->next;
}
while(fast->next){
slow = slow->next;
fast = fast->next;
}
return slow;
}
如何找出链表的中间节点?
链接:876. 链表的中间结点力扣
题目描述:
给你单链表的头结点 head
,请你找出并返回链表的中间结点。
如果有两个中间结点,则返回第二个中间结点。
示例:
输入:head = [1,2,3,4,5]
输出:[3,4,5]
解释:链表只有一个中间结点,值为 3
输入:head = [1,2,3,4,5,6]
输出:[4,5,6]
解释:该链表有两个中间结点,值分别为 3 和 4 ,返回第二个结点
解题思路:
思路:双指针,定义快指针一次走两步,慢指针一次走一步。需要注意边界条件,“如果有两个中间结点,则返回第二个中间结点”,则有快指针前进条件:当前快慢指针下一节点均非空。
代码实现
struct ListNode* middleNode(struct ListNode* head){
struct ListNode* slow = head;
struct ListNode* fast = head;
while(fast && fast->next){
slow = slow->next;
fast = fast->next->next;
}
return slow;
}
反转链表
链接:剑指 Offer 24. 反转链力扣
题目描述:
定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。
示例:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
解题思路:
思路:反转链表实际就是重新指向结构体中的next指针,我们需要修改下一个节点的next指针指向前一个节点。所以,在遍历链表时我们要逐个修改链表的指针指向。这个题目也可以用递归来做, -直递归到链表的最后一个结点,该结点就是反转后的头结点,记作head.
此后,每次函数在返回的过程中,让当前结点的下一个结点的next指针指向当前节点。同时让当前结点的next指针指向NULL ,从而实现从链表尾部开始的局部反转。当递归函数全部出栈后,链表反转完成。
代码实现
struct ListNode* reverseList(struct ListNode* head){
struct ListNode* mid = head;
struct ListNode* former = NULL;
struct ListNode* latter = NULL;
while(mid != NULL){
latter = mid->next;
mid->next = former;
former = mid;
mid = latter;
}
return former;
}
递归解法
struct ListNode* reverseList(struct ListNode* head){
if(head == NULL || head->next == NULL) return head;
else{
struct ListNode* mid = head;
struct ListNode* latter = mid->next;
head = reverseList(latter);
latter->next = mid;
mid->next = NULL;
return head;
}
}
环形链表
链接:141. 环形链表力扣
题目描述:
给你一个链表的头节点 head
,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next
指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos
来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos
不作为参数进行传递 。仅仅是为了标识链表的实际情况。
如果链表中存在环 ,则返回 true
。 否则,返回 false
。
示例:
输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。
解题思路:
思路:反转链表实际就是重新指向结构体中的next指针,我们需要修改下一个节点的next指针指向前一个节点。所以,在遍历链表时我们要逐个修改链表的指针指向。这个题目也可以用递归来做, -直递归到链表的最后一个结点,该结点就是反转后的头结点,记作head.
此后,每次函数在返回的过程中,让当前结点的下一个结点的next指针指向当前节点。同时让当前结点的next指针指向NULL ,从而实现从链表尾部开始的局部反转。当递归函数全部出栈后,链表反转完成。
代码实现
维护哈希表查询(cpp)
class Solution {
public:
bool hasCycle(ListNode *head) {
unordered_set<ListNode *> seem;
while(head){
if(seem.count(head)) return true;
else{
seem.insert(head);
head = head->next;
}
}
return false;
}
};
快慢指针(c)
bool hasCycle(struct ListNode *head) {
struct ListNode *slow = head;
struct ListNode *fast = head;
while(fast != NULL && fast->next != NULL){
if(fast->next == slow) return true;
fast = fast->next->next;
slow = slow->next;
}
return false;
}
单链表相交,如何求交点?
链接:面试题 02.07. 链表相交力扣
题目描述:
给你两个单链表的头节点 headA
和 headB
,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null
。
图示两个链表在节点 c1
开始相交:
题目数据 保证 整个链式结构中不存在环。
注意,函数返回结果后,链表必须 保持其原始结构 。
示例:
输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3
输出:Intersected at '8'
解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。
在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。
解题思路:
思路:根据题意,首先有,交点必在两单链表上,如下图所示:
无论是否BD≠AB,都有AD+BD+DC = BD+ AD+DC,即AC + BD = BC + AD
则有,每条链表遍历自身后再遍历另一条链表,必然于交点重合。
代码实现
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
struct ListNode *A = headA;
struct ListNode *B = headB;
while(A!=B){
if(A == NULL) A = headB;
else A = A->next;
if(B == NULL) B = headA;
else B = B->next;
}
return A;
}
用普通算法实现两个有序链表的合并
链接:21. 合并两个有序链表力扣
题目描述:
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
提示:
- 两个链表的节点数目范围是
[0, 50]
-100 <= Node.val <= 100
l1
和l2
均按 非递减顺序 排列
示例:
输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]
解题思路:
方法一:非递归
两个链表是排序过得,所以我们只需要同时遍历两个链表,比较链表元素的大小,将小的连接到新的链表中即可。后遍历完的链表直接连接到新链表末端即可。
方法二:递归
在两表都不为空的情况下,如果一个表当前value更小,把该表的next修改为两表当前较小值。
代码实现
方法一:非递归
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2){
if(list1 == NULL) return list2;
if(list2 == NULL) return list1;
//注意此步分配内存
struct ListNode* head = (struct ListNode*)malloc(sizeof(struct ListNode));
struct ListNode* cur = head;
while(list1 && list2){
if(list1->val > list2->val){
cur-> next = list2;
list2 = list2->next;
}else{
cur-> next = list1;
list1 = list1->next;
}
cur = cur->next;
}
cur->next = (list1 == NULL? list2:list1);
return head->next;
}
方法二:递归
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2){
if(list1 == NULL) return list2;
if(list2 == NULL) return list1;
if(list1->val < list2->val){
list1->next = mergeTwoLists(list1->next,list2);
return list1;
}else{
list2->next = mergeTwoLists(list2->next,list1);
return list2;
}
}