1.删除链表的倒数第n个节点
给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。
给定一个链表: 1->2->3->4->5, 和 n = 2.
当删除了倒数第二个节点后,链表变为 1->2->3->5.
解
在只循环一次的情况下,这题有两种解法
- 一种是用数组存储每次遍历的链表,这种比较简单但浪费一定空间。
- 第二种是快慢指针,快指针先移动n步,然后快慢指针一起移动直到快指针到表尾,此时慢指针的地址为所要删除节点的前一个节点。
注意:需要考虑边界条件,此题的边界条是当n为链表的长度时,此时删除的节点为头节点,需要返回head->next。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
if(!head)return NULL;
ListNode* r = head;
while(n>0){
r = r->next;
n--;
}
if(!r)
return head->next;
ListNode* l = head;
while(r->next){
r = r->next;
l = l->next;
}
l->next = l->next->next;
return head;
}
};
2.两两交换链表中的节点
给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
示例:
给定 1->2->3->4, 你应该返回 2->1->4->3.
解
- 遍历链表,记住第一个交换的前一个节点,然后交换。这种方法比较简单,边界情况有两种,第一种是第一次交换时,前一个节点为空,解决方法是第一次交换特殊处理不放进循环。第二种情况是最后剩下的为单数,这个只需要判断第一个node->next是否为空就行。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
if(!head || !head->next)return head;
ListNode* n1 = head;
ListNode* n2 = head->next;
ListNode* n3;
n1->next = n2->next;
n2->next = n1;
head = n2;
n3 = n1;
n1 = n1->next;
while(n1 && n1->next){
n2 = n1->next;
n1->next = n2->next;
n2->next = n1;
n3->next = n2;
n3 = n1;
n1 = n1->next;
}
return head;
}
};
- 递归解决,终止条件为!head || !head->next,返回值为处理完后的子链表,传递给两个交换节点的前一个,后一个节点再指向前一个,再返回。递归比遍历方法简化在遍历是从后往前的,所以不需要记录前一个节点,因为他直接作为返回值传递给上一个函数,因此也不需要考虑头节点的情况。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
if(!head || !head->next)return head;
ListNode* node = head->next;
head->next = swapPairs(node->next);
node->next = head;
return node;
}
};
3.删除排序链表中的重复元素
给定一个排序链表,删除所有含有重复数字的节点,只保留原始链表中 没有重复出现 的数字。
示例 1:
输入: 1->2->3->3->4->4->5
输出: 1->2->5
示例 2:
输入: 1->1->1->2->3
输出: 2->3
解:
其实大部分链表问题都可以分为两种,遍历和递归,此题也不例外。
-
遍历,遍历也比较简单,因为没有重复的数字,所以需要一个prev节点,涉及至少三个节点,操作比较繁琐,但算法时间复杂度为O(n)。
-
递归,每次递归需要分两种情况,当存在重复元素时,函数返回值应赋值给最后一个元素的下一个元素,并返回下一个元素。当不存在重复元素时,函数返回值赋值给下一个元素,此时是返回当前节点。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
if(!head || !head->next)return head;
if(head->next && head->next->val == head->val){
while(head->next && head->next->val == head->val)
head = head->next;
return deleteDuplicates(head->next);
}
head->next = deleteDuplicates(head->next);
return head;
}
};
4.分隔链表
给定一个链表和一个特定值 x,对链表进行分隔,使得所有小于 x 的节点都在大于或等于 x 的节点之前。
你应当保留两个分区中每个节点的初始相对位置。
示例:
输入: head = 1->4->3->2->5->2, x = 3
输出: 1->2->2->4->3->5
这道题用递归做很麻烦因为他是要对链表的两边进行操作,这时可以利用链表的插入特性进行一趟循环遍历将链表分隔。
思路:创建两个节点,这两个节点分别插入大于等于x和小于x的节点,最后将插入小于节点的尾节点的下一个指针指向头节点(这是一个虚拟节点,没有任何意义,只是为了方便插入)的下一个节点,便完成了节点的分隔,这利用了链表插入以及连接方便的特点,所以开销非常小。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* partition(ListNode* head, int x) {
ListNode* n1 = new ListNode(0);
ListNode* n2 = new ListNode(0);
ListNode* n3 = n1;
ListNode* n4 = n2;
while(head){
if(head->val < x){
n3->next = head;
n3 = head;
}else{
n4->next = head;
n4 = head;
}
head = head->next;
}
n3->next = n2->next;
n4->next = NULL;
return n1->next;
}
};
5.翻转链表
这是一道很经典的题目,问题不难,包含两种解法,分别是迭代和递归
- 迭代法需要三个指针,当前指针,前一个指针,后一个指针,原因很简单,不解释了。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if(!head || !head->next) return head;
ListNode* prev = NULL;
ListNode* node = head;
ListNode* next = head->next;
while(head){
next = head->next;
head->next = prev;
prev = head;
head = next;
}
return prev;
}
};
- 递归
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if(!head || !head->next)return head;
ListNode* next = head->next;
ListNode* ret = reverseList(head->next);
next->next = head;
head->next = NULL;
return ret;
}
};
这种递归每次都返回尾节点,因为递归的特性,所有不需要记录前一个结点,进行翻转后直接返回就行。
第二种递归(我觉得这种比较好理解)
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if(!head || !head->next) return head;
ListNode* ret = head;
while(ret->next)ret = ret->next;
solution(head);
head->next = NULL;
return ret;
}
ListNode* solution(ListNode* head)
{
if(!head || !head->next)return head;
solution(head->next)->next = head;
return head;
}
};
这种递归每次返回下一个节点,只需要将返回的值的下一个节点指向当前节点,再返回就行。需要注意两点,第一个是返回的结果,因为题目要求返回头节点,所以我们需要在执行递归的时候记录尾结点,最后用来返回。第二个是递归完成后,因为是从head开始执行递归,递归完成后head的节点并没有翻转,所以最后需要将head节点执行空节点,否则就会形成一个环,执行会超出时间限制。