PS:虚拟头节点是真的好用, 凡是需要考虑左右边界的问题, 加个虚拟头节点准没错
1.删除链表倒数第K个节点
题目要求:
难度:✨✨
解答:一前一后双指针法 要删除倒数第K个节点,就要找到倒数第K+1个节点
- 创建一个虚拟头节点pre,指向链表head
-
right left 指针都先指向pre
-
right先走K步 然后 right 和 left 再开始同步走 直到right.next为null
-
此时 left指向倒数K+1个节点
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode pre = new ListNode(0);
pre.next = head;
ListNode right = pre, left = pre;
while(n != 0) {
right = right.next;
n--;
}
while(right.next != null) {
right = right.next;
left = left.next;
}
left.next = left.next.next;
return pre.next;
}
}
2.链表是否有环(环形链表)
题目要求:
难度:✨✨
解答:2倍速双指针
- 慢指针 slow 走了 k 步,那么快指针 fast 一定走了 2k 步
- 相遇时 他们一定在环中某个点。 假设当前点距离环的起点距离为X,则环的起点距离链表起点head距离就为K-X
- 那么 将任意一个指针指向head节点 两个指针在同步走K-X步 就一定可以相遇。
public ListNode detectCycle(ListNode head) {
ListNode fast, slow;
fast = slow = head;
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
if (fast == slow) break;
}
// 上面的代码跳出循环时 要么 两指针相遇 要么遇到null
if (fast == null || fast.next == null) {
// fast 遇到空指针说明没有环
return null;
}
// 重新指向头结点
slow = head;
// 快慢指针同步前进,相交点就是环起点
while (slow != fast) {
fast = fast.next;
slow = slow.next;
}
return slow;
}
3.环形单链表的约瑟夫问题
题目要求:
据说著名犹太历史学家Josephus有过以下故事:在罗马人占领乔塔帕特后,39个犹太人与Josephus 及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,报数到3的人就自杀,然后再由下一个人重新报1,报数到3的人再自杀,这样依次下去,直到剩下最后一个人时,那个人可以自由选择自己的命运。这就是著名的约瑟夫问题。现在请用单向环形链表描述该结构并呈现整个自杀过程。
输入:一个环形单向链表的头节点head和报数的值m。返回:最后生存下来的节点,且这个节点自己组成环形单向链表,其他节点都删掉。
难度:✨✨ ✨
解答:直接算出谁最后生存
- 普通解法:不断遍历链表,报数m就删除,然后修复成环形,直到最后。O(n×m)
- 进阶解法:
- 节点B与其所报的数X的对应关系:设链表长为i
B(节点编号) X(循环报的数) 1 1 2 2 ..... i i 1 i+1 2 i+2 推出:节点编号=(报的数-1)%链表长度+1
-
节点删除前后对应关系:链表长度为i,删除后为i-1old=(new+s-1)%i +1
-
old=(new+m-1)%i+1。至此,我们终于得到了Num(i-1)—new和 Num(i)-old
public Node josephusKil12 (Node head, int m){ if (head == null ll head.next == head ll m< 1){ return head; } Node cur = head.next; int tmp = 1;// tmp ->list sizewhile (cur != head){ tmp++; cur =cur.next; ) tmp = getLive (tmp,m); while(--tmp !=0){ head = head.next; ) head .next = head; return head; public int getLive(int i, int m) { if(i==1){ return 1; ) return (getLive (i - 1,m) +m- 1)%i + l; )
4.相交链表
题目要求:
难度:🌟
解答:
- 将连个链表拼接,使两个指针同时达到起点。
- ps:图来自双指针技巧秒杀七道链表题目 :: labuladong的算法小抄
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode p1=headA;
ListNode p2=headB;
while(p1!=p2){
if(p1==null){
p1=headB;
}else{
p1=p1.next;
}
if(p2==null){
p2=headA;
}else{
p2=p2.next;
}
}
return p1;
}
}
5.合并K个有序链表
题目要求:
难度:🌟🌟🌟
解答:
- 优先队列PriorityQueue,重写比较器Comparator 实现最小优先
- 有K个链表,最小优先队列的大小就为K
- 每次从队列中取出一个节点currentnode,当currentnode.next不为空时就将其补入队列
- 创造一个虚拟头节点和遍历指针
class Solution {
public ListNode mergeKLists(ListNode[] lists) {
ListNode newhead=new ListNode(-100);
ListNode p=newhead;
PriorityQueue<ListNode> queue=new PriorityQueue(new Comparator<ListNode>() {
@Override
public int compare(ListNode o1, ListNode o2) {
return o1.val-o2.val;
}
});
for(ListNode head:lists){
if(head!=null){
queue.add(head);
}
}
while(!queue.isEmpty()){
ListNode currentnode=queue.poll();
p.next=currentnode;
if(currentnode.next!=null){
queue.add(currentnode.next);
}
p=p.next;
}
return newhead.next;
}
}
6.反转链表中的一部分
题目要求:
难度:🌟🌟
解答:
- 双指针头插法
- 设置两个指针,一个守卫指针guard指向left左边第一个节点。
- 一个边界指针boundary指向left。
- 循环将boundary.next头插法插入到guard后面。
class Solution {
public ListNode reverseBetween(ListNode head, int left, int right) {
ListNode virtualHead = new ListNode(-1);
virtualHead.next=head;
ListNode guard = virtualHead;
ListNode boundary = virtualHead.next;
for(int i=0;i<left-1;i++){ //先移动到起点
guard=guard.next;
boundary=boundary.next;
}
for(int k=0;k<right-left;k++){
ListNode insert = boundary.next;
boundary.next=boundary.next.next;
insert.next=guard.next;
guard.next=insert;
}
return virtualHead.next;
}
}
7.反转链表的前N个节点
题目要求:
难度:🌟
解答:
- 递归
- 不要跳进递归,而是要根据刚才的函数定义,来弄清楚这段代码会产生什么结果
-
head
节点在递归反转之后不一定是最后一个节点了(因为不是翻转整个链表),所以要记录后驱successor
(第n + 1
个节点),反转之后将head
连接上。
class Solution {
public ListNode reverseList(ListNode head,int n) {
ListNode successor = null; // 后驱节点
return reverse N(head,n);
}
// 反转以 head 为起点的 n 个节点,返回新的头结点
public ListNode reverseN(ListNode head, int n) {
if (n == 1) {
// 记录第 n + 1 个节点
successor = head.next;
return head;
}
// 以 head.next 为起点,需要反转前 n - 1 个节点
ListNode last = reverseN(head.next, n - 1);
head.next.next = head;
// 让反转之后的 head 节点和后面的节点连起来
head.next = successor; 🌟🌟🌟
return last;
}
}
PS:若为翻转整个链表
ListNode reverse(ListNode head) {
if (head == null || head.next == null) {
return head;
}
ListNode last = reverse(head.next);
head.next.next = head;
head.next = null; 🌟🌟🌟
return last;
}
8.K个一组翻转链表
题目要求:
难度:🌟🌟🌟
解答:
- 从head开始 K个一组翻转,翻转结束后 递归 将第K+1个节点作为head开始翻转
- 定义两个边界指针 指向第1个和第K+1个节点
- 再将整个链表连起来
class Solution {
public ListNode reverseKGroup(ListNode head, int k) {
if(head == null) return null;
ListNode boundary_A,boundary_B;
boundary_A=boundary_B=head;
//找到区间
for(int i=0;i<k;i++){
if(boundary_B == null){
return head;
}else{
boundary_B=boundary_B.next;
}
}
ListNode newhead = reverse(boundary_A,boundary_B);
boundary_A.next=reverseKGroup(boundary_B,k);
return newhead;
}
public ListNode reverse(ListNode boundary_A,ListNode boundary_B){
ListNode previous,current,theNext;
previous=null;
current=theNext=boundary_A;
while(theNext != boundary_B){ //挨个翻转
theNext=current.next;
current.next=previous;
previous=current;
current=theNext;
}
return previous;
}
}
9.判断回文链表
题目要求:
难度:🌟
解答:
(1)栈
- 先将链表元素押入栈中,并记录链表长度。
- 遍历链表前1/2 部分,每遍历一个节点就stack.pop() 比较是否相等。
class Solution {
public boolean isPalindrome(ListNode head) {
if(head == null) return false;
ListNode p = head;
Stack<Integer> stack = new Stack();
int length = 0;
while(p != null){
stack.push(p.val);
p=p.next;
length++;
}
length >>= 1;
while(length>=0){
if(head.val != stack.pop()){
return false;
}
head=head.next;
length--;
}
return true;
}
}
(2)递归
- 利用递归逆序打印链表的思想
- 先递归,找到最后一个节点,再从尾部逆序+头部正序比较
private void printListNode(ListNode head) {
if (head == null)
return;
printListNode(head.next);
System.out.println(head.val);
}
逆序打印单链表
-----------------------------------------------------
class Solution {
ListNode head;
public boolean isPalindrome(ListNode head) {
this.head=head;
ListNode temp=head;
return check(temp);
}
public boolean check(ListNode temp) {
if(temp == null){// head从前往后 temp先递归到最后 然后从后往前
return true;
}
boolean result = check(temp.next) && (temp.val == head.val);
head=head.next;
return result;
}
}
(3)反转后半部分链表
- 先用 快慢双指针 找到链表中点,注意处理链表为奇数个点情况
- 反转后半部分链表
- 一一比较