该部分主要针对链表反转中变式问题进行总结,进一步理解链表反转过程,深化链表反转变式问题的解题思路,巩固链表反转模板,扩充区间链表反转的新模板。
区间链表反转问题是链表反转问题的经典变式问题,主要有头插法和穿针引线法两大解题策略,其中穿针引线方法是应用链表反转模板,头插法则作为链表反转问题领域的新模板。
我们从穿针引线方法入手开始分析,温故知新!
何谓穿针引线?几块布料缝合在一起形成一块完整的布料就是穿针引线法的形象比喻!
对于指定区间反转问题,我们可以将原有链表切分成三段A、B、C,只需要将B段进行反转,然后将三段缝合,即可完成指定区间链表反转问题!
在穿针引线过程中,关键点是什么呢?我猜你已经知道了!A段尾节点pre,B段头节点leftNode,B段尾节点rightNode,C段头节点succ。厘清思路,直接上代码!
public static ListNode reverseBetween(ListNode head, int left, int right) {
//找到三段链表pre leftNode rightNode succ
ListNode dummy = new ListNode(-1);
dummy.next = head;
ListNode pre = dummy, leftNode, rightNode, succ;
for(int i = 0; i < left - 1; i++){
pre = pre.next;
}
leftNode = pre.next;
rightNode = pre.next;
for(int i = left; i < right; i++){
rightNode = rightNode.next;
}
succ = rightNode.next;
//断链
pre.next = null;
rightNode.next = null;
//反转leftNode和rightNode,模板
reverseList(leftNode);
//拼接链表
pre.next = rightNode;
leftNode.next = succ;
return dummy.next;
}
//反转链表模板
public static ListNode reverseList(ListNode head) {
ListNode pre = null, cur = head, next;
while(cur != null){
next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
return pre;
}
既然需要将B段进行反转,《算法通关村第二关——链表反转白银挑战笔记》中多次提到头插法,可以将链表进行反转,那我们也采用头插法,遍历B段链表中的每一个元素,将B段链表头插法加入A段链表的表尾,完成链表反转任务!
这么一听,头插法和穿针引线法的思路一样,那为什么还作为第二个方法呢?答:头插法过程中原有链表没有断链!(嗯,穿针引线法是将几段链表缝合起来,头插法没有断链,那确实可以独立作为一种方法)
令人振奋的是,确实是这样,且该方法可作为模板使用!那赶紧分析分析啊,等不及了!别急,慢慢来!
看到pre,cur和next是不是倍感亲切,哪里用到了?答:链表反转模板之不使用虚拟头节点!很遗憾的告诉你,链表反转模板之不使用虚拟头节点的算法过程断链处理,当然不适用于我们这个没有断链的模板啦!
别慌,有救!看我分析!从特殊到一般,初始B段链表如上图,首先将链表的前两个元素进行反转,顺序由12变成了21,我们可以发现,next节点头插法接入pre节点之后。
一般情况,B段链表部分反转,比如原来1234部分发生反转变成2134,即pre2134,下一步需要将3头插法接入pre节点之后,使其变换成pre3214。那么,规律就可以总结了!
首先,将待反转节点做断链处理,然后,将断链的节点头插法插入pre节点之后,依次循环。那么,待反转节点到底是谁?是cur还是next?答:只能是next,不然会cur后继节点会发生断链!
厘清思路,直接上代码!
public static ListNode reverseBetween2(ListNode head, int left, int right) {
ListNode dummy = new ListNode(-1);
dummy.next = head;
ListNode pre = dummy, cur, next;
//找到left的前驱节点
for(int i = 0; i < left -1; i++){
pre = pre.next;
}
cur = pre.next;
//反转链表
//先取出取出next节点,然后头插
for(int i = left; i < right; i++){
next = cur.next;
cur.next = next.next;
next.next = pre.next;
pre.next = next;
}
return dummy.next;
}
两两交换链表中节点,不就是对区间长度为2的子链表进行翻转嘛?以上两个方法都可以解决!
那我给你改改变量的名字,看看你还会不会刚才的模板,会不会做变通呢?怎么做,取出node2,然后将node2头插法加入cur后面完成node2和node1的反转,反转完成后,需要完成后面节点的反转,因此让cur指向node1,此时node1重新指向3,node2重新指向4。
废话不多说,直接上代码!(赶紧巩固刚刚学的模板!)
public static ListNode swapPairs(ListNode head) {
ListNode dummy = new ListNode(-1);
dummy.next = head;
ListNode cur = dummy, node1, node2;
while(cur.next != null && cur.next.next != null){
node1 = cur.next;
node2 = cur.next.next;
node1.next = node2.next;
node2.next = cur.next;
cur.next = node2;
cur = node1;
}
return dummy.next;
}
题目LeetCode369其描述为,一个数字采用链表进行存储,高位数字存储在链表表头,低位数字存储在链表的表尾,将该数字加1,按照同样的规则将加1后的数字用链表进行存储。
题目分析,很显然,加法需要从个位开始计算,然后计算十位、百位,依次...这个过程就是从链表表尾开始向表头方向进行计算的呀。嗯,很明显的逆向过程,什么数据结构?答:栈!
第一种思路清晰明了,但是逆向过程,还有一个方法吧,头插法,什么领域?答:链表反转!第二种思路呼之欲出!剩下的只有一点点细节处理,是先进位,还是先计算?代码如下!
栈实现!
public static ListNode plusOne1(ListNode head) {
Stack stack = new Stack<>();
ListNode cur = head;
ListNode dummy = new ListNode(-1);
while(cur != null){
stack.push(cur);
cur = cur.next;
}
stack.peek().val += 1;
int increase = 0;//进位
int val = 0;
while(!stack.empty()){
val = (stack.peek().val + increase) % 10;
increase = (stack.peek().val + increase) / 10;
stack.peek().val = val;
cur = stack.pop();
cur.next = dummy.next;
dummy.next = cur;
}
//最后是否还有进位情况
if(increase != 0){
ListNode increaseNode = new ListNode(increase);
increaseNode.next = dummy.next;
dummy.next = increaseNode;
}
return dummy.next;
}
链表反转实现!
public static ListNode plusOne2(ListNode head) {
head = reverseList(head);
ListNode cur = head;
cur.val += 1;
int increase = 0;
int val = 0;
ListNode tail = null;
while(cur != null){
val = (cur.val + increase) % 10;
increase = (cur.val + increase) / 10;
cur.val = val;
if(cur.next == null){
tail = cur;
}
cur = cur.next;
}
if(increase != 0){
ListNode increaseNode = new ListNode(increase);
tail.next = increaseNode;
}
return reverseList(head);
}
什么,刚才那题太简单了?行行行,再给你出一题!(地上qi个猴,树上qi个猴,一共几个猴?)正经点,算法训练呢!题目描述LeetCode445。
看完题目描述,paper tiger直接上代码!(还不如刚才那个题目呢!)
public static ListNode addInListByReverse(ListNode head1, ListNode head2) {
head1 = reverse(head1);
head2 = reverse(head2);
int increase = 0;
int val = 0;
ListNode cur1 = head1, cur2 = head2, tail = null;
while(cur1 != null && cur2 != null){
val = (cur1.val + cur2.val + increase) % 10;
increase = (cur1.val + cur2.val + increase) / 10;
cur1.val = val;
if(cur1.next == null || cur2.next == null){
tail = cur1;
}
cur1 = cur1.next;
cur2 = cur2.next;
}
tail.next = cur1 == null ? cur2 : cur1;
while(cur1 != null){
val = (cur1.val + increase) % 10;
increase = (cur1.val + increase) / 10;
cur1.val = val;
if(cur1.next == null){
tail = cur1;
}
cur1 = cur1.next;
}
while(cur2 != null){
val = (cur2.val + increase) % 10;
increase = (cur2.val + increase) / 10;
cur2.val = val;
if(cur2.next == null){
tail = cur2;
}
cur2 = cur2.next;
}
if(increase != 0){
ListNode increaseNode = new ListNode(increase);
tail.next = increaseNode;
}
return reverse(head1);
}
回文序列前面介绍的方法是采用栈进行处理的。现在我们可以放心大胆的使用链表反转来处理回文序列了!
回文序列关键点在于,找到中间节点,反转一半链表,比较即可!那么fast、slow、pre、cur、next这些老朋友又要出来干活咯!能不能少叫几个老朋友啊,行吧,少叫一个next,由于slow移动的时候slow = slow.next。因此slow扮演slow和next两个角色。直接上代码!
奇数节点时,如12321 slow指向3,此时链表变成pre指向的21和slow指向的321,因此,slow需要向后移动一步之后再做比较!
public static boolean isPalindromeByReverse(ListNode head) {
//slow起到slow和next两个作用
ListNode slow = head, fast = head;
ListNode pre = null, cur = head;
while(fast != null && fast.next != null){
cur = slow;
slow = slow.next;
fast = fast.next.next;
cur.next = pre;
pre = cur;
}
//奇数节点情况
if(fast != null){
slow = slow.next;
}
while(slow != null && pre != null){
if(slow.val != pre.val){
return false;
}
slow = slow.next;
pre = pre.next;
}
return true;
}
Ok,《算法通关村第二关——链表反转白银挑战笔记》结束,喜欢的朋友三联加关注!关注鱼市带给你不一样的算法小感悟!(幻听)
再次,感谢鱼骨头教官的学习路线!鱼皮的宣传!小y的陪伴!ok,拜拜,第二关第三幕见!