链表结构
/**
* 算法链表结构
*/
public class ListNode {
public int val;
public ListNode next;
ListNode() {}
public ListNode(int val) { this.val = val; }
ListNode(int val, ListNode next) { this.val = val; this.next = next; }
public static ListNode build(Integer... obj){
return build(Arrays.asList(obj));
}
public static ListNode build(List<Integer> arr){
if(arr.isEmpty()){
return null;
}
ListNode listNode = new ListNode(arr.get(0));
ListNode cur=listNode;
for (int i = 1; i < arr.size(); i++) {
cur.next=new ListNode(arr.get(i));
cur=cur.next;
}
return listNode;
}
/**
* 构造成环的链表
* @param index 成环的位置
* @param obj 链表节点内容
* @return
*/
public static ListNode buildCycle(Integer index,Integer... obj){
List<Integer> arr = Arrays.asList(obj);
ListNode listNode = new ListNode(arr.get(0));
ListNode cur=listNode;
ListNode pos=null;
for (int i = 1; i < arr.size(); i++) {
if(index==i){
pos=cur;
}
cur.next=new ListNode(arr.get(i));
cur=cur.next;
}
cur.next=pos;
return listNode;
}
public void print(){
ListNode node=this;
while(node!=null){
System.out.print(node.val+",");
node=node.next;
}
System.out.println();
}
}
反转链表
题目
给定链表头结点,要求原地反转该链表,返回链表新的头结点。 例:1->2->3,反转后链表应为3->2->1,返回节点3;思路
/**
* 这种思路的核心在于不需要动head,真正原地移动,但需要构建一个假节点方便返回
* pre-1-2-3
* 拿掉2,所以要先获取到2;
* 1和3建立连接;变为pre-1-3 2-3
* 2指向pre后面的节点 pre-1-3 2-1-3
* pre指向2 pre-2-1-3
* 全程不需要动head,当head或者head.next为空时,表明最后一个节点也移动到了pre后面
* 因此返回pre.next
*/
static ListNode reverse(ListNode head) {
ListNode pre=new ListNode(-1);
pre.next=head;
ListNode next;
//pre-1-2-3
while(head!=null&&head.next!=null){
//next=2
next=head.next;
//1-3
head.next=next.next;
//2-1
next.next=pre.next;
//pre-2
pre.next=next;
}
return pre.next;
}
/**
* 这个解法的思路我称之为接头霸王:核心构建一个新链表,节点为null。即同时存在null 1-2-3,然后一个一个让右边这个链表的头摘下来指向左边的头
* null 1-2-3
* 1-null 2-3
* 2-1-null 3
* 3-2-1-null null
* 右边链表头没了之后,要让next作为头,所以还要先获取next
* 左边链表有了新头之后,要作为pre,给下个头做准备,因此pre=head
*/
static ListNode reverse2(ListNode head) {
ListNode pre=null,next=null;
while(head!=null){
next=head.next;
//接头
head.next=pre;
//左边链表更新
pre=head;
//右边链表更新
head=next;
}
return pre;
}
链表环节点
判断是否成环
题目
给你一个链表的头节点 `head` ,判断链表中是否有环。思路
类比于龟兔赛跑,只要存在环,那么两个不一样速度的指针终会相遇,因此,采用快慢指针来做实现
static Boolean hasCycle(ListNode head) {
ListNode slow = head,fast = head;
while(slow!=null&&fast!=null){
slow=slow.next;
fast=fast.next;
if(fast!=null){
fast=fast.next;
if(slow==fast){
return true;
}
}
}
return false;
}
找到成环的首节点
题目
给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。思路
当快慢指针相遇时,快指针走过的路是慢指针走过的路的2倍; 假设相遇是在b点, fast=a+b+c; slow=a+b; fast=2slow=2(a+b)=a+b+c; c=a+b或者说a=c-b; 既然当前slow处于b,那么slow再走c-b就到了相遇的节点,而c-b正好是a,所以让fast从链表的头节点开始一步一步走,当和slow相遇时,即表明走了a步,那就是环节点。
static ListNode detectCycle(ListNode head) {
ListNode slow = head,fast = head;
while(slow!=null&&fast!=null){
slow=slow.next;
fast=fast.next;
if(fast!=null){
fast=fast.next;
if(slow==fast){
fast=head;
while(slow!=fast){
fast=fast.next;
slow=slow.next;
}
return fast;
}
}
}
return null;
}
删除链表的倒数第N个节点
思路:
典型的双指针应用,通过先让fast指针走n步,然后slow和fast一起走,当fast走到头的时候,slow走到的就是倒数第n个节点。由于要断开,因此还要记录old节点。
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode old=new ListNode(-1,head),result=old;
ListNode slow=head,fast=head;
while(n>0){
fast=fast.next;
n--;
}
while(fast!=null){
old=slow;
slow=slow.next;
fast=fast.next;
}
old.next=slow!=null?slow.next:null;
return result.next;
}
旋转链表
给你一个链表的头节点 head
,旋转链表,将链表每个节点向右移动 k
个位置。
思路:
思路1:多次旋转链表
如例图所示,首先将整个链表反转,即变为54321,然后反转前k个链表45321,然后反转后k个链表45123
思路2:先遍历整个链表,获取到链表的长度n,然后计算真正要移动的k=k%n;让快指针走k步,当快指针走到尾结点时,慢指针刚好走到待移动的节点,返回结果是慢指针,然后让快指针指向head。(有些边界细节要优化,但思路是这样的),时间复杂度是O(2n)
思路3:对于思路2的进一步优化,核心有两点,首先还是要让快指针走,当快指针已经走了k步的时候,表明k<=n的,那么慢指针开始走,当快指针走到tail的时候,慢指针就走到了新的head。当k>n的时候,快指针走到tail,slow也还是head,此时已经有了n,因此直接计算k=k%n,让慢指针走n-k步,就可以找到新的head,该思路的时间复杂度是O(n+k)
代码:
这里只展示第三种思路的代码,其他逻辑请看 代码
// @Title: 旋转链表 (Rotate List)
// @Author: qisiii
// @Date: 2024-09-25 15:16:48
// @Runtime: 0 ms
// @Memory: 41.6 MB
// @comment: 一种只需要遍历O(n+k)次的解法
// @flag: GREEN
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
* 遍历次数最多O(n+k)次
* 当k<n,通过双指针去找到第k个节点和第n个节点,返回结果是k.next,让n.next=head即可
* 当k>n,此时双指针中fast走到最后,只需要让slow指针再走n-k步即可,当然n=n%k,这里面n正好是k的倍数的时候,是不用旋转的,因此直接return head;
*/
class Solution {
public ListNode rotateRight(ListNode head, int k) {
if(k==0||head==null){
return head;
}
int count = 0;
ListNode fast = head;
ListNode slow = head;
//当k<=length的时候,通过让fast先走k步找到两个链表的尾结点,如例1中的3和5
while (fast.next != null) {
fast = fast.next;
if (count >= k) {
slow = slow.next;
}
count++;
}
//当k>length的时候,此时fast已经处于尾部,接下来只需要让slow移动(length-k%length)步就可以了,
//还是拿例1距离,假如n为7,length是5,此时length-2就是slow要走的步数
if (slow==head) {
int length=count+1;
//如果k刚好是length的倍数,那就不用旋转
if(k%length==0){
return slow;
}
count=length-k%length-1;
while(count>0){
slow=slow.next;
count--;
}
}
ListNode result = slow.next;
fast.next = head;
slow.next = null;
return result;
}
}