1,反转单链表
来源:206. 反转链表 && 剑指 Offer 24. 反转链表
迭代思路:采用头插法创建一个新的单链表。时间复杂度O(N),空间复杂度O(1)。
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode *result = NULL;
while (head) {
ListNode *next = head->next; // 保存下一个节点
head->next = result; // 插入单链表开头
result = head; // 更新单链表
head = next; // 继续迭代
}
return result;
}
};
递归思路:较复杂。先反转后面链表,再令下一节点指向自己,自己作为尾节点指向NULL。时间复杂度O(N),空间复杂度O(N)。
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if (head == NULL || head->next == NULL) return head;
ListNode *next = head->next;
ListNode *result = reverseList(next);
next->next = head;
head->next = NULL;
return result;
}
};
2,判断单链表是否有环
来源:141. 环形链表
思路:双指针,定义2个指针,一快一慢,快指针每次移动2个节点,慢指针每次移动1个节点。如果快指针结束,无环;如果快慢指针相等,有环。
class Solution {
public:
bool hasCycle(ListNode *head) {
ListNode *fast = head;
ListNode *slow = head;
while (fast && fast->next) {
fast = fast->next->next;
slow = slow->next;
if (fast == slow) return true; // 先移动后判断
}
return false;
}
};
3,两个链表的第一个公共节点
来源:剑指 Offer 52. 两个链表的第一个公共节点 && 160. 相交链表
思路:分别计算两个链表的长度,较长的一方先行移动长度差后再同时移动。
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
int la = size(headA);
int lb = size(headB);
if (la > lb) {
int t = la - lb;
while (t--) {
headA = headA->next;
}
} else {
int t = lb - la;
while (t--) {
headB = headB->next;
}
}
while (headA) {
if (headA == headB) {
return headA;
}
headA = headA->next;
headB = headB->next;
}
return NULL;
}
int size(ListNode *p) {
int sz = 0;
while (p) {
sz++;
p = p->next;
}
return sz;
}
};
另一种简洁的思路:定义2个指针分别指向两个链表,当某个指针遍历完成后指向另一链表继续遍历,直到找到交叉点。
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
ListNode *p1 = headA;
ListNode *p2 = headB;
while (p1 != p2) {
p1 = (p1 == NULL) ? headB : p1->next;
p2 = (p2 == NULL) ? headA : p2->next;
}
return p1;
}
};
4,两链表是否相交
若相交返回第一个交点,否则返回NULL。
思路:如果两链表相交,尾节点必然相同,反之亦然。先判断尾节点是否相同,若相同则同题3。
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
if (headA == NULL || headB == NULL) return NULL;
if (tail(headA) != tail(headB)) return NULL;
ListNode *p1 = headA;
ListNode *p2 = headB;
while (p1 != p2) {
p1 = (p1 == NULL) ? headB : p1->next;
p2 = (p2 == NULL) ? headA : p2->next;
}
return p1;
}
ListNode *tail(ListNode *head) {
while (head->next) head = head->next;
return head;
}
};
5,链表倒数第k个节点
思路1:双指针。定义2个指针,一前一后,前指针先移动k个节点后两指针同时移动,当前指针结束时,后指针即为结果。
class Solution {
public:
ListNode* getKthFromEnd(ListNode* head, int k) {
ListNode *first = head;
while (k-- > 0) first = first->next;
ListNode *second = head;
while (first) {
first = first->next;
second = second->next;
}
return second;
}
};
思路2:求链表长度n,正向第n-k个节点即为结果。
class Solution {
public:
ListNode* getKthFromEnd(ListNode* head, int k) {
k = size(head) - k;
ListNode *p = head;
while (k-- > 0) p = p->next;
return p;
}
int size(ListNode *p) {
int sz = 0;
while (p) {
sz++;
p = p->next;
}
return sz;
}
};
6,链表的中间节点
来源:876. 链表的中间结点
思路:双指针,定义2个指针,一快一慢,快指针每次移动2个节点,慢指针每次移动1个节点,当快指针无法移动时,慢指针即为结果。
class Solution {
public:
ListNode* middleNode(ListNode* head) {
ListNode *fast = head;
ListNode *slow = head;
while (fast && fast->next) {
fast = fast->next->next;
slow = slow->next;
}
return slow;
}
};
同样,可先计算链表长度n,跳过前n/2个节点后返回。
7,删除非尾节点
函数参数只有一个:要被删除的节点。
思路:将下一个节点数据保存到当前节点,删除下一个节点。想不到则写不出,小技巧而已。
class Solution {
public:
void deleteNode(ListNode* node) {
ListNode *next = node->next;
node->val = next->val;
node->next = next->next;
}
};
8,删除指定元素的所有节点
来源:203. 移除链表元素(有重复元素) && 剑指 Offer 18. 删除链表的节点(无重复元素)
思路:无论重复与否,采用尾插法重新创建链表并返回。尾插法技巧:多创建一个节点,不再判断头节点是否为NULL,代码简洁。
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
ListNode *result = new ListNode(0); // 多创建一个节点,不再判断result是否为NULL
ListNode *tail = result;
while (head) {
ListNode *next = head->next; // 保存下一节点
head->next = NULL; // 断开链接
if (head->val != val) { // 满足条件
tail->next = head;
tail = head;
}
head = next;
}
return result->next;
}
};
9,合并两个有序链表
来源:21. 合并两个有序链表 && 剑指 Offer 25. 合并两个排序的链表
迭代思路:尾插法重新生成新链表
class Solution {
public:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
ListNode *result = new ListNode(0);
ListNode *tail = result;
while (l1 && l2) {
if (l1->val < l2->val) {
ListNode *next = l1->next;
l1->next = NULL;
tail->next = l1;
tail = l1;
l1 = next;
} else {
ListNode *next = l2->next;
l2->next = NULL;
tail->next = l2;
tail = l2;
l2 = next;
}
}
if (l1) tail->next = l1;
else tail->next = l2;
return result->next;
}
};
递归思路:
class Solution {
public:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
if (l1 == NULL) return l2;
if (l2 == NULL) return l1;
if (l1->val < l2->val) {
l1->next = mergeTwoLists(l1->next, l2);
return l1;
} else {
l2->next = mergeTwoLists(l1, l2->next);
return l2;
}
}
};
10,合并多个有序链表
来源:23. 合并K个升序链表
思路:分治,链表两两合并直到剩余1个。
class Solution {
public:
ListNode* mergeKLists(vector<ListNode*>& lists) {
int sz = lists.size();
if (sz == 0) return NULL;
while (sz > 1) {
int half = sz / 2;
for (int i = 0; i < half; i++) {
lists[i] = mergeTwoLists(lists[i], lists[sz - i - 1]);
}
sz = sz - half; // 大小减小一半
}
return lists[0];
}
// 合并两个有序链表
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
ListNode *result = new ListNode(0);
ListNode *tail = result;
while (l1 && l2) {
if (l1->val < l2->val) {
ListNode *next = l1->next;
l1->next = NULL;
tail->next = l1;
tail = l1;
l1 = next;
} else {
ListNode *next = l2->next;
l2->next = NULL;
tail->next = l2;
tail = l2;
l2 = next;
}
}
if (l1) tail->next = l1;
else tail->next = l2;
return result->next;
}
};
11,根据val分隔链表
将小于val的放置于链表前面,大于等于val的放置于链表后面,保持初始相对位置。
来源:86. 分隔链表 && 面试题 02.04. 分割链表(无需保持初始相对位置)
思路:定义2个链表small和big,small存储小于val的节点,big存储其他节点。small和big采用尾插法创建。
class Solution {
public:
ListNode* partition(ListNode* head, int x) {
ListNode *small = new ListNode(0); // 小于x的链表
ListNode *stail = small;
ListNode *big = new ListNode(0); // 大于等于x的链表
ListNode *btail = big;
while (head) {
ListNode *next = head->next;
head->next = NULL;
if (head->val < x) {
stail->next = head;
stail = head;
} else {
btail->next = head;
btail = head;
}
head = next;
}
stail->next = big->next;
return small->next;
}
};
12,将链表平均切割成k个链表
来源:725. 分隔链表
思路:链表总长度/k=n余m,每段链表长度为n,因为有余数m,前m段链表长度+1。
class Solution {
public:
vector<ListNode*> splitListToParts(ListNode* root, int k) {
vector<ListNode*> result(k, NULL);
int sz = size(root);
int n = sz / k;
int m = sz % k;
for (int i = 0; i < k; i++) {
int l = n-1; // 为了断开最后一个节点的next指针,这里少数一个节点。
if (i < m) l += 1;
// 数前l个节点
ListNode *head = root;
while (l-- > 0) root = root->next;
if (root == NULL) break;
ListNode *next = root->next;
root->next = NULL;
result[i] = head;
root = next;
}
return result;
}
int size(ListNode *p) {
int sz = 0;
while (p) {
sz++;
p = p->next;
}
return sz;
}
};
13,反转单链表的一部分
来源:92. 反转链表 II
思路:重新构建单链表,链表分成前、中、后三部分,前部分采用尾插法构建,后面部分直接返回,中间部分采用头插法构建(需要注意tail指针,tail始终指向结果链表的最后一个节点)。
class Solution {
public:
ListNode* reverseBetween(ListNode* head, int left, int right) {
ListNode *result = new ListNode(0);
ListNode *tail = result;
ListNode *last = NULL;
int cnt = 1;
while (head) {
ListNode *next = head->next;
if (cnt < left) { // 前面部分
tail->next = head;
tail = head;
} else if (cnt > right) { // 后面部分
tail->next = head;
break;
} else { // 中间部分
head->next = NULL;
if (last == NULL) { // last指向前面部分的最后一个节点
last = tail;
last->next = head;
tail = head;
} else {
head->next = last->next;
last->next = head;
}
}
head = next;
cnt++;
}
return result->next;
}
};
14,回文单链表
来源:面试题 02.06. 回文链表 && 234. 回文链表
思路:将链表平均分成前后两个链表,反转后面链表,对比两个链表是否一样。
class Solution {
public:
bool isPalindrome(ListNode* head) {
int sz = size(head);
int half = sz / 2;
if (sz % 2 == 1) half += 1;
ListNode *p = head;
while (half-- > 0) p = p->next;
// 链表分成两部分p和q,p的长度大于等于q的长度,反转q与p比较
ListNode *q = reverseList(p);
p = head;
while (q) {
if (p->val != q->val) return false;
p = p->next;
q = q->next;
}
return true;
}
int size(ListNode *p) {
int sz = 0;
while (p) {
sz++;
p = p->next;
}
return sz;
}
ListNode* reverseList(ListNode* head) {
ListNode *result = NULL;
while (head) {
ListNode *next = head->next; // 保存下一个节点
head->next = result; // 插入单链表开头
result = head; // 更新单链表
head = next; // 继续迭代
}
return result;
}
};
15,奇偶链表
给定一个单链表,把所有奇数位置节点和偶数位置节点分别排在一起。
来源:328. 奇偶链表
思路:定义2个链表p和q,分别记录奇数位置节点和偶数位置节点。
class Solution {
public:
ListNode* oddEvenList(ListNode* head) {
ListNode *p = new ListNode(0);
ListNode *ptail = p;
ListNode *q = new ListNode(0);
ListNode *qtail = q;
int cnt = 1;
while (head) {
ListNode *next = head->next;
head->next = NULL;
if (cnt++ % 2 == 1) {
ptail->next = head;
ptail = head;
} else {
qtail->next = head;
qtail = head;
}
head = next;
}
ptail->next = q->next;
return p->next;
}
};
16,链表入环的第一个节点
来源:142. 环形链表 II
不正常思路1:利用节点地址的大小,先申请的地址小,后申请的地址大。下一个节点地址小,则返回下一个节点。
struct ListNode *detectCycle(struct ListNode *head) {
if (head == NULL) return NULL;
while (head) {
if (head >= head->next) return head->next;
head = head->next;
}
return NULL;
}
不正常思路2:遍历链表,修改节点值,如果是正数,则+100000,否则-100000。当遇到>100000或<-100000的节点即为结果。
struct ListNode *detectCycle(struct ListNode *head) {
int v = 1000000;
while (head) {
if (head->val < -v || head->val > v) {
return head;
} else {
head->val += head->val < 0 ? -v : v;
head = head->next;
}
}
return NULL;
}
思路:讨论有环的情况,借用官方题解图如下,
定义快慢指针,快指针每次移动2个节点,慢指针每次移动1个节点,二者相遇于紫色点。
假如快指针沿着环走了n圈,则快指针移动节点数量:a + b + (b + c) * n。
假如慢指针沿着环走了m圈,则慢指针移动节点数量:a + b + (b + c) * m。
快指针移动数量是慢指针的2倍,所以:
a + b + (b + c) * n = 2 (a + b + (b + c) * m)
n(b + c) = a + b + 2m(b + c)
a + b = (n - 2m) (b + c)
n-2m必定是一个整数,假设为k,则:
a + b = k (b + c)
a - c = k(b+c) - b - c
a - c = (k-1)(b+c)
所以a-c后剩余的节点数量是环上节点数量的整数倍。
当快慢指针相遇时,定义一个指针p从head开始,当p与慢指针相遇时即为所求。
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode *fast = head;
ListNode *slow = head;
while (fast && fast->next) {
fast = fast->next->next;
slow = slow->next;
if (fast == slow) { // 有环时判断
ListNode *p = head;
while (p != slow) {
p = p->next;
slow = slow->next;
}
return p;
}
}
return NULL;
}
};
17,两链表相加
思路:类大数相加,注意进位。
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
ListNode *result = new ListNode(0);
ListNode *tail = result;
int flag = 0; // 进位
while (l1 || l2) { // 一方为空时继续相加
int s = flag;
if (l1) s += l1->val;
if (l2) s += l2->val;
if (s > 9) {
s = s - 10;
flag = 1;
} else {
flag = 0;
}
ListNode *n = new ListNode(s);
tail->next = n;
tail = n;
if (l1) l1 = l1->next;
if (l2) l2 = l2->next;
}
if (flag) {
ListNode *n = new ListNode(flag);
tail->next = n;
}
return result->next;
}
};
18,链表向右移动k个位置
来源:61. 旋转链表
思路:将链表分成前后两部分A和B,长度分别为sz-k和k,返回BA。
class Solution {
public:
ListNode* rotateRight(ListNode* head, int k) {
int sz = size(head);
if (sz < 2) return head;
k = k % sz;
if (k == 0) return head;
// 链表分成两部分A和B,长度分别为sz-k和k,返回BA
ListNode *p = head;
int a = sz - k;
while (--a > 0) p = p->next; // p指向A的最后一个节点
ListNode *result = p->next;
p->next = NULL;
// 链接B和A
p = result;
while (p->next) p = p->next;
p->next = head;
return result;
}
int size(ListNode *p) {
int sz = 0;
while (p) {
sz++;
p = p->next;
}
return sz;
}
};
19,根据子集分成多段
来源:817. 链表组件
思路:子集创建成hash。如果节点在子集中,则计数并跳过其后在子集中的节点
class Solution {
public:
int numComponents(ListNode* head, vector<int>& nums) {
vector<int> g(10000, false);
for (auto n : nums) g[n] = true;
int result = 0;
while (head) {
if (g[head->val]) { // 如果节点在子集中,则计数并跳过其后在子集中的节点
while (head && g[head->val]) {
head = head->next;
}
result++;
} else {
head = head->next;
}
}
return result;
}
};
20,链表替换另一链表的部分
来源:1669. 合并两个链表
思路:尾插法创建新链表。注意a,b区间。
class Solution {
public:
ListNode* mergeInBetween(ListNode* list1, int a, int b, ListNode* list2) {
ListNode *result = new ListNode(0);
ListNode *tail = result;
int cnt = 0;
while (cnt++ < a) {
tail->next = list1;
tail = list1;
list1 = list1->next;
}
while (list2) {
tail->next = list2;
tail = list2;
list2 = list2->next;
}
while (cnt++ <= b) {
list1 = list1->next;
}
tail->next = list1->next;
return result->next;
}
};
21,第k个节点与倒数第k个节点交换
思路:利用题5计算倒数第k个节点,然后正数第k个节点,交换。
class Solution {
public:
ListNode* swapNodes(ListNode* head, int k) {
ListNode *second = getKthFromEnd(head, k);
ListNode *first = head;
while (--k > 0) first = first->next;
swap(first->val, second->val);
return head;
}
ListNode* getKthFromEnd(ListNode* head, int k) {
ListNode *first = head;
while (k-- > 0) first = first->next;
ListNode *second = head;
while (first) {
first = first->next;
second = second->next;
}
return second;
}
};
22,链表排序
要求:时间复杂度O(N logN),空间复杂度O(1)
来源:148. 排序链表
思路:递归不满足空间复杂度O(1),所以不能使用递归。要想满足时间复杂度,可使用归并排序。
长度为k的两个有序链表合并为长度为2K的链表,k=1,2,4,8...
class Solution {
public:
ListNode* sortList(ListNode* head) {
int sz = size(head);
ListNode *result = new ListNode(0, head);
for (int k = 1; k < sz; k *= 2) {
ListNode *prev = result;
ListNode *curr = result->next;
while (curr) {
// 将curr链表分成3部分:head1, head2, left
// head1和head2长度最大为k,left为剩余节点
ListNode *head1 = curr;
ListNode *head2 = NULL;
ListNode *left = NULL;
for (int i = 1; i < k && curr; i++) curr = curr->next;
if (curr) {
head2 = curr->next;
curr->next = NULL; // 让head1有尾节点
curr = head2;
for (int i = 1; i < k && curr; i++) curr = curr->next;
if (curr) {
left = curr->next; // 剩余节点
curr->next = NULL;
}
}
ListNode *merged = mergeTwoLists(head1, head2);
prev->next = merged;
prev = getTail(merged);
curr = left;
}
}
return result->next;
}
void print(char *title, ListNode *h) {
printf("%s:", title);
while (h) {
printf("%d ", h->val);
h = h->next;
}
printf("\n");
}
int size(ListNode *p) {
int sz = 0;
while (p) {
sz++;
p = p->next;
}
return sz;
}
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
ListNode *result = new ListNode(0);
ListNode *tail = result;
while (l1 && l2) {
if (l1->val < l2->val) {
ListNode *next = l1->next;
l1->next = NULL;
tail->next = l1;
tail = l1;
l1 = next;
} else {
ListNode *next = l2->next;
l2->next = NULL;
tail->next = l2;
tail = l2;
l2 = next;
}
}
if (l1) tail->next = l1;
else tail->next = l2;
return result->next;
}
ListNode *getTail(ListNode *l) {
if (l == NULL) return NULL;
while (l->next) l = l->next;
return l;
}
};
23,复制复杂链表
来源:剑指 Offer 35. 复杂链表的复制 && 138. 复制带随机指针的链表
思路1:定义一个map,key是原节点地址,value是复制的新节点地址。value.random = first.random.second。
思路2:将复制的新节点挂接到原节点后,行程一个新的节点:原节点1 - 新节点1 - 原节点2 - 新节点2 - 原节点3 - 新节点3 。。。,新节点random=原节点random->next,最后拆分链表。
class Solution {
public:
Node* copyRandomList(Node* head) {
if (head == NULL) return NULL;
Node *p = head;
// 复制节点,接到原节点后
while (p) {
Node* tmp = new Node(p->val);
tmp->next = p->next;
p->next = tmp;
p = tmp->next;
}
// 更新random指针
p = head;
while (p) {
if (p->random) p->next->random = p->random->next;
p = p->next->next;
}
// 拆分两链表
Node *prev = head;
Node *curr = head->next;
Node *result = curr;
while (curr->next) {
prev->next = prev->next->next;
curr->next = curr->next->next;
prev = prev->next;
curr = curr->next;
}
prev->next = NULL;
return result;
}
};