日常杂谈💁🏻
在链表这一节主要还是学习单链表相关的操作知识,其实在之前学习的时候主要是对next.next这些操作分辨的不是很清楚,所以在后面的操作过程中,对真正操作这个对应的线的概念,没有什么具体的概念,其实今天在操作链表翻转的过程中,才真的对线和next有了一点模糊的概念,针对具体的线的概念在操作的时候,应该要针对具体的next来改变线的方向,具体的head也是一个具体的线的方向,在修改的时候,可以将对应的具体的线的方向通过head的next来进行修改和控制,废话不多说,直接搞题。\
LeetCode习题
这个在206 题中,
你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
示例 1:
输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]
示例 2:
输入:head = [1,2]
输出:[2,1]
示例 3:
输入:head = []
输出:[]
这个反转链表的题不得不说,初级和中级但凡考到链表算法相关的面试题,出现的概率非常的多,这个就是简单,之前在了解过数据结构的时候,根本不知道怎么操作的这个线,学习过了之后感觉还是很麻烦操作这个线,其实针对我第一次看这道题的思考,首先你这个必须会给一个前置的node相关的类的属性,所以一般有一个node的属性,其实用我的工程的思维来将,就是node里面有一个int 属性,还有一个 node 的熟悉,这类似什么类似student类里面有一个course的属性,其实这个就是对象里面套对象,我之前学习数据结构是什么,对象的基本结构不清楚针对这种情况下;
public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
代码就是ListNode里面有有一个自己的ListNode 的属性,其实一开始我就没有想明白,链表为什么就这样设计,其实想的方向就是错了,因为他链接的下一个对象永远都还是一样的对象,是他自己但是是不一样的内容,是没错的,就是线性链表,结构一样,但是内容不一样,以前在学的时候,这个东西一直没明白,为啥对象里面还有在写一个自己,因为是线下的,你下一个对象的结构还是这个结构,至于网上讲的什么next存放的是下一个对象的啥地址啥存放的下一个内容,其实我理解的,这个下一个对象,其实还是这个对象,只不过内容变了,相较于线性表ArrayList,这个LinkedList的指针可以自己来控制,非常的方便,所以指针怎么控制,就是通过控制在一个单元结构体内部的指向俩进行控制的,所以他这个里面的next其实就是指的是下一个元素的内容了。这个真的是只能意会。哎,之前他们那些网上讲的,地址,内容啥,节点啥的,都是辅助理解的,真的抽象出来还就是那个意思,但是你不理解,就是不理解,为啥节点里面放了一个节点,next就是指的下一个节点的内容。其实就是那个意思,非常的抽象。
在理清了这个元素结构内部的构成了之后,就可以根据下面的内容来进行翻转的操作,这个翻转操作其实可以进行两部操作。第一步操做可以用递归是先翻转。第二种也可以用迭代实现翻转。
第一种递归实现。\
public ListNode reverseList(ListNode head) {
if(head == null ||head.next ==null ) return head;
ListNode newHead =reverseList(head.next);
head.next.next = head;
head.next =null;
return newHead;
}
在这个递归实现的过程中,理解起来,第一次的思路必须要理解清楚才行,主要还是思科的方式的理解,首先第一点,在理解完这个这个递归首选的退出条件的时候,可以看到当前的这个条件下如果链表是空的或者next是空的直接返回当前的链表,这个是没有什么疑问的。但是在设置递归的的下一步操作的时候,需要进行当前节点的next的节点来进行递归。也就是说如果不是到head为空的情况下要一直递归下去,然后接下来就开始进行指针的线变化的操作,首先是head的next的next来进行重新执行,相当于 1--2--3就是2后面本来指向的是3吧,咱们直接就把2后面的那个线就是head的next的next指向1 ,那这个1是谁指的,指向这个1的就是head其实这个有个疑问,那这个指向1的不应该是head.next么,为啥是head,其实我一开始也是疑问,后面我理解这个head的next其实是2不是1,1就是head,head就是1 ,就是这操作。所以这个理解了之后,那第一步的指针还完了之后,就是下面的指针环线的操作,就是将1 的指针指向空,那就是1的后面的那条线就是head的next,其实在写这个里面的时候,各种线的next不好记住,容易混淆,记住了head他是模拟了一个指针,是为了方便记忆,所以搞了一个什么类似头指针的操作,实际上真实的场景就是,head就是第一个元素,而head的next就是第二个元素了,head的next的next就是2后面的那个线就是第三个元素。记住了这个后面的操作就好操作了
第二种实现是迭代的实现 \
public ListNode reverseList(ListNode head) {
if(head == null ||head.next ==null ) return head;
ListNode newHead = null;
while (head !=null){
ListNode temp = head.next;
head.next = newHead;
newHead = head;
head = temp;
}
return newHead;
}
迭代的实现就篇简单一些,在进行迭代操作的时候,首先判断的也是边界条件就是当前的条件下是否是空的情况,在判断完这个之后就有两部操作,第一步就是判断当前的节点是不是空,这个判断完之后就要进行循环体里面的判断,这个首先线不能断,这个是非常重要的,你线一断,后面再找回来就非常难找回来,所以在1--2--3这种先,要进行逆序的操作过程中的时候,首先要进行的操作先把1--2 ,这个1后面的线存到一个中间变量里面,例如就是listNode tmp 里面,然后在外面定义一个类似newHead的操作,首先执行的措施是让这个temp保存当前这个1后面的线,然后在让这个1后面的线指向了一个newhead,然后呢这个newhead就指向这个head,head在执行这个temp,这个操作有点啰嗦,其实这个里面指针的操作是一气呵成的。
- 设置中间变量,temp 保存1后面的线即head.next;
- 将1后面的线执行newhead,其实就是指向了一个新创建的node节点(null),即就是head.next=newhead;
- 这个操作下在将newhead=head,这个操作因为要记住一点链表在走下一个操作的时候始终是以head进行操作的,这个一定要记住,不管什么,他模拟的虚拟的头都是head,当然叫法是这样叫的,这个大概是什么意思的,因为你的中间节点1后面的线保存了,后面就把head.next(1后面的线指向空了)主要这个时候1后面是没有东西了,2---3都是处于游离 头在哪里,就是head在哪,head需要重新给赋值操作了,这个时候head就需要被用来给newhead来指向,这个是啥意思,意识就是newhead 的原来指向空了,但是现在不指向空了,指向了1,然后后面我们的真的head就指向了后面1后面的线;
- 这个里面有点绕其实我思考的时候,这个里面用迭代在进行实现的过程中,在具体的操作的时候,会有一些差距的,那些差距呢,就是在真实的操作场景下,因为我始终刚刚操作改的是head.next其实就是1后面的线改成指向了空,然后这个时候有让newhead,就是指向空的那个线,指向了1,然后head他就指向了1后面的那条线。好然后开始循环,接下来就是2后面的那条线保存到temp下,然后就是head.next开始指向newhead,这个时候已经是1了,然后在让这个newhead,在指向2,head的在指向3,就完成了3--2--1的操作。感觉这个迭代的过程不太好理解,就是非常的抽象,但是就是这样慢慢慢的梳理,主要是线的逻辑如果不把线的逻辑搞清楚,或者理顺,这个链表后面学习起来是非常的麻烦的,枯燥无味,并且也不属于是计算机的范畴,选择个,就是看操作当前的指令的执行,其实他这个算法,真的会用上么,根本就不会用上,而在我看来他真正可以用到的就是这个重复的过程,不停的抽象,然后抽象成为这4行代码,才是联系这个题的本身,本身去看这个题,或者记住这个策略,并没有什么意义,重点还是在根据这个题去分解问题,然后拆解步骤,梳理成代码的过程,才是要学习的过程。
专注,坚持,持之以恒\
2023.07.05