1.链表基础知识
LeetCode的链表基本上是结构体实现的。
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
分类:单链表、双链表
操作类型:插入、删除、修改、查询,凡是头节点head可能被操作的题目,通常设置dummy虚拟节点,可以减少边界情况~
2.合并类题目
21. 合并两个有序链表(剑指 Offer 25. 合并两个排序的链表)
class Solution {
public:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
auto dummy = new ListNode(-1);//虚拟头节点
auto p1 = l1, p2 = l2, tmp = dummy;
while(p1 && p2)
{
if(p1->val <= p2->val) {tmp->next = p1; p1 = p1->next;}
else {tmp->next = p2; p2 = p2->next;}
tmp = tmp->next;
}
if(p1) tmp->next = p1;
if(p2) tmp->next = p2;
return dummy->next;
}
};
23. 合并K个升序链表
归并思想,借助两两合并
class Solution {
public:
//两个链表合并,返回链表头
ListNode* mergeTwoLists(ListNode* l1,ListNode* l2)
{
auto dummy = new ListNode(-1);//虚拟头节点
auto p1 = l1, p2 = l2, tmp = dummy;
while(p1 && p2)
{
if(p1->val <= p2->val) {tmp->next = p1; p1 = p1->next;}
else {tmp->next = p2; p2 = p2->next;}
tmp = tmp->next;
}
if(p1) tmp->next = p1;
if(p2) tmp->next = p2;
return dummy->next;
}
//归并思想
ListNode* merge(vector<ListNode*>& lists, int l, int r)
{
if(l == r) return lists[l];
if(l > r) return nullptr;
int mid = (l + r) >> 1;
return mergeTwoLists(merge(lists, l, mid),merge(lists, mid + 1, r));
}
ListNode* mergeKLists(vector<ListNode*>& lists) {
if(lists.size() < 1) return nullptr;
return merge(lists, 0,lists.size() - 1);
}
};
3.删除类题目
一般而言,删除某个节点需要找到前驱节点~可以借助两个指针
19. 删除链表的倒数第N个节点
此题保证了n有效
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
//删除倒数第n个,那就找倒数第n+1个
auto dummy = new ListNode(-1);
dummy ->next = head;
auto p1 = dummy, p2 = dummy;
while(n -- ) p1 = p1->next;//first往后走n步,
while(p1->next)//second指向倒数第n+1个点
{
p1 = p1->next;
p2 = p2->next;
}
p2->next = p2->next->next;
return dummy->next;
}
};
剑指 Offer 22. 链表中倒数第k个节点
另外返回倒数第k个节点的题目,k可能大于链表总数,需要判断:p1走k-1步后若为空,说明整个就不足k个,返回头节点即可
class Solution {
public:
ListNode* getKthFromEnd(ListNode* head, int k) {
//找倒数第k个
auto p1 = head, p2 = head;
//不用设置虚拟节点,多了以下一个判断而已
k --;
while(k -- && p1 ) p1 = p1->next;
if(p1 == nullptr) return head;
while(p1->next)//second指向倒数第n+1个点
{
p1 = p1->next;
p2 = p2->next;
}
return p2;
}
};
剑指 Offer 18. 删除链表的节点
class Solution {
public:
ListNode* deleteNode(ListNode* head, int val) {
auto dummy = new ListNode(-1);
dummy->next = head;
auto p1 = dummy;
//找要删除的节点的前一个节点,所以用p1->next判断是不是找到了
while(p1->next && p1->next->val != val)
p1 = p1->next;
//while停止循环的两种可能:找到了,为空了
if(p1->next->val == val) p1->next = p1->next->next;
return dummy->next;
}
};
83. 删除排序链表中的重复元素
这个题可以保留第一个出现的元素,所以比较简单,目的还是找需要删除节点的上一个节点,遍历的时候判断next的val和当前是否相同,相同则删掉
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
//采取保留第一个元素的方法:遍历每个元素,和下一个相同则删除下一个,不同则移动到下一个
ListNode* cur = head;
while(cur)
{
ListNode* tmp = cur->next;
while( tmp && tmp->val == cur->val) tmp = tmp->next;
cur->next = tmp;
cur = cur->next;
}
return head;
}
};
82. 删除排序链表中的重复元素 II
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
//1.dummy节点;2.扫描一段节点,个数>1则全部删除,=1则遍历移动
ListNode* dummy = new ListNode(-1);
dummy->next = head;
ListNode* cur = dummy;
while(cur->next)//每次扫描后一段节点
{
ListNode* tmp = cur->next;
while(tmp && tmp->val == cur->next->val) tmp = tmp->next;
if(tmp == cur->next->next)
{
cur = cur->next;
}
else cur->next = tmp;
}
return dummy->next;
}
};
4.有环链表类题目
141. 环形链表
class Solution {
public:
//方法一:hash表,如果遍历到已经存在的点,就是有环,否则能遍历到null就是无
bool hasCycle(ListNode *head) {
//1.链表为空或者只有一个元素;false
if(!head || !head->next) return false;//空节点或者只有一个节点
ListNode* fast = head;
ListNode* slow = head;
while(fast)
{
fast = fast->next;
slow = slow->next;
if(fast) fast = fast->next;
if(fast == slow) break;
}
if(fast) return true;
else return false;
}
};
142. 环形链表 II
在上一道题基础上,多了相遇后继续走的过程,可以严格证明的
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
auto fast = head,slow = head;
//第一部分和上一道题完全一样~
while(fast)
{
fast = fast->next;
slow = slow ->next;
if(fast) fast = fast->next;
else break;
//在环内第一次相遇后,让fast回到起点继续走,走a后两个肯定都到达入口点
if(fast == slow)
{
fast = head;
while(slow!=fast)
{
slow = slow->next;
fast = fast->next;
}
return slow;
}
}
return NULL;
}
};
5.相交链表类题目
160. 相交链表(剑指 Offer 52. 两个链表的第一个公共节点)
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
//让p走a1a2+c1c2c3+b1b2b3
//让q走b1b2b3+c1c2c3+a1a2
//然后两个链表走完所有点后肯定会在c1相遇,如果不相交,c1就为null,也直接返回就好了
auto p = headA, q = headB;
while(p != q)
{
if(p) p = p->next;
else p = headB;
if(q) q = q->next;
else q = headA;
}
return p;
}
};
6.调整顺序类题目
206. 反转链表(剑指 Offer 24. 反转链表)(剑指 Offer 06. 从尾到头打印链表)
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if(head==nullptr || head->next == nullptr) return head;
ListNode* pre = nullptr;
auto cur = head;
while(cur)
{
auto next = cur->next;
cur->next = pre;
pre = cur,cur = next;
}
return pre;
}
};
92. 反转链表 II
在上一题基础上主要是多了寻找区间和保留节点的步骤~
class Solution {
public:
ListNode* reverseBetween(ListNode* head, int m, int n) {
if(m == n) return head;
auto dummy = new ListNode(-1);
dummy->next = head;
//找ad两点
auto a = dummy, d = dummy;
m --;
while(m --) a = a->next;
while(n --) d = d->next;
//找bc两点
auto b = a->next;
auto c = d->next;
//修改b->next开始到d的指针,用pre和cur变量
auto pre = b, cur = b->next;
while(cur != c)
{
auto next = cur->next;
cur->next = pre;
pre = cur;
cur = next;
}
//最后连接起来
a->next = d;
b->next = c;
return dummy->next;
}
};
61. 旋转链表
class Solution {
public:
ListNode* rotateRight(ListNode* head, int k) {
if(!head || !head->next) return head;
//先求总长度,k%n
int n = 0;
for(auto tmp = head;tmp;tmp = tmp->next) n ++;
k %= n;
//找倒数第k+1个点和最后一个点
auto p1 = head,p2 = head;
while(k --) p1 = p1->next;//先走k步
while(p1->next)
{
p1 = p1->next;
p2 = p2->next;
}
//p2在倒数第k+1个点,p1在最后一个点
p1->next = head;
head = p2->next;
p2->next = nullptr;
return head;
}
};
24. 两两交换链表中的节点
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
auto dummy = new ListNode(-1);
dummy ->next = head;
for(auto p = dummy; p && p->next && p->next->next;)
{//p和要交换的一对ab一起,移动时p要移动到下一对ab的前面,也就是直接p=a
auto a = p->next, b = a->next;
p->next = b;
a->next = b->next;
b->next = a;
p = a;
}
return dummy->next;
}
};
234. 回文链表
class Solution {
public:
/*
方法一:用数组存储再用首尾指针判断:时间o(n)空间o(n)
方法二:由于只需要o(1)空间,所以是两个指针同时移动和比较,需要翻转后一半的链表:用快慢指针找到中间的点,翻转中间点以后的点
*/
bool isPalindrome(ListNode* head) {
if(!head || !head->next) return true;
ListNode* fast = head;//找中间节点
ListNode* slow = head;
while(fast)
{
fast = fast->next;
slow = slow->next;
if(fast) fast = fast->next;
}
ListNode* pre = nullptr;//旋转右边的链表
ListNode* cur = slow;
while(cur)
{
ListNode* tmp = cur->next;
cur->next = pre;
pre = cur;
cur = tmp;
}
ListNode* p1 = pre;//逐个比较
ListNode* p2 = head;
while(p1 && p2)
{
if(p1->val != p2->val) return false;
else
{
p1 = p1->next;
p2 = p2->next;
}
}
return true;
}
};
7.其余简单顺序题目
2. 两数相加
从低位开始加,用t存储往前的进位即可,最后还要判断是否有进位1
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
if(!l1 && !l2) return nullptr;
auto p1 = l1,p2 = l2;
auto dummy = new ListNode(-1);
auto head = dummy;
int t = 0;
while(p1 || p2)
{
int x = t;
if(p1) x += p1->val,p1 = p1->next;
if(p2) x += p2->val,p2 = p2->next;
auto tmp = new ListNode(x % 10);
head -> next = tmp;
head = tmp;
t = x / 10;
}
if(t) {auto tmp = new ListNode(1); head->next = tmp;}
return dummy->next;
}
};
328. 奇偶链表
class Solution {
public:
ListNode* oddEvenList(ListNode* head) {
//e找偶数点,o找奇数点,两个互相借助,最后拼接头节点
if(!head || !head->next) return head;
ListNode* ehead = head->next;
ListNode* e = ehead;
ListNode* o = head;
while(e && e->next)
{
o->next = e->next;
o = o->next;
e->next = o->next;
e = e->next;
}
o->next = ehead;//奇前偶后
return head;
}
};