文章目录
一、力扣中等篇
1.1去重【有序2+无序1】
情况1:移除排好序的序列中的重复元素【包括重复元素】
我们可以利用链表已排序的特性,通过一次趟一次次遍历完成操作。因为链表是排序的,重复元素一定相邻,所以只需比较当前节点和下一个节点的值。
cpp
ListNode* deleteDuplicates(ListNode* head) {
if(head==nullptr || head->next==nullptr) return head;
ListNode* p=head;
while(p->next!=nullptr){//p->next->next不能无效
if(p->val == p->next->val){
p->next=p->next->next;
}
else p=p->next;
}
return head;
}
情况2:移除排好序的序列中的重复元素【不包括重复元素】
- 当碰到重复值后:记录该重复值,将所有等于该重复值的节点跳过
- 没碰到重复值:将pre和p都向后移动一个,继续判断。
注意终止条件【P!=null,因为会涉及到判断p->val】
cpp
ListNode* deleteDuplicates(ListNode* head) {
if(head==nullptr || head->next==nullptr) return head;
ListNode* dummy=new ListNode(0,head);
ListNode* prev=dummy;
ListNode* p=head;
while(p!=nullptr && p->next!=nullptr){
if(p->val==p->next->val){
int x=p->val;
while(p!=nullptr && p->val==x){
p=p->next;
}
prev->next=p;
}
else{
prev=prev->next;
p=p->next;
}
}
return dummy->next;
}
情况3:无序链表直接去重
当链表无序时,去重操作无法像有序链表那样通过简单比较相邻节点来完成,需要借助额外的数据结构来记录已出现过的值。常用的方法是使用集合来存储已经遍历过的节点值visited,从而快速判断当前节点是否重复。
- 当前节点是重复节点:删除该节点【因此还应该记录cur节点的前一个节点prev】,两个节点向后移动。
- 当前节点不是重复节点:加入visited,两个节点向后移动。
cpp
ListNode* removeDuplicateNodes(ListNode* head) {
if(head==nullptr || head->next==nullptr) return head;
unordered_set<int> visited;
ListNode* prev=head;
ListNode* cur=head->next;
visited.insert(head->val);
while(cur!=nullptr){
//是重复节点
if(visited.find(cur->val) != visited.end()){
prev->next=cur->next;
cur=prev->next;
}
//不是重复节点
else{
visited.insert(cur->val);
prev=prev->next;
cur=cur->next;
}
}
return head;
}
1.2分隔链表
1.2.1分割链表【按val分2组】

思路就是创建两个临时链表,分别存储小于 x 的节点和大于或等于 x 的节点,最后将这两个链表连接起来,保持原有节点的相对顺序。
cpp
ListNode* partition(ListNode* head, int x) {
//链表1:smallhead,寻找第一个比x小的数
//链表2:largehead:大于等于x的数
ListNode* smalldummy=new ListNode();
ListNode* largedummy=new ListNode();
ListNode* small=smalldummy;
ListNode* large=largedummy;
ListNode* p=head;
while(p!=nullptr){
if(p->val < x){
small->next=p;
small=small->next;
p=p->next;
}else{
large->next=p;
large=large->next;
p=p->next;
}
}
large->next=nullptr;
small->next=largedummy->next;
return smalldummy->next;
}
1.2.2奇偶链表【按index分成2组】

思路很简单,和上述类似,两个随机链表,将index为奇数和偶数的分隔开后重新连接。
cpp
ListNode* oddEvenList(ListNode* head) {
ListNode* list1dummy=new ListNode();
ListNode* list2dummy=new ListNode();
ListNode* list1=list1dummy;
ListNode* list2=list2dummy;
ListNode* p=head;
int index=1;
while(p!=nullptr){
if(index%2!=0){
list1->next=p;
list1=list1->next;
p=p->next;
}else{
list2->next=p;
list2=list2->next;
p=p->next;
}
index++;
}
list2->next=nullptr;
list1->next=list2dummy->next;
return list1dummy->next;
}
1.2.3分隔链表【按组别分成K组】
我们需要将单链表分隔成 k 个连续部分,使各部分长度尽可能相等,且前面部分长度大于或等于后面部分。
- 步骤 1:计算总长度,遍历链表得到总长度 length = 8
- 步骤 2:计算各部分长度:
- 基础长度 base_len = 8 / 3 = 3
- 额外节点数 extra = 8 % 3 = 2
- 因此:第 1 、2部分长度为 3,第 3 部分长度为 2
- 步骤 3:分割链表
- 处理第 1 部分:cur遍历 3 个节点后(向后移动2次)指向 3,断开 3->next【1->2->3】
- 处理第 2 部分:cur遍历 3个节点后(向后移动2次)指向 6,断开 6->next【4->5->6】
- 处理第 3 部分:cur遍历 2 个节点后(向后移动1次) 指向 8,断开 8->next【7->8】
cpp
vector<ListNode*> splitListToParts(ListNode* head, int k) {
vector<ListNode*> result;
//计算链表长度
int length=0;
ListNode*p=head;
while(p!=nullptr){
p=p->next;
length++;
}
//计算每一组的长度
int base=length/k;
int extra=length%k;
ListNode* cur=head;
//一共K组
for(int i=1;i<=k;i++){
int count=base+(i<=extra?1:0);//计算当前组的长度
ListNode* start=cur;
//将cur向后移动count-1次
for(int j=1;j<=count-1;j++){
if(cur!=nullptr) cur=cur->next;
}
//断开链接
if(cur!=nullptr){
ListNode* nextstart=cur->next;
cur->next=nullptr;
cur=nextstart;
}
//加入结果
result.push_back(start);
}
return result;
}
1.2.4旋转链表【分隔+环】
旋转链表:将链表每个节点向右移动 k 个位置。
思路1:将链表分成两个小链表,然后将两个链表交换位置,重新连接即可。
cpp
int getlength(ListNode* head){
int length=1;
ListNode*p=head;
while(p->next!=nullptr){
p=p->next;
length++;
}
return length;
}
ListNode* rotateRight(ListNode* head, int k) {
if(head==nullptr|| head->next==nullptr) return head;
int length=getlength(head);
k=k%length;//处理K
if(k==0) return head;
//第1段链表
ListNode* list1head=head;
ListNode* list1tail=head;
for(int i=1;i<=length-k-1;i++){
list1tail=list1tail->next;
}
//第2段链表
ListNode* list2head=list1tail->next;
ListNode* list2tail=head;
while(list2tail->next!=nullptr) list2tail=list2tail->next;
//连接两段链表
list1tail->next=nullptr;//断开第一段的尾部
list2tail->next=list1head;//连接
return list2head;
}
思路2:先将链表连成环,再找到新的头节点位置并断开环,从而完成旋转操作。
cpp
ListNode* rotateRight(ListNode* head, int k) {
if(head==nullptr|| head->next==nullptr) return head;
//计算链表长度的同时,将p指向最后的尾结点
int length=1;
ListNode*p=head;
while(p->next!=nullptr){
p=p->next;
length++;
}
//连成环
p->next=head;
k=k%length;//处理K
if(k==0) {
p->next=nullptr;
return head;
}
//新的尾部节点结点在倒数第K-1个位置,新的头结点在tail--->next
ListNode* newtail=head;
for(int i=1;i<=length-k-1;i++){
newtail=newtail->next;
}
ListNode* newhead=newtail->next;
//断开环
newtail->next=nullptr;
return newhead;
}
1.2.5重排链表【分割,反转,合并】
给定一个单链表 L 的头节点 head ,单链表 L 表示为:
L0 → L1 → ... → Ln - 1 → Ln
请将其重新排列后变为:
L0 → Ln → L1 → Ln - 1 → L2 → Ln - 2 → ...
不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
思路就是变出:n->n-1->n-2->...出来
- 双指针法找到中间节点,拆分
- 后半部分反转既可以得到所需要的n->n-1->n-2->...
- 将两部分交替合并

cpp
ListNode* mid(ListNode* head){}
ListNode* reverse(ListNode* head){}
void merge(ListNode* list1,ListNode* list2){
ListNode* l1=list1;
ListNode* l2=list2;
while(l1!=nullptr && l2!=nullptr){
ListNode* l1next=l1->next;
ListNode* l2next=l2->next;
l1->next=l2;
l2->next=l1next;
l1=l1next;
l2=l2next;
}
}
void reorderList(ListNode* head) {
if(head==nullptr || head->next==nullptr) return ;
//找到中间节点
ListNode* midnode=mid(head);
//分成两个链表
ListNode* l1=head;
ListNode* l2=midnode->next;
midnode->next=nullptr;
//反转l2
l2=reverse(l2);
//合并
merge(l1,l2);
}
1.3排序
1.3.1插入排序
如下为数组插入排序的代码。其原理是:
- 将数组分为【已经排好序的】【待插入元素】【未排序】
- 然后将待插入元素插入到已经排好序的序列中。
sorted
指向已经排好序的序列尾部cur
指向当前待插入元素pre
指向插入位置的前一个元素
cpp
void InsertSort(int arr[],int n){
//从第二个元素,下标为1的元素开始
for(int i=1;i<n;i++){
//Step1:找到当前待插入元素
int key=arr[i];
//Step2:从后向前寻找合适的插入位置
int j;
for(j=i-1;j>=0;j--){
if(arr[j]>key) arr[j+1]=arr[j]; //不是合适的插入位置
else break; //是合适的插入位置
}
//Step3:插入
arr[j+1]=key;
}
}
cpp
ListNode* insertionSortList(ListNode* head) {
if(head==nullptr || head->next==nullptr) return head;
ListNode* dummy=new ListNode(0,head);
ListNode* sorted=head;
ListNode* cur=head->next;
while(cur!=nullptr){
if(cur->val >= sorted->val){//刚好是有序的,直接向后移动
sorted=cur;
cur=sorted->next;
}else{ //找到待插入位置的前一个节点
ListNode* pre=dummy;
while(pre->next->val <= cur->val){
pre=pre->next;
}
sorted->next=cur->next;
cur->next=pre->next;
pre->next=cur;
cur=sorted->next;
}
}
return dummy->next;
}
1.3.2归并排序
使用递归做归并排序:
- 使用快慢指针法找到链表中点,将链表分为左右两部分。需要注意的是fast从head->next开始,这样使得左部分长度 ≤ 右部分长度。【对于1->2。若fast=head,结束后会使得mid=slow指向2,fast指向null。递归调用时左链表不会缩小,最终触发无限递归】
- 对左右两部分分别进行递归排序。递归终止条件:当链表为空或只有一个节点时,直接返回(已排序)
- 将两个有序链表合并成一个。
cpp
ListNode* mid(ListNode* head){
ListNode* slow=head;
ListNode* fast=head->next;//这样能确保中点偏左,避免左链表无法拆分
while(fast!=nullptr && fast->next!=nullptr){
slow=slow->next;
fast=fast->next->next;
}
return slow;
}
//升序+升序合并=升序
ListNode* merge(ListNode* list1,ListNode* list2){}
ListNode* sortList(ListNode* head) {
if(head==nullptr || head->next==nullptr) return head;
//将链表分成两个链表
ListNode* midnode=mid(head);
ListNode* righthead=midnode->next;
midnode->next=nullptr;
//递归排序左链表和右链表
ListNode* left=sortList(head);
ListNode* right=sortList(righthead);
//合并左右的有序链表
return merge(left,right);
}
1.4链表求和
1.4.1两数相加1
它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
最高位:尾结点。最低位:头结点。

cpp
while(A不为空 || B不为空 || 最后没有进位){
A的当前值
B的当前值
sum=A的当前值+B的当前值+进位carry 【进位初始为0】
最终当前位=和%10;
最终进位=和/10;
}
需要注意的是:最后一位的进位。
cpp
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
ListNode* dummy=new ListNode();
ListNode* p=l1;
ListNode* q=l2;
ListNode* cur=dummy;
int carry=0;
while(p!=nullptr || q!=nullptr || carry != 0){
//获取当前值
int val1=(p==nullptr)?0:p->val;
int val2=(q==nullptr)?0:q->val;
//计算
int sum=val1+val2+carry;
int curval=sum%10;
carry=sum/10;
//新建节点,插入链表
ListNode* node=new ListNode(curval);
cur->next=node;
//三个指针都向后移动
cur=cur->next;
if(p!=nullptr) p=p->next;
if(q!=nullptr) q=q->next;
}
return dummy->next;
}
1.4.2两数相加2
最高位:头结点。最低位:尾结点。
只需要将链表反转:变成两数相加1的头结点为最低位。
然后再将结果反转即可。
1.4.3链表翻倍
- 思路1:看成是两个一模一样的链表相加就好了。
- 思路2:反转链表------乘2计算进位等------结果反转。