当前位置: 首页 > news >正文

数据结构——线性表(链表,力扣中等篇,技巧型)

文章目录

  • 一、力扣中等篇
    • 1.1去重【有序2+无序1】
    • 1.2分隔链表
      • 1.2.1分割链表【按val分2组】
      • 1.2.2奇偶链表【按index分成2组】
      • 1.2.3分隔链表【按组别分成K组】
      • 1.2.4旋转链表【分隔+环】
      • 1.2.5重排链表【分割,反转,合并】
    • 1.3排序
      • 1.3.1插入排序
      • 1.3.2归并排序
    • 1.4链表求和
      • 1.4.1两数相加1
      • 1.4.2两数相加2
      • 1.4.3链表翻倍

一、力扣中等篇

序号题目链接
1有序链表去重(包含重复节点)https://leetcode.cn/problems/remove-duplicates-from-sorted-list/description/?envType=problem-list-v2&envId=linked-list
2有序链表去重(不包含重复节点)https://leetcode.cn/problems/remove-duplicates-from-sorted-list-ii/?envType=problem-list-v2&envId=linked-list
3无序链表去重https://leetcode.cn/problems/remove-duplicate-node-lcci/description/?envType=problem-list-v2&envId=linked-list
4分隔链表【按值分2组】https://leetcode.cn/problems/partition-list/description/?envType=problem-list-v2&envId=linked-list
5奇偶链表【按index分2组】https://leetcode.cn/problems/odd-even-linked-list/description/?envType=problem-list-v2&envId=linked-list
6分隔链表【按K分K组】https://leetcode.cn/problems/split-linked-list-in-parts/description/?envType=problem-list-v2&envId=linked-list
7旋转链表https://leetcode.cn/problems/rotate-list/description/?envType=problem-list-v2&envId=linked-list
8重排链表https://leetcode.cn/problems/reorder-list/solutions/2843011/yi-mu-liao-ran-de-tu-shi-bu-zou-by-shawx-k3o8/?envType=problem-list-v2&envId=linked-list
序号题目链接
1链表插入排序https://leetcode.cn/problems/insertion-sort-list/description/?envType=problem-list-v2&envId=linked-list
2链表排序https://leetcode.cn/problems/sort-list/description/?envType=problem-list-v2&envId=linked-list
3两数相加1https://leetcode.cn/problems/add-two-numbers/description/?envType=problem-list-v2&envId=linked-list
4两数相加2https://leetcode.cn/problems/add-two-numbers-ii/description/?envType=problem-list-v2&envId=linked-list
5链表翻倍https://leetcode.cn/problems/double-a-number-represented-as-a-linked-list/description/?envType=problem-list-v2&envId=linked-list

1.1去重【有序2+无序1】

情况1:移除排好序的序列中的重复元素【包括重复元素】
我们可以利用链表已排序的特性,通过一次趟一次次遍历完成操作。因为链表是排序的,重复元素一定相邻,所以只需比较当前节点和下一个节点的值。
在这里插入图片描述

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】
    在这里插入图片描述
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,两个节点向后移动。
    在这里插入图片描述
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 的节点,最后将这两个链表连接起来,保持原有节点的相对顺序。

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为奇数和偶数的分隔开后重新连接。

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】
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:将链表分成两个小链表,然后将两个链表交换位置,重新连接即可。

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;//处理Kif(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:先将链表连成环,再找到新的头节点位置并断开环,从而完成旋转操作。

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;//处理Kif(k==0) {p->next=nullptr;return head;}//新的尾部节点结点在倒数第K-1个位置,新的头结点在tail—>nextListNode* 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->…
  • 将两部分交替合并
    在这里插入图片描述

在这里插入图片描述

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;//反转l2l2=reverse(l2);//合并merge(l1,l2);
}

1.3排序

1.3.1插入排序

如下为数组插入排序的代码。其原理是:

  • 将数组分为【已经排好序的】【待插入元素】【未排序】
  • 然后将待插入元素插入到已经排好序的序列中。
  • sorted指向已经排好序的序列尾部
  • cur指向当前待插入元素
  • pre指向插入位置的前一个元素
    在这里插入图片描述
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;} 
} 
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。递归调用时左链表不会缩小,最终触发无限递归】
  • 对左右两部分分别进行递归排序。递归终止条件:当链表为空或只有一个节点时,直接返回(已排序)
  • 将两个有序链表合并成一个。
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

它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
最高位:尾结点。最低位:头结点。

在这里插入图片描述

while(A不为空 || B不为空 || 最后没有进位){A的当前值B的当前值sum=A的当前值+B的当前值+进位carry  【进位初始为0】最终当前位=%10;最终进位=/10}
需要注意的是:最后一位的进位。
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计算进位等——结果反转。
http://www.dtcms.com/a/349549.html

相关文章:

  • Postman 模拟mcp tool调用过程
  • 【数据结构】顺序表详解
  • Flink hop window(滑动窗口)详解
  • leetcode 498. 对角线遍历 中等
  • Linux下的软件编程——网络编程(http)
  • C++14 到 C++20 全面解析:语言新特性、标准库演进与实战案例
  • 【二叉树 - LeetCode】617. 合并二叉树
  • [QMT量化交易小白入门]-八十三、8月因为通信行业,QMT平台ETF轮动策略年化达到了168.56%
  • 降本增效:基于 JavaScript 的 AI 编程 IDE 上下文压缩优化方案
  • CloudBase云开发MCP + CodeBuddy IDE:打造智能化全栈理财助手的完整实践
  • 本地生活新风口:“我店模式”入局正当时??
  • Web程序设计
  • 【前端安全】前端安全第一课:防止 XSS 和 CSRF 攻击的常见手法
  • 新型HTTP走私攻击技术使攻击者可注入恶意请求
  • 从0死磕全栈第1天:从写一个React的hello world开始
  • k8s笔记04-常用部署命令
  • 血缘元数据采集开放标准:OpenLineage Integrations Apache Spark Quickstart with Jupyter
  • SDC命令详解:使用set_timing_derate命令进行约束
  • 基于C语言实现的KV存储引擎(二)
  • ‌重塑培训架构,助力企业人才战略升级‌
  • 【C语言16天强化训练】从基础入门到进阶:Day 10
  • CPLD与FPGA
  • 《Password Guessing Using Large Language Models》——论文阅读
  • 企业级Java项目整合ELK日志收集分析可视化
  • [论文阅读]RQ-RAG: Learning to Refine Queries for Retrieval Augmented Generation
  • 大模型知识--MCP
  • 无人机芯片休眠模式解析
  • Linux系统的网络管理(一)
  • 血缘元数据采集开放标准:OpenLineage Integrations Apache Spark Main Concepts Installation
  • 05 开发环境和远程仓库Gitlab准备