【力扣刷题】(二)链表专题

链表

1,链表

链表是一种结点相连的数据结构,其中典型的结点构成包括一个存储数据的 data 域和一个存储执行下一结点的指针域 next。双链表则将指针域细化为前驱节点 pre 和后驱节点 next。循环链表则将尾节点和头节点相连。

因此,链表的分类按照实现方式包括单链表、双链表、循环链表。

链表的设计原则是增删快,查询慢。更多链表知识,可以参考:

Java 中的链表:

链表在 Java 中的实现是

2,力扣题型

2.1,203移除链表元素

给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点

输入:head = [1,2,6,3,4,5,6], val = 6
输出:[1,2,3,4,5]

链接:https://leetcode-cn.com/problems/remove-linked-list-elements/

  • 递归写法(不需要设置头结点)
class Solution {
    
    
    public ListNode removeElements(ListNode head, int val) {
    
    
        if(head == null) return null;
        //移动到尾部,返回值在压栈的时候表示下一结点,弹栈时表示前一结点
        head.next = removeElements(head.next,val);//传的是啥返回的就是啥
        //弹栈时head作为当前结点,匹配则返回前一节点;跳过该节点就相当于删除了。
        if(head.val == val) return head.next;
        //否则,返回前一节点继续
        else{
    
    return head;}
    }
}

一般方法,将待删除之前一个节点指向待删节点之后一个节点即可。

如果待删除节点是头结点将无法操作。因此增加一个虚拟头结点,统一解决方法。

/**
 * 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 removeElements(ListNode head, int val) {
    
    
        ListNode dummy = new ListNode(-1); //新建一个节点做头节点
        dummy.next = head;
        ListNode cur = dummy;
        while(cur.next != null){
    
    
            if(cur.next.val == val){
    
    
                cur.next = cur.next.next;
            }else{
    
    
                cur = cur.next; //否则移动到下一个节点
            }
        }
         //为什么返回的头结点是这个而非cur,因为cur一直在移动
         //为什么是dummy.next而非dummy,因为dummy是个虚拟的头节点,要返回自己的
        return dummy.next;
    }
}

2.2,206反转链表

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

1-> 2 -> 3 -> 4 -> null

null <- 1 <- 2 <- 3 <- 4

链接:https://leetcode-cn.com/problems/reverse-linked-list/

迭代法:

class Solution {
    
    
    public ListNode reverseList(ListNode head) {
    
    
        ListNode prev = null; //前一个结点
        ListNode next = null; //下一个结点(临时存储)
        ListNode curr = head; //当前结点
        while(curr != null){
    
    
            next = curr.next;
            curr.next = prev;
            prev = curr;
            curr = next;
        }
        return prev;
    }
}

递归法:递归单元的执行就是把除了头结点以外的剩余节点进行翻转。

class Solution{
    
    
	publiC ListNode reverseList(ListNode head){
    
    
        //递归的停止条件
        //我们通常使用head.next判断就可以了,head为null是为了防止链表本身为null的情况
        if(head == null || head.next == null)return head;
        //压栈,直到移动到最后一个结点
        ListNode prev = reverseList(head.next);
        head.next.next = head; //弹栈的过程就是把当前结点指向前一个结点的过程
        head.next = null; //把前一结点指向null
        return prev;
    }
}

语句分析: 1-> 2 -> 3 -> 4 -> null

reverseList(3.next)=reverseList(4),其中4.next为null,所以会弹栈跳出;返回的prev即为4.此时head为3。

弹栈后执行:3.next.next=3;3.next = null。即4->3->null

下一步:3->2-null;2->1->null。只压了三次栈,弹了4次栈。

测试demo:

public class ReverseListTest {
     
     
 static int ya = 0;
 static int tan = 0;
 public static void main(String[] args) {
     
     
     ListNode head = new ListNode(1);
     ListNode node2 = new ListNode(2);
     ListNode node3 = new ListNode(3);
     ListNode node4 = new ListNode(4);
     head.next = node2;
     node2.next = node3;
     node3.next = node4;
     node4.next = null;
     reverseList(head);
 }
 public static ListNode reverseList(ListNode head){
     
     
     ya++;
     System.out.println("压栈第"+ya+"次");

     if(head == null || head.next == null)return head;
     //压栈,直到移动到最后一个结点
     ListNode prev = reverseList(head.next);

     tan++;
     System.out.println("弹栈第"+tan+"次");

     head.next.next = head; //弹栈的过程就是把当前结点指向前一个结点的过程
     head.next = null; //把前一结点指向null
     return prev;
 }
}
class ListNode{
     
     
 int val;
 ListNode next;
 ListNode(int x){
     
     
     val = x;
 }
}
压栈第1次
压栈第2次
压栈第3次
压栈第4次
弹栈第1次
弹栈第2次
弹栈第3次

2.3,24两两交换链表中的结点

给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。

你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

输入:head = [1,2,3,4]
输出:[2,1,4,3]

链接:https://leetcode-cn.com/problems/swap-nodes-in-pairs/

思路:

image-20210723110511558

迭代法:更新是一个过程模拟。

class Solution {
    
    
    public ListNode swapPairs(ListNode head) {
    
    
        ListNode dummy = new ListNode(0);//虚拟头结点
        dummy.next = head; //加入结点
        ListNode temp = dummy;
        while(temp.next !=null && temp.next.next !=null){
    
    
            ListNode node1 = temp.next;
            ListNode node2 = temp.next.next;
            temp.next = node2; //temp复位node2
            node1.next = node2.next; //node1指向node3
            node2.next = node1; //node2指向node1

            temp = node1; //前进一步
        }
        return dummy.next;
    }
}

递归法:更简单一点,每次只指向前一个

class Solution {
    
    
    public ListNode swapPairs(ListNode head) {
    
    
        // 1,结束条件
       if(head == null || head.next == null) return head;
    //    2,移动
       ListNode next = head.next;
    //    注意要间隔一个
       head.next = swapPairs(next.next);
    //    弹栈执行
       next.next = head;
       return next;
    }
}

2.4,19删除链表的倒数第N个节点

给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。

输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]

输入:head = [1], n = 1
输出:[]

链接:https://leetcode-cn.com/problems/remove-nth-node-from-end-of-list/

思路:该题是双指针的经典应用:首先让快指针先移动n,随后慢指针开始同时移动;当快指针走完时,慢指针就走到了指定位置的前一个位置,直接删除即可。(这里快指针的本质就相当于找到链表的长度了)

移动停止条件:(两种方式)

fIndex !=null:快指针移动到最后一个的NULL,此时慢指针需要提前到虚拟节点出处开始移动,才能保持同步;虚拟节点的使用统一了在头结点处理的问题。

class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        // 双指针
        // 因为无法直接获取链表长度,从头往后遍历
        ListNode dummy = new ListNode(0,head);
        ListNode fIndex = head;
        ListNode sIndex = dummy;
        if(head == null || head.next == null) return null;
        // 先移动n个长度,
        for(int i = 0;i<n;i++){
            fIndex = fIndex.next;
        }
        while(fIndex!=null){
             fIndex = fIndex.next;
             sIndex = sIndex.next;
        }
        sIndex.next = sIndex.next.next;
        return dummy.next;
    }
}

fIndex.next != null,此时快指针少移动一个,移动到了最后位置,那么慢指针就可以从头指针开始,即可保持同步。

class Solution {
    
    
    public ListNode removeNthFromEnd(ListNode head, int n) {
    
    
        // 双指针
        // 因为无法直接获取链表长度,从头往后遍历
        ListNode fIndex = head;
        ListNode sIndex = head;
        // 先做判空处理
        if(head == null || head.next == null) return null;
        // 先移动n个长度,
        for(int i = 0;i<n;i++){
    
    
            fIndex = fIndex.next;
        }
        
        // 注意:这里就需要考虑删除节点为头结点的情况
        // 即删除倒数第n个,移动了n个
        if(fIndex == null) return head.next;
        
        while(fIndex.next!=null){
    
    
             fIndex = fIndex.next;
             sIndex = sIndex.next;
        }
        sIndex.next = sIndex.next.next;
        return head;
    }
}

(其实,官方题解也给到了我们最为原始的思路,即先算出链表的长度,再从头往后遍历。参考题解

2.5,面试题 02.07. 链表相交

给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。

图示两个链表在节点 c1 开始相交:

img
链接:https://leetcode-cn.com/problems/intersection-of-two-linked-lists-lcci

注意这里相交的是开始的某一串节点,而不是一个节点;因此两链表的最后一个节点必须相等;所以两个链表要右对齐。右对齐之后直接遍历即可。

public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
    
    
        // 此种方法的缺点是要考虑到所有输出为null的情况
        if (headA == null || headB == null) return null;
        int sa=0,sb = 0;
        // 算出各自的长度
        ListNode tailA = headA,tailB = headB;
        while(tailA != null){
    
    
            tailA = tailA.next;
            sa++;
        }
        while(tailB != null){
    
    
            tailB = tailB.next;
            sb++;
        }
    	//最后一个不一样直接退出
        if(tailA!=tailB) return null;
        int diff = Math.abs(sa-sb);
        if(sa>sb){
    
      //A比B长,A先移动diff位
            while(diff-- > 0) headA=headA.next;
        }
        if(sa<sb){
    
    
            while(diff-- > 0) headB=headB.next;
        }
        // 此时就可以直接遍历处理了
        while(headA != headB){
    
    
            headA = headA.next;
            headB = headB.next;
        }
        return headA;
    }

2.6,环形链表||

给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null

输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。

链接:https://leetcode-cn.com/problems/linked-list-cycle-ii/submissions/

方法一,快慢指针:

为什么一定能够相遇?如果同时从头结点出发,且快指针每次走两步,慢指针每次走一步;若存在环,则快慢指针就一定能在环内相遇。当追上slow后,此时一个头节点再和slow同时开始走,那么他们相遇的时候就是环点的位置。推导公式依据快指针走的路是慢指针的2倍。

public class Solution {
    
    
    public ListNode detectCycle(ListNode head) {
    
    
        //同时从头结点出发
        ListNode fast = head;
        ListNode slow = head;
        while(fast!=null){
    
    
            slow = slow.next;
            if(fast.next!=null){
    
    
                fast = fast.next.next;//每次走两步
            }else{
    
    
                return null;
            }
            if(fast == slow){
    
    
                //此时prev指针走slow多走的就可以走到环点
                ListNode prev = head;
                while(prev != slow){
    
    
                    prev = prev.next;
                    slow = slow.next;
                } 
                return prev; 
            }   
        }
        return null;
    }
}

还可以使用HashSet的不重复特性。

public class Solution {
    
    
    public ListNode detectCycle(ListNode head) {
    
    
        // 利用hashset的不重复特性
        HashSet<ListNode> hashSet = new HashSet<ListNode>();
        ListNode curr = head;
        while(curr!=null){
    
    
             if(!hashSet.contains(curr)){
    
    
                hashSet.add(curr);
                curr = curr.next;
            }else{
    
    
                return curr;
            }
        } 
        return null;
    }
}

3,总结

递归

迭代

双指针

猜你喜欢

转载自blog.csdn.net/qq_40589204/article/details/119859006