9

18道经典链表题刷题总结——WeetCode1 链表系列 - Cuzzz

 2 years ago
source link: https://www.cnblogs.com/cuzzz/p/16907466.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

系列文章目录和关于我

前言:#

WeetCode = Week leetCode 寓意每周刷点leetCode 题目

链表是我恢复刷题手感最喜欢做的系列,其没用太多的算法思想,单纯考验对指针的理解,和coding能力,但是其中也是有一些技巧的,比如哑节点,这个是非常使用的解题技巧,能避免繁琐的if else 处理头部,下面是笔者本周刷的一些链表题目。下周准备刷单调栈,或者树等其他系列题目。

一丶 [ 两数相加](2. 两数相加 - 力扣(Leetcode))#

image-20221116220014281

思路:#

简简单单就是一手模拟,两个指针分别位于两个链表头,然后一起向右,没经过一个节点,就求和得到sum,如果之前存在进位,那么sum需要加1,然后如果sum大于等于10,需要记录存在进位,方便下一轮判断是否需要进位,然后new除一个链表节点,其值位sum%10。

注意:#

  1. 两个链表同时结束,但是最后两个节点值之和存在进位,比如

    1->2->3

    2->6->8

    这时候答案应该是:3->8->1->1,注意最后的1,这里我们需要判断,如果二者同时结束,那么需要在末尾加1

  2. 两个链表不是同时结束,这时候有点合并有序数组的意思,需要继续遍历长的链表,并且还是需要处理进位。比如

    1->2->3

    2->3->8->6->5

    答案应该是 3->5->1->(注意到此存在一个进位3+8>10下一个节点应该是1+6)->7->5

代码:#

 public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        //两个链表存在任何一个为null 都返回另外一个
        if(l1 == null ){
            return l2;
        }

        if(l2 == null ){
            return l1;
        }
        
        //记录是否存在进位
        boolean hasCarry = false;
     	//哑巴节点,其next就是头节点
        ListNode preHead = new ListNode();
     	//forward 用来串联求和后生成的节点,其实是结果链表的尾巴
        ListNode forward = preHead;
     	
     	//二者都不为null的时候
         while(l1 != null && l2 != null){
			//求和 如果之前存在进位 那么需要加1
            int sum = (l1.val + l2.val)+ (hasCarry?1:0);
			//记录是否进位 为下轮做准备
            hasCarry = sum>=10;
             //取模
            sum = sum%10;
            //连接
            ListNode newNode = new ListNode(sum);
            forward.next = newNode;
			//一起向下
            forward = forward.next;
            l1 = l1.next;
            l2 = l2.next;
         }
		
       //链表长度相同 且存在进位 那么需要特殊处理
        if(l1 == null && l2 == null ){
            if(hasCarry){
              forward.next = new ListNode(1);
            }
            return preHead.next;
        }
     	//拿到更长的链表
        ListNode longerList = (l1 == null)?l2:l1;
        
     	//继续循环
        while(longerList != null){
            int sum = longerList.val+(hasCarry?1:0);
            hasCarry = sum>=10;
            sum = sum%10;
            ListNode newNode = new ListNode(sum);
            forward.next = newNode;
            forward = forward.next;
            longerList = longerList.next;
        }
	  
       //如果最后还存在进位 那么new 一个节点
       if(hasCarry){
              forward.next = new ListNode(1);
        }
     	
        //返回节点
        return preHead.next;
    }

二丶 删除链表的倒数第 N 个结点 #

image-20221116222623325

image-20221116222735565

思路:#

粗暴的思路:先遍历一次拿到链表长度为sz,然后就可以倒数第n是第几个节点了,然后再遍历一次删除即可。但是这样做层次就低了,这时候我们可以使用快慢指针,快指针先走n步,等快指针走到尾部的时候,慢指针就是要删除的倒数第n个节点了。我们可以使用额外的一个指针记录慢指针的前一个,或者使用哑节点,让慢指针从哑节点开始,这样slow最后就是删除节点的前一个

代码:#

  public ListNode removeNthFromEnd(ListNode head, int n) {
         if(head == null || head.next == null){
            return null;
        }
      	//哑节点
        ListNode preHead = new ListNode(-1,head);
        ListNode fast = head;
        ListNode slow = preHead;
		
        //快指针先走
        while(n>0){
            fast=fast.next;
            n--;
        }
        while(fast!=null){
            fast =fast.next;
            slow = slow.next;
        }
        slow.next = slow.next.next;
        return preHead.next;
    }

三丶[合并两个有序链表](21. 合并两个有序链表 - 力扣(Leetcode))#

image-20221116225435284

思路:#

没啥好说的,和第一题几乎一模一样

代码:#

 public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        if(list1 == null){
            return list2;
        }
        if(list2 == null){
            return list1;
        }
        ListNode preHead = new ListNode();
        ListNode tail = preHead;
        while(list1 != null && list2 != null){
            if(list1.val >= list2.val){
                tail.next = list2;
                ListNode nextNode = list2.next;
                list2.next = null;
                list2 = nextNode;
            }else {
                tail.next = list1;
                ListNode nextNode = list1.next;
                list1.next = null;
                list1 = nextNode;
            }
            tail = tail.next;
        }

        tail.next = list1 == null ? list2 : list1;
        return preHead.next;
    }

四丶 合并K个升序链表 #

思路&对应代码:#

1.递归,分治#

第三题我们写了合并两个有序链表,我们把大规模的合并k个分解成n个合并2个即可,首先我们把大任务,分解成合并左半部分,和合并右半部分

image-20221116231731481

和归并排序的思路是一致的

  • 递归的出口是什么,子任务只有一个链表,只是直接返回一个链表即可,子任务只有两个链表,这时候合并两个链表即可
  • 怎么合并两个有序链表,如题三
public ListNode mergeKLists(ListNode[] lists) {
		//入参数组为null 返回null
    //空数组 返回null
        if(lists==null || lists.length==0){
            return null;
        }
    
    	//调用递归方法
        return merge2(lists ,0 ,lists.length-1);
    }
    private ListNode merge2(ListNode[] lists,int start,int end){
		//base case 只有一个链表 直接返回一个链表
        if(start == end){
            return lists[start];
        }
        //子任务只有两个链表
     	 if(start == end-1){
            return mergeTwoLists(lists[start],lists[end]);
        }
        //分治
        int mid = (start+end)/2;
        //合并左边
        ListNode mergeLeft = merge2(lists,start,mid);
		//合并右边
        ListNode mergeRight = merge2(lists,mid+1,end);
        //把左右合并
        return mergeTwoLists(mergeLeft,mergeRight); 
    }
     public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        if(list1 == null){
            return list2;
        }
        if(list2 == null){
            return list1;
        }
        ListNode preHead = new ListNode();
        ListNode tail = preHead;
        while(list1 != null && list2 != null){
            if(list1.val >= list2.val){
                tail.next = list2;
                ListNode nextNode = list2.next;
                list2.next = null;
                list2 = nextNode;
            }else {
                tail.next = list1;
                ListNode nextNode = list1.next;
                list1.next = null;
                list1 = nextNode;
            }
            tail = tail.next;
        }

        tail.next = list1 == null ? list2 : list1;
        return preHead.next;
    }

2.优先队列#

我们想下暴力解法,每次合并一个节点就遍历整个数组找最小节点合并,这种做法慢在哪儿,慢在我们需要找到数组中剩下节点中最小节点,进行合并。那么有没有一种数据结构,可以让拿到最小节点的o(1)时间复杂度昵——优先队列

  • 队列优先级是啥——节点的值
  • 队列如何初始化——首先放入数组中所有链表的头节点
  • 队列如何入队——每次一个节点合并的后都把其next节点进行入队
  • 何时停止循环——队列为空、
public ListNode mergeKLists(ListNode[] lists) {
        if(lists==null){
            return null;
        }
        if(lists.length==0){
            return null;
        }
        return mergewithHeap(lists);
    }
	
    private ListNode mergewithHeap(ListNode[] lists){
        //哑节点
        ListNode preHead = new ListNode();
		//尾巴用于串联这些节点
        ListNode tail =preHead;
		//优先队列 传入Comparetor 比较val
        PriorityQueue<ListNode> heap = new PriorityQueue<ListNode>((l1,l2)->l1.val-l2.val);
        //初始化队列
        for(int i = 0;i<lists.length;i++){
            if(lists[i]!=null){
                heap.offer(lists[i]);
            }
           
        }
		
        //队列不为空
        while(!heap.isEmpty()){
            //当前最西澳
           ListNode min =  heap.poll();
			//串联起来
           tail.next =min;
			//更新尾巴
            tail =tail.next;
			//继续入队
            if(min.next!=null){
                heap.offer(min.next);      
           }
          
        }
        return preHead.next;
        
//这里我把优先队列变量名命为heap 是因为java中的优先队列是基于数组的堆实现
//需要注意入队时offer 出队时poll 并且入队不能是null 

五丶[两两交换链表中的节点](24. 两两交换链表中的节点 - 力扣(Leetcode))#

image-20221119121107676

思路:#

简简单单模拟,初始化如下变量

image-20221119121438814

交换s和f 如下效果

image-20221119121634003

接下来需要更新这些变量

image-20221119121920252

如此往复直到f为null,但是需要注意空指针的处理

代码:#

/**
 * 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; }
 * }
 */
class Solution {
    public ListNode swapPairs(ListNode head) {
        

        //没必要交换
        if(head == null || head.next == null){
            return head;
        }

        //只有两个节点
        if(head.next.next == null){
            ListNode newHead = head.next;
            newHead.next = head;
            head.next = null;
            return newHead;
        }

        //哑节点
        ListNode preHead = new ListNode(-1,head);
        ListNode pre = preHead;
        ListNode slow = head;
        ListNode fast = head.next;
        ListNode next = fast.next;
        
        //交换
        while(fast != null){
            

            pre.next = fast;
            fast.next = slow;
            slow.next = next;

            if(next == null){
                return preHead.next;
            }

            pre = slow;
            slow = next;
            fast = slow.next;

            if(fast == null){
                return preHead.next;
            }

            next = fast.next;
        }
    
        return preHead.next;
    }
}

六丶[反转链表](206. 反转链表 - 力扣(Leetcode))#

image-20221119122242190

思路:#

简简单单模拟

先初始化如下节点

image-20221119123933970

image-20221119124011462

image-20221119124213730

代码:#

/**
 * 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; }
 * }
 */
class Solution {
    public ListNode reverseList(ListNode head) {
        if(head == null || head.next == null){
            return head;
        }
	
	    //哑节点
        ListNode preHead = new ListNode(-1,head);
		//当前需要操作的节点
        ListNode cur = head.next;
        //下一个节点
        ListNode next = cur.next;
	   //尾巴
        ListNode tail = preHead.next;

        while(cur != null){
       		//翻转
            cur.next = preHead.next;
            tail.next = next;
            preHead.next = cur;
			
			//更新
            cur = next;
            if(next == null){
                return preHead.next;
            }
            next = next.next; 
        }
        return preHead.next;
    }
}

七丶[K 个一组翻转链表](25. K 个一组翻转链表 - 力扣(Leetcode))#

image-20221119124353135

思路:#

第六题,我们实现了反转链表,那么k个一翻转的逻辑,这个翻转的过程是一样的,接下来我们只需要把这k个节点先摘下来,然后进行翻转,翻转返回新的头和尾巴,然后和原来链表连接起来继续翻转即可

image-20221119124728886

image-20221119125054227

代码:#

/**
 * 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; }
 * }
 */
class Solution {
    
    public ListNode reverseKGroup(ListNode head, int k) {
		
        //无需翻转的情况
        if(head == null || head.next == null || k == 1){
            return head;
        }
        //哑节点
        ListNode preHead = new ListNode(-1,head);
		
        //翻转后负责把链表重新连接起来
        ListNode pre = preHead;
        
        //翻转 快慢之间的部分
        ListNode slow = head;
        ListNode fast = findKNext(slow,k);
        
        //如果上来就不足k 个 直接g
        if(fast == null){
            return preHead.next;
        }
      
        //循环翻转
       while(fast != null) {
          
         //先保存下 更新的时候需要用
         ListNode next = fast.next;
        
         //断开 不然reverseList会一直翻转下去
         fast.next = null;
         //翻转快慢之间的部分返回翻转后的尾巴
         ListNode[] resArray  = reverseList(slow);
         ListNode rHead = resArray[0];
         ListNode rTail = resArray[1];
		
         // 连接 把翻转后的内容连接上去
         pre.next = rHead;
         rTail.next = next;
         
         //更新
         slow = next;
         fast = findKNext(slow,k);
         pre = rTail;
       }

      return preHead.next;
    }

	//node 慢节点。k是题目中的k个一反转,我们要找到fast
    //如果不足fast 和 slow 之间一共k个节点(包括自己)
    private ListNode findKNext(ListNode node,int k){
        while(k>1){
            if(node == null){
                return null;
            }
            node = node.next;
            k--;
        }
        return node;
    }
    
    //翻转 并返回 头和尾
    private ListNode[] reverseList(ListNode head) {
        
	
	    //哑节点
        ListNode preHead = new ListNode(-1,head);
		//当前需要操作的节点
        ListNode cur = head.next;
        //下一个节点
        ListNode next = cur.next;
	   //尾巴
        ListNode tail = preHead.next;

        while(cur != null){
       		//翻转
            cur.next = preHead.next;
            tail.next = next;
            preHead.next = cur;
			
			//更新
            cur = next;
            if(next == null){
                return  new ListNode[]{preHead.next,tail};
            }
            next = next.next; 
        }
        return new ListNode[]{preHead.next,tail};
    }
}

八丶[旋转链表](61. 旋转链表 - 力扣(Leetcode))#

image-20221119131315363

思路:#

首先需要注意的是,如果链表长度为len,向右移动len个位置,其实和原本链表一样,所有其实我们只需要移动k%len个位置即可

移动k个,其实就是把最后k个节点连接到链表头部

代码:#

/**
 * 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; }
 * }
 */
class Solution {
    public ListNode rotateRight(ListNode head, int k) {
        if(head == null || head.next==null || k == 0){
            return head;
        }

        //求长度
        ListNode lenTemp = head;
        int len = 0;
        while(lenTemp != null){
            lenTemp = lenTemp.next;
            len ++;
        }

        //我们需要旋转的次数
        k = k % len;
        //刚好整数倍 那么直接返回头
        if(k == 0){
            return head;
        }
        //移动k个,其实就是把最后k个节点连接到链表头部

        //快慢指针找到倒数第k个的前一个
        ListNode fast = head;
        ListNode slow = head;

        while(k!=0){
            fast = fast.next;
            k--;
        } 
        
        while(fast.next!=null){
            fast = fast.next;
            slow = slow.next;
        }

        //到这fast 就是尾巴 slow是倒数第k+1个 slow.next 就是新的头

        //那么颠倒下倒数k个节点 和 头的位置
        ListNode newHead = slow.next;
        slow.next = null;
        fast.next= head;
        return newHead;
    }
}

九丶[删除排序链表中的重复元素](83. 删除排序链表中的重复元素 - 力扣(Leetcode))#

image-20221119161015916

思路:#

首先这是一个排序链表,这意味着相同值的节点是相邻的。

初始化一个哑节点p,和新链表的尾巴节点t,c表示当前遍历的节点

image-20221119161712713

如果c和下一个节点值不同 那么c可以保留,串到t后,更新到绿色位置,遇到重复的节点,就让c走到最后一个重复的节点,然后让t指向c,后更新t和c继续遍历

image-20221119161941607

代码:#

/**
 * 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; }
 * }
 */
class Solution {
    public ListNode deleteDuplicates(ListNode head) {
         if(head == null || head.next == null){
            return head;
        }
	
        //哑节点
        ListNode preHead = new ListNode(-1,head);
		//尾巴节点
        ListNode tail = preHead;
		//当前遍历的节点
        ListNode cur = head;
        while(cur != null){
            
            //如果下一个节点为空 或者 下一个节点和当前节点值不为空
            //那么当前节点保留,让tail的下一个指向当前节点
            if(cur.next == null || cur.val != cur.next.val){
                tail.next = cur;
                tail = cur;
                cur = cur.next;
                continue;
            }
			
            //到此说明重复了 记录下重复的值
            int duplicateValue = cur.val;
            
            //下一个节点
            ListNode nextNode = cur.next;
            //一直到下一个节点为空 或者值不重复了
            while(nextNode != null && nextNode.val == duplicateValue){
                nextNode = nextNode.next;
            }
			
            //到这就是不重复的 删除这其中的重复的节点
            cur.next = nextNode;

            //连接
            tail.next = cur;
            //刷新进入下一轮循环
            tail = cur;
            cur = cur.next;

        }

        return preHead.next;
    }
}

十丶[删除排序链表中的重复元素 II](82. 删除排序链表中的重复元素 II - 力扣(Leetcode))#

image-20221119162218129

思路:#

思路和第九题差不多,唯一的差别是重复节点不能保留,所以发生重复的时候需要把tail的下一个节点置为null

代码:#

/**
 * 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; }
 * }
 */
class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        if(head == null || head.next == null){
            return head;
        }
        //哑节点
        ListNode preHead = new ListNode(-1,head);
        //新链表尾节点
        ListNode tail = preHead;
        //当前变遍历到的节点
        ListNode cur = head;
        
        
        while(cur != null){
            //如果下一个节点为null 那么必然不会与下一个节点值相同
            //或者下一个节点和当前节点 值不同
            //那么说明当前节点可以假如到新链表中
            //让尾巴的下一个指向当前节点
            if(cur.next == null || cur.val != cur.next.val){
                tail.next = cur;
                tail = cur;
            }

            //如果相同 那么一直到最后一个值相等的节点
            while(cur.next != null && cur.val == cur.next.val){
                //说明这部分重复了,我们首先让新链表不要和这部分连接到一起
                tail.next = null;
                cur = cur.next;
            }
            //cur向下 就必然是不相同的节点
            cur = cur.next;
        }
     

        return preHead.next;
    }
}

十一丶[分隔链表](86. 分隔链表 - 力扣(Leetcode))#

image-20221119162904478

思路:#

题目乍一看可能没思路,纠结于怎么保持相对顺序不变,其实只需要使用两个哑节点,一个记录大于等于x,一个小于x的节点,最后把这两个哑节点代表的链表的进行串联即可

代码:#

/**
 * 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; }
 * }
 */
class Solution {
    public ListNode partition(ListNode head, int x) {
        if(head == null || head.next == null){
            return head;
        }

        //小于x的哑节点 和尾节点
        ListNode lessPreHead = new ListNode();
        ListNode lessTail = lessPreHead;

        //大于等于x的哑节点 和尾节点
        ListNode betterEqualHead = new ListNode();
        ListNode betterEqualTail = betterEqualHead;

        ListNode cur = head;

        //遍历
        while(cur != null){
            ListNode curNext = cur.next;

            //如果小于 那么连接到 小于链表上
            if(cur.val < x){
                lessTail.next = cur;
                lessTail = lessTail.next;
                cur.next = null;
            }else{
                //反之连接到大于等于链表
                betterEqualTail.next = cur;
                betterEqualTail = betterEqualTail.next;
                cur.next = null;
            }
            cur = curNext;
        }

        lessPreHead = lessPreHead.next;
        betterEqualHead = betterEqualHead.next;

        //没有大于等于x的节点 那么返回小于头
        if(betterEqualHead == null){
            return lessPreHead;
        }
        //没用小于x的节点 返回大于等于头
         if(lessPreHead == null){
            return betterEqualHead;
        }
        //连接起来
        lessTail.next = betterEqualHead;
        return lessPreHead;
    }

}

十二丶[环形链表](141. 环形链表 - 力扣(Leetcode))#

image-20221119165127635

思路:#

如果可以使用set缓存所有的节点,然后遍历的时候发现next存在于set中那么可以判断其有环,但是这样空间复杂度为n,所以我们需要记住一个结论,快慢指针都从头开始出发,快指针一次走两步,慢指针一次一步,如果二者相遇说明有环,如果慢指针为null了还没相遇那么说明无环(「乌龟」和「兔子」在链表上移动,「兔子」跑得快,「乌龟」跑得慢。当「乌龟」和「兔子」从链表上的同一个节点开始移动时,如果该链表中没有环,那么「兔子」将一直处于「乌龟」的前方;如果该链表中有环,那么「兔子」会先于「乌龟」进入环,并且一直在环内移动。等到「乌龟」进入环时,由于「兔子」的速度快,它一定会在某个时刻与乌龟相遇,即套了「乌龟」若干圈。)

代码:#

public class Solution {
    public boolean hasCycle(ListNode head) {
        if(head == null || head.next == null){
            return false;
        }
        ListNode fast = head;
        ListNode slow = head;
        do{
            if(fast == null||fast.next==null){
                return false;
            }
            fast = fast.next.next;
            slow = slow.next;
            if(fast == slow){
                return true;
            }
        }while(slow != null);
        return false;
    }
}

十三丶[环形链表 II](142. 环形链表 II - 力扣(Leetcode))#

image-20221119170215849

思路:#

需要记住一个结论,快慢指针同时出发,快一次两步,慢一次一步,相遇的时候就是链表存在环,之后快指针从头开始,慢指针继续运动,二者都一次走一步,相等的时候就是入环节点的位置

代码:#

public class Solution {
    public ListNode detectCycle(ListNode head) {
         if(head == null || head.next == null){
            return null;
        }
        ListNode fast = head;
        ListNode slow = head;
        do{
            if(fast == null || fast.next==null){
                return null;
            }
            fast = fast.next.next;
            slow = slow.next;
            if(fast == slow){
                break;
            }
        }while(slow != null);


        fast = head;
        while(fast != slow){
            fast =fast.next;
            slow = slow.next;
        }
        return fast;
    }
}

十四丶[复制带随机指针的链表](138. 复制带随机指针的链表 - 力扣(Leetcode))#

image-20221119170948847

思路:#

如果可以使用map存储每一个节点的下一个节点, 和random指针节点,那么这个题就没什么难度,但是如果追求极致的空间不使用额外空间的话,还是有点巧妙的

  • 复制每一个节点的next,并且让复制节点和原节点使用next串联起来,做到如下效果

    image-20221119171344205

    蓝色是复制的节点,红色是原节点

    这时我们其实可以很快的得到蓝色2的random指针指向的是蓝色的4,也就是红色4的next

  • 接下来我们要把两个链表拆开,并且复制random指针

    image-20221119171659344

    我们遍历到红色2的时候发现,其具备random指向了公司的4,那么蓝色2的指向就是红色4的下一个

代码:#

/*
// Definition for a Node.
class Node {
    int val;
    Node next;
    Node random;

    public Node(int val) {
        this.val = val;
        this.next = null;
        this.random = null;
    }
}
*/

class Solution {
    public Node copyRandomList(Node head) {
        if(head == null ){
            return null;
        }

        Node cur = head;
        //深拷贝
        while(cur != null){
            Node copy = new Node(cur.val);
            Node next = cur.next;
            cur.next = copy;
            copy.next = next;
            cur = next;
        }

        //拷贝后的头
        Node copyHead = head.next;

        //接下来需要复制random指向
        cur = head;
        while(cur != null){
            Node copy = cur.next;

            //拷贝random
            if(cur.random != null){
                copy.random = cur.random.next;
            }
            cur = copy.next;
        }
        
        //拆分
        cur = head;
        while(cur != null){
            Node copy = cur.next;
            Node sourceNext = copy.next;
            cur.next = sourceNext;
            if(sourceNext != null){
             copy.next = sourceNext.next;
            }
            cur =sourceNext;
        }
        return copyHead;
    }
}

十五丶[LRU 缓存](146. LRU 缓存 - 力扣(Leetcode))#

image-20221119222321207

思路:#

LRU 最近最少使用,如果看过linkedHashMap源码,我们可以知道,让linkedHashMap按照访问顺序排序然后复写removeEldestEntry让容量大于最大容量的时候删除节点即可实现lru淘汰策略(mybatis源码中的LRU缓存便是如此实现的)。原理便是最近被访问(put 或者get)的内容,放在链表的头部,这样链表的尾部便是最近最少访问的缓存内容,所以我们只要使用链表来维护这个顺序,使用hashMap实现查找即可

代码:#

class LRUCache {

	//双向链表
    static class Node {
        Node pre;
        Node next;
        int key ;
        int val;
    }
	
    //最大容量
    int maxSize;
	//当前容量
    int size=0;
	//头 哑节点
    Node head;
    //尾 哑节点
    Node tail;
	//缓存内容
    Map<Integer,Node> map = new HashMap<>();
    
    //初始化
    public LRUCache(int capacity) {
     maxSize = capacity;
     head = new Node();
     tail = new Node();
     head.next = tail;
     tail.pre = head;
    }

    public int get(int key) {
        
        Node n = map.get(key);
        //缓存中没 返回-1
        if(n == null){
            return -1;
        }

        //缓存中存在,说明最近被使用到 那么调整到队列头部
        adjustToHead(n);

        return n.val;
    }

    public void put(int key, int value) {
       
         Node n = map.get(key);
       	
        //缓存中最开始没用 那么需要 new 一个节点存到map中
        if(n == null){
            n = new Node();
            n.val = value;
            n.key = key;
            map.put(key,n);
            size++;
        }else{
        	//缓存中有 那么改变值
            n.val = value;
        }
        
        //调整到队列头部
        adjustToHead(n);

    }

    //将节点移动到头部 如果容量超过需要删除尾部节点
    void adjustToHead(Node n){
        if(n == head.next){
            //判断是否需要删除最近最少使用的内容
            removeTailIfNeed();
            return;
        }
        
        //调整到头部
        Node sourceFirst = head.next;
        if(n.pre != null){
            n.pre.next = n.next;
            n.next.pre = n.pre;
        }
        n.next = sourceFirst;
        sourceFirst.pre = n;
        n.pre = head;
        head.next = n;
        //判断是否需要删除最近最少使用的内容
        removeTailIfNeed();
    }
	
    //删除最近最少使用的内容
    void removeTailIfNeed(){
        if(size > maxSize){
            map.remove(tail.pre.key);
            size -- ;
            Node needRemove = tail.pre;
            needRemove.pre.next = tail;
            tail.pre = needRemove.pre;
        }

    }
}


十六丶[回文链表](234. 回文链表 - 力扣(Leetcode))#

image-20221119223706420

思路:#

如果可以使用额外数据结构保存链表中的值,那么这个问题非常简单,但是如果不允许使用额外空间,这个问题就有点巧妙了

首先我们要找到链表的重点(1->2->3找到2,1->2->3->4 找到2)然后将中点右侧部分进行反转,返回再比较中点左半部分 和 右半部分是否相同的数值,最后还需要把右半部分翻转回来,复原链表

代码:#

/**
 * 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; }
 * }
 */
class Solution {
    public boolean isPalindrome(ListNode head) {
        
        //0个节点, 一个节点 直接true
        if(head == null || head.next == null){
            return true;
        }
        //两个节点 看两个节点值是否相同
        if(head.next.next == null){
            return head.val == head.next.val;
        }
        
        //找中点
        ListNode slow = head;
        ListNode fast = head;
        while(fast.next!=null&&fast.next.next!=null){
            slow = slow.next;
            fast = fast.next.next;
        }

        //中点
        ListNode half = slow;
        
        //需要翻转的右半部分
        ListNode needReverseHead = half.next;
		//翻转 数组第1个是头 第二个是翻转后的尾
        ListNode[]rArray = reverseList(needReverseHead);
        ListNode halfHead = rArray[0];

        //标记是否 回文
        boolean flag = true;
		//比较是否回文
        while(halfHead!=null){
            flag = halfHead.val==head.val;
            if(!flag){
                break;
            }
            halfHead = halfHead.next;
            head = head.next;
        }
	
        //翻转回去
        ListNode[] recovery = reverseList(rArray[0]);
		//复原链表
        slow.next = recovery[0];

        return flag;


    }

     //翻转 并返回 头和尾
    private ListNode[] reverseList(ListNode head) {
        
        if(head==null){
            return null;
        }
        if(head.next == null){
            return new ListNode[]{head,null};
        }
	    //哑节点
        ListNode preHead = new ListNode(-1,head);
		//当前需要操作的节点
        ListNode cur = head.next;
        //下一个节点
        ListNode next = cur.next;
	   //尾巴
        ListNode tail = preHead.next;

        while(cur != null){
       		//翻转
            cur.next = preHead.next;
            tail.next = next;
            preHead.next = cur;
			
			//更新
            cur = next;
            if(next == null){
                return  new ListNode[]{preHead.next,tail};
            }
            next = next.next; 
        }
        return new ListNode[]{preHead.next,tail};
    }
}

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK