链表相关题目-leetcode简单

目录

1. 合并两个有序链表

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

3. 环形链表

4.相交链表

5.移除链表元素

6.反转链表

7.回文链表

8.删除链表中的节点

9.链表的中间节点

1. 合并两个有序链表

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

方法一、利用递归思想

class Solution {
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        if(l1==nullptr)
            return l2;
        if(l2==nullptr)
            return l1;

        if(l1->val < l2->val){
            l1->next = mergeTwoLists(l1->next,l2);
            return l1;
        }else{
            l2->next = mergeTwoLists(l2->next,l1);
            return l2;
        }

    }
}; 

方法二、迭代

class Solution {
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        ListNode* newHead = new ListNode(-1);
        ListNode* p = newHead;
        while(l1!=nullptr && l2!=nullptr){
            if(l1->val <= l2->val){
                p->next = l1;
                l1 = l1->next;
            }else{
                p->next = l2;
                l2 = l2->next;
            }
            p = p->next;
        }

        p->next = (l1!=nullptr)? l1:l2;
        return newHead->next;

    }
};

  

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

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

方法一、由于要删除元素,因此要保存要删除元素的前一个结点

class Solution {
public:
    ListNode* deleteDuplicates(ListNode* head) {
        if(head==nullptr)
            return head;
        ListNode* pre = head;
        ListNode* curr = pre->next;
        while(curr){
            while(curr && pre->val == curr->val){
                ListNode* tmp = curr;
                curr = curr->next;
                pre->next = curr;
                tmp = nullptr;  //清除野指针
            }
            pre = pre->next;
            if(pre)
                curr = pre->next;
            else
                curr = nullptr;
        }
        return head;
    }
};

  

3. 环形链表

给定一个链表,判断链表中是否有环。

方法一、快慢指针

class Solution {
public:
    bool hasCycle(ListNode *head) {
        if(head==nullptr)
            return false;   //注意空链表,认为是没有环的

        ListNode *slow = head;
        ListNode *fast = head;
        while(fast){
            ListNode *tmp = fast->next;
            if(tmp){
                fast = tmp->next;
            }else{
                return false;
            }

            slow = slow->next;
            if(fast==slow)
                return true;

        }
        return false;
    }
};  

方法二、哈希表

通过检查一个结点此前是否被访问过来判断链表是否为环形链表。常用的方法是使用哈希表

class Solution {
public:
    bool hasCycle(ListNode *head) {
        if(head==nullptr)
            return false;

        ListNode *curr=head;
        set<ListNode*> ss;  //注意存的类型是ListNode*
        while(curr){
            if(ss.find(curr)!=ss.end()){ //存在
                return true;
            }else{
                ss.insert(curr);
                curr = curr->next;
            }
        }
        return false;

    }
};

  

方法三、反转链表(唯一的问题是破坏了原始链表)

环形链表中,环形会死循环没办法判断边界条件,因此我们我们每遍历一个链表就把后面的指向前面的,这样环形要再次循环时会反方向走,最后他会回到头节点,从而循环结束。
环形初始情况:

倒置后:

没有环形就是普通链表的倒置。
所以最后只要判断倒置后的首节点是不是head节点,是则true,不是则false。
时间复杂度最坏是2n(相当于从最后再来一次),即O(n),空间上只用了三个指针即O(1)。

public class Solution {
    public boolean hasCycle(ListNode head) {
        if(head==null)return false;
        if(head.next==head)return true;
        if(head.next==null)return false;
        ListNode p=head.next,q=p.next,x;
        head.next=null;
        for(x=head,p.next=x;q!=null;p.next=x,x=p,p=q,q=q.next);//倒置整个链表
        if(p==head)return true;//如果回到头节点说明存在环,不是则不存在环
        else return false;
    }
}

  

4. 相交链表

编写一个程序,找到两个单链表相交的起始节点。

在节点 c1 开始相交

方法一: 暴力法

对链表A中的每一个结点 a_i,遍历整个链表 B 并检查链表 B 中是否存在结点和 a_i相同。

复杂度分析

时间复杂度 : O(mn)。
空间复杂度 : O(1)。

方法二、哈希表

遍历链表A并将每个结点的地址/引用存储在哈希表中。然后检查链表B中的每一个结点 b_i是否在哈希表中。若在,则 b_i为相交结点。

复杂度分析
时间复杂度 : O(m+n)。
空间复杂度 : O(m)或O(n)。

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        if(headA==nullptr || headB==nullptr)
            return nullptr;

        ListNode *p = headA;
        set<ListNode *> ss;
        while(p){
            ss.insert(p);
            p = p->next;
        }

        p = headB;
        while(p){
            if(ss.find(p)!=ss.end()){ //找到相同元素
                return p;
            }else{
                p = p->next;
            }
        }
        return nullptr;
    }
};  

方法三、双指针法

创建两个指针pA和pB,分别初始化为链表A和B的头结点。然后让它们向后逐结点遍历。

当pA到达链表的尾部时,将它重定位到链表B的头结点 (你没看错,就是链表B); 类似的,当pB到达链表的尾部时,将它重定位到链表A的头结点。
若在某一时刻pA和pB相遇,则pA/pB为相交结点。
想弄清楚为什么这样可行, 可以考虑以下两个链表: A={1,3,5,7,9,11}和B={2,4,9,11},相交于结点9。由于B.length (=4)< A.length(=6),pB比pA少经过2个结点,会先到达尾部。将pB重定向到A的头结点,pA重定向到B的头结点后,pB要比pA多走2个结点。因此,它们会同时到达交点。
如果两个链表存在相交,它们末尾的结点必然相同。因此当pA/pB到达链表结尾时,记录下链表A/B对应的元素。若最后元素不相同,则两个链表不相交。

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        if(headA==nullptr || headB==nullptr)
            return nullptr;

        ListNode *pa = headA;
        ListNode *pb = headB;
        /*
         * 如果是没有相交的链表,最后两个指针将分别指向尾部的nullptr,由相等退出循环
        */
        while(pa != pb){
            pa = (pa==nullptr)?headB:pa->next;
            pb = (pb==nullptr)?headA:pb->next;
        }

        return pa;

    }
};

  

5. 移除链表元素

删除链表中等于给定值 val 的所有节点。

方法一、新建虚拟头节点

由于可能会设计到头节点的删除,因此新建一个头头节点

class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        if(head==nullptr)
            return nullptr;

        ListNode* newHead = new ListNode(-1);
        newHead->next=head;
        ListNode *pre = newHead, *curr=head;
        while(curr){
            if(curr->val==val){//删除
                pre->next=curr->next;
                curr=curr->next;
            }else{
                pre = curr;
                curr=curr->next;
            }
        }
        return newHead->next;

    }
};  

方法二、递归

class Solution {
    public ListNode removeElements(ListNode head, int val) {
       if(head==null)
           return null;
        head.next=removeElements(head.next,val);  //一直到尾元素,才开始处理下面的语句,即从后往前判断是否由target值。
        if(head.val==val){
            return head.next;
        }else{
            return head;
        }
    }
}

  

方法三、头节点另考虑

class Solution {
    public ListNode removeElements(ListNode head, int val) {
        //删除值相同的头结点后,可能新的头结点也值相等,用循环解决
        while(head!=null&&head.val==val){
            head=head.next;
        }
        if(head==null)
            return head;
        ListNode prev=head;
        //确保当前结点后还有结点
        while(prev.next!=null){
            if(prev.next.val==val){
                prev.next=prev.next.next;
            }else{
                prev=prev.next;
            }
        }
        return head;
    }
}

  

6. 反转链表

方法一、递归

递归版本稍微复杂一些,其关键在于反向工作。假设列表的其余部分已经被反转,现在我该如何反转它前面的部分?

class Solution {
public:
    ListNode* reverseList(ListNode* head) { //用三个相邻的指针从前往后移
        if(head==nullptr || head->next==nullptr)
            return head;
        ListNode *pre = reverseList(head->next);
        head->next->next=head;
        head->next =nullptr;
        return pre;
    }
};

  

方法二、迭代

头插法:新建虚拟头节点

class Solution {
public:
    ListNode* reverseList(ListNode* head) { //头插法
        if(head==nullptr)
            return head;
        ListNode* newHead = new ListNode(-1);
        newHead->next = head;
        ListNode* pre=head;
        ListNode* curr=pre->next;
        while(curr){
            pre->next = curr->next;
            curr->next = newHead->next; //每一次插入到头元素,此处不是pre
            newHead->next = curr;
            curr = pre->next; //指向下一个要插入的节点
        }

        return newHead->next;
    }
};

用三个相邻的指针从前往后移

class Solution {
public:
    ListNode* reverseList(ListNode* head) { //用三个相邻的指针从前往后移
        if(head==nullptr)
            return head;
        ListNode *pre = nullptr, *curr=head, *pnext=head->next;
        while(pnext){
            curr->next=pre;
            pre = curr;
            curr=pnext;
            pnext=pnext->next;
        }
        curr->next=pre;
        return curr;

    }
};

  

7. 回文链表

8. 删除链表中的节点

9. 链表的中间结点

原文地址:https://www.cnblogs.com/GuoXinxin/p/11706297.html

时间: 2024-11-08 23:55:27

链表相关题目-leetcode简单的相关文章

剑指offer题目系列三(链表相关题目)

本篇延续上一篇剑指offer题目系列二,介绍<剑指offer>第二版中的四个题目:O(1)时间内删除链表结点.链表中倒数第k个结点.反转链表.合并两个排序的链表.同样,这些题目并非严格按照书中的顺序展示的,而是按自己学习的顺序,每个题目包含了分析和代码. 9.O(1)时间内删除链表结点 题目: 在O(1)时间内删除链表结点.给定单链表的头指针和一个结点指针,定义一个方法在O(1)时间内删除该结点. 单链表的定义如下: 解答: 单向链表删除一个结点,最直观的想法是从链表的头结点开始顺序遍历查找要

链表相关的leetcode重要题目

Leetcode 92:反转链表II 解决这道题需要三个步骤: 找到需要反转的第一个节点.可以通过头节点前进m-1步,找到反转开始的位置. 将需要反转的部分进行反转.参考Leetcode 206:反转链表. 将反转部分与剩余部分进行链接.其中分为两种情况,m=1与m>1.当m=1时,仅需要将反转部分反转前的头节点的next指向反转部分反转前尾节点的后继,新链表的头节点为反转部分反转前的尾节点.当m>1时,还需要将反转部分反转前头节点的前驱的next指向反转部分反转前的末节点,新链表的头节点仍为

leetcode 单链表相关题目汇总

  leetcode-19-Remove Nth From End of List—移除链表中倒数第n个元素 leetcode-21-Merge Two Sorted Lists—两个已排序链表归并 leetcode-23-Merge k Sorted Lists—k个已排序链表归并 leetcode-24-Swap Nodes in Pairs—链表中元素两两一组交换 leetcode-25-Reverse Nodes in K-Group—链表中元素k个一组逆序 leetcode-61-Ro

链表相关题目总结

一.两数相加: 由于是刷的第一题,先写下: 本题是两个链表相加,方法是生成一个新的链表(其实是一个节点不断指向下一个节点,这样依次连起来的)去存储这个两个的和. 首先生成一个新的链表h,h是头指针:然后再让指针t指向h.    新建私有方法,该方法的作用是返回链表的有效数据,并让链表指向后继节点.即p好p->next指向相同的地址. 然后建立一个进位标志carry,让sum = x + y  + carry:,将sum %10作为新节点node的初始值.让node和*t指向相同的地址,记住*t是

LeetCode: Palindrome 回文相关题目

LeetCode: Palindrome 回文相关题目汇总 LeetCode: Palindrome Partitioning 解题报告 LeetCode: Palindrome Partitioning II 解题报告 Leetcode:[DP]Longest Palindromic Substring 解题报告 LeetCode: Valid Palindrome 解题报告

链表相关算法题总结 1

链表题目对算法的要求度不高,但实际写的过程中需要注意语言细节,考虑精细度的地方很多. 1.链表结构与基本操作 1.1 添加节点 一般情况: cur ->next = prev ->next; prev ->next = cur; 表头插入: cur ->next = head; head = cur; 1.2删除节点 一般情况:(已知待删除节点的前驱节点) ListNode* temp = prev->next; prev->next = prev->next-&

进程和cpu的相关知识和简单调优方案

进程就是一段执行的程序,每当一个程序运行时,对于操作系统本身来说,就创建了一个进程,并且分配了对应的资源.进程可以分为3个类别: 1.交互式进程(I/O) 2.批处理进程 (CPU) 3.实时进程 (REAL-TIME) 对于交互式进程来说,一般其占用的cpu时间片很段,但是优先级偏高;批处理进程占用的cpu时间片很长,但是优先级偏底;实时进程是内核所使用的,其优先级高于前面两种. 上面说到了优先级,linux进程是具有优先级的,一般分为两种: 1.实时优先级 2.静态优先级 实时优先级的取值范

【常用算法思路分析系列】链表相关高频题集

本文是[常用算法思路分析系列]的第四篇,总结链表相关的高频题目和解题思路.本文分析如下几个问题:1.环形链表的差值问题:2.只能访问单个结点的删除问题:3.链表的分化:4.打印两个链表的公共部分:5.把链表的每k个结点逆序:6.删除链表中指定结点:7.判断链表是否为回文结构:8.复杂链表的复制:9.判断链表是否有环:10.判断两个无环链表是否相交:11.判断两个有环链表是否相交:12.判断两个链表(状态未定)是否相交. 本系列前三篇导航: [常用算法思路分析系列]排序高频题集 [常用算法思路分析

ARPG游戏打击感相关的技术简单总结

说好的技术总结,根据近期的工作总结一下体现游戏打击感相关的技术.一般arpg的游戏打击感除了场景的氛围的烘托,策划的数值:连击,奖励伤害数字的连贯积累反馈,硬直加物理击飞ragdoll,更不可忽视的也是最基本的是保证攻击受击图像效果,声音和打击.受击动作的准确配合.这个过程就可以有一些技术辅助体现了.例如挥刀过程中出现的刀光轨迹.对表现气流的变化造成的空间扭曲.甚至由于技能强势战斗激烈造成的相机晃动.还有一些全屏特效的烘托,比如全屏的colorgrading控制黑白甚至饱和度.反色的渐变.抑或是