力扣(leetcode)题目总结——链表篇

leetcode 经典题分类

  • 链表
  • 数组
  • 字符串
  • 哈希表
  • 二分法
  • 双指针
  • 滑动窗口
  • 递归/回溯
  • 动态规划
  • 二叉树
  • 辅助栈

本系列专栏:点击进入 leetcode题目分类 关注走一波


前言 :本系列文章初衷是为了按类别整理出力扣(leetcode)最经典 题目,一共100多道题,每道题都给出了非常详细的解题思路算法步骤代码实现 。很多同学刚开始刷题都是按照力扣顺序刷题,其实这样对新手不太适用,刷题效果也很不好。因为力扣题目顺序是随机的,并没有按照算法分类,导致同一类型的算法强化训练不够,最后刷完也是迷迷糊糊的。所以本系列文章就是来帮你完成算法分类,针对每种算法做强化训练,保证让你以后遇到题目直接秒杀!

文章目录


两数相加

【题目描述】

给你两个非空 的链表,表示两个非负的整数。它们每位数字都是按照逆序 的方式存储的,并且每个节点只能存储一位数字。请你将两个数相加,并以相同形式返回一个表示和的链表。

【输入输出实例】

示例 1:

输入:l1 = [2,4,3], l2 = [5,6,4]

输出:[7,0,8] (342 + 465 = 807)

示例 2:

输入:l1 = [0], l2 = [0]

输出:[0]

示例 3:

输入:l1 = [9,9,9,9,9,9,9], l2 = [9,9,9,9]

输出:[8,9,9,9,0,0,0,1]

【算法思想】

建立一个新的链表,利用while循环直到指向链表1和链表2的指针全部为空为止,利用三目运算符来获取链表1和链表2的某结点的数值(如果链表1或链表2为空,则对应数值为0);

将获取到两链表对应的结点数值做加法运算(不要忘记+进位),同时更新下一次加法计算的进位,创建新的结点加入到链表中(利用尾插法,需要提前创建头指针和尾指针)。

最后while循环结束后,需要判断是否还有进位存在,来选择是否创建新的结点加入到链表中。

【算法描述】

cpp 复制代码
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) 
{
    ListNode* head = new ListNode(0);  //头指针指向虚拟头结点
    ListNode* tail = head;  //尾指针------------用于不断插入新节点(尾插法)
    int carry = 0;  //表示进位
    while(l1 || l2)  //创建一个新链表
    {
        int num1 = l1 ? l1->val : 0;
        int num2 = l2 ? l2->val : 0;
        int sum = num1 + num2 + carry;  //对应两数相加(包括进位)
        carry = sum / 10;  //进位
        ListNode* p = new ListNode(sum % 10);  //创建新结点
        tail->next = p;  //链接到新链表末尾
        tail = tail->next;  //更新tail尾指针
        if(l1)  //继续向后相加l1和l2对应的数
        {
            l1 = l1->next;
        }
        if(l2)
        {
            l2 = l2->next;
        }
    }
    if(carry != 0)  //当最后还有进位时,直接链接到新链表后
    {
        tail->next = new ListNode(carry);
        tail = tail->next;
    }
    return head->next;
}

删除链表的倒数第N个结点

【题目描述】

给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。尝试使用一趟扫描实现。

【输入输出实例】

示例 1:

输入:head = [1,2,3,4,5], n = 2

输出:[1,2,3,5]

示例 2:

输入:head = [1], n = 1

输出:[]

示例 3:

输入:head = [1,2], n = 1

输出:[1]

【算法思路】

可以使用两个指针left和right同时对链表进行遍历,并且使得right比left超前n个节点。当right遍历到链表的末尾时,left就恰好处于倒数第n个节点。

(1)创建虚头结点,令left指针指向它。

(2)移动right指针,使得right指针比left指针超前n个节点。

(3)当right指针比left指针超前n个节点时,两指针开始同时移动,当right指针遍历到链表的末尾时,left指针就恰好处于倒数第n个节点。

【算法描述】

cpp 复制代码
//双指针迭代法
ListNode* removeNthFromEnd(ListNode* head, int n) {
    ListNode* virhead = new ListNode(0, head);    //虚拟头结点
    ListNode* p_right = head;   //右指针
    ListNode* p_left = virhead;   //左指针
    while(n-- && p_right)    //让右指针超前左指针n个结点
    {
        p_right = p_right->next;
    }
    while(p_right)    //两指针同时向前遍历,直到右指针到链表末尾,左指针就恰好处于倒数第n个节点
    {
        p_right = p_right->next;
        p_left = p_left->next;
    }
    ListNode* p = p_left->next;    //删除倒数第n个结点
    p_left->next = p->next;
    delete p;
    return virhead->next;
}

//双指针递归法
ListNode* removeNthFromEnd(ListNode* head, int n) {
    ListNode* virhead = new ListNode(0, head);    //虚拟头结点
    ListNode* p_right = head;   //右指针
    ListNode* p_left = virhead;   //左指针
    while(n-- && p_right)    //让右指针超前左指针n个结点
    {
        p_right = p_right->next;
    }
    remove(p_left, p_right);
    return virhead->next;
}
//两指针同时向前遍历,直到右指针到链表末尾,左指针就恰好处于倒数第n个节点
void remove(ListNode* p_left, ListNode* p_right) {
    if(!p_right)    //删除倒数第n个结点
    {
        ListNode* p = p_left->next;
        p_left->next = p->next;
        delete p;
        return;
    }
    return remove(p_left->next, p_right->next);    //两指针同时向前遍历
}

【知识点】

在不知道链表或数组等数据类型总长度的情况下求倒数第n个结点的值,并且要求只循环一遍的情况下,可以利用双指针的方法,使得right比left超前n个节点。当right遍历到链表的末尾时,left就恰好处于倒数第n个节点。

合并两个有序链表

【题目描述】

将两个升序链表合并为一个新的升序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

【输入输出实例】

示例 1:

输入:l1 = [1,2,4], l2 = [1,3,4]

输出:[1,1,2,3,4,4]

示例 2:

输入:l1 = [], l2 = []

输出:[]

示例 3:

输入:l1 = [], l2 = [0]

输出:[0]

【算法思路】

方法一:双指针法

当list1和list2都不是空链表时,判断list1和list2哪一个链表的头节点的值更小,将较小值的节点添加到结果里,当一个节点被添加到结果里之后,将对应链表中的节点向后移一位。

首先,我们设定一个哨兵节点head ,这可以在最后让我们比较容易地返回合并后的链表。我们维护一个pre指针,我们需要做的是调整它的next指针。然后,我们重复以下过程,直到list1或者list2指向了null :如果list1当前节点的值小于等于list2,我们就把list1当前的节点接在pre节点的后面同时将list1指针往后移一位。否则,我们对list2做同样的操作。不管我们将哪一个元素接在了后面,我们都需要把pre向后移一位。

在循环终止的时候,list1和list2至多有一个是非空的。由于输入的两个链表都是有序的,所以不管哪个链表是非空的,它包含的所有元素都比前面已经合并链表中的所有元素都要大。这意味着我们只需要简单地将非空链表接在合并链表的后面,并返回合并链表即可。

方法二:优先队列

用容量为2的小堆顶优先队列。起始时把两条链表的头结点都放进去,然后出队当前优先队列中最小的(直接出队头元素即可,因为优先队列在插入时已经排好序),挂到新链表上,然后让出队的那个节点的下一个结点(next)入队,再出队当前优先队列中最小的,直到优先队列为空。

【算法描述】

cpp 复制代码
//双指针法
ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) 
{
    ListNode* head = new ListNode(0);    //设立虚拟头结点
    ListNode* pre = head;    //用于连接新链表
    while(list1 && list2)
    {
        //找两链表中val值小的结点接入到新链表中
        if(list1->val <= list2->val)
        {
            pre->next = list1;
            list1 = list1->next;
        }
        else
        {
            pre->next = list2;
            list2 = list2->next;
        }
        pre = pre->next;    //更新pre指针
    }
    pre->next = list1 ? list1 : list2;  //哪个链表先遍历完,则就把另一个链表剩余结点直接连接到新链表后
    return head->next;
}


//优先队列
struct com    //重写仿函数
{
    bool operator()(ListNode* a, ListNode* b)
    {
        return a->val > b->val;
    }
};
ListNode* mergeTwoLists(ListNode* list1, ListNode* list2)
{
    priority_queue<ListNode*, vector<ListNode*>, com> q;  //创建优先队列
    if(list1)    //先将两头结点入队
    {
        q.push(list1);
    }
    if(list2)
    {
        q.push(list2);
    }
    ListNode* head = new ListNode(0);  //设立虚拟头结点
    ListNode* pre = head;  //用于连接新链表
    while(!q.empty())
    {
        ListNode* curr = q.top();  //队首结点是val值最小的
        q.pop();
        pre->next = curr;    //链接新链表
        pre = curr;    //更新pre指针
        if(curr->next)  //将该链表上的下一个结点压入
        {
            q.push(curr->next);
        }
    }
    return head->next;
}

合并K个升序链表

【题目描述】

给你一个链表数组,每个链表都已经按升序排列。请你将所有链表合并到一个升序链表中,返回合并后的链表。

【输入输出实例】

示例 1:

输入:lists = [[1,4,5], [1,3,4], [2,6]]

输出:[1,1,2,3,4,4,5,6]

解释:链表数组如下:

[

1->4->5,

1->3->4,

2->6

]

将它们合并到一个有序链表中得到:1->1->2->3->4->4->5->6

示例 2:

输入:lists = []

输出:[]

示例 3:

输入:lists = [[]]

输出:[]

【算法思路】

使用优先队列:

容器lists中有k个链表,则用容量为K的小堆顶优先队列。起始时把每条链表的头结点都放进去,然后出队当前优先队列中最小的(直接出队头元素即可,因为优先队列在插入时已经排好序),挂上新链表,然后让出队的那个节点的下一个结点(next)入队,再出队当前优先队列中最小的,直到优先队列为空。

【算法描述】

cpp 复制代码
//优先队列
struct com
{
    bool operator()(ListNode* a, ListNode* b)   //重写仿函数
    {
        return a->val > b->val;    //小顶堆:val小的在队头
    }
};
ListNode* mergeKLists(vector<ListNode*>& lists)
{
    priority_queue<ListNode*, vector<ListNode*>, com> q;  //创建优先队列
    for(auto node : lists)   //先将所有头结点入队
    {
        if(node)   //排除空结点
        {
            q.push(node);   //与普通queue不一样的是,优先级队列插入时要排序
        }
    }
    ListNode* head = new ListNode(0);  //虚拟头结点
    ListNode* pre = head;
    while(!q.empty())
    {
        ListNode* curr = q.top();    //出队的必是队列里val最小的
        q.pop();
        pre->next = curr;    //链接新链表
        pre = curr;    //更新pre指针
        if(curr->next)    //将该链表的后续节点插入
        {
            q.push(curr->next);
        }
    }
    return head->next;    //返回新链表头结点
}

//笨办法:将结点数值全部放入一个容器中,排序后重新构造链表
ListNode* mergeKLists(vector<ListNode*>& lists) 
{
    vector<int> nodeVal;    //存放所有链表结点数值
    for(auto node : lists)    //逐个结点遍历存入nodeVal
    {
        while(node)
        {
            nodeVal.push_back(node->val);
            node = node->next;
        }
    }
    sort(nodeVal.begin(), nodeVal.end());    //排序
    ListNode* head = new ListNode(0);    //虚拟头结点
    ListNode* pre = head;
    for(auto val : nodeVal)  //构造一条新链表
    {
        ListNode* curr = new ListNode(val);
        pre->next = curr;
        pre = curr;
    }
    return head->next;    //返回新链表头结点
}

【知识点】

优先队列

(1)概念:

普通的队列是一种先进先出的数据结构,元素在队列尾追加,而从队列头删除。而在优先队列中,元素被赋予优先级。在插入时自动排序,当访问元素时,具有最高优先级的元素最先出列。

我们可以自定义其中数据的优先级,让优先级高的排在队列前面,优先出队,优先队列具有队列的所有特性,包括基本操作,只是在这基础上添加了内部的一个排序,它本质是一个堆实现的。

(2)定义:priority_queue<Type, Container, Functional>

  • Type是队列中存放的数据类型;
  • Container是容器类型(Container必须是用数组实现的容器,比如vector, deque等等,但不能用list。STL里面默认用的是vector,一般为vector);
  • Functional就是比较的方式,当需要用自定义的数据类型时才需要传入这三个参数,使用基本数据类型时,只需要传入数据类型,默认是大顶堆。

(3)使用:

① int类型

cpp 复制代码
//greater和less是std实现的两个仿函数
//升序队列------小顶堆:小数在队头
priority_queue<int, vector<int>, greater<int>> q; 
//降序队列------大顶堆:大数在队头
priority_queue<int, vector<int>, less<int>> q;
//对于基础类型,只需要传入数据类型即可,默认是大顶堆
priority_queue<int> a;

② pair<int, int>类型

cpp 复制代码
//对于pair类型的比较,先比较第一个元素,在第一个相等的情况下再比较第二个。 
priority_queue<pair<int, int> > a;  //默认是大顶堆
pair<int, int> b(1, 2); 
pair<int, int> c(1, 3); 
pair<int, int> d(2, 5); 
a.push(d); 
a.push(c); 
a.push(b);
// 则依次出队时,结果为:
// 2  5 
// 1  3 
// 1  2

③ 自定义类型

cpp 复制代码
//方法1
struct tmp1  
{
    int x;
    tmp1(int a) { x = a; }
    bool operator<(const tmp1& a) const  //运算符重载<
    {
    	return x < a.x;  //大顶堆
    }
};
tmp1 a(1);
tmp1 b(2);
tmp1 c(3);
priority_queue<tmp1> d;
d.push(b);
d.push(c);
d.push(a);
//则依次出队时,结果为:3 2 1

//方法2------常用
struct tmp2   //重写仿函数
{
	bool operator()(tmp1 a, tmp1 b)  //注意是tmp1(才有a.x)
    {
    	return a.x > b.x;  //小顶堆
    }
};
tmp1 a(1);
tmp1 b(2);
tmp1 c(3);
priority_queue<tmp1, vector<tmp1>, tmp2> f;
f.push(c);
f.push(b);
f.push(a);
//则依次出队时,结果为:1 2 3

(4)常用操作:

  • top 访问队头元素(普通队列为front)
  • empty 队列是否为空
  • size 返回队列内元素个数
  • push 插入元素到队尾 (并排序)
  • emplace 原地构造一个元素并插入队列
  • pop 弹出队头元素
  • swap 交换内容

两两交换链表中的节点

【题目描述】

给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。

【输入输出实例】

示例 1:

输入:head = [1,2,3,4]

输出:[2,1,4,3]

示例 2:

输入:head = []

输出:[]

示例 3:

输入:head = [1]

输出:[1]

【算法思路】

【算法描述】

cpp 复制代码
ListNode* swapPairs(ListNode* head) 
{
    ListNode* virHead = new ListNode(0, head);    //虚拟头结点
    ListNode* pre = newHead;   
    ListNode* curr = head;
    while(curr && curr->next)
    {
        pre->next = curr->next;      //步骤一
        curr->next = curr->next->next;    //步骤三
        pre->next->next = curr;    //步骤二
        pre = curr;    //更新pre
        curr = curr->next;    //更新curr
    }
    return virHead->next;
}

K个一组翻转链表

【题目描述】

给你链表的头节点head,每 k 个节点一组进行翻转,请你返回修改后的链表。

k是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。

不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。

【输入输出实例】

示例 1:

输入:head = [1,2,3,4,5], k = 2

输出:[2,1,4,3,5]

示例 2:

输入:head = [1,2,3,4,5], k = 3

输出:[3,2,1,4,5]

【算法思路】

先算出该链表的所有结点个数,根据结点个数和k算出待翻转的链表组数,则依次遍历这些组。

在遍历各组链表之前,用一个指针preTail来维护上一组的翻转后链表的尾结点,便于连接各组链表(可以建立虚拟头结点,这样的话对于第一组来说,上一组的翻转后尾结点就是虚拟头结点,保证第一组和后面的组都一样)。

遍历各组时,在翻转当前组链表之前,要先用一个指针currHead记录当前组在翻转前的头结点(对于下一组来说,该结点为上一组的翻转后链表的尾结点)。然后利用双指针对当前组链表进行翻转,翻转后用preTail来连接当前组链表,再将preTail更新为currHead。

按照上面步骤来依次循环遍历各组链表,直到到最后一组不够k,则不用翻转,直接将前一组尾结点preTail指向该组头结点即可。

【算法描述】

cpp 复制代码
ListNode* reverseKGroup(ListNode* head, int k) {
    ListNode* newHead = new ListNode(0, head);  //虚拟头结点
    ListNode* pre = newHead;  //双指针实现翻转
    ListNode* curr = head;
    int size = 0;  //记录结点个数
    while(curr)  //求链表的结点个数
    {
        size++;
        curr = curr->next;
    }
    curr = head;

    ListNode* preTail = newHead;  //记录前一组已翻转链表的尾结点
    for(int i = 0; i < size/k; i++)  //遍历待翻转的每组链表
    {
        ListNode* currHead = curr;    //记录当前组在翻转前的头结点
        for(int j = 0; j < k; j++)    //对当前组链表进行翻转
        {
            ListNode* temp = curr->next;
            curr->next = pre;
            pre = curr;
            curr = temp;
        }
        preTail->next = pre;  //链接前一组链表和当前组链表
        preTail = currHead;  //将preTail更新为当前组已翻转链表的尾结点
    }
    preTail->next = curr;  //最后一组不够k,则不用翻转,直接将前一组尾结点指向该组头结点即可
    return newHead->next;
}

旋转链表

【题目描述】

给你一个链表的头节点 head ,旋转链表,将链表每个节点向右移动 k 个位置。

【输入输出实例】

示例 1:

输入:head = [1,2,3,4,5], k = 2

输出:[4,5,1,2,3]

示例 2:

输入:head = [0,1,2], k = 4

输出:[2,0,1]

【算法思路】

  • 先遍历一次链表,遍历到链表尾结点,求得链表总长度num,同时将链表尾结点与首结点连接起来;

  • 再删除无效的旋转次数,例如示例1:head = [1,2,3,4,5], k = 2;如果k = 5,则旋转完还是原链表,相当于没有旋转。k = 6相当于k = 1;k = 7相当于k = 2 ... 所以要对 k 取余:k %= num;

  • 再找到原链表的倒数第 k+1个结点,该结点的 next 就是新链表的头结点,再将第 k+1个节点与其 next 结点断开即可。

【算法描述】

cpp 复制代码
ListNode* rotateRight(ListNode* head, int k) {
    if(!head || !head->next || !k)   //特殊情况:旋转后还是原链表,直接返回
    {
        return head;
    }
    ListNode* curr = head;
    int num = 1;    //记录链表总结点数
    while(curr->next)    //找到尾结点,并计算链表总结点数
    {
        curr = curr->next;
        num++;
    }
    curr->next = head;    //将尾结点与首结点链接起来
    k %= num;    //删除无效的旋转次数
    for(int i = 0; i < num - k; i++)    //找倒数第k+1个节点
    {
        curr = curr->next;
    }
    ListNode* newHead = curr->next;    //将倒数第k个节点作为首结点
    curr->next = NULL;    //将倒数第k+1个节点作为尾结点
    return newHead;
}

删除排序链表中的重复元素

【题目描述】

给定一个已排序的链表的头 head , 删除所有重复的元素,使每个元素只出现一次 。返回已排序的链表 。

【输入输出实例】

示例 1:

输入:head = [1,1,2]

输出:[1,2]

示例 2:

输入:head = [1,1,2,3,3]

输出:[1,2,3]

【算法思路】

由于给定的链表是排好序的,因此重复的元素在链表中出现的位置是连续的,因此我们只需要对链表进行一次遍历,就可以删除重复的元素。所以从头结点开始遍历链表:

  • 如果发现当前节点 curr 与下一个节点 curr->next 对应的 val 相同,那么我们就把 curr->next 节点删除,同时用 curr 链接后面的节点;

  • 如果当前 curr 与 curr->next 对应的 val 不相同,那么说明链表中只有一个元素值为 curr->val 的节点,那么我们就可以继续向后遍历链表,即更新 curr = curr->next 指针。

【算法描述】

cpp 复制代码
ListNode* deleteDuplicates(ListNode* head) {
    ListNode* curr = head;
    while(curr && curr->next)    //遍历链表
    {
        if(curr->val == curr->next->val)    //找到重复数字的结点
        {
            ListNode* temp = curr->next;    //删除重复数字的结点,只保留一个即可
            curr->next = temp->next;
            delete temp;
            continue;
        }
        curr = curr->next;    //未遇到重复结点时,继续往后遍历
    }
    return head;
}

删除排序链表中的重复元素II

【题目描述】

给定一个已排序的链表的头 head , 删除原始链表中所有重复数字的节点,只留下不同的数字 。返回已排序的链表 。

【输入输出实例】

示例 1:

输入:head = [1,2,3,3,4,4,5]

输出:[1,2,5]

示例 2:

输入:head = [1,1,1,2,3]

输出:[2,3]

【算法思路】

由于给定的链表是排好序的,因此重复的元素在链表中出现的位置是连续的,因此我们只需要对链表进行一次遍历,就可以删除重复的元素。由于链表的头节点可能会被删除,因此我们需要额外使用一个虚拟头节点。

我们使用两个指针 pre(慢指针)和 curr(快指针)分别指向链表的虚拟头节点和头节点,随后开始对链表进行遍历:

  • 如果发现当前节点 curr 与下一个节点 curr->next 对应的 val 相同,那么我们就需要将 curr 以及所有后面拥有相同 val 的链表节点全部删除(删到只剩最后一个相同 val 的节点时,这时该节点为 curr,因为不符合 curr->val == curr->next->val 而退出循环,所以需要单独删除)。删除完最后一个重复结点后,用前一个不重复结点 pre 来链接后面的结点;

  • 如果当前 curr 与 curr->next 对应的 val 不相同,那么说明链表中只有一个元素值为 curr->val 的节点,那么我们就可以继续向后遍历链表,即更新 pre = curr; curr = curr->next; 两指针。

注意: C++ 代码中要释放被删除的链表节点的空间。

【算法描述】

cpp 复制代码
ListNode* deleteDuplicates(ListNode* head) {
    ListNode* virHead = new ListNode(0, head);    //创建虚拟头结点
    ListNode* pre = virHead;    //慢指针
    ListNode* curr = head;    //快指针
    while(curr && curr->next)    //遍历链表
    {
        if(curr->val == curr->next->val)    //找到重复数字的结点
        {
            while(curr->next && curr->val == curr->next->val)   //删除重复数字的结点
            {
                ListNode* temp = curr;
                curr = curr->next;
                delete temp;
            }
            ListNode* temp = curr;   //删除重复结点的最后一个
            curr = curr->next;
            delete temp;
            pre->next = curr;    //链接后面的结点
            continue;
        }
        pre = curr;    //未遇到重复结点时,不断往后更新两指针
        curr = curr->next;
    }
    return virHead->next;
}

分隔链表

【题目描述】

给你一个链表的头节点 head 和一个特定值 x ,请你对链表进行分隔,使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。

你应当保留两个分区中每个节点的初始相对位置。

【输入输出实例】

示例 1:

输入:head = [1,4,3,2,5,2], x = 3

输出:[1,2,2,4,3,5]

示例 2:

输入:head = [2,1], x = 2

输出:[1,2]

【算法思路】

我们只需维护两个链表 small 和 large 即可,small 链表按顺序存储所有小于 x 的节点,large 链表按顺序存储所有大于等于 x 的节点。遍历完原链表后,我们只要将 small 链表尾节点指向 large 链表的头节点即能完成对链表的分隔。

  • 为了处理头节点的边界条件,我们设 smallHead 和 largeHead 分别为两个链表的虚拟头节点。随后,从前往后遍历链表,判断当前链表的节点值是否小于 x,如果小于就将该节点链接到 small 链表后,否则链接到 large 链表后;

  • 遍历结束后,我们将 large 链表的尾部 next 指针置空,这是因为尾节点复用的是原链表的节点,而其 next 指针可能指向一个小于 x 的节点,我们需要切断这个引用。同时将 small 链表的尾部 next 指针指向 largeHead->next(即真正意义上的 large 链表的头节点)。最后返回 smallHead->next 指针即为我们要求的答案。

【算法描述】

cpp 复制代码
//利用两个链表虚拟头结点,分别构造所有结点val < x 或 val ≥ x的两条链表,再将两条链表链接起来
ListNode* partition(ListNode* head, int x) {
    ListNode* smallHead = new ListNode(0);    //val < x的结点构成链表的虚拟头结点
    ListNode* currSmall = smallHead;
    ListNode* largeHead = new ListNode(0);    //val ≥ x的结点构成链表的虚拟头结点
    ListNode* currLarge = largeHead;
    while(head)    //遍历链表
    { 
        if(head->val < x)    //构造所有结点 val < x 的链表
        {
            currSmall->next = head;
            currSmall = currSmall->next;
        }
        else    //构造所有结点 val ≥ x 的链表
        {
            currLarge->next = head;
            currLarge = currLarge->next;
        }
        head = head->next;
    }
    currLarge->next = NULL;    //val ≥ x的链表尾部next置NULL
    currSmall->next = largeHead->next;    //val ≥ x 的链表拼接在 val < x 的链表后面
    return smallHead->next;
}

反转链表

【题目描述】

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

【输入输出实例】

示例 1:

输入:head = [1,2,3,4,5]

输出:[5,4,3,2,1]

示例 2:

输入:head = [1,2]

输出:[2,1]

示例 3:

输入:head = []

输出:[]

【算法思路】

  • 定义两个指针: pre 和 cur ;pre 在前 cur 在后。
  • 每次让 pre 的 next 指向 cur ,实现一次局部反转。
  • 局部反转完成之后,pre 和 cur 同时往前移动一个位置。
  • 循环上述过程,直至 pre 到达链表尾部

【算法描述】

cpp 复制代码
ListNode* reverseList(ListNode* head) {
    ListNode* cur = NULL, *pre = head;
    while (pre != NULL) {
        ListNode* t = pre->next;
        pre->next = cur;
        cur = pre;
        pre = t;
    }
    return cur;
}

反转链表II

【题目描述】

给你单链表的头指针head和两个整数 left和right (left <= right)。请你反转从位置left到位置right的链表节点,返回反转后的链表。

【输入输出实例】

示例 1:

输入:head = [1,2,3,4,5], left = 2, right = 4

输出:[1,4,3,2,5]

示例 2:

输入:head = [5], left = 1, right = 1

输出:[5]

【算法思路】

我们以下图中黄色区域的链表反转为例。

使用206、反转链表的解法,反转left到right部分以后,再拼接起来。我们还需要提前记录left结点和其前一个节点pre。如图所示:

算法步骤:

  • 第 1 步:先将待反转的区域反转;
  • 第 2 步:把pre的next指针指向反转以后的链表头节点,把反转以后的链表的尾节点(即提前记录的left结点)的next指针指向succ。

【算法描述】

cpp 复制代码
//穿针引线:反转left到right的链表结点,再更新left前一个结点的链接和更新right后一个结点的链接
ListNode* reverseBetween(ListNode* head, int left, int right) {
    ListNode* newHead = new ListNode(0, head);   //虚拟头结点
    ListNode* curr = newHead;   //当前结点
    ListNode* leftNode = head;   //left位置的结点
    ListNode* leftPreNode = newHead;   //left位置的前一个结点
    //找left位置的结点和前一个结点
    for(int i = 0; i < left - 1; i++) 
    {
        curr = curr->next;
    }
    leftPreNode = curr;
    leftNode = curr->next;
    //反转 left 到 right 的链表结点
    ListNode* pre = curr;  //反转时要记录前一个结点
    curr = curr->next;
    for(int i = left; i <= right; i++)
    {
        ListNode* temp = curr->next;  //反转时要记录后一个结点
        curr->next = pre;
        pre = curr;  //更新pre
        curr = temp;  //更新curr
    }
    leftPreNode->next = pre;  //更新left前一个结点的链接
    leftNode->next = curr;  //更新right后一个结点的链接
    return newHead->next;
}

恭喜你全部读完啦!古人云:温故而知新。赶紧收藏关注起来,用之前再翻一翻吧~


📣推荐阅读

C/C++后端开发面试总结:点击进入 后端开发面经 关注走一波

C++重点知识:点击进入 C++重点知识 关注走一波

力扣(leetcode)题目分类:点击进入 leetcode题目分类 关注走一波

相关推荐
荒古前8 分钟前
龟兔赛跑 PTA
c语言·算法
Colinnian11 分钟前
Codeforces Round 994 (Div. 2)-D题
算法·动态规划
用户00993831430117 分钟前
代码随想录算法训练营第十三天 | 二叉树part01
数据结构·算法
shinelord明20 分钟前
【再谈设计模式】享元模式~对象共享的优化妙手
开发语言·数据结构·算法·设计模式·软件工程
დ旧言~27 分钟前
专题八:背包问题
算法·leetcode·动态规划·推荐算法
小俊俊的博客29 分钟前
海康RGBD相机使用C++和Opencv采集图像记录
c++·opencv·海康·rgbd相机
_WndProc44 分钟前
C++ 日志输出
开发语言·c++·算法
薄荷故人_1 小时前
从零开始的C++之旅——红黑树及其实现
数据结构·c++
m0_748240021 小时前
Chromium 中chrome.webRequest扩展接口定义c++
网络·c++·chrome