【链表】相关类型题目整理[一]

一、力扣206.反转链表

  • 给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
  • 示例1:

在这里插入图片描述

  • 示例2:

在这里插入图片描述

  • 示例3:

在这里插入图片描述

(一)不带头节点的单链表原地反转

  • 单链表结构:
struct ListNode
{
    int val;
    struct ListNode* next;
}ListNode;

  • 思路如下:

在这里插入图片描述

在这里插入图片描述

  • 代码如下:
struct ListNode* reverseList(struct ListNode* head)
{
    if (head == nullptr || head->next == nullptr) return head;
    struct ListNode* pre = nullptr;
    struct ListNode* cur = head;
    struct ListNode* next = nullptr;
    while (cur != nullptr)
    {
        next = cur->next;
        cur->next = pre;
        pre = cur;
        cur = next;
    }
    return pre;
}

(二)利用栈的先进后出特点

  • 由于栈有先进后出的特点,因此我们可以在不破坏原链表的情况下,将链表的节点放进栈里面,然后进行反转。
  • 和原地反转不同的是,这种方法需要以一个栈为代价,但是不会破坏单链表的原始结构。
  • 在面试中我们应该尽量问清楚面试官对题目的要求,特别是时间和空间复杂度。
  • 思路是比较简单的,因此我们直接看代码:
struct ListNode* reverseList(struct ListNode* head)
{
    if (head == nullptr || head->next == nullptr)
    {
        return head;
    }
    struct ListNode* phead = head;
    stack<int> st;
    while (phead != nullptr)
    {
        st.push(phead->val);
        phead = phead->next;
    }
    phead = head;
    while (!st.empty())
    {
        phead->val = st.top(); st.pop();
        phead = phead->next;
    }
    return head;
}

(三)递归(空间复杂度O(n))

  • 递归和栈的思想有点类似,利用栈是因为栈的先进后出的特点,而递归其实分为两个过程----调用和回退,因此我们可以在递归回退的过程中解决反转这个问题。
  • 思路如下:

在这里插入图片描述

  • 代码如下:
struct ListNode* reverseList(struct ListNode* head)
{
    if (!head || !head->next) 
    {
        return head;
    }
    struct ListNode* newHead = reverseList(head->next);
    head->next->next = head;
    head->next = nullptr;
    return newHead;
}

二、力扣92.反转链表 II

  • 给你单链表的头指针 head 和两个整数 left 和 right ,其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回 反转后的链表。
  • 输入输出示例:

在这里插入图片描述

  • 提示:

在这里插入图片描述

(一)穿针引线----理清思路再编码

  • 思路如下图所示:

在这里插入图片描述

  • 首先考虑到left位置可能位于头节点,所以我们先开辟一个伪头节点出来;
  • 接下来我们要找到left位置的前一个节点和right位置的后一个节点并保存下来;
  • 然后我们需要找到要反转的区间链表;
  • 找到之后把这段区间和原来的链表断开;
  • 将这段区间进行链表反转;
  • 反转完成之后再和原来的链表进行合并。
  • 代码如下:
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) {}
    
};


//反转链表
void reverse(ListNode* head)
{
    struct ListNode* cur = head;
    struct ListNode* pre = nullptr;
    struct ListNode* next = nullptr;
    while (cur != nullptr)
    {
        next = cur->next;
        cur->next = pre;
        pre = cur;
        cur = next;
    }
}

ListNode* reverseBetween(ListNode* head, int left, int right)
{
    if (head == nullptr || head->next == nullptr || right == left)
    {
        return head;
    }
    //自定义一个头节点,处理left在头节点位置的情况
    ListNode* vir = new ListNode(-1);
    vir->next = head;


    //先找到left位置的前一个节点
    ListNode* leftpre = vir;
    for (int i = 0; i < left - 1; i++)
    {
        leftpre = leftpre->next;
    }

    //然后走right - left +1步找到right位置的节点
    ListNode* rightNode = leftpre;
    for (int i = 0; i < right - left + 1; i++)
    {
        rightNode = rightNode->next;
    }

    //截取链表
    ListNode* leftNode = leftpre->next;
    ListNode* rightsuc = rightNode->next;

    //切断链表
    leftpre->next = nullptr;
    rightNode->next = nullptr;

    //反转leftNode~rightNode之间的链表
    reverse(leftNode);

    //合并链表
    leftpre->next = rightNode;
    leftNode->next = rightsuc;
    return vir->next;
}

(二)一次遍历反转链表----头插法

  • 参考力扣官方题解,特别详细~很容易懂
  • 假设有下面这样一个链表:

在这里插入图片描述

  • 整体思想是:在需要反转的区间里,每遍历到一个节点,让这个新节点来到反转部分的起始位置。下面的图展示了整个流程。

在这里插入图片描述

  • 下面我们具体解释如何实现:
  • 使用三个指针变量 pre、curr、next 来记录反转的过程中需要的变量,它们的意义如下:
  • curr:指向待反转区域的第一个节点 left;
  • next:永远指向 curr 的下一个节点,循环过程中,curr 变化以后 next 会变化;
  • pre:永远指向待反转区域的第一个节点 left 的前一个节点,在循环过程中不变。
  • 第 1 步,我们使用 ①、②、③ 标注「穿针引线」的步骤。

在这里插入图片描述

  • 先将 curr 的下一个节点记录为 next;
  • 执行操作 ①:把 curr 的下一个节点指向 next 的下一个节点;
  • 执行操作 ②:把 next 的下一个节点指向 pre 的下一个节点;
  • 执行操作 ③:把 pre 的下一个节点指向 next。
  • 第 1 步完成以后「拉直」的效果如下:

在这里插入图片描述

  • 同理,第二步:

在这里插入图片描述

  • 第三步,同理

在这里插入图片描述

  • 代码如下:
ListNode* reverseBetween(ListNode* head, int left, int right)
{
    if (head == nullptr || head->next == nullptr || left == right)
    {
        return head;
    }

    //构造一个伪节点
    ListNode* vir = new ListNode(-1);
    vir->next = head;

    //找到left位置的前一个节点
    ListNode* pre = vir;
    for (int i = 0; i < left - 1; i++)
    {
        pre = pre->next;
    }

    //找到反转区间的第一个节点
    ListNode* cur = pre->next;

    //left~right区间内共需要反转right - left次
    for (int i = 0; i < right - left; i++)
    {
        ListNode* next = cur->next;
        cur->next = next->next;
        next->next = pre->next;
        pre->next = next;
    }
    return vir->next;
}

三、剑指 Offer 06.从尾到头打印链表

  • 输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。
  • 输入:head = [1,3,2]
  • 输出:[2,3,1]

(一)利用链表反转解决

  • 思路比较简单,就是先将链表反转,然后将该链表从头到尾遍历,将节点的val值依次放入vector中。
  • 代码:
//链表原地反转
struct ListNode* reverseList(struct ListNode* head)
{
    if (head == nullptr || head->next == nullptr) return head;
    struct ListNode* pre = nullptr;
    struct ListNode* cur = head;
    struct ListNode* next = nullptr;
    while (cur != nullptr)
    {
        next = cur->next;
        cur->next = pre;
        pre = cur;
        cur = next;
    }
    return pre;
}

vector<int> reversePrint(ListNode* head)
{
    vector<int> res;
    if (head == nullptr)
    {
        return res;
    }
    struct ListNode* phead = reverseList(head);
    while (phead != nullptr)
    {
        res.push_back(phead->val);
        phead = phead->next;
    }
    return res;
}

(二)递归

  • 在回退的过程中将节点中的val值依次装入vector中。
  • 代码如下:
vector<int> reversePrint(ListNode* head)
{
    vector<int> res;
    if (head == nullptr)
    {
        return res;
    }
    res = reversePrint(head->next);
    res.push_back(head->val);
    return res;
}

四、力扣25.k个一组反转链表

  • 给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。
  • k 是一个正整数,它的值小于或等于链表的长度。
  • 如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。
  • 进阶:
  • 你可以设计一个只使用常数额外空间的算法来解决此问题吗?
  • 你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。
  • 输入输出示例:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 提示:

在这里插入图片描述

(一)利用栈进行辅助【直接修改节点的val值】

  • 这种解法思路比较简单,也比较好想:
  • 先计算出所有节点的个数;
  • 然后通过所有节点的个数和k的值计算共需要反转的组数;
  • 然后将每组的k个节点的val值装进栈中,然后再出栈覆盖掉原来的k个节点的val值;
  • 题目完成。
  • 代码如下:
ListNode* reverseKGroup(ListNode* head, int k) 
{
    if (head == nullptr || head->next == nullptr || k == 1)
    {
        return head;
    }

    //先计算出链表中的所有节点数
    int count = 0;
    ListNode* phead = head;
    while (phead != nullptr)
    {
        count++;
        phead = phead->next;
    }

    //计算出共需要反转的组数
    int tmp = count / k;

    //还需要一个栈来存储需要反转的每组节点的val值
    stack<int> st;
    phead = head;
    ListNode* list = head;

    //共有tmp组需要进行反转操作
    for (int i = 0; i < tmp; i++)
    {
        //将每组的k个节点入栈
        for (int j = 0; j < k; j++)
        {
            if (phead != nullptr)
            {
                st.push(phead->val);
                phead = phead->next;
            }
        }

        //将栈中的val出栈(进行反转)
        while (!st.empty())
        {
            list->val = st.top(); st.pop();
            list = list->next;
        }
    }
    return head;
}

(二)原链表上进行反转【头插法】

  • 思路用下图简单说明:

在这里插入图片描述

  • 代码如下:
ListNode* reverseKGroup(ListNode* head, int k)
{
    if (head == nullptr || head->next == nullptr || k == 1)
    {
        return head;
    }

    //先计算出链表中的所有节点数
    int count = 0;
    ListNode* phead = head;
    while (phead != nullptr)
    {
        count++;
        phead = phead->next;
    }

    //计算出共需要反转的组数
    int tmp = count / k;

    //定义一个伪头节点
    ListNode* vir = new ListNode(-1);
    ListNode* pre = vir;
    ListNode* cur = head;
    ListNode* next = nullptr;
    vir->next = head;
    

    //共有tmp组需要进行反转操作
    for (int i = 0; i < tmp; i++)
    {
        for (int j = 0; j < k - 1; j++)
        {
            if (cur->next != nullptr)
            {
                next = cur->next;
                cur->next = next->next;
                next->next = pre->next;
                pre->next = next;
            }
        }
        pre = cur;
        cur = cur->next;
    }
    return vir->next;
}

五、力扣61.旋转链表

  • 给你一个链表的头节点 head ,旋转链表,将链表每个节点向右移动 k 个位置。
  • 输入输出示例:

在这里插入图片描述
在这里插入图片描述

  • 提示:

在这里插入图片描述

(一)利用数学思想将题目简化

  • 通过题目我们可以分析出来这样一个信息:

在这里插入图片描述

  • 有了上面这样一个思路,代码写起来就很简单啦:
ListNode* rotateRight(ListNode* head, int k)
{
    if (head == nullptr || head->next == nullptr || k == 0)
    {
        return head;
    }

    //先计算出节点的个数
    int count = 0;
    ListNode* newhead = head;
    while (newhead != nullptr)
    {
        count++;
        newhead = newhead->next;
    }

    //当k的值大于节点的个数时,把循环除去
    int cnt = k % count;

    //考虑到cnt==0时直接将原链表的头返回即可
    if (cnt == 0)
    {
        return head;
    }

    //找到第cnt个节点的前一个节点
    newhead = head;
    for (int i = 1; i < count - cnt; i++)
    {
        if (newhead->next != nullptr)
        {
            newhead = newhead->next;
        }
    }

    //找到第cnt个节点
    ListNode* phead = newhead->next;
    //将链表从第cnt节点这断开
    newhead->next = nullptr;

    //找到链表的尾部
    ListNode* tail = phead;
    while (tail->next != nullptr)
    {
        tail = tail->next;
    }

    //将head连接到tail的尾部
    tail->next = head;

    return phead;
}

六、剑指Offer 22.链表中倒数第k个节点

  • 输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。
  • 例如,一个链表有 6 个节点,从头节点开始,它们的值依次是 1、2、3、4、5、6。这个链表的倒数第 3 个节点是值为 4 的节点。
  • 输入输出示例:
  • 给定一个链表: 1->2->3->4->5, 和 k = 2.
  • 返回链表 4->5.

(一)遍历统计链表长度

  • 先遍历统计链表长度,记为 n ;
  • 设置一个指针走(n−k) 步,即可找到链表倒数第 k 个节点。
  • 代码如下:
ListNode* getKthFromEnd(ListNode* head, int k)
{
    ListNode* phead = head;
    int count = 0;
    while (phead != nullptr)
    {
        count++;
        phead = phead->next;
    }

    int n = count - k;
    phead = head;
    while (n > 0)
    {
        phead = phead->next;
        n--;
    }
    return phead;
}

旺财加油!✨

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值