与链表相关的问题一般都是通过维护多个节点指针,在链表中穿针引线来解决的。另外一个常用的技巧是创建一个dummy指针,使之链接到root。
83. 删除排序链表中的重复元素
给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。
示例 1:
输入: 1->1->2 输出: 1->2
示例 2:
输入: 1->1->2->3->3 输出: 1->2->3
struct ListNode {
int val;
ListNode *next;
ListNode(int x) : val(x), next(NULL) {}
};
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
ListNode* resHead = head;
ListNode* curNode = head;
while (curNode != NULL&& curNode->next != NULL) {
// 如果当前节点的val和下一节点的val相等,那么将当前节点链接到下下个节点,意思就是抛弃了相同的下一个节点
if (curNode->val == curNode->next->val) {
curNode->next = curNode->next->next;
}
else { // 否则就是下一节点和当前节点不相同,那么curNode移位
curNode = curNode->next;
}
}
return head;
}
};
86. 分隔链表
给定一个链表和一个特定值 x,对链表进行分隔,使得所有小于 x 的节点都在大于或等于 x 的节点之前。
你应当保留两个分区中每个节点的初始相对位置。
示例:
输入: head = 1->4->3->2->5->2, x = 3 输出: 1->2->2->4->3->5
// 思路: 就是将小于x和不小于x的划分到两个新创建的列表,最后将两个列表链接起来即可,缺点是申请了多余的空间,时间复杂度为n
class Solution1 {
public:
ListNode* partition(ListNode* head, int x) {
ListNode* res = new ListNode(0);
res->next = head;
ListNode* smallCurNode = res;
ListNode* bigHead = new ListNode(0);
ListNode* bigCurNode = bigHead;
ListNode* curNode = head;
while (curNode != NULL) {
// 小于x,链接到小段
if (curNode->val < x) {
smallCurNode->next = new ListNode(curNode->val);
smallCurNode = smallCurNode->next;
}
else { // 不小于x,链接到大段
bigCurNode->next = new ListNode(curNode->val);
bigCurNode = bigCurNode->next;
}
curNode = curNode->next;
}
// 两个段链接起来
smallCurNode->next = bigHead->next;
return res->next;
}
};
328. 奇偶链表
给定一个单链表,把所有的奇数节点和偶数节点分别排在一起。请注意,这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性。
请尝试使用原地算法完成。你的算法的空间复杂度应为 O(1),时间复杂度应为 O(nodes),nodes 为节点总数。
示例 1:
输入: 1->2->3->4->5->NULL 输出: 1->3->5->2->4->NULL
示例 2:
输入: 2->1->3->5->6->4->7->NULL 输出: 2->3->6->7->1->5->4->NULL
说明:
- 应当保持奇数节点和偶数节点的相对顺序。
- 链表的第一个节点视为奇数节点,第二个节点视为偶数节点,以此类推。
// 思路: 和上一道题相似,但是要在原来的空间里的相应节点做后继指针的跳变,同理,还是记录好偶数的头结点,
// 然后奇偶列表分别做各自的连接,待循环完成,将奇偶列表链接起来,维护好奇偶列表最后一个节点的额指针变量
class Solution2 {
public:
ListNode* oddEvenList(ListNode* head) {
if (head == NULL || head->next == NULL)
return head;
ListNode* oddTail = head;
ListNode* evenTail = head->next;
ListNode* evenHead = evenTail;
while (evenTail != NULL && evenTail->next!=NULL) {
// 偶数段后继节点为奇数,所以奇数段连接到此节点
oddTail->next = evenTail->next;
// 奇数段tail后移
oddTail = oddTail->next;
// 偶数段后继节点连接至下一个偶数节点
evenTail->next = oddTail->next;
// 偶数段tail后移
evenTail = evenTail->next;
}
// 奇数段和偶数段连接
oddTail->next = evenHead;
return head;
}
};
2. 两数相加
给定两个非空链表来表示两个非负整数。位数按照逆序方式存储,它们的每个节点只存储单个数字。将两数相加返回一个新的链表。
你可以假设除了数字 0 之外,这两个数字都不会以零开头。
示例:
输入:(2 -> 4 -> 3) + (5 -> 6 -> 4) 输出:7 -> 0 -> 8 原因:342 + 465 = 807
class Solution3 {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
ListNode* curL1 = l1;
ListNode* curL2 = l2;
ListNode* res = new ListNode(0);
ListNode* head = res;
int inHeight = 0; // 进位
while (curL1 != NULL || curL2 != NULL || inHeight != 0) {
int numL1 = 0; // l1当前位的值
int numL2 = 0; // l2当前位的值
if (curL1 != NULL) {
numL1 = curL1->val;
curL1 = curL1->next; // 循环变量维护
}
if (curL2 != NULL) {
numL2 = curL2->val;
curL2 = curL2->next; // 循环变量维护
}
// 计算当前位的数字
int numNow = numL1 + numL2 + inHeight;
// 计算进位
inHeight = numNow / 10;
// 得到当前位的最终数字
numNow = numNow % 10;
// 循环变量维护
res->next = new ListNode(numNow);
res = res->next;
}
return head->next;
}
};
203. 删除链表中的节点
删除链表中等于给定值 val 的所有节点。
示例:
输入: 1->2->6->3->4->5->6, val = 6 输出: 1->2->3->4->5
// 思路: 太简单
class Solution4 {
public:
ListNode* removeElements(ListNode* head, int val) {
ListNode* dummmyHead = new ListNode(0);
dummmyHead->next = head;
ListNode* curNode = dummmyHead;
while (curNode->next != NULL) {
if (curNode->next->val == val) {
ListNode* delNode = curNode->next;
curNode->next = curNode->next->next;
delete delNode;
}
else
curNode = curNode->next;
}
return dummmyHead->next;
}
};
21. 合并两个有序链表
将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例:
输入:1->2->4, 1->3->4 输出:1->1->2->3->4->4
class Solution6 {
public:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
ListNode* dummmyHead = new ListNode(0);
ListNode* curNode = dummmyHead;
ListNode* curNodeL1 = l1;
ListNode* curNodeL2 = l2;
while (curNodeL1 != NULL || curNodeL2 != NULL) {
// merge
if (curNodeL1 == NULL) {
curNode->next = new ListNode(curNodeL2->val);
curNode = curNode->next;
curNodeL2 = curNodeL2->next;
}
else if (curNodeL2 == NULL) {
curNode->next = new ListNode(curNodeL1->val);
curNode = curNode->next;
curNodeL1 = curNodeL1->next;
}
else if (curNodeL1->val <= curNodeL2->val) {
curNode->next = new ListNode(curNodeL1->val);
curNode = curNode->next;
curNodeL1 = curNodeL1->next;
}
else {
curNode->next = new ListNode(curNodeL2->val);
curNode = curNode->next;
curNodeL2 = curNodeL2->next;
}
}
return dummmyHead->next;
}
};
24. 两两交换链表中的节点
给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。
示例:
给定1->2->3->4
, 你应该返回2->1->4->3
.
说明:
- 你的算法只能使用常数的额外空间。
- 你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
// 思路: 维护好pre,node1,node2,next四个指针,代表的意义分别为待翻转节点对的前一节点,node1和node2为待翻转的节点对,next为待翻转节点对的后继节点
class Solution7 {
public:
ListNode* swapPairs(ListNode* head) {
ListNode* dummmyHead = new ListNode(0);
dummmyHead->next = head;
ListNode* pre = dummmyHead;
// pre的后继两个节点都有值才需要翻转,不都有值那么不需要操作了
while (pre->next && pre->next->next) {
ListNode* node1 = pre->next;
ListNode* node2 = node1->next;
ListNode* next = node2->next;
pre->next = node2;
node2->next = node1;
node1->next = next;
pre = node1;
}
return dummmyHead->next;
}
};
206. 反转链表
反转一个单链表。
示例:
输入: 1->2->3->4->5->NULL 输出: 5->4->3->2->1->NULL
进阶:
你可以迭代或递归地反转链表。你能否用两种方法解决这道题?
// 思路: 维护好 pre,cur,next三个指针,分别指向当前待操作节点的前一节点,当前节点,后一节点
// 循环不变量为cur,直到cur为空指针,跳出循环
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* pre = NULL;
ListNode* cur = head;
while (cur != NULL) {
ListNode* next = cur->next;
// 翻转的同时维护好三个指针
cur->next = pre;
pre = cur;
cur = next;
}
return pre;
}
};
92. 反转链表 II
反转从位置 m 到 n 的链表。请使用一趟扫描完成反转。
说明:
1 ≤ m ≤ n ≤ 链表长度。
示例:
输入: 1->2->3->4->5->NULL, m = 2, n = 4 输出: 1->4->3->2->5->NULL
// 思路: 分为两个步骤,第一步是找到m结点所在位置,第二步就是进行反转直到n结点。反转的方法就是每读到一个结点,把它插入到m结点前面位置,
// 然后m结点接到读到结点的下一个。总共只需要一次扫描,所以时间是O(n),只需要几个辅助指针,空间是O(1)。
class Solution1 {
public:
ListNode* reverseBetween(ListNode* head, int m, int n) {
ListNode* res = new ListNode(0);
res->next = head;
ListNode* prestart = res;
ListNode* start = head;
for (int i = 1; i < m; i++) {
prestart = start;
start = start->next;
}
// 翻转,将当前节点的后一节点插入到翻转段的最前面
for (int i = m; i < n; i++) {
// 记录当前节点的后一节点
ListNode* temp = start->next;
// 将当前节点后继到后一节点的后一节点
start->next = temp->next;
// 将temp节点,即当前节点本来的后一节点插入到翻转段的最前面
temp->next = prestart->next;
prestart->next = temp;
}
return res->next;
}
};