众所周知,算法作为互联网民工的大厂敲门砖,不会不行,会了其实也没什么用。本文以个人理解尝试用轻松自然的方式帮助大家理解这个算法题。
本文代码以java为例,阅读时间约20分钟
首先,存在一个链表,如下图所示
现在要对这个链表进行反转,也就是变为
直观地想,反转是交换链表节点的位置,比如1先和2交换,再和3交换,一直换到5为止。可至少要有两个对象,这种交换的行为才能发生。但通常这类题只给一个头节点,怎么办呢?别忘了,这个是链表,我们可以把头节点后面的对象用一个对象引用去指代。目前就具备了交换的条件。
那我们怎么进行交换呢?上面说的1和2交换、2和3交换,类似于冒泡排序,因为以数组的下标作为索引交换时,操作是比较方便的,对于链表我们不这样做。那怎么交换呢,想一想我们的目的,即要把1-->2变成2-->1,就是指向关系变化嘛,我们把原有的指向关系破坏掉不就行了。
交换1和2时,首先1要变成末尾,也就是1要指向null,但这件事我们不能第一步做,因为我们先要知道另一个交换对象2在哪里,2需要利用1去链表上找到。所以先找到2,然后破坏1的指向关系,把1指向null,这时等于空间中存在了两个链表,一个是只有1的链表,一个是以2最为头节点的链表。这时候有人会说,我要反转链表,你整两个链表干啥?你会不会? 别急,这就来了,现在我们把对1的操作给2重复下,先用一个对象引用通过2指向3,然后再把2指向1。这时候空间中的两个链表是2-->1 和以3开头的长链表
就是这样,从结果上看2和1交换了,剩下的就是重复过程了。
class ListNode{
int val;
public ListNode(int val){
this.val = val;
}
}
public ListNode reverseList(ListNode head){
//newHead是反转中的新链表头节点,初始值为null,逐渐变为1,2...
ListNode newhead = null;
while(head != null){
//确定要破坏的节点
ListNode cur = head;
//破坏之前先找到后续
head = head.next;
//开始破坏关系
cur.next = newHead;
//保持newHead为新链表的头
newHead = cur;
}
return newHead;
}
放松下,喝杯咖啡,卡布奇诺最好不过了
对了上面的方法叫头插法,也就是2会插在1之前,3会插在2之前,还有一种方法是尾插法,这时候有人会说,学这种算法就是eat shit,为什么还要学好几种吃法,我深表同意,暂且先按下牢骚,看看这个方法。尾插法我们实际上是分成两个链表去操作的,而头插法只需要使用一个链表,具体是引入一个冗余节点,每次执行固定插入,在冗余节点后的第一个位置将next引用指向的节点插入.
如图所示,dummy.next始终指向的是dummy节点后的第一个节点,next引用指向的是head所指向节点的的后续节点。
上图展示了一次插入过程,因为插入也是交换,肯定要破坏原有的连接关系,但是不能丢失节点数据
步骤1:next要交换,必须先把next的后续节点数据保存起来,我们这里先交给head.next(为什么是head.next,因为head.next指向的节点,已经由next指向了,不会丢失)
步骤2,3:next要插入到dummy.next,这个简单,next节点的后续保存dummy.next原来的值,再把dummy.next指向next,纯粹的链表插入操作。
至此我们完成了一次插入,后续以此类推
public ListNode reverseList(ListNode head){
ListNode dummy = new ListNode(-1);
dummy.next = head;
while(head != null && head.next != null){
ListNode next = head.next;
head.next = next.next;
next.next = dummy.next;
dummy.next = next;
}
return dummy.next;
}
看完上面的,你会了反转链表。缺德的来了,你还需要反转部分链表,看图
看到没,对比初始链表,这里只把2-->3-->4变为了4-->3-->2。你可能会想到,把链表切为三段,第一段不变,反转2到4,第三段不变,最后把这三段拼起来。没错你可以这样做,但这个切分操作遍历一次链表,反转又需要遍历一次。可以这样做,但没必要。实际部分反转你只需要想明白一件事,什么时候开始反转,什么时候结束反转。
想想。。。
你一定会觉得这很简单,因为题目会告诉你反转区间。实际上就这么简单。
public ListNode reverseBetween(ListNode head,int start,int end){
ListNode dummy = new ListNode(-1);
dummy.next = head;
//dummy节点为了利用头插法,指向要反转节点的前一个
//pre是为了遍历
ListNode pre = dummy;
//遍历到指定位置
for(int i=0;i<start-1;i++){
pre = pre.next;
}
//这里就是头插法的实现
ListNode preHead = pre;
ListNode cur = pre.next;
for(int i=0;i<end-start;i++){
ListNode next = cur.next;
cur.next = next.next;
next.next = preHead.next;
preHead.next = next;
}
return dummy.next;
}
没想到你会看到这里,嘿嘿,恭喜你浪费了你人生中的20分钟