总结:链表问题除了添加节点,删除节点,查找节点等常规操作,还要关注一种方法:双指针法,双指针的应用例如题目4;而在双指针法中又可以细化出一种方法:快慢指针,题目5,题目6均是快慢指针的实际应用,而题目2其实也可以使用快慢指针去解决,所以快慢指针是一种很重要的解决链表问题的方法。
题目1:删除链表中的节点
请编写一个函数,使其可以删除某个链表中给定的(非末尾)节点,你将只被给定要求被删除的节点。
现有一个链表 -- head = [4,5,1,9],它可以表示为:
示例 1:
输入: head = [4,5,1,9], node = 5
输出: [4,1,9]
解释: 给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9.
示例 2:
输入: head = [4,5,1,9], node = 1
输出: [4,5,9]
解释: 给定你链表中值为 1 的第三个节点,那么在调用了你的函数之后,该链表应变为 4 -> 5 -> 9.
思路:将node后一个节点的值赋给node节点,再将node后一个节点的指针域赋给node节点,这样等于用node后一个节点替换了node,做到了对node的删除。
void deleteNode(ListNode* node)
{
node->val = node->next->val;
node->next = node->next->next;
}
题目2:删除链表的倒数第N个节点
给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。
示例:
给定一个链表: 1->2->3->4->5, 和 n = 2.
当删除了倒数第二个节点后,链表变为 1->2->3->5.
说明:
给定的 n 保证是有效的。
思路:先遍历一遍链表得到链表长度len,之后将指针point移到第len-n个节点处,也就是要删除节点的前一个节点,最后完成删除。但要注意一种特殊情况,就是要删除节点为链表第一个节点这种情况。
ListNode* removeNthFromEnd(ListNode* head, int n)
{
ListNode* point = head;
int len = 0;
while(point!=NULL)
{
len += 1;
point = point->next;
}
point = head;
if(n==len)
{
head = head->next;
}
else
{
int index = len-n;
for(int i=0;i<index-1;i++)
point = point->next;
point->next = point->next->next;
}
return head;
}
题目3:反转链表
反转一个单链表。
示例:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
思路:利用头插法翻转链表,用point指针依次遍历链表元素,每遍历一个元素就将其放在结果链表的头结点之前,再用头结点指针指向该元素,重复上述过程,直到point指向NULL。
ListNode* reverseList(ListNode* head)
{
if(head==NULL||head->next==NULL)
return head;
ListNode* point = head->next;
head->next = NULL;
while(point!=NULL)
{
ListNode* temp = point->next;
point->next = head;
head = point;
point = temp;
}
return head;
}
题目4:合并两个有序链表
将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例:
输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
思路:使用双指针分别指向两个链表的第一个节点,比较两指针指向节点的值大小,将较大节点放入结果链表,之后将较大节点指针后移,重复上述过程,直到有一个指针指向NULL,最后将还有剩余节点的链表的节点全部放入结果链表。
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2)
{
ListNode* head = new ListNode(0);
ListNode* point = head;
ListNode* point1 = l1;
ListNode* point2 = l2;
while(point1!=NULL&&point2!=NULL)
{
int num1 = point1->val;
int num2 = point2->val;
if(num1<=num2)
{
point->next = point1;
point1 = point1->next;
}
else
{
point->next = point2;
point2 = point2->next;
}
point = point->next;
}
if(point1!=NULL)
point->next = point1;
if(point2!=NULL)
point->next = point2;
return head->next;
}
题目5:回文链表
请判断一个链表是否为回文链表。
示例 1:
输入: 1->2
输出: false
示例 2:
输入: 1->2->2->1
输出: true
思路1:利用快慢指针,慢指针走一步,快指针走两步,当快指针走到末尾,慢指针正好在链表中节点(链表长度为奇则正好在中点,链表长度为偶则在上中点处),在慢指针移动过程中将前半部分链表节点记录在一个栈中,在慢指针到达中点后再继续向后移动慢指针遍历后半链表,并将慢指针指向节点与栈顶元素比较,若相等则栈顶元素出栈,继续遍历,若不等则返回false,重复上述过程,直到链表全部遍历完成。
bool isPalindrome(ListNode* head)
{
if(head==NULL||head->next==NULL)
return true;
vector<ListNode*> stack;
//快慢指针找中点
ListNode* slow = head;
ListNode* fast = head;
while(fast->next!=NULL&&fast->next->next!=NULL)
{
stack.push_back(slow);
slow = slow->next;
fast = fast->next->next;
}
//当慢指针到达中点时已经跳出循环,中点并未入栈,链表长度为奇时,
//中节点不参与回文判断,所以只处理链表长度为偶的情况即可。
if(fast->next!=NULL&&fast->next->next==NULL)
stack.push_back(slow);
//遍历后半链表,并判断是否回文
slow = slow->next;
while(slow!=NULL)
{
if(slow->val!=stack.back()->val)
return false;
stack.pop_back();
slow = slow->next;
}
return true;
}
思路2:同样利用快慢指针,找到链表的中节点;之后翻转后半部分链表,再利用双指针,一个指向head,一个指向后半部分第一个节点,比较两指针指向节点的值,若相等则两指针同时向后移动,若不等则返回false;重复上述过程直到后半部分指针指向空。
bool isPalindrome(ListNode* head)
{
if(head==NULL||head->next==NULL)
return true;
//快慢指针找中点
ListNode* slow = head;
ListNode* fast = head;
while(fast->next!=NULL&&fast->next->next!=NULL)
{
slow = slow->next;
fast = fast->next->next;
}
//翻转后半部分链表
ListNode* point = slow->next->next;
slow->next->next = NULL;
while(point!=NULL)
{
ListNode* temp = point->next;
point->next = slow->next;
slow->next = point;
point = temp;
}
//判断是否回文
fast = slow->next;
slow = head;
while(fast!=NULL)
{
if(slow->val!=fast->val)
return false;
fast = fast->next;
slow = slow->next;
}
return true;
}
题目6:环形链表
给定一个链表,判断链表中是否有环。
为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。
示例 1:
输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
输入:head = [1,2], pos = 0
输出:true
解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:
输入:head = [1], pos = -1
输出:false
解释:链表中没有环。
思路:快慢指针的经典应用,让快慢指针从链表头开始遍历,快指针向前移动两个位置,慢指针向前移动一个位置,如果快指针到达NULL,说明链表以NULL为结尾,不是循环链表。反之快指针追上慢指针,则表示出现了循环。
bool hasCycle(ListNode *head)
{
if(head==NULL)
return false;
ListNode* slow = head;
ListNode* fast = head;
while(fast!=NULL&&fast->next!=NULL)
{
slow = slow->next;
fast = fast->next->next;
if(slow==fast)
return true;
}
return false;
}