/**
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
文中的m,n都是有效的
基本操作
查
查找第 m 个 结点
增
在末尾加入结点
public void addNode(ListNode head, int val) {
ListNode cur = head;
// 只有下个节点不是空节点
while (cur.next != null) {
cur = cur.next;
}
cur.next = new ListNode(val);
}
在第m个结点之前或者之后加入结点
public ListNode addNode(ListNode head, int val, int m) {
ListNode dummyHead = new ListNode(0);
dummyHead.next = head;
ListNode cur = dummyHead;
//如果是在 m 之后 插入结点
//找到m
//while (m-- > 0) {
// cur = cur.next;
//}
// 找到 m 的 前驱
while (--m > 0) {
cur = cur.next;
}
ListNode next = cur.next;
cur.next = new ListNode(val);
cur.next.next = next;
return dummyHead.next;
}
删
删除最后一个结点,注意删除只有一个结点的情况,用虚拟头结点最省事
public ListNode removeNode(ListNode head) {
ListNode dummyHead = new ListNode(0);
dummyHead.next = head;
ListNode cur = dummyHead;
// 找到倒数第二个结点
while (cur.next != null && cur.next.next != null) {
cur = cur.next;
}
cur.next = null;
return dummyHead.next;
}
删除第 i 个结点
public ListNode removeNode(ListNode head, int m) {
ListNode dummyHead = new ListNode(0);
dummyHead.next = head;
ListNode cur = dummyHead;
// 找到第 m 个结点的前驱
while (--m > 0) {
cur = cur.next;
}
// 直接修改前驱的后继改为 m 的后继
cur.next = cur.next.next;
return dummyHead.next;
}
变形操作
接下来的操作基本结合 多指针 + 虚拟头结点
查找倒数第m个结点
public ListNode query(ListNode head, int m) {
ListNode l1 = head;
ListNode l2 = head;
// 先让指针 l2 移动 m 步
while (m-- > 0) {
l2 = l2.next;
}
// 然后 l1, l2 以前移动,知道l2指向空
// 这个时候l1指向的正好是倒数的第m个结点
while (l2 != null) {
l1 = l1.next;
l2 = l2.next;
}
return l1;
}
删除倒数第 m 个结点
public ListNode removeNode(ListNode head, int m) {
ListNode dummyHead = new ListNode(0);
dummyHead.next = head;
ListNode l1 = dummyHead;
ListNode l2 = dummyHead;
// 因为是倒数第 m个结点,所以我们需要找到倒数的 第 m+1个 结点
// 注意这里我们使用了 虚拟头结点
while (m-- >= 0) {
l2 = l2.next;
}
// 找到删除结点的前驱
while (l2 != null) {
l1 = l1.next;
l2 = l2.next;
}
l1.next = l1.next.next;
return dummyHead.next;
}
将链表进行翻转
1->2->3->4
4->3->2->1
利用虚拟头结点,不断的像在虚拟头结点的头部之后插入结点,并且修改插入结点后面的指向。
本质就是一个向链表头部不断添加结点。
public ListNode reverse(ListNode head) {
ListNode dummyHead = new ListNode(0);
ListNode cur = head;
while(cur != null) {
ListNode next = cur.next;
cur.next = dummyHead.next;
dummyHead.next = cur;
cur = next;
}
return dummyHead.next;
}
反转m到n之间的链表
1->2->3->4->5->6,反转第2个结点到第5个结点
1->5->4->3->2->6
这道题目也是应用三个指针来解决问题
首先
pre 指向 m 前驱
start 指向 m
then 指向 m 的下一指针
然后
- 修改start 指向 then 的下一个结点
- then 指向 pre 的下一个结点
- pre 指向 then
- 最后then 指向 start 的下一个结点
- 重复步骤1到4 n-m次
public ListNode reverseBetween(ListNode head, int m, int n) {
ListNode dummyHead = new ListNode(0);
dummyHead.next = head;
ListNode pre = dummyHead;
int i = m;
while (--i > 0) {
pre = pre.next;
}
ListNode start = pre.next;
ListNode then = start.next;
int diff = n - m;
while (diff-- > 0) {
start.next = then.next;
then.next = pre.next;
pre.next = then;
then = start.next;
}
return dummyHead.next;
}
给定一个链表和一个值x,对它进行分区,使所有小于x的节点都位于大于或等于x的节点之前。
您应该保留两个分区中每个节点的原始相对顺序。
Input: head = 1->4->3->2->5->2, x = 3
Output: 1->2->2->4->3->5
这个题仍然是一个链表中的多指针应用,只要清楚了每次指针是怎么走的。基本上思路就有了。这个题和上面那个题很类似,也利用了三个指针。
public ListNode partition(ListNode head, int x) {
ListNode dummyHead = new ListNode(0);
dummyHead.next = head;
ListNode pre = dummyHead;
while (pre.next != null && pre.next.val < x) pre = pre.next;
if (pre.next == null) return dummyHead.next;
ListNode start = pre.next;
ListNode then = start.next;
while (then != null) {
if (then.val >= x) {
start = then;
then = then.next;
} else {
start.next = then.next;
then.next = pre.next;
pre.next = then;
pre = pre.next;
then = start.next;
}
}
return dummyHead.next;
}
总结
总的来说指针的操作都需要结合一个虚拟头来进行操作,涉及到删除和添加操作的时候考虑多个指针协同工作。我觉得对于自己来说就是发现指针每次指向的变化容易把自己弄晕,这也是指针规律难点所在。