1. K个一组反转
LeetCode25.给你一个链表,每K个结点一组进行反转,请你返回反转后的链表,k是一个正整数,它的值小于或等于链表长度,如果结点总数不是k的整数倍,那么请将最后剩余的结点保持原有顺序。
这个问题画图非常复杂,读者要一边想一边画,
1.1 头插法
关键理解部分在于如何运用虚拟结点,虚拟节点理解了,两种方法并没有那么难,头插法顾名思义就是一直在头部插入,这里主要分为,已经反转,正在反转和未反转三个部分。反转之前要遍历一次链表获取长度len,确定要分为几组n = len/k,然后运用头部插入思想,
这里先讲一下一组链表运用头插法反转:http://t.csdn.cn/O2Ifr
搞明白头插法和穿针引线法再来看在这个题:
这里以讲解第二组为例,第二组以node(1)作为虚拟结点, node(4)作为第一次的头部结点,
注意:正在反转链表的交换次数,即什么时候停止,什么时候转到下一组。
下一组以上组的尾结点作为虚拟结点,再运用头插法,依次类推,看代码实现:
public static ListNode reverseKGroup(ListNode head,int k){
//1.先计算链表长度len
ListNode cur = head;
int len = 0;
while (cur != null){
cur = cur.next;
len++;
}
//2.计算反转几组
int n = len/k;
//3.创建虚拟节点,头插法反转,注意反转几组,及每组反转的次数。
ListNode dummy = new ListNode(0);
dummy.next = head;
ListNode pre = dummy;
for (int i = 0; i < n; i++) {
for (int j = 0; j < k - 1; j++) {
ListNode next = cur.next;
cur.next = next.next;
next.next = pre.next;
pre.next = next;
}
//4.外层控制组数,每一组反转完之后,重新定pre和cur;
pre = cur;
cur = cur.next;
}
return dummy.next;
}
1.2 穿针引线法
与头插法一致,要分组反转,一组一组的处理,将其分成,已经反转,正在反转和未反转三部分,为了处理好头结点,我们新建虚拟头结点,
在遍历的过程中,根据是否为k我们要找到四个关键位置,用pre、start、end和next标志
上边文章有讲解穿针引线法:http://t.csdn.cn/V0uBn
首先需要遍历根据k值找寻end结点指向的位置,对正在反转的部分进行反转,将end.next = null,同头插法差不多,用上一组的node(1)作为虚拟节点辅助反转,我们将反转方法独立出来,head表示反转方法的参数,
反转完成然后进行缝补,和调整指针的指向,
调整变量位置为下一次反转做准备。
代码实现:
public static ListNode reverseKGroup(ListNode head, int k) {
ListNode dummy = new ListNode(0);
dummy.next = head;
ListNode pre = head;
ListNode end = head;
//1.找到需要处理的区间,end的位置
while (end.next != null) {
//这里不够k长度整数倍则保持原有顺序
for (int i = 0; i < k && end != null; i++) {
end = end.next;
}
if (end == null) {
//上次已经缝补好,这里只要跳出就好
break;
}
//2.将需要处理的区间截取下来
ListNode start = pre.next;
ListNode next = end.next;
end.next = null;
//3.执行反转,
//4.pre.next 和 start.next将反转的区间缝补回去
pre.next = reverse(start);
start.next = next;
//5.调整指针为下一组反转做准备
pre = start;
end = pre;
}
return dummy.next;
}
public static ListNode reverse(ListNode head) {
ListNode pre = null;
ListNode cur = head;
while (cur != null) {
ListNode next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
return pre;
}
2. 链表总结
在做题的时候,脑中要有图,一道题,一张图,链表在算法专栏的前几篇文章都有讲解,接下来也会持续更新,哈哈哈,有中大v的感觉。
2.1 链表的概念、创建、遍历、插入、删除。
算法的基础是数据结构,任何数据结构的基础就是增删改查,基础很重要,在专栏的第一篇文章就讲解了插入,这里总结下删除吧,文章连接:http://t.csdn.cn/ULcd7
头部删除
尾部删除
中间删除
2.2 判断链表是否是回文序列
LeetCode234,解题,将链表元素全部压入栈中,一边遍历链表, 一边出栈,只要有一个不相等,就不是回文序列,
也可用快慢双指针,找取中间结点,压入一半元素,然后一边出栈,一边遍历后半部分元素,
代码实现:
public static boolean isHuiWen(ListNode head){
ListNode cur = head;
Stack<Integer> s = new Stack<>();
//将链表压入栈中,
while (cur != null){
s.push(cur.val);
cur = cur.next;
}
//一边出栈一边遍历比较
while (head != null){
if (head.val != s.pop()){
return false;
}
head = head.next;
}
return true;
}
2.3 合并有序链表
LeetCode21,将两个升序链表合并成一个新的链表并返回,新链表通过拼接给定的两个链表所有结点组成的,
新建一个链表,然后分别遍历两个链表,每次选择最小结点接入新的链表,有一个链表先被排完,将另一个链表拆下来,拼接上去,过程本身就是插入和删除操作。
public static ListNode merageTwoList(ListNode head1,ListNode head2){
//创建虚拟链表,指向新链表的第一个元素
ListNode newHead = new ListNode(0);
ListNode res = newHead;
while (head1 != null && head2 != null){
//情况一:都不为空
if (head1 != null && head2 != null){
if (head1.val < head2.val){
newHead.next = head1;
head1 = head1.next;
}else if (head1.val > head2.val){
newHead.next = head2;
head2 = head2.next;
}else {
//相等的情况,将两个都连接
newHead.next = head1;
head1 = head1.next;
newHead = newHead.next;
newHead.next = head2;
head2 = head2.next;
}
newHead = newHead.next;
}else if(head1 != null && head2 == null){
newHead.next = head1;
head1 = head1.next;
newHead = newHead.next;
} else if(head1 == null && head2 != null){
newHead.next = head2;
head2 = head2.next;
newHead = newHead.next;
}
}
return res.next;
}
2.4 双指针专题
2.4.1 寻找中间结点
LeetCode876,一个非空链表,返回中间 结点,如果有两个则返回第二个。
寻找中间结点就是快慢指针问题,快指针一次走两步,慢指针一次走一步,快指针到达标为的时候,慢指针正好在中间结点。
需要考虑的是偶数个结点的时候是返回哪一个,标准快慢指针返回的是中间两个的第二个。
代码实现:
public static ListNode middleNode(ListNode head){
ListNode slow = head , fast = head;
while (fast != null && fast.next != null){
slow = slow.next;
fast = fast.next.next;
}
return slow;
}
2.4.2寻找倒数第k个元素
使用快慢指针,先将fast遍历到k+1个结点,注意不是遍历k+1次,slow仍指向第一个结点,此时两者正好相差k个结点,之后两指针同步向后走,fast指向空结点的时候,slow整好指向倒数第k个结点。
public static ListNode getEndk(ListNode head, int k){
ListNode slow = head , fast = head;
//1.快指针走k次走到k+1结点位置
while (fast != null && k >0){
fast = fast.next;
k--;
}
while (fast != null){
fast = fast.next;
slow = slow.next;
}
return slow;
}
2.4.3旋转链表
LeetCode61,给你一个链表的头结点head,旋转链表,将链表每个结点向右移动k个位置。
解题思路:先用快慢指针找到k的位置,然后将两个链表拼接,k有可能大于链表长度,需要先获取链表长度,k = k % len,如果k=0则链表不用反转,直接返回头结点:
public static ListNode reverse(ListNode head, int k) {
if (head == null && k == 0) {
return head;
}
ListNode temp = head;
ListNode slow = head;
ListNode fast = head;
//1.head先走一遍,统计出链表的个数,,完成之后head就变成null
int len = 0;
while (head != null) {
head = head.next;
len++;
}
if (k % len == 0) {
return temp;
}
//2.快指针先走k步,取模是防止k>len
while ((k % len) > 0) {
fast = fast.next;
k--;
}
//3. 快慢指针一起走找到k的前一个位置,因为用的是fast.next
while (fast.next != null) {
fast = fast.next;
slow = slow.next;
}
//4.将k位置,断开两条链表拼接,记录slow.next作为头部
ListNode res = slow.next;
slow.next = null;
fast.next = temp;
return res;
}