链表反转的拓展问题(算法通关村第二关白银挑战)
1指定区间反转
-
LeetCode92: 给你单链表的头指针 head 和两个整数 left 和 right,其中 left <= right。请你反转从位置 left 到位置 right的链表节点,返回反转后的链表。
-
示例1: 输入:head = [1,2,3,4,5] , left = 2, right = 4 输出:[1, 4, 3, 2, 5]
1.1头插法
-
反转的整体思想是,在需要反转的区间里,每遍历到一个节点,让这个新节点来到反转部分的起始位置。
-
前面带虚拟节点的插入操作,每走一步都要考虑各种指针怎么指,既要将节点摘下来接到对应的位置上,还要保证后续节点能够找到。
-
//1.1 头插法 public LinkListNode reverseBetween(LinkListNode head, int left, int right){ LinkListNode dummyNode = new LinkListNode(-1); dummyNode.next = head; LinkListNode prev = dummyNode; for(int i = 0; i < left-1; i++){ prev = prev.next; } LinkListNode cur = prev.next; LinkListNode next; for(int j = 0; j < right-left; j++){ next = cur.next; cur.next = next.next; next.next = prev.next; prev.next = next; } return dummyNode.next; }
1.2穿针引线法
-
先确定好需要反转的部分,也就是下图中 left 到 right 之间,然后再将三段链表拼接起来。这种方式类似裁缝一样,找准位置剪下来,再缝回去。
-
算法步骤:
- 第一步:先将待反转的区域反转;
- 第二步:把 pre 的 next 指针指向反转以后的链表的头节点,把反转后的链表的尾结点的 next 指针指向 succ。
-
//1.2 穿针引线法 public LinkListNode reverseBetween2(LinkListNode head, int left, int right){ LinkListNode dummyNode = new LinkListNode(-1); dummyNode.next = head; LinkListNode prev = dummyNode; //第一步:从虚拟头结点走 left-1 步,来到 left 节点的前一个节点 for(int i = 0; i < left-1; i++){ prev = prev.next; } //第2步:从prev再走走 right - left + 1 步,来到 right节点 LinkListNode rightNode = prev; for(int i = 0; i < right-left+1; i++){ rightNode = rightNode.next; } //第三步:切出一个子链表 LinkListNode leftNode = prev.next; LinkListNode succ = rightNode.next; rightNode.next = null; //第四步:反转链表的子区间 reverseLinkedList(leftNode); //第五步:接回到原来的链表中 prev.next = rightNode; leftNode.next = succ; return dummyNode.next; } private void reverseLinkedList(LinkListNode head){ //反转一个链表 LinkListNode pre = null; LinkListNode cur = head; while(cur != null){ LinkListNode next = cur.next; cur.next = pre; pre = cur; cur = next; } }
2两两交换链表中的节点
-
LeetCode24: 给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。
-
输入:head = [1, 2, 3, 4] 输出: [2, 1, 4, 3] -
public LinkListNode SwapPairs(LinkListNode head){ LinkListNode dummyNode = new LinkListNode(-1); dummyNode.next = head; LinkListNode temp = dummyNode; while(temp.next != null && temp.next.next != null){ LinkListNode node1 = temp.next; LinkListNode node2 = temp.next.next; temp.next = node2; node1.next = node2.next; node2.next = node1; temp = node1; } return dummyNode.next; }
3单链表相加
-
LeetCode369: 用一个非空单链表来表示一个非负整数,然后将这个整数加一。假设这个整数除了 0 本身,没有任何前导的 0。这个整数的各个数位按照高位在链表头部,低位在链表尾部的顺序排列。
-
示例:: 输入:[1, 2, 3] 输出:[1, 2 , 4] -
加法的计算过程:
- 计算从低位开始,而链表从高位开始,所以处理就必须反转过来,此时可以用栈,也可以使用链表的反转来实现。
- 基于栈的实现,先把给出的链表放到栈中,然后从栈中弹出栈顶数字digit, 加的时候要考虑一下进位的情况。
-
public LinkListNode plusOne(LinkListNode head){ Stack<Integer> st = new Stack(); while(head != null){ st.push(head.val); head = head.next; } int carry = 0;//记录进位 LinkListNode dummy = new LinkListNode(0); int addr = 1; while(!st.isEmpty() || carry > 0){ int digit = st.empty() ? 0 : st.pop(); int sum = digit + addr + carry; carry = sum >= 10 ? 1 : 0; sum = sum >= 10 ? sum-10: sum; LinkListNode cur = new LinkListNode(sum); cur.next = dummy.next; dummy.next = cur; addr = 0; } return dummy.next; }
4链表加法
-
LeetCode445: 给你两个非空链表来代表两个非负整数。数字最高位位于链表开始位置。他们的每个节点只储存一位数字。将这两数相加会返回一个新的链表。
-
示例: 输入:(6-> 1->7)+ (2-> 9 -> 5),即617 + 295 输出:9 -> 1 -> 2,即912
4.1使用栈实现
-
public LinkListNode addInListByStack(LinkListNode head1, LinkListNode head2){ Stack<Integer> st1 = new Stack(); Stack<Integer> st2 = new Stack(); while(head1 != null){ st1.push(head1.val); head1 = head1.next; } while(head2 != null){ st2.push(head2.val); head2 = head2.next; } int carry = 0; LinkListNode dummy = new LinkListNode(0); while(!st1.isEmpty() || !st2.isEmpty() || carry > 0){ int digit1 = st1.empty() ? 0 : st1.pop(); int digit2 = st2.empty() ? 0 : st2.pop(); int sum = digit1 + digit2 + carry; carry = sum/10; sum = sum%10; LinkListNode cur = new LinkListNode(sum); cur.next = dummy.next; dummy.next = cur; } return dummy.next; }
4.2使用链表反转实现
-
使用链表反转,现将两个链表分别反转,最后计算完之后再将结果反转,一共有三次反转操作,所以必然将反转抽取出一个方法。
-
public LinkListNode addInList(LinkListNode head1, LinkListNode head2){ reverse(head1); reverse(head2); LinkListNode dummyNode = new LinkListNode(-1); LinkListNode cur = dummyNode; int carry = 0;//进位 while(head1 != null || head2 != null){ int val = carry; if(head1 != null){ val += head1.val; head1 = head1.next; } if(head2 != null){ val += head2.val; head2 = head2.next; } cur.next = new LinkListNode(val % 10); carry = val/10; cur = cur.next; } if(carry > 0){ cur.next = new LinkListNode(carry); } return reverse(dummyNode.next); } private LinkListNode reverse(LinkListNode head){ //反转一个链表 LinkListNode pre = null; LinkListNode cur = head; while(cur != null){ LinkListNode next = cur.next; cur.next = pre; pre = cur; cur = next; } return pre; }
5.再论链表的回文序列问题
-
“快慢指针+一半反转”
-
//再论链表的回文序列问题 public boolean isPalindrome(LinkListNode head){ if(head == null || head.next == null){ return false; } LinkListNode slow = head, fast = head; LinkListNode prev = head, prepre = null; while(fast != null || fast.next != null){ prev = slow; slow = slow.next; fast = fast.next; //将前半段部分链表反转 prev.next = prepre; prepre = prev; } if(fast != null){ slow = slow.next; } while(prev != null && slow != null){ if(prev.val != slow.val){ return false; } prev = prev.next; slow = slow.next; } return true; }