83. 删除排序链表中的重复元素
题目描述
给定一个已排序的链表的头 head , 删除所有重复的元素,使每个元素只出现一次 。返回 已排序的链表 。
输入:head = [1,1,2]
输出:[1,2]
解题思路:用一个指向节点类型的指针保存头结点,用另一个指向节点类型的指针对该链表进行遍历,由于是有序的,当出现不同的值就说明不会再出现跟前面的值相同的节点了,最后循环结束的条件是遍历到最后一个节点的时候,也就是该节点的next 指向空的时候,停止循环,返回该保存的头结点,另外,如果传过来的头结点是空,则直接返回空。
参考代码:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* deleteDuplicates(struct ListNode* head){
if(!head)//如果head为空直接返回
{
return head;
}
struct ListNode* ret = head;//保存头结点
while(head->next!=NULL)//当head为尾节点时停下来
{
if(head->val!=head->next->val)//判断两个相邻节点的值是否相等,如果不相等,head就往后走
{
head = head->next;
continue;
}
//如果想等,直接跳过这个节点,相当于删除
head->next = head->next->next;
}
return ret;
}
141.环形链表
题目描述
给你一个链表的头节点 head ,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。
如果链表中存在环 ,则返回 true 。 否则,返回 false 。
输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。
输入:head = [1,2], pos = 0
输出:true
解释:链表中有一个环,其尾部连接到第一个节点。
输入:head = [1], pos = -1
输出:false
解释:链表中没有环。
解题思路:
这个题我们可以用一个快慢指针来写,这里可以考虑到一个追及问题,如果该链表没有环,则快指针永远不可能和慢指针相遇,而最后会走到末尾,如果有环,那么因为快指针永远比慢指针快一步,并且他们之间是有距离的,所以一定会相遇。
参考代码:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
bool hasCycle(struct ListNode *head) {
struct ListNode * fast = head;
struct ListNode * slow = head;
while(fast&&fast->next)
{
slow = slow->next;//慢指针,一次走一步
fast = fast->next->next;//快指针一次走两步
if(slow==fast)
{
return true;//一旦相遇说明有环
}
}
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 个节点。
输入:intersectVal = 2, listA = [0,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1
输出:Intersected at ‘2’
解释:相交节点的值为 2 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [0,9,1,2,4],链表 B 为 [3,2,4]。
在 A 中,相交节点前有 3 个节点;在 B 中,相交节点前有 1 个节点。
输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2
输出:null
解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。
由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。
这两个链表不相交,因此返回 null 。
解题思路:让pA从headA链表的头节点,pB从headB链表的头结点同时开始走,当走到空的时候就让另一条链表的头结点赋值给相应的节点,比如pA把headA都走完了还没有与pB相等,则把headB赋值给pA让pA继续走下去,同理pB从另一边这样走,就相当于如果两条链表有交点则这两条链表可以看成打了节的绳子,从一个左往右走,一个从右往左走,如果有交点,则pA和pB必然相遇,否则没有交点。
参考代码:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
if(headA==NULL||headB==NULL)
return NULL;
struct ListNode* pA = headA;
struct ListNode* pB = headB;
while(pA!=pB)
{
pA = pA!=NULL? pA->next:headB;
pB = pB!=NULL? pB->next:headA;
}
return pA;
}
206.反转链表
题目描述
给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]
有两种方法可以写
方法一:把箭头掰成指向自己前面的节点
如上图所示,很形象的其实
参考代码:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* reverseList(struct ListNode* head) {
//方法一
if(head==NULL)
{
return NULL;
}
//初始条件
struct ListNode *n1 = NULL,*n2 = head,*n3 = n2->next;
//结束条件
while(n2)
{
n2->next = n1;//把当前节点的下一个指向前面的那个节点,相当于形象的掰指针箭头方向
n1 = n2;
n2 = n3;
if(n3)
{
n3 = n3->next;
}
}
return n1;//走到最后的n1就是该链表的头结点
方法二:头插法
解题思路:
头插法特性:
可以把先头插的节点都可以挤到后面去,后面头插的节点放到最前面
参考代码:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* reverseList(struct ListNode* head){
//方法二
struct ListNode* cur = head;
struct ListNode* newhead = NULL;
while(cur)
{
struct ListNode* next = cur->next;
//头插
cur->next = newhead;
newhead = cur;
cur = next;
}
return newhead;
}
21.合并两个有序链表
题目描述
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的
输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]
示例二:
输入:l1 = [ ], l2 = [ ]
输出:[ ]
解题思路:
定义一个新的头节点初始化为空,用两个节点指针分别从两个有序链表的头结点开始遍历并且比较两个节点的值,一 一比较,小的直接尾插到新的头结点后面,直接遍历下去,直到其中一个链表遍历完,然后把另一个链表剩下的节点直接链接到新链表的后面,如果两个链表其中一个为空,那么就直接返回另一个链表。
参考代码:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2){
if(list1==NULL)
{
return list2;
}
if(list2==NULL)
{
return list1;
}
struct ListNode *tail = NULL;
struct ListNode *head = NULL;
if(list1->val<list2->val)
{
head = tail = list1;
list1 = list1->next;
}
else
{
head = tail = list2;
list2 = list2->next;
}
while(list1!=NULL&&list2!=NULL)
{
if(list1->val<list2->val)
{
tail->next = list1;
list1 = list1->next;
}
else
{
tail->next = list2;
list2 = list2->next;
}
tail = tail->next;
}
if(list1)
{
tail->next = list1;
}
if(list2)
{
tail->next = list2;
}
return head;
}
234. 回文链表
题目描述
给你一个单链表的头节点 head ,请你判断该链表是否为回文链表。如果是,返回 true ;否则,返回 false 。
输入:head = [1,2,2,1]
输出:true
输入:head = [1,2]
输出:false
解题思路:
我是通过快慢指针先找到中间节点,然后再通过调用前面的反转链表的函数,把后半部分进行反转,然后一一对比,直到比到中间节点如果还相等则为回文链表
参考代码:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* reverseList(struct ListNode* head){
if(head==NULL)
return NULL;
struct ListNode* prev = NULL;
struct ListNode* cur = head;
struct ListNode* last = cur->next;
head->next = NULL;
while(cur)
{
cur->next = prev;
prev = cur;
cur = last;
if(last)
{
last = last->next;
}
}
return prev;
}
bool isPalindrome(struct ListNode* head){
if(head==NULL||head->next==NULL)
return true;
struct ListNode* low = head;
struct ListNode* fast = head;
if((low->next->next)==NULL)
{
if(low->val!=low->next->val)
{
return false;
}
else
return true;
}
while((fast!=NULL)&&(fast->next!=NULL))
{
low = low->next;
fast = fast->next->next;
}
struct ListNode* cur = low;
struct ListNode* newhead = reverseList(cur);
while(newhead!=low->next)
{
if(newhead->val!=head->val)
{
return false;
}
head = head->next;
newhead = newhead->next;
}
return true;
}
面试题 02.02. 返回倒数第 k 个节点
题目描述
实现一种算法,找出单向链表中倒数第 k 个节点。返回该节点的值。
示例:
输入: 1->2->3->4->5 和 k = 2
输出: 4
解题思路:定义一个快慢指针先让快指针从头走k步到第k个节点,然后再让慢指针从头开始走,直到快指针走到最后一个空节点为止
相当于快指针先走了k步,然后快指针又走了链表长度-k次,其实这个步数也就是慢指针走的步数,也就是链表的倒数第k个节点
参考代码:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
int kthToLast(struct ListNode* head, int k){
struct ListNode* fast = head;
struct ListNode* low = head;
while(k--)
{
fast = fast->next;
}
while(fast)
{
fast = fast->next;
low = low->next;
}
return low->val;
}
总结
真的,不说别的,咋们就是说是不是链表这块动不动就用快慢指针,把快慢指针学会并且灵活应用,解答链表题对你会有很大帮助。