【算法】链表常见算法2

【算法】—— 链表常见算法2

1. 链表的中间结点

1.1 问题描述

​ 给定一个头结点为 head 的非空单链表,返回链表的中间结点。

​ 如果有两个中间结点,则返回第二个中间结点。

示例 1:

输入:[1,2,3,4,5]
输出:此列表中的结点 3

示例 2:

输入:[1,2,3,4,5,6]
输出:此列表中的结点 4

1.2 实现思路

​ 使用快慢指针实现,遍历链表,快指针走两步,慢指针走一步,快指针遍历完,慢指针的位置就是中间结点的位置

中间结点

1.3 代码实现

struct ListNode* middleNode(struct ListNode* head)
{
    struct ListNode* fast = head;
    struct ListNode* slow = head;

    //fast指针或fast的下一个为NULL
    while (fast!=NULL && fast->next != NULL)
    {
        slow = slow->next;
        fast = fast->next->next;
    }

    return slow;
}

2. 链表中倒数第k个结点

2.1 问题描述

输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。

例如,一个链表有 6 个节点,从头节点开始,它们的值依次是 1、2、3、4、5、6。这个链表的倒数第 3 个节点是值为 4 的节点。

示例:

给定一个链表: 1->2->3->4->5, 和 k = 2.
返回链表 4->5.

2.2 实现思路

​ 利用快慢指针,快指针先走k步,然后快慢指针一起走,快指针遍历完,慢指针的位置就是倒数第k个

  1. 快指针先走k步

倒数k结点1

  1. 快慢指针一起走,直到快指针遍历完,返回慢指针

倒数k结点2

2.3 代码实现

struct ListNode* getKthFromEnd(struct ListNode* head, int k)
{
    struct ListNode* fast = head;
    struct ListNode* slow = head;

    while (k--)     //先走k-1步
    {
        fast = fast->next;
    }

    while (fast != NULL)
    {
        fast = fast->next;
        slow = slow->next;
    }

    return slow;
}

3. 链表分割

3.1 问题描述

​ 现有一链表的头指针ListNode* pHead,给一定值x,编写一段代码将所有小于x的结点排在其余结点之前,且不能改变原来的数据顺序,返回重新排列后的链表的头指针。

示例

输入:5 {3 6 8 2 4 9 7 1}
输出:3 2 4 1 6 8 9 7

3.2 实现思路

方法一(先分类再合并)

分类合并1

  1. 创建两个链表头指针,一个用来放小于x的位于前面的结点,一个用来放大于等于x的结点

分类合并2

  1. 最后将两个链表合并到一起

分类合并3

ListNode* partition(struct ListNode* pHead, int x)
{
    struct ListNode* cur = pHead;
    struct ListNode h1 = { 0 };
    struct ListNode h2 = { 0 };
    struct ListNode* p1 = &h1;
    struct ListNode* p2 = &h2;

    while (cur != NULL)
    {
        if (cur->data < x)		//收集小于x的结点的第一个链表
        {
            p1->next = cur;
            p1 = p1->next;
        }
        else				//收集大于等于x的结点的第二个链表
        {
            p2->next = cur;
            p2 = p2->next;
        }
        cur = cur->next;
    }
    
 	//拼接两个链表
    p2->next = NULL;	//第二个链表末尾置空
    p1->next = h2.next;
    pHead = h1.next;
    
    return pHead;
}

方法二(原地插入)

​ 遍历链表,将小于x的结点插入到链表前面

  1. 先为链表插入头结点,指针slow作为慢指针始终指向小于x的结点要插入的位置

  2. fast作为快指针进行遍历数组,prefastnext分别指向fast指针的前后位置

  3. fast指向的结点小于x 且slowpre指针指向同一个位置时,无需插入操作所有指针统一向下一位移动

    • per == slowprenext改变会影响slownext的指向

原地插入1

原地插入2

  1. fast指向的结点大于x时,要将fastprefastnext向后移动继续遍历,slow不变,等待小于x的结点插入

原地插入3

原地插入4

  1. fast指向的结点小于x 且slowpre指针指向不同位置时,将fast指向的结点插入到slow后边,所有指针后移,除了pre
    1. 插入的方式改变next的连接就可以,一共三条线
    2. fast指向的结点被插入到前面,pre指针用来连接fast后面的链表,fast向后递增,所以此时pre指向的结点依然作为fast的前一个结点

原地插入5

原地插入6

代码实现

ListNode* partition(ListNode* pHead, int x)
{
    if (pHead == NULL || pHead->next == NULL)
    {
        return pHead;
    }

    ListNode temp = { 0 };
    temp.next = pHead;

    ListNode* slow = &temp;
    ListNode* pre = &temp;
    ListNode* fast = pHead;
    ListNode* fastnext = pHead;

    while (fast != NULL)
    {
        fastnext = fast->next;
        if (fast->data < x)		//小于x的结点
        {
            if (pre != slow)	//pre与slow不重合时才插入
            {
                pre->next = fastnext;
                fast->next = slow->next;
                slow->next = fast;
            }
            else	//重合时不插入,所有结点向后递增
            {
                pre = pre->next;
            }
            
            slow = slow->next;
        }
        else	//大于等于x的结点不做处理,除了slow其余指针向后递增
        {
            pre = pre->next;
        }
        fast = fastnext;
    }

    pHead = temp.next;
    return pHead;
}

4. 回文链表

4.1 问题描述

给你一个单链表的头节点 head ,请你判断该链表是否为回文链表。如果是,返回 true ;否则,返回 false 。

img

示例 1:

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

示例 2:

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

4.2 实现思路

  1. 获取该链表的中间结点,调用中间结点的函数(本文第一个算法)

回文链表1

  1. 逆置后半段链表,调用反转单链表函数(点击跳转),由于前半段指针指向没有改变,所以2依然指向3

回文链表2

  1. 比较两个链表的数值,直到一方遍历结束则是回文链表(实际上该3结点是两个链表公用的)

回文链表3

4.3 代码实现

//获取中间结点
struct ListNode* middleNode(struct ListNode* head)
{
    struct ListNode* fast = head;
    struct ListNode* slow = head;

    //fast指针或fast的下一个为NULL
    while (fast!=NULL && fast->next != NULL)
    {
        slow = slow->next;
        fast = fast->next->next;
    }

    return slow;
}

//反转单链表
struct ListNode* reverseList(struct ListNode* head)
{
    struct ListNode* prev = NULL;
    struct ListNode* cur = head;
    struct ListNode* next = NULL;

    while (cur != NULL)
    {
        next = cur->next;
        cur->next = prev;
        prev = cur;
        cur = next;
    }
    return prev;
}

//判断回文链表
bool isPalindrome(struct ListNode* head)
{
    struct ListNode* mid = middleNode(head);
    struct ListNode* rev = reverseList(mid);
    
    while (rev != NULL && head != NULL)
    {
        if (rev->val != head->val)
        {
            return false;
        }
        head = head->next;
        rev = rev->next;
    }
    return true;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值