Leetcode——链表

2. 两数相加

给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。

请你将两个数相加,并以相同形式返回一个表示和的链表。

你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
示例1
解题思路: 给定的两个链表,由于是逆序排列,从链表第一个结点开始是低位。设置一个进位标志,将两链表的结点值用sum依次相加,和大于等于十时需要进位。结果存在另一链表中。

/**
 * Definition for singly-linked list.
 * 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) {}
 * };
 */
class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        //ListNode *p=l1,*q=l2;    //链表指针
        ListNode* result=new ListNode();//存放结果的链表头
        ListNode* r=result;
        int sum=0;
        bool carry=false; //进位标志
        //当两链表都不为空时
        while(l1!=NULL||l2!=NULL)
        {
            sum=0;
            //将两链表对应位置的结点值加起来
            if(l1!=NULL)
            {
                sum+=l1->val;
                l1=l1->next;
            }
            if(l2!=NULL)
            {
                sum+=l2->val;
                l2=l2->next;
            }
            //如果上一次相加发现有进位,则sum再加一
            if(carry)
                sum++;
            //将求和得到的结点对10取余后值赋给下一结点
            r->next=new ListNode(sum%10);
            //位置指针向后
            r=r->next;
 			//本次求和后的进位与否标志
            carry=(sum>=10)?true:false;
        }
        //当两链表均为空,而还需要再进位(两数相加后的位数超过原来数的位数)时,再开辟一个新节点
        if(carry)
        {
            r->next=new ListNode(1);
        }
        return result->next;
    }
};

237. 删除链表中的节点

写一个函数来删除单链表中的某个节点,在不给出链表的情况下直接删除这个节点。待删除节点不是尾节点。
在这里插入图片描述
解题思路:

  • 这道题真的非常妙,刚开始看的时候还以为题出错了,怎么少给了参数,没有链表怎么删这个节点。后来又读了几遍搞懂了题意,但是还是不会做啊呜呜呜,看看评论。解法真的很简单很妙,我自己是真的想不到。
  • 要直接删除一个节点,可以将这个节点的值变成他的下一个节点,然后跳过下一个节点。
  • 也就是说成为另一个人,然后再让那个人消失,那么原来的我也就消失了。细思极恐。
  • 两行代码搞定。
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    void deleteNode(ListNode* node) {
    	//变成下一个人
        node->val=node->next->val;
        //然后让他消失
        node->next=node->next->next;
    }
};

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

给你一个链表,删除链表的倒数第n个结点,并且返回链表的头结点。
在这里插入图片描述
解法一:计算链表长度

  • 先遍历一次链表,计算长度L,然后再遍历一次到L-n+1的位置,下一个结点就是要删除的,跳过它。
  • 构造一个头结点dummy,接在head的前面,当需要删除首元结点时就可以不用做判断,最终返回dummy的下一个结点指针。
  • 这个方法挺慢的,时间O(L),空间O(1)
/**
 * Definition for singly-linked list.
 * 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) {}
 * };
 */
class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        int size=0;
        //构造一个头节点
        ListNode * dummy=new ListNode(0,head);
        ListNode *i=head,*j=dummy;
        //第一次遍历,计算链表的大小
        while(i)
        {
            i=i->next;
            size++;
        }
        //第二次,遍历到要删除的倒数第n个结点前面的位置
        for(int c=1;c<size-n+1;c++)
        {
            j=j->next;
        }
        //跳过待删除的那个结点
        j->next=j->next->next;
        ListNode* ans = dummy->next;
        delete dummy;
        return ans;
    }
};

解法二:双指针

  • 利用快慢两个指针,初始时first快指针指向head,慢指针second指向dummy
  • 快指针先走n步,到达第n个结点,此时快慢指针间相隔n个结点,然后快慢指针开始同时走,直到快指针到达表尾。
  • 慢指针从头开始走,当快指针指向空时,慢指针的下一个结点就是待删除的倒数第n个结点
    在这里插入图片描述
/**
 * Definition for singly-linked list.
 * 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) {}
 * };
 */
class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        //双指针解法
        //先构造一个头结点,其下一结点为head,便于删除第一个结点
        ListNode * dummy=new ListNode(0,head);
        ListNode * first=head,*second=dummy; //first是快指针,second是慢指针
        //i先走n步
        for(int i=0;i<n;i++)
        {
            first=first->next;
        }
        //当快指针走到了头时(first为空),慢指针指向结点的下一个结点就是待删除结点。
        while(first)
        {
            first=first->next;
            second=second->next;
        }
        //跳过倒数第n个结点
        second->next=second->next->next;
        ListNode * ans=dummy->next;
        delete dummy;
        return ans;
    }
};

206. 反转链表

反转一个单链表。
在这里插入图片描述
解法一 迭代
遍历链表,将当前结点inext域设为它的前一个结点,相当于将箭头转个向。设置之前需要用一个临时结点保存i原本的下一个结点。

/**
 * Definition for singly-linked list.
 * 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) {}
 * };
 */
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        //遍历链表,将当前结点的next设为它前面的那个结点
        ListNode * pre=nullptr;
        ListNode * i=head;
        while(i)
        {
        	//保存i原本的下一个结点
            ListNode * next=i->next;
            i->next=pre;
            pre=i;
            i=next;
        }
        return pre;
    }
};

解法二 递归

递归的三条件:

  • 大问题分解成两个问题
  • 子问题的求解方式和大问题一样(将链表分解为首元结点和除首元结点以外的所有节点,然后递归调用)
  • 存在最小子问题(递归终止)
  • 参考
/**
 * Definition for singly-linked list.
 * 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) {}
 * };
 */
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        //用递归,将原问题分解为首元结点和除首元结点以外结点的子问题
        //终止条件,当递的过程到达子链表只剩一个元素时,直接将其返回
        //因为一个单节点不用进行反转
        if(!head||!head->next)
        {
            return head;
        }
        //递归调用
        ListNode * p=reverseList(head->next);
        head->next->next=head;
        head->next=nullptr;
        //返回的部分已经做好了反转
        return p;
    }
};

21. 合并两个有序链表

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
l1l2均按 非递减顺序 排列
在这里插入图片描述
解法一 暴力遍历

  • 设置一个头结点,然后遍历l1l2,将值较小的那个结点接在头结点后,然后指向下一结点。
  • 时间O(M+N),空间O(1)
  • 思路简单,容易理解
/**
 * Definition for singly-linked list.
 * 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) {}
 * };
 */
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        //用双指针,暴力解法
        ListNode*dummy=new ListNode(0);
        //总链表指针
        ListNode*pre=dummy;
        //遍历两个链表,将较小结点接在总链表后面
        while(l1&&l2)
        {
            if(l1->val<l2->val)
            {
                pre->next=l1;
                pre=l1;
                l1=l1->next;
            }
            else
            {
                pre->next=l2;
                pre=l2;
                l2=l2->next;
            }
        }
        //当有一方到达表尾时,将将另一个表的剩余部分全部接在总链表之后
        if(l1)
            pre->next=l1;
        if(l2)
            pre->next=l2;
        ListNode*ans=dummy->next;
        delete dummy;
        return ans;
    }
};

解法二 递归

  • 合并l1l2,先看l1l2的首元结点哪个更小
  • 然后较小结点的next指针指向其余节点的合并结果(递归调用)
  • 终止条件是两个链表中有一个到达表尾,然后返回另外一个链表所有剩余部分
  • 官方题解
    在这里插入图片描述
  • 时间·O(M+N)·,空间·O(M+N)·,·M·和·N·是两个链表的长度
/**
 * Definition for singly-linked list.
 * 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) {}
 * };
 */
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        //递归解法
        //判断 l1 和 l2 头结点哪个更小,然后较小结点的 next 指针指向其余结点的合并结果。(调用递归)
        //终止条件
        if(!l1)
            return l2;
        if(!l2)
            return l1;
        if(l1->val<=l2->val)
        {
           	//递归调用
            l1->next=mergeTwoLists(l1->next,l2);
            return l1;
        }
        l2->next=mergeTwoLists(l1,l2->next);
        return l2;
    }
};

234. 回文链表

请判断一个链表是否为回文链表。
O(n) 时间复杂度和 O(1)空间复杂度解决此题
在这里插入图片描述
解题思路: (来自官方题解)

  • 将链表后半部分反转(会修改链表结构),将前半部分和后半部分进行比较,比较后将链表恢复原样。
  • 分成五个步骤
  1. 找到前半部分链表的尾节点。我们也可以使用快慢指针在一次遍历中找到:慢指针一次走一步,快指针一次走两步,快慢指针同时出发。当快指针移动到链表的末尾时,慢指针恰好到链表的中间。通过慢指针将链表分为两部分。若链表有奇数个节点,则中间的节点应该看作是前半部分。
  2. 反转后半部分链表。可以使用「206. 反转链表」问题中的解决方法来反转链表的后半部分。
  3. 判断是否回文。比较两个部分的值,当后半部分到达末尾则比较完成,可以忽略计数情况中的中间节点。
  4. 恢复链表。与步骤二使用的函数相同,再反转一次恢复链表本身。
  5. 返回结果。
    代码
/**
 * Definition for singly-linked list.
 * 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) {}
 * };
 */
class Solution {
public:
    bool isPalindrome(ListNode* head) {
        //快慢指针
        ListNode * fast=head;
        ListNode * slow=head;
        while(fast->next&&fast->next->next)
        {
            fast=fast->next->next;
            slow=slow->next;
        }
        //快指针到达表尾时退出
        //翻转后半段链表
        ListNode * p=reverseList(slow->next);
        ListNode * q=head;
        ListNode * second_half=p;
        //逐个比较前半段和后半段子链表
        //ListNode * temp=fast;
        while(p)
        {
            if(p->val==q->val)
            {
                p=p->next;
                q=q->next;
            }
            else
                return false;
        }
        //还原链表
        slow->next=reverseList(second_half);
        return true;
    }
    ListNode* reverseList(ListNode* head) {
        //用递归,将原问题分解为首元结点和除首元结点以外结点的子问题
        //终止条件,当递的过程到达子链表只剩一个元素时,直接将其返回
        //因为一个单节点不用进行反转
        if(!head||!head->next)
        {
            return head;
        }
        //递归调用
        ListNode * p=reverseList(head->next);
        head->next->next=head;
        head->next=nullptr;
        return p;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Rust 是一种现代的编程语言,特别适合处理内存安全和线程安全的代码。在 LeetCode 中,链表是经常出现的题目练习类型,Rust 语言也是一种非常适合处理链表的语言。接下来,本文将从 Rust 语言的特点、链表的定义和操作,以及 Rust 在 LeetCode链表题目的练习等几个方面进行介绍和讲解。 Rust 语言的特点: Rust 是一种现代化的高性能、系统级、功能强大的编程语言,旨在提高软件的可靠性和安全性。Rust 语言具有如下几个特点: 1. 内存安全性:Rust 语言支持内存安全性和原语级的并发,可以有效地预防内存泄漏,空悬指针以及数据竞争等问题,保证程序的稳定性和可靠性。 2. 高性能:Rust 语言采用了“零成本抽象化”的设计思想,具有 C/C++ 等传统高性能语言的速度和效率。 3. 静态类型检查:Rust 语言支持静态类型检查,可以在编译时检查类型错误,避免一些运行时错误。 链表的定义和操作: 链表是一种数据结构,由一个个节点组成,每个节点保存着数据,并指向下一个节点。链表的定义和操作如下: 1. 定义:链表是由节点组成的数据结构,每个节点包含一个数据元素和一个指向下一个节点的指针。 2. 操作:链表的常用操作包括插入、删除、查找等,其中,插入操作主要包括在链表首尾插入节点和在指定位置插入节点等,删除操作主要包括删除链表首尾节点和删除指定位置节点等,查找操作主要包括根据数据元素查找节点和根据指针查找节点等。 Rust 在 LeetCode链表题目的练习: 在 LeetCode 中,链表是常见的题目类型,而 Rust 语言也是一个非常适合练习链表题目的语言。在 Rust 中,我们可以定义结构体表示链表的节点,使用指针表示节点的指向关系,然后实现各种操作函数来处理链表操作。 例如,针对 LeetCode 中的链表题目,我们可以用 Rust 语言来编写解法,例如,反转链表,合并两个有序链表,删除链表中的重复元素等等,这样可以更好地熟悉 Rust 语言的使用和链表的操作,提高算法和编程能力。 总之,在 Rust 中处理链表是非常方便和高效的,而 LeetCode 中的练习也是一个非常好的机会,让我们更好地掌握 Rust 语言和链表数据结构的知识。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值