From dfdec0d511dad84e3e80b974fdf08c0818275a70 Mon Sep 17 00:00:00 2001 From: chefyuan Date: Fri, 19 Mar 2021 15:07:33 +0800 Subject: [PATCH] test --- gif-algorithm/链表篇/234. 回文链表.md | 125 +++++++++++++++ gif-algorithm/链表篇/leetcode142环形链表2.md | 149 +++++++++++++++++ .../leetcode82删除排序链表中的重复元素II.md | 64 ++++++++ gif-algorithm/链表篇/关于链表的那些事.md | 151 ++++++++++++++++++ .../链表篇/剑指Offer25合并两个排序的链表.md | 45 ++++++ .../剑指Offer52两个链表的第一个公共节点.md | 86 ++++++++++ .../链表篇/剑指offer2倒数第k个节点.md | 54 +++++++ gif-algorithm/链表篇/空.md | 0 .../链表篇/面试题 02.03. 删除中间节点.md | 60 +++++++ .../链表篇/面试题 02.05. 链表求和.md | 104 ++++++++++++ 10 files changed, 838 insertions(+) create mode 100644 gif-algorithm/链表篇/234. 回文链表.md create mode 100644 gif-algorithm/链表篇/leetcode142环形链表2.md create mode 100644 gif-algorithm/链表篇/leetcode82删除排序链表中的重复元素II.md create mode 100644 gif-algorithm/链表篇/关于链表的那些事.md create mode 100644 gif-algorithm/链表篇/剑指Offer25合并两个排序的链表.md create mode 100644 gif-algorithm/链表篇/剑指Offer52两个链表的第一个公共节点.md create mode 100644 gif-algorithm/链表篇/剑指offer2倒数第k个节点.md delete mode 100644 gif-algorithm/链表篇/空.md create mode 100644 gif-algorithm/链表篇/面试题 02.03. 删除中间节点.md create mode 100644 gif-algorithm/链表篇/面试题 02.05. 链表求和.md diff --git a/gif-algorithm/链表篇/234. 回文链表.md b/gif-algorithm/链表篇/234. 回文链表.md new file mode 100644 index 0000000..1ea9309 --- /dev/null +++ b/gif-algorithm/链表篇/234. 回文链表.md @@ -0,0 +1,125 @@ + + +#### leetcode 234. 回文链表 + +请判断一个链表是否为回文链表。 + +示例 1: + +``` +输入: 1->2 +输出: false +``` + + +示例 2: + +```java +输入: 1->2->2->1 +输出: true +``` + +题目解析: + +题目理解起来很简单,判断是否为回文,如果单纯判断一个字符串或者数组是不是回文很容易。因为数组查询元素的时间复杂度为O(1),但是链表的查询时间复杂度为O(n),而且题目中的链表为单链表,指针只能后移不能前移。所以我们判断起来会比较困难。 + +巧用数组法: + +我们首先将链表的所有元素都保存在数组中,然后再利用双指针遍历数组,进而来判断是否为回文。这个方法很容易理解,而且代码实现也比较简单。 + +```java +class Solution { + public boolean isPalindrome(ListNode head) { + //这里需要用动态数组,因为我们不知道链表的长度 + List arr = new ArrayList(); + ListNode copynode = head; + //将链表的值复制到数组中 + while (copynode != null) { + arr.add(copynode.val); + copynode = copynode.next; + } + // 双指针遍历数组 + int back = 0; + int pro = arr.size() - 1; + while (back < pro) { + //判断两个指针的值是否相等 + if (!arr.get(pro).equals(arr.get(back))) { + return false; + } + //移动指针 + back++; + pro--; + } + return true; + } +} + +``` + +这个方法可以直接通过,但是这个方法需要辅助数组,那我们还有其他更好的方法吗? + +双指针翻转链表法 + +在上个题目中我们知道了如何找到链表的中间节点,那我们可以在找到中间节点之后,对后半部分进行翻转,翻转之后,重新遍历前半部分和后半部分进行判断是否为回文。 + +动图解析: + +![翻转链表部分](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/翻转链表部分.1v2ncl72ligw.gif) + +```java +class Solution { + public boolean isPalindrome(ListNode head) { + if (head==null || head.next==null) { + return true; + } + //找到中间节点,也就是翻转的头节点,这个在昨天的题目中讲到 + //但是今天和昨天有一些不一样的地方就是,如果有两个中间节点返回第一个,昨天的题目是第二个 + ListNode midenode = searchmidnode(head); + //原地翻转链表,需要两个辅助指针。这个也是面试题目,大家可以做一下 + //这里我们用的是midnode.next需要注意,因为我们找到的是中点,但是我们翻转的是后半部分 + + ListNode backhalf = reverse(midenode.next); + //遍历两部分链表,判断值是否相等 + ListNode p1 = head; + ListNode p2 = backhalf; + while (p2 != null) { + if (p1.val != p2.val) { + return false; + } + p1 = p1.next; + p2 = p2.next; + } + // 还原链表并返回结果,这一步是需要注意的,我们不可以破坏初始结构,我们只是判断是否为回文, + //当然如果没有这一步也是可以AC,但是面试的时候题目要求可能会有这一条。 + midenode.next = reverse(backhalf); + return true; + } + //找到中间的部分 + public ListNode searchmidnode (ListNode head) { + ListNode fast = new ListNode(-1); + ListNode slow = new ListNode(-1); + fast = head; + slow = head; + //找到中点 + while (fast.next != null && fast.next.next != null) { + fast = fast.next.next; + slow = slow.next; + } + return slow; + } + //翻转链表 + public ListNode reverse (ListNode slow) { + ListNode low = null; + ListNode temp = null; + //翻转链表 + while (slow != null) { + temp = slow.next; + slow.next = low; + low = slow; + slow = temp; + } + return low; + } +} +``` + diff --git a/gif-algorithm/链表篇/leetcode142环形链表2.md b/gif-algorithm/链表篇/leetcode142环形链表2.md new file mode 100644 index 0000000..be88e54 --- /dev/null +++ b/gif-algorithm/链表篇/leetcode142环形链表2.md @@ -0,0 +1,149 @@ +#### leetcode 142. 环形链表 II + +题目描述: + +今天给大家介绍比较有特点的题目,也是一个特别经典的题目,判断链表中有没有环,并返回环的入口。 + +我们可以先做一下这个题目,就是如何判断链表中是否有环呢?下图则为链表存在环的情况。 + +![image-20201027175552961](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/image-20201027175552961.63sz69rbes00.png) + +判断链表是否有环是很简单的一个问题,我们只需利用之前的快慢指针即可,大家想一下,指针只要进入环内就只能在环中循环,那么我们可以利用快慢指针,虽然慢指针的速度小于快指针但是,总会进入环中,当两个指针都处于环中时,因为移动速度不同,两个指针必会相遇。 + +我们可以这样假设,两个孩子在操场顺时针跑步,一个跑的快,一个跑的慢,跑的快的那个孩子总会追上跑的慢的孩子。 + +环形链表: + +```java +public class Solution { + public boolean hasCycle(ListNode head) { + //特殊情况,无节点或只有一个节点的情况 + if(head == null || head.next == null){ + return false; + } + //设置快慢指针 + ListNode pro = head.next; + ListNode last = head; + //循环条件 + while( pro != null && pro.next!=null){ + pro=pro.next.next; + last=last.next; + //两指针相遇 + if(pro == last){ + return true; + } + } + //循环结束,指针没有相遇,说明没有环。相当于快指针遍历了一遍链表 + return false; + + } +} +``` + +判断链表是不是含有环很简单,但是我们想找到环的入口可能就没有那么容易了。(入口则为下图绿色节点) + +然后我们返回的则为绿色节点的索引,则返回2。 + +![image-20201027180921770](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/image-20201027180921770.21fh8pt3cuv4.png) + + + +### HashSet + +我们可以利用HashSet来做,之前的文章说过HashSet是一个不允许有重复元素的集合。所以我们通过HashSet来保存链表节点,对链表进行遍历,如果链表不存在环则每个节点都会被存入环中,但是当链表中存在环时,则会发重复存储链表节点的情况,所以当我们发现HashSet中含有某节点时说明该节点为环的入口,返回即可。 + +下图中,存储顺序为 0,1,2,3,4,5,6,**2 **因为2已经存在,则返回。 + +![image-20201027182649669](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/image-20201027182649669.2g8gq4ik6xs0.png) + + + +```java +public class Solution { + public ListNode detectCycle(ListNode head) { + + if (head == null) { + return head; + } + if (head.next == null) { + return head.next; + } + //创建新的HashSet,用于保存节点 + HashSet hash = new HashSet(); + //遍历链表 + while (head != null) { + //判断哈希表中是否含有某节点,没有则保存,含有则返回该节点 + if (hash.contains(head)) { + return head; + } + //不含有,则进行保存,并移动指针 + hash.add(head); + head = head.next; + } + return head; + } +} +``` + + + +### 快慢指针 + +这个方法是比较巧妙的方法,但是不容易想到,也不太容易理解,利用快慢指针判断是否有环很容易,但是判断环的入口就没有那么容易,之前说过快慢指针肯定会在环内相遇,见下图。 + +![image-20201027184755943](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/image-20201027184755943.3edot8s2xi60.png) + +上图黄色节点为快慢指针相遇的节点,此时 + +快指针走的距离:**a+(b+c)n+b** + +很容易理解b+c为环的长度,a为直线距离,b为绕了n圈之后又走了一段距离才相遇,所以相遇时走的总路程为a+(b+c)n+b,合并同类项得a+(n+1)b+nc。 + +慢指针走的距离:**a+(b+c)m+b**,m代表圈数。 + +然后我们设快指针得速度是慢指针的2倍,含义为相同时间内,快指针走过的距离是慢指针的2倍。 + +**a+(n+1)b+nc=2[a+(m+1)b+mc]**整理得**a+b=(n-2m)(b+c),**那么我们可以从这个等式上面发现什么呢?**b+c** + +为一圈的长度。也就是说a+b等于n-2m个环的长度。为了便于理解我们看一种特殊情况,当n-2m等于1,那么a+b=b+c整理得,a=c此时我们只需重新释放两个指针,一个从head释放,一个从相遇点释放,速度相同,因为a=c所以他俩必会在环入口处相遇,则求得入口节点索引。 + +算法流程: + +1.设置快慢指针,快指针速度为慢指针的2倍 + +2.找出相遇点 + +3.在head处和相遇点同时释放相同速度且速度为1的指针,两指针必会在环入口处相遇 + +![环形链表2](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/环形链表2.elwu1pw2lw0.gif) + + + +```java +public class Solution { + public ListNode detectCycle(ListNode head) { + //快慢指针 + ListNode fast = head; + ListNode low = head; + //设置循环条件 + while (fast != null && fast.next != null) { + fast = fast.next.next; + low = low.next; + //相遇 + if (fast == low) { + //设置一个新的指针,从头节点出发,慢指针速度为1,所以可以使用慢指针从相遇点出发 + ListNode newnode = head; + while (newnode != low) { + low = low.next; + newnode = newnode.next; + } + //在环入口相遇 + return low; + } + } + return null; + + } +} +``` + diff --git a/gif-algorithm/链表篇/leetcode82删除排序链表中的重复元素II.md b/gif-algorithm/链表篇/leetcode82删除排序链表中的重复元素II.md new file mode 100644 index 0000000..b00a280 --- /dev/null +++ b/gif-algorithm/链表篇/leetcode82删除排序链表中的重复元素II.md @@ -0,0 +1,64 @@ +#### 82. 删除排序链表中的重复元素 II + +题目描述 + +给定一个排序链表,删除所有含有重复数字的节点,只保留原始链表中没有重复出现的数字。 + +示例 1: + +```java +输入: 1->2->3->3->4->4->5 +输出: 1->2->5 +``` + + +示例 2: + +```java +输入: 1->1->1->2->3 +输出: 2->3 +``` + +> 注意这里会将重复的值全部删除,1,1,2,3最后只会保留2,3。 + +这道题目还是很简单的,更多的是考察大家的代码完整性,删除节点也是题库中的一类题目,我们可以可以通过这个题目举一反三。去完成其他删除阶段的题目。 + +链表的题目建议大家能有指针实现还是尽量用指针实现,很多链表题目都可以利用辅助空间实现,我们也可以用,学会了那种方法的同时应该再想一下可不可以利用指针来完成。下面我们来思考一下这个题目如何用指针实现吧! + +做题思路: + +这个题目也是利用我们的双指针思想,一个走在前面,一个在后面紧跟,前面的指针就好比是侦察兵,当发现重复节点时,后面指针停止移动,侦察兵继续移动,直到移动完重复节点,然后将该节点赋值给后节点。思路是不是很简单啊,那么我们来看一下动图模拟吧。 + +注:这里为了表达更直观,所以仅显示了该链表中存在的节点 + +![删除重复节点2](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/删除重复节点2.3btmii5cgxa0.gif) + +```java +class Solution { + public ListNode deleteDuplicates(ListNode head) { + if(head == null||head.next==null){ + return head; + } + ListNode pre = head; + ListNode low = new ListNode(0); + low.next = pre; + ListNode ret = new ListNode(-1); + ret = low; + while(pre != null && pre.next != null) { + if (pre.val == pre.next.val) { + while (pre != null && pre.next != null && pre.val == pre.next.val) { + pre = pre.next; + } + pre = pre.next; + low.next = pre; + } + else{ + pre = pre.next; + low = low.next; + } + } + return ret.next; + } +} +``` + diff --git a/gif-algorithm/链表篇/关于链表的那些事.md b/gif-algorithm/链表篇/关于链表的那些事.md new file mode 100644 index 0000000..be6529e --- /dev/null +++ b/gif-algorithm/链表篇/关于链表的那些事.md @@ -0,0 +1,151 @@ +# 链表详解 + +阅读完本文你会有以下收获 + +1.知道什么是链表? + +2.了解链表的几种类型。 + +3.了解链表如何构造。 + +4.链表的存储方式 + +5.如何遍历链表 + +6.了解链表的操作。 + +7.知道链表和数组的不同点 + +8.掌握链表的经典题目。 + +### 链表的定义: + +> 定义:链表是一种递归的数据结构,他或者为空(null),或者是指向一个结点(node)的引用,该结点含有一个泛型的元素和一个指向另一条链表的引用。 + +我们来对其解读一下,链表是一种常见且基础的数据结构,是一种线性表,但是他不是按线性顺序存取数据,而是在每一个节点里存到下一个节点的地址。我们可以这样理解,链表是通过指针串联在一起的线性结构,每一个链表结点由两部分组成,数据域及指针域,链表的最后一个结点指向null。也就是我们所说的空指针。 + +### 链表的几种类型 + +我们先来看一下链表的可视化表示方法,以便更好的对其理解。 + +- 用长方形表示对象 +- 将实例变量的值写在长方形中; +- 用指向被引用对象的箭头表示引用关系。 + +#### 单链表 + +一个单向链表包含两个值: 当前节点的值和一个指向下一个节点的链接。 + +我们通过上面说到的可视化表示方法,构造单链表的可视化模型,如图所示。 + +![image-20201101143220993](E:\Typora笔记\CSDN\leetcode通关笔记\静态图\单链表.png) + + + +#### 双向链表 + +上面提到了单链表的节点只能指向节点的下一个节点。而双向链表有三个整数值: 数值、向后的节点链接、向前的节点链接,所以双链表既能向前查询也可以向后查询。 + +![双链表](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/双链表.3cw4hra1g3q0.png) + +#### + +还有一个常用的链表则为循环单链表,则单链表尾部的指针指向头节点。例如在leetcode61旋转链表中,我们就是先将链表闭合成环,找到新的打开位置,并定义新的表头和表尾。 + +### 构造链表 + +java是面向对象语言,实现链表很容易。我们首先用一个嵌套类来定义节点的抽象数据类型 + +```java +private class Node { + Item item; + Node next; +} +``` + +现在我们需要构造一条含有one,two,three的链表,我们首先为每个元素创造一个节点 + +```java +Node first = new Node(); +Node second = new Node(); +Node third = new Node(); +``` + +将每个节点的item域设为所需的值 + +```java +first.item = "one"; +second.item = "two"; +third.item = "three"; +``` + +然后我们设置next域来构造链表 + +```java +first.next = second; +second.next = third; +``` + +注:此时third的next仍为null,即被初始化的值。 + +### 链表的存储方式 + +我们知道了如何构造链表,我们再来说一下链表的存储方式。 + +我们都知道数组在内存中是连续分布的,但是链表在内存不是连续分配的。链表是通过指针域的指针链接内存中的各个节点。 + +所以链表在内存中是散乱分布在内存中的某地址上,分配机制取决于操作系统的内存管理。我们可以根据下图来进行理解。 + +![image-20201101153659912](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/image-20201101153659912.9neaap4ogtc.png) + +### 遍历链表 + +链表的遍历我们通常使用while循环(for循环也可以但是代码不够简洁)下面我们来看一下链表的遍历代码 + +for: + +```java +for (Node x = first; x != null; x = x.next) { + //处理x.item +} +``` + +while: + +``` +Node x = first; +while (x!=null) { + //处理x.item + x = x.next; +} +``` + +### 链表的几种操作 + +#### 添加节点 + +![image-20201101155937520](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/image-20201101155937520.my13cevp2cg.png) + + + +#### 删除节点 + +删除B节点,如图所示 + +![image-20201101155003257](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/image-20201101155003257.4onlntrwj2i0.png) + +我们只需将A节点的next指针指向C节点即可。 + +有的同学可能会有这种疑问,B节点这样不会留着内存里吗?java含有自己的内存回收机制,不用自己手动释放内存了,但是C++,则需要手动释放。 + +我们通过上图的删除和插入都是O(1)操作。 + +链表和数组的比较 + +| | 插入/删除操作(时间复杂度) | 查询(时间复杂度) | 存储方式 | +| ---- | ------------------------- | ------------------ | ------------ | +| 数组 | O(n) | O(1) | 内存连续分布 | +| 链表 | O(1) | O(n) | 内存散乱分布 | + + + diff --git a/gif-algorithm/链表篇/剑指Offer25合并两个排序的链表.md b/gif-algorithm/链表篇/剑指Offer25合并两个排序的链表.md new file mode 100644 index 0000000..06545f3 --- /dev/null +++ b/gif-algorithm/链表篇/剑指Offer25合并两个排序的链表.md @@ -0,0 +1,45 @@ +#### 剑指offer25合并两个有序链表 + +将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 + +示例: + +``` +输入:1->2->4, 1->3->4 +输出:1->1->2->3->4->4 +``` + +今天的题目思路很简单,但是一遍AC也是不容易的。链表大部分题目考察的都是考生代码的完整性和鲁棒性,所以有些题目我们看着思路很简单,但是想直接通过还是需要下一翻工夫的,所以建议大家将所有链表的题目都自己写一下。实在没有时间做的同学,可以自己在脑子里打一遍代码,想清没一行代码的作用。 + +迭代法: + +因为我们有两个升序链表,我们需要将其合并,那么我们需要创建一个新节点headpre,然后我们利用双指针思想,每个链表放置一个指针,然后进行遍历并对比当前指针指向的值。然后headpre.next指向较小值的那个节点,不断迭代,直至到达某一有序链表底部,此时一个链表遍历完成,然后我们将未完全遍历的链表接在我们接在合并链表之后即可。 + +这是我们迭代做法,另外这个题目还有一个递归方法,目前先不写,等链表掌握差不多的时候会单独写一篇关于递归的文章,也算是为树的题目做铺垫。 + +动图讲解: + +![合并数组](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/合并数组.216f4nn4lti8.gif) + +```java + public ListNode mergeTwoLists(ListNode l1, ListNode l2) { + ListNode headpro = new ListNode(-1); + ListNode headtemp = headpro; + while (l1 != null && l2 != null) { + //接上大的那个 + if (l1.val >= l2.val) { + headpro.next = l2; + l2 = l2.next; + } + else { + headpro.next = l1; + l1 = l1.next; + } + headpro = headpro.next; + } + headpro.next = l1 != null ? l1:l2; + return headtemp.next; + + } +``` + diff --git a/gif-algorithm/链表篇/剑指Offer52两个链表的第一个公共节点.md b/gif-algorithm/链表篇/剑指Offer52两个链表的第一个公共节点.md new file mode 100644 index 0000000..62c05e8 --- /dev/null +++ b/gif-algorithm/链表篇/剑指Offer52两个链表的第一个公共节点.md @@ -0,0 +1,86 @@ +#### 剑指 Offer 52. 两个链表的第一个公共节点 + +### 前言 + +今天给大家带来一个不是那么难的题目,这个题目的解答方法很多,只要能AC的就是好方法,虽然题目不是特别难但是也是剑指offer上的经典题目所以大家要记得打卡呀。 + +然后今天我们的链表板块就算结束啦。周末的时候我会对链表的题目做一个总结,俗话说温故而知新嘛。好啦废话不多说,我们一起来看一下今天的题目吧 + +题目描述: + +输入两个链表,找出它们的第一个公共节点。如下图,返回黄色结点即可。 + + + +![image-20201029215837844](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/image-20201029215837844.7ezoerpghyk0.png) + + + +题目表达是不是也很简单,这个题目我的方法一共有两个,一种就是用HashSet进行存储,一种就是利用双指针,大家有更好的可以在下面讨论呀。 + +### HashSet + +这个方法是比较简单的,主要思路就是,先遍历一个链表将链表的所有值都存到哈希表中,然后再遍历另一个链表,如果发现某个结点在哈希表中已经存在那我们直接返回该节点即可,代码也很简单。 + +```java +public class Solution { + public ListNode getIntersectionNode (ListNode headA, ListNode headB) { + ListNode tempa = headA; + ListNode tempb = headB; + //定义hash表 + HashSet arr = new HashSet(); + while (tempa != null) { + arr.add(tempa); + tempa = tempa.next; + } + while (tempb != null) { + if (arr.contains(tempb)) { + return tempb; + } + tempb = tempb.next; + } + return tempb; + + } +} +``` + +下面这个方法比较巧妙,不是特别容易想到,大家可以自己实现一下,这个方法也是利用我们的双指针思想。 + +下面我们直接看动图吧,特别直观,一下就可以搞懂。 + +![第一次相交的点](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/第一次相交的点.5nbxf5t3hgk0.gif) + +是不是一下就懂了呀,我们利用双指针,当某一指针遍历完链表之后,然后掉头去另一个链表的头部,继续遍历。因为速度相同所以他们第二次遍历的时候肯定会相遇,是不是很浪漫啊! + +```java +public class Solution { + public ListNode getIntersectionNode (ListNode headA, ListNode headB) { + //定义两个节点 + ListNode tempa = headA; + ListNode tempb = headB; + //循环 + while (tempa != tempb) { + //如果不为空就指针下移,为空就跳到另一链表的头部 + tempa = tempa!=null ? tempa.next:headB; + tempb = tempb!=null ? tempb.next:headA; + } + return tempa; + } +} +``` + + + +好啦,链表的题目就结束啦,希望大家能有所收获,下周就要更新新的题型啦,继续坚持,肯定会有收获的。 + + + + + + + + + + + diff --git a/gif-algorithm/链表篇/剑指offer2倒数第k个节点.md b/gif-algorithm/链表篇/剑指offer2倒数第k个节点.md new file mode 100644 index 0000000..6736b4f --- /dev/null +++ b/gif-algorithm/链表篇/剑指offer2倒数第k个节点.md @@ -0,0 +1,54 @@ +# 题目描述:链表中倒数第k个节点 + +题目: + +输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。例如,一个链表有6个节点,从头节点开始,它们的值依次是1、2、3、4、5、6。这个链表的倒数第3个节点是值为4的节点。 + +题目分析: + +自己思考一下 + +我们遇到这个题目,可能会有什么答题思路呢? + +你看我说的对不对,是不是会想到先遍历一遍链表知道 链表节点的个数,然后再计算出倒数第n个节点。 + +比如链表长度为10,倒数第3个节点,不就是正数第8个节点呀,这种方法当然可以啦,是可以实现的,那么我们再思考一下有没有其他方法呢?哦,对,我们可以将链表元素保存到数组里面,然后直接就可以知道倒数第K个节点了。这个方法确实比刚才那个方法省时间了,但是所耗的空间更多了,那我们还有什么方法吗? + +我们可以继续利用我们的双指针呀,但是我们应该怎么做呢? + +双指针法: + +首先一个指针移动K-1位(这里可以根据你的初始化指针决定),然后另一个指针开始启动,他俩移动速度一样,所以他俩始终相差K-1位,当第一个指针到达链表尾部时,第二个指针的指向则为倒数第K个节点。 + +![倒数k个节点](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/倒数k个节点.3uh5e9jvbwc0.gif) + +感觉这个方法既巧妙又简单,大家可以自己动手打一下,这个题目是经典题目。 + +```java +class Solution { + public ListNode getKthFromEnd(ListNode head, int k) { + //特殊情况 + if(head==null){ + return head; + } + //初始化两个指针 + ListNode pro = new ListNode(-1); + ListNode after = new ListNode(-1); + //定义指针指向 + pro = head; + after = head; + //先移动绿指针到指定位置 + for(int i = 0; i < k-1; i++) { + pro=pro.next; + } + //两个指针同时移动 + while (pro.next != null) { + pro = pro.next; + after = after.next; + } + //返回倒数第k个节点 + return after; + } +} +``` + diff --git a/gif-algorithm/链表篇/空.md b/gif-algorithm/链表篇/空.md deleted file mode 100644 index e69de29..0000000 diff --git a/gif-algorithm/链表篇/面试题 02.03. 删除中间节点.md b/gif-algorithm/链表篇/面试题 02.03. 删除中间节点.md new file mode 100644 index 0000000..de19419 --- /dev/null +++ b/gif-algorithm/链表篇/面试题 02.03. 删除中间节点.md @@ -0,0 +1,60 @@ +#### 面试题 02.03. 删除中间节点 + +给定一个头结点为 head的非空单链表,返回链表的中间结点。 + +如果有两个中间结点,则返回第二个中间结点。 + +**示例 1:** + +```java +输入:[1,2,3,4,5] +输出:3 +``` + +> 说明:因为只有一个中间节点 + +**示例 2:** + +```java +输入:[1,2,3,4,5,6] +输出:4 +``` + +> 说明:有两个中间节点所以返回后面那个 + +## 题目解析: + +又精心筛选了一个题目,本来想写一下删除节点的题目,然后发现这个题目更符合目前的节奏,所以先写一下这个题目,明天再给大家写删除节点的题目。 + +大家先不要看我的题解,先自己想一下怎么做。这个这个题目是想让我们找出中间节点,昨天的题目是让我们倒数第K个节点,想一下这两个题目有什么联系呢? + +先说一下刚开始刷题的小伙伴可能会想到的题解,两次遍历链表,第一次遍历获取链表长度,第二次遍历获取中间链表。 + +这个方法很OK,利用数组先将所有链表元素存入数组里,然后再直接获得中间节点。这个也很OK,那么我们有没有一次遍历,且不开辟辅助空间的方法呢? + +昨天的题目是一前一后双指针,两个指针之间始终相差k-1位,我们今天也利用一下双指针的做法吧。 + +这种类型的双指针是我们做链表的题目经常用到的,叫做快慢指针。 + +一个指针走的快,一个指针走的慢,这个题目我们可以让快指针一次走两步,慢指针一次走一步,当快指针到达链表尾部的时候,慢指针不就到达中间节点了吗? + +链表中节点的个数有可能为奇数也有可能为偶数,这是两种情况,但是我们输出是相同的,那就是输出slow指针指向的节点 + +![中间节点](E:\Typora笔记\CSDN\leetcode通关笔记\博客动图\中间节点.gif) + +```java +class Solution { + public ListNode middleNode(ListNode head) { + ListNode fast = head;//快指针 + ListNode slow = head;//慢指针 + //循环条件,思考一下跳出循环的情况 + while (fast!=null && fast.next != null) { + fast = fast.next.next; + slow = slow.next; + } + //返回slow指针指向的节点 + return slow; + } +} +``` + diff --git a/gif-algorithm/链表篇/面试题 02.05. 链表求和.md b/gif-algorithm/链表篇/面试题 02.05. 链表求和.md new file mode 100644 index 0000000..43bdea5 --- /dev/null +++ b/gif-algorithm/链表篇/面试题 02.05. 链表求和.md @@ -0,0 +1,104 @@ +#### 面试题 02.05. 链表求和 + +之前我们一起做了链表中的几个经典题型,找到倒数第k个节点,找链表中点,判断链表中环的起点,合并链表,反转链表,删除链表中重复值。这些是链表中的经典问题,面试中也经常会考的问题,然后下面我们继续做一道链表题目,也是面试中经常会考的题目,链表求和问题。 + +另外有一些小伙伴说,虽然一天一道题不算多,但是每天读题,做题加消化稍微有点跟不上,所以我打算每个周的工作日进行更新题目,到周末的时候对本周的题目进行总结,然后为大家再写一些别的东西。下面我们一起来看一下今天的题目吧。 + +> 为保证严谨性,所有文章中的代码都在网站进行验证,大家可以放心食用。 + +题目描述: + +给定两个用链表表示的整数,每个节点包含一个数位。 + +这些数位是反向存放的,也就是个位排在链表首部。 + +编写函数对这两个整数求和,并用链表形式返回结果。 + +示例1: + +```java +输入:(7 -> 1 -> 6) + (5 -> 9 -> 2),即617 + 295 +输出:2 -> 1 -> 9,即912 +``` + +示例2: + +```java +输入:(9 -> 9) + (9 -> 9),即99+99 +输出:8->9->1 +``` + +示例3: + +```java +输入:(5)+(5),即5+5 +输出:0->1 +``` + +**题目解析:** + +这个题目很容易理解,就是将链表数值进行求和,刚开始做题的同学可能会有这种思路,这个题目我们分别遍历两个链表得到他们的数,然后进行相加,再放到新的链表中,但是这样是行不通的, + +因为我们需要考虑溢出的情况,java 中 int 型的范围为 -2147483648 到 +2147483648,即 -2^31 到 2^31。 + +所以链表比较长的话进行求和就会溢出,所以我们不能提取过之后再进行相加, + +我们应该对链表的每一位进行相加,然后通过链表的和,判断是否需要像下一位进行传递, + +就好比小时候我们用竖式进行加法一样,判断两位相加是否大于10,大于10则进1。 + +了解了思路,但是想完全实现代码也不是特别容易,这里需要注意的三个点就是, + +1.我们需要根据两个链表的长度,不断对新链表添加节点 + +2.需要创建一个变量用来保存进位值。 + +3.当跳出循环之后,需要根据进位值来判断需不需要再对链表长度加1. + +这三条可以结合代码理解进行。 + +注:进位值只能是0或1,因为每一位最大为9,9+9=18; + +![链表求和](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/链表求和.1yh4ymdee3k0.gif) + +注:这里需要注意得时,链表遍历结束,我们应该跳出循环,但是我们的nlist仍在尾部添加了1节点,那是因为跳出循环时,summod值为1,所以我们需要在尾部再添加一个节点。 + +```java +class Solution { + public ListNode addTwoNumbers(ListNode l1, ListNode l2) { + //返回链表 + ListNode nList = new ListNode(-1); + ListNode tempnode = nList; + //用来保存进位值,初始化为0 + int summod = 0; + while(l1 != null || l2 != null) { + //如果l1的链表为空则l1num为0,若是不为空,则为链表的节点值 + //判断是否为空,为空就设为0 + int l1num = l1 == null ? 0 : l1.val; + int l2num = l2 == null ? 0 : l2.val; + //将链表的值和进位值相加,得到为返回链表的值 + int sum = l1num+l2num+summod; + //更新进位值,例18/10=1,9/10=0 + summod = sum/10; + //新节点保存的值,18%8=8,则添加8 + sum = sum%10; + //添加节点 + tempnode.next = new ListNode(sum); + //移动指针 + tempnode = tempnode.next; + if (l1 != null) { + l1 = l1.next; + } + if (l2 != null) { + l2 = l2.next; + } + } + //最后根据进位值判断需不需要继续添加节点 + if (summod == 1) { + tempnode.next = new ListNode(summod); + } + return nList.next; + } +} +``` +