链表OJ题练习1

链表的中间结点(快慢指针)

链接:链表的中间结点
问题描述:

给定一个带有头结点 head 的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点

分析:

  1. 定义快慢两个指针快指针走两部的同时,慢指针走一步
  2. 当快指针走到尾的时候慢指针位置指向的地址就是中间节
    注意奇数和偶数个节点

代码如下

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */


struct ListNode* middleNode(struct ListNode* head){
    struct ListNode*slow=head;
    struct ListNode*fast=head;
    if(head==NULL)
    {
        return NULL;
    }
    while(fast!=NULL)
    {
        if(fast->next==NULL)
            return slow;
        slow=slow->next;
        fast=(fast->next)->next;
    }
    return slow;
}

变形:输入一个链表,输出该链表中倒数第k个结点
链接:链表中倒数第k个结点

分析:

  1. 同样定义快慢两个指针,但是这里的快指针是先向后走k步,慢指针不变
  2. 再让快慢指针同时以相同速度走,快指针到尾,慢指针即为倒数第k个节点

代码如下

/**
 * struct ListNode {
 *	int val;
 *	struct ListNode *next;
 * };
 */

/**
 * 
 * @param pListHead ListNode类 
 * @param k int整型 
 * @return ListNode类
 */
struct ListNode* FindKthToTail(struct ListNode* pListHead, int k ) {
        struct ListNode* fast, *slow;
        slow = fast = pListHead;
        if(pListHead==NULL)
            return NULL;
        while(k--)
        {
            if(fast==NULL)
                return NULL;
            fast = fast->next;
        }
        while(fast)
        {
            slow = slow->next;
            fast = fast->next;
        }

        return slow;
}

链表的分割

链接链表分割
问题描述

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

分析:

  1. 创建两个链表,分别存放小于x的节点和大于等于x的节点,分别进行尾插
  2. 注意大链表的尾最后还会指向链表中的某一部分,形成环,所以注意断开链表
  3. 注意空指针,空链表的情况

代码如下

/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) : val(x), next(NULL) {}
};*/
class Partition {
public:
    ListNode* partition(ListNode* pHead, int x) {
        if(pHead==NULL)
            return NULL;
        struct ListNode*less=NULL;
        struct ListNode*lesstail=NULL;
        struct ListNode*bigger=NULL;
        struct ListNode*biggertail=NULL;
        struct ListNode*cur=pHead;
        //放入大小两个链表再链接
        while(cur!=NULL)
        {
            if(cur->val < x){
                if(less==NULL)
                {
                    less=cur;
                    lesstail=cur;
//                     lesstail=lesstail->next;
                }
                    
                else{
                    
                    lesstail->next=cur;
                    lesstail=lesstail->next;
                }
            }
            else{
                if(bigger==NULL)
                {
                    bigger=cur;
                    biggertail=cur;
                    //biggertail=lesstail->next;
                }
                    
                else{
                    
                    biggertail->next=cur;
                    biggertail=biggertail->next;
                }
            }
            cur=cur->next;
        }
        if(bigger!=NULL)
            biggertail->next=NULL;
        
        
        if(less==NULL)
        {
            return bigger;
        }
            
        if(bigger==NULL)
        {
            lesstail->next=NULL;
            return less;
        }
        lesstail->next=bigger;
        return less;
    }
};

链表的回文结构

链接链表的回文结构

问题描述:
对于一个链表,请设计一个时间复杂度为O(n),额外空间复杂度为O(1)的算法,判断其是否为回文结构。
给定一个链表的头指针A,请返回一个bool值,代表其是否为回文结构。保证链表长度小于等于900。
测试样例:
1->2->2->1
返回:true

分析:
先用快慢指针找到中间节点,然后把后半部分逆置,最近前后两部分一一比对,如果节点的值全部相同,则即为回文。
注意:必到被逆置的链表指向空为止即可

代码如下

/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) : val(x), next(NULL) {}
};*/
class PalindromeList {
public:
    struct ListNode* reverse(ListNode *phead){
        struct ListNode *prev=NULL;
        struct ListNode *cur=phead;
        struct ListNode *latter;
        while(cur)
        {
            latter=cur->next;
            cur->next=prev;
            prev=cur;
            cur=latter;
        }
        return prev;
}
    bool chkPalindrome(ListNode* A) {
        struct ListNode *cur=A;
        struct ListNode *fast=A;
        struct ListNode *slow=A;
        while(fast&&fast->next)
        {
            fast=fast->next->next;
            slow=slow->next;
        }
        struct ListNode *newhead=reverse(slow);
        while(newhead)
        {
            if((cur->val)!=(newhead->val))
                return false;
            else{
                cur=cur->next;
                newhead=newhead->next;
            }
        }
        return true;
    }
};

相交链表

链接相交链表

问题描述:
给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回null
题目数据 保证 整个链式结构中不存在环。
注意,函数返回结果后,链表必须 保持其原始结构 。
进阶:
你能否设计一个时间复杂度 O(n) 、仅用 O(1) 内存的解决方案?

分析

  1. 先计算出两个链表的长度
  2. 让长的链表先走相差的长度
  3. 然后两个链表同时走,直到遇到相同的节点,即为第一个公共节点

代码如下:

struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
    int lenA = 0, lenB = 0;
    struct ListNode* curA = headA, *curB = headB;
    //计算链表长度
    while(curA) {
        ++lenA;
        curA = curA->next;
    }
    
    while(curB) {
        ++lenB;
        curB = curB->next;
    }
    
    int gap = abs(lenA-lenB);
    struct ListNode* longList = headA, *shortList = headB;
    if(lenA < lenB) {
        longList = headB;
        shortList = headA;
    }
    //让长链表先走几步
    while(gap--){
        longList = longList->next;
    }
    //两个链表同时走,直到遇到相同的节点
    while(longList && shortList)
    {
        if(longList == shortList) {
            return longList;
        }
        else {
            longList = longList->next;
            shortList = shortList->next;
        }
    }
    
    return NULL;
}

环形链表 II

链接:环形链表Ⅱ

问题描述:
给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null
为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意,pos 仅仅是用于标识环的情况,并不会作为参数传递到函数中。
说明:不允许修改给定的链表。
进阶:
你是否可以使用 O(1) 空间解决此题?

分析1(数学结论):

  1. 定义两个指针快指针每次走两步,慢指针走一步(最后两指针必相遇),记录此时相遇位置
  2. 重新定义两个指针,一个从链表头开始走,一个从相遇位置开始走,最后必相交在入口点(链表开始入环的第一个节点)

以上是数学证明出的结论
详见详解环形链表

分析2(普通方法):
在这里插入图片描述

  1. 定义两个指针快指针每次走两步,慢指针走一步(最后两指针必相遇),记录此时相遇位置为meet同时记录下meet->next将meet->next置为空(作为标识),返回记录下meet->next
  2. 复用链表相交问题的逻辑如图比较A B,较长的链表先走到和短链表一样长,两链表再同时走,最后必在入口点相遇

代码如下(法一)

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode *detectCycle(struct ListNode *head) {
    struct ListNode *fast=head,*slow=head;
    struct ListNode *judge;
    while(fast&&fast->next)
    {
        fast=fast->next->next;
        slow=slow->next;
        if(fast==slow)
        {
            judge=fast;
            break;
        }
            
    }
    struct ListNode *first=head;
    while(first&&first->next&&judge&&judge->next)
    {
        if(first==judge)
        {
            return first;
        }
        else
        {
            first=first->next;
            judge=judge->next;
        }    
        
        
    }
    return NULL;
}

复制带随机指针的链表

链接:复制带随机指针的链表

问题描述:
给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random ,该指针可以指向链表中的任何节点或空节点。
构造这个链表的 深拷贝。 深拷贝应该正好由 n 个 全新 节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的 next 指针和 random 指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点 。

分析:

  1. 拷贝链表的每一个节点,拷贝的节点先链接到被拷贝节点的后面
  2. 复制随机指针的链接:拷贝节点的随机指针指向被拷贝节点随机指针的下一个位置
  3. 拆解链表,把拷贝的链表从原链表中拆解出来,同时还原 原节点
  4. 分三步是因为后两步一起做的话会导致如果random指针在链表前面,由于我们已经改了链表的指向就不能指向正确的random节点了

代码如下

/**
 * Definition for a Node.
 * struct Node {
 *     int val;
 *     struct Node *next;
 *     struct Node *random;
 * };
 */

struct Node* copyRandomList(struct Node* head) {
    if(head==NULL)
        return NULL;
 
    struct Node*cur=head;
	while(cur)
    {
        struct Node*next=cur->next;
        struct Node*newcur=(struct Node*)malloc(sizeof(struct Node));
        cur->next=newcur;
        newcur->next=next;
        newcur->val=cur->val;
        cur=next;
    }
    cur=head;
    
    while(cur)//这里注意分三步 链random和next不能一起
    {
        struct Node*next=cur->next;

        if(cur->random!=NULL)
            next->random=cur->random->next;
        else
            next->random=NULL;
        cur=next->next;
    }
    cur=head;
    struct Node*newhead=cur->next;
    while(cur->next->next)
    {
        struct Node*copy=cur->next;
        struct Node*newcur=copy->next;
        copy->next=newcur->next;
        cur=newcur;
    }
    return newhead;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值