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

文章目录

一、力扣中等篇

序号 题目 链接
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 两数相加1 https://leetcode.cn/problems/add-two-numbers/description/?envType=problem-list-v2\&envId=linked-list
4 两数相加2 https://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:移除排好序的序列中的重复元素【包括重复元素】

我们可以利用链表已排序的特性,通过一次趟一次次遍历完成操作。因为链表是排序的,重复元素一定相邻,所以只需比较当前节点和下一个节点的值。

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计算进位等------结果反转。
相关推荐
月盈缺2 小时前
学习嵌入式的第二十五天——哈希表和内核链表
学习·链表·散列表
小xin过拟合3 小时前
day20 二叉树part7
开发语言·数据结构·c++·笔记·算法
刘 大 望3 小时前
网络编程--TCP/UDP Socket套接字
java·运维·服务器·网络·数据结构·java-ee·intellij-idea
寻星探路3 小时前
数据结构青铜到王者第三话---ArrayList与顺序表(1)
数据结构
今后1233 小时前
【数据结构】顺序表详解
数据结构·顺序表
啟明起鸣3 小时前
【数据结构】B 树——高度近似可”独木成林“的榕树——详细解说与其 C 代码实现
c语言·开发语言·数据结构
这周也會开心3 小时前
数据结构-ArrayList
数据结构
hrrrrb3 小时前
【数据结构】栈和队列——队列
数据结构
XMZH030423 小时前
数据结构:单向链表的逆置;双向循环链表;栈,输出栈,销毁栈;顺序表和链表的区别和优缺点;0825
数据结构·链表·