链表及相关常见问题

1. 单向链表

1.1 节点定义

package linkedlist;

public class ListNode {
    private int data;
    private ListNode next;
    public int getData() {
        return data;
    }
    public void setData(int data) {
        this.data = data;
    }
    public ListNode getNext() {
        return next;
    }
    public void setNext(ListNode next) {
        this.next = next;
    }
    public ListNode(int data) {
        this.data = data;
    }
    public ListNode() {
       
    }
}

1.2 单向链表操作

package linkedlist;

public class MyLinkedList {

    private ListNode head=null;
    private int sz=0;//节点数量
    public MyLinkedList(){
        head=new ListNode();//添加的头节点(哑节点) 不包含数据 方便操作
    }
   
    //在位置pos上插入节点
    public void insertInLinkedList(ListNode newNode,int pos){
        if(pos>sz+1||pos<1)//合法的插入位置[1,sz+1]
        {
            System.out.println("插入位置错误");
            return;
        }
        ListNode pre=head;
        int cnt=1;
        while(cnt<pos){
            pre=pre.getNext();//找到待插入位置前一个位置
            cnt++;
        }
        //比如pos=1 相当于在头节点位置插入
        ListNode next=pre.getNext();
        pre.setNext(newNode);
        newNode.setNext(next);
        sz++;
        
    }
    //删除pos位置上的节点
    public void deletetInLinkedList(int pos){
        if(pos>sz||pos<1)//合法的删除位置[1,sz]
        {
            System.out.println("删除位置错误");
            return;
        }
        int cnt=1;
        ListNode pre=head;
        while(cnt<pos){
            pre=pre.getNext();//找到待删除位置上一个位置
            cnt++;
        }
        ListNode del=pre.getNext();
        pre.setNext(del.getNext());
        del.setNext(null);
        sz--;

    }
    public void print(){
        if(sz==0){
            System.out.println("链表为空");
            return;
        }
        ListNode cur=head.getNext();
        while(cur!=null){
            System.out.print(cur.getData()+"->");
            cur=cur.getNext();
        }
        System.out.println();
    }
    public int getSize(){
        return sz;
    }
    //清空链表
    public void clear(){
        ListNode cur=head.getNext(),tmp;
        while(cur!=null){
            tmp=cur.getNext();
            cur.setNext(null);
            cur=tmp;
        }
        head.setNext(null);
        sz=0;
    }
    public static void main(String[] args) {
        MyLinkedList list=new MyLinkedList();
        ListNode node1=new ListNode(1);
        ListNode node2=new ListNode(2);
        ListNode node3=new ListNode(3);
        ListNode node4=new ListNode(4);
        list.insertInLinkedList(node1,1);
        list.insertInLinkedList(node2,2);
        list.insertInLinkedList(node3,3);
        list.insertInLinkedList(node4,4);
        list.print();
        list.deletetInLinkedList(2);
        list.print();
        list.deletetInLinkedList(1);
        list.print();
        list.clear();
        list.print();
    }
}

2. 双向链表

2.1 节点定义

package linkedlist;
public class DLLNode{
    private int data;
    private DLLNode pre;
    private DLLNode next;
    public int getData() {
        return data;
    }
    public void setData(int data) {
        this.data = data;
    }
    public DLLNode getPre() {
        return pre;
    }
    public void setPre(DLLNode pre) {
        this.pre = pre;
    }
    public DLLNode getNext() {
        return next;
    }
    public void setNext(DLLNode next) {
        this.next = next;
    }
    public DLLNode(int data) {
        this.data = data;
    }
    public DLLNode() {
    }
}

2.2 双向链表操作

package linkedlist;

public class MyDDLLinkedList {
    private DLLNode head;
    int sz;
    public MyDDLLinkedList(){
        head=new DLLNode();//创建dummy节点
        sz=0;
    }
    //在pos位置上插入一个新节点
    public void insert(DLLNode newNode,int pos){
        if(pos<1||pos>sz+1){
            System.out.println("插入位置不合法");
            return;
        }
        int cnt=1;
        DLLNode pre=head;
        while(cnt<pos){
            cnt++;
            pre=pre.getNext();//找到待插入位置的上一个位置
        }
        DLLNode next=pre.getNext();
        pre.setNext(newNode);
        newNode.setNext(next);
        newNode.setPre(pre);
        if(next!=null)//如果插入的是最后一个位置 pre刚开始就是指向最后一个节点
            next.setPre(newNode);//加入判断 防止next为null出现空指针异常
        sz++;
    }
    //删除pos位置上的节点
    public void delete(int pos){
        if(pos<1||pos>sz){
            System.out.println("删除位置不合法");
            return;
        }
        int cnt=1;
        DLLNode pre=head;
        while(cnt<pos){
            cnt++;
            pre=pre.getNext();//找到待删除位置的上一个位置
        }
        DLLNode cur=pre.getNext();//cur指向待删除节点
        DLLNode next=cur.getNext();//next指向待删除节点的下一个节点
        pre.setNext(next);
        if(next!=null)
            next.setPre(pre);
        cur.setPre(null);
        cur.setNext(null);
        cur=null;
        sz--;

    }
    //顺序打印
    public void print(){
        if(sz==0){
            System.out.println("链表为空");
            return;
        }
        DLLNode cur=head.getNext();
        while(cur!=null){
            System.out.print(cur.getData()+"->");
            cur=cur.getNext();
        }
        System.out.println();
    }
    //逆序打印
    public void printReverse(){
        if(sz==0){
            System.out.println("链表为空");
            return;
        }
        int cnt=1;
        DLLNode cur=head.getNext();
        while(cnt<sz){
            cur=cur.getNext();
            cnt++;
        }
       
        cnt=1;
        while(cur!=head){
            System.out.print(cur.getData()+"->");
            cur=cur.getPre();
           
        }
           
        System.out.println();
    }
    public static void main(String[] args) {
        MyDDLLinkedList list=new MyDDLLinkedList();
        DLLNode node1=new DLLNode(1);
        DLLNode node2=new DLLNode(2);
        DLLNode node3=new DLLNode(3);
        DLLNode node4=new DLLNode(4);
        list.insert(node1,1);
        list.insert(node2,2);
        list.insert(node3,3);
        list.insert(node4,4);
        list.print();
        list.printReverse();
        list.delete(2);
        list.print();
        list.printReverse();
        list.delete(1);
        list.print();
        list.printReverse();
       
       
    }
}

3. 循环链表

在单链表的基础上使得最后一个节点的指向不是null, 而是指向第一个节点

package linkedlist;

public class MyCircualList {
    private ListNode head=null;
    private ListNode tail;//指向尾结点
    private int sz=0;//节点数量
    public MyCircualList(){
        head=new ListNode();//添加的头节点(哑节点) 不包含数据 方便操作
    }
   
    //在位置pos上插入节点
    public void insertInLinkedList(ListNode newNode,int pos){
        if(pos>sz+1||pos<1)//合法的插入位置[1,sz+1]
        {
            System.out.println("插入位置错误");
            return;
        }
        ListNode pre=head;
        int cnt=1;
        while(cnt<pos){
            pre=pre.getNext();//找到待插入位置前一个位置
            cnt++;
        }
        //比如pos=1 相当于在头节点位置插入
        ListNode next=pre.getNext();
        pre.setNext(newNode);
        tail=newNode;
        tail.setNext(head.getNext());//最后一个节点指向头节点
        sz++;
        
    }
    //删除第pos个节点
    public void deletetInLinkedList(int pos){
        if(pos>sz||pos<1)//合法的删除位置[1,sz]
        {
            System.out.println("删除位置错误");
            return;
        }
        int cnt=1;
        ListNode pre=head;
        while(cnt<pos){
            pre=pre.getNext();//找到待删除位置上一个位置
            cnt++;
        }
        ListNode del=pre.getNext();
        pre.setNext(del.getNext());
        if(pos==1&&tail!=del){//删除的是第一个节点 并且链表中不止有1个节点
            tail.setNext(del.getNext());//修改尾结点指向
        }
        if(pos==sz)//删除的是表中的最后一个节点
            tail=null;
        
        del.setNext(null);
        sz--;

    }
    //从链表的第pos个节点开始遍历
    public void print(int pos){
        if(sz==0){
            System.out.println("链表为空");
            return;
        }
        if(pos<1||pos>sz){
            System.out.println("遍历开始位置错误");
            return;
        }
        ListNode cur=head.getNext();
        int cnt=1;
        while(cnt<pos){
            cur=cur.getNext();//找到第pos个节点
            cnt++;
        }
        cnt=1;
        while(cnt<=sz){
            System.out.print(cur.getData()+"->");
            cur=cur.getNext();
            cnt++;
        }
        System.out.println();
    }
    public int getSize(){
        return sz;
    }
    public void clear(){
        ListNode cur=head.getNext(),tmp;
        while(cur!=null){
            tmp=cur.getNext();
            cur.setNext(null);
            cur=tmp;
        }
        head.setNext(null);
        sz=0;
    }
    public static void main(String[] args) {
        MyCircualList list=new MyCircualList();
        ListNode node1=new ListNode(1);
        ListNode node2=new ListNode(2);
        ListNode node3=new ListNode(3);
        ListNode node4=new ListNode(4);
        list.insertInLinkedList(node1,1);
        list.insertInLinkedList(node2,2);
        list.insertInLinkedList(node3,3);
        list.insertInLinkedList(node4,4);
        list.print(1);
        list.print(2);
        list.deletetInLinkedList(2);
        list.print(1);
        list.print(2);
        list.deletetInLinkedList(1);
        list.print(1);
        list.print(2);
        list.clear();
        list.print(2);
    }
}

4. 链表常见问题

4.1 找到链表的倒数第n个节点

快慢指针 快指针先走n步 然后快慢指针一起走 这样当快指针变成null时 慢指针就指向倒数第n个节点

  public ListNode findRevNthNode(int n){
        ListNode slow=head.getNext(),fast=head.getNext();//slow和fast指向头节点
        for(int i=1;i<=n;i++)
            fast=fast.getNext();
        while(fast!=null){
            fast=fast.getNext();
            slow=slow.getNext();
        }
        return slow;
    }

4.2 判断链表是否有环

快慢指针 快指针每次走两步 慢指针每次走一步 如果二者最终相遇说明存在环 否则不存在

 public static boolean hasCircle(ListNode head){
        ListNode slow=head,fast=head;//slow和fast指向头节点
        while(slow!=null&&fast.getNext()!=null){
            slow=slow.getNext();
            fast=fast.getNext().getNext();
            if(slow==null||fast==null)
                return false;
            if(slow==fast)
                return true;
        }
        return false;
    }

4.3 如果链表有环,求出环的入环点

当链表有环时,根据4.2的算法,快慢指针会相遇,相遇之后,使其中一个指针从head头节点重新开始走,此时快慢指针速度一样,二者再次相遇的地方就是入环点

在这里插入图片描述

证明:快指针的速度是慢指针的2倍,当相遇时,假设慢指针走了k步,则快指针走了2k步,由于二者最后位置相同,所以快指针多走的k步实际上就环的长度的整数倍(类似于操场跑步,可能多跑了1圈,也可能多跑了2圈);假设入环点到相遇点的距离是m,则起点到入环点的距离是k-m, 而从相遇点再走到入环点的距离也是k-m, 因此相遇后,让一个节点从起点开始走,一个节点从相遇点开始走,此时速度一样,则再次相遇时,相遇点就是入环点

public ListNode detectCycle(ListNode head) {
        if(head==null)
            return null;
        ListNode fast=head,slow=head;
        boolean hasCircle=false;
        while(fast!=null&&fast.next!=null){
            fast=fast.next.next;
            slow=slow.next;
            if(fast==null||slow==null){//等于null说明没有环
                return null;
            }
            if(fast==slow){//存在环 退出循环开始后面找环的入口
                hasCircle=true;
                break;
            }
        }
        if(!hasCircle)
            return null;
        fast=head;//一个指向头节点
        while(fast!=slow){//再次相遇的地方就是入环口
            fast=fast.next;
            slow=slow.next;   
        }
        return fast;

4.4 如果有环,求出环的长度

根据4.3中的图,当快慢指针相遇时,慢指针不动(我在原位置等你),然后快指针没次一个步长移动,当快指针再次走到相遇的位置时,运动的步数即是环的长度

 public int getCycleLength(ListNode head) {
        if(head==null)
            return 0;
        ListNode fast=head,slow=head;
        boolean hasCircle=false;
        while(fast!=null&&fast.getNext()!=null){
            fast=fast.getNext().getNext();
            slow=slow.getNext();
            if(fast==null||slow==null){//等于null说明没有环
                break;
            }
            if(fast==slow){//存在环 退出循环开始后面找环的入口
                hasCircle=true;
                break;
            }
        }
        if(!hasCircle)
            return 0;
        int cnt=1;
        fast=fast.getNext();
        while(fast!=slow){
            fast=fast.getNext();
            cnt++;
        }
        return cnt;
    }

4.5 在有序列表中插入一个节点,使得链表依然有序

//升序为例
    public static ListNode insertInSortedList(ListNode head,ListNode newNode){
        if(head==null)
            return newNode;
        ListNode cur=head,pre=null;
        while(cur!=null&&cur.getData()<newNode.getData()){
            pre=cur;
            cur=cur.getNext();
        }
        if(pre==null){//新节点值最小 那么直接在作为新的头节点
            newNode.setNext(head);
            return newNode;
        }
        ListNode next=pre.getNext();
        pre.setNext(newNode);
        newNode.setNext(next);
        return head;
        
    }

4.6 逆置单链表

public static ListNode reverse(ListNode head){
        ListNode pre=null,next=null;
        ListNode cur=head;
        while(cur!=null){
            next=cur.getNext();
            cur.setNext(pre);
            pre=cur;
            cur=next;
        }
        return pre;
    }    

4.7 求出两条链表的相交节点

image-20220427105732106

先使用两个指针pA pB分别指向headA headB 当pA!=pB时 如果A链表没有遍历完,pA往后走一步,如果A遍历完了,就让pA指向headB, 从B链表的头部开始移动; 对于headB同理

在这里插入图片描述

证明:

pA走到公共节点时走过的路程: a+(b-c)

pB走到公共节点时走过的路程: b+(a-c)

a+(b-c)= b+(a-c) 因此该距离内就能相遇 当两条链表没有公共部分时,c=0, 即走过的路程为a+b, 也就是pA pB都遍历了两条链表,最后都指向null, 此时pA也等于pB , 没有交点时返回null

 public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode pA=headA,pB=headB;
         while(pA!=pB)
         {
            pA=pA==null?headB:pA.next;
            pB=pB==null?headA:pB.next;
         }
         return pA;
    }

4.8 找到链表的中间节点

快慢指针 快指针的速度是慢指针的两倍

链表节点数为奇数: 起始:fast=slow=head 当fast到达最后一个节点时(fast.next==null) slow达到中间节点

链表节点数为偶数fast=slow=head 当fast到达倒数第二节点时(fast.next.next==null) slow达到中间节点

为了统一者两种情况,将节点进行0-n-1编号 偶数节点时只移到快指针 奇数节点时两个指针都移到 当快指针达到最好一个节点时 慢指针达到中间节点(奇数是最中间 偶数的中间偏左)

 public static ListNode findMiddle(ListNode head){
        if(head==null)
            return null;
        ListNode fast=head,slow=head;
        int i=0;
        while(fast.getNext()!=null){
            if(i%2==0){
                fast=fast.getNext();
            }else{
                fast=fast.getNext();
                slow=slow.getNext();
            }
            i++;
        }
        return slow;
    }   

4.9 逆序打印链表

采用递归做法

 public static void printRev(ListNode head){
        if(head==null)
            return;
        printRev(head.getNext());
        System.out.println(head.getData());
    } 

4.10 判断链表长度是奇数还是偶数

一个指针从头节点开始,每次走两步,如果最后指向null,说明偶数,如果最后指向最后一个节点,说明奇数

public static int evenOrOdd(ListNode head){
        ListNode p=head;
        while(p!=null&&p.getNext()!=null){
            p=p.getNext().getNext();
        }
        if(p==null)
            return 0;//偶数个节点
        else
            return 1;//奇数个节点
    }

4.11 将两个有序链表合并为一个有序链表

public ListNode mergeTwoSortedList(ListNode headA,ListNode headB){
        ListNode dummy=new ListNode();//创建一个哑节点 方便操作
        ListNode cur=dummy;
        while(headA!=null&&headB!=null)
        {
            if(headA.getData()<headB.getData()){//找小的
                cur.setNext(headA);
                headA=headA.getNext();
            }
            else{
                cur.setNext(headB);
                headB=headB.getNext();
            }
            cur=cur.getNext();
        }
        if(headA!=null)//链表A还有剩余 直接加上
            cur.setNext(headA);
        if(headB!=null)
            cur.setNext(headB);
        return dummy.getNext();
    }

4.12 逐对交换链表节点

image-20220427175536440

//递归
 public ListNode swapPairs(ListNode head) {
       if(head==null||head.next==null)//链表为空或者只有1个节点
            return head;
        ListNode newHead=head.next;//新的头节点是节点2
        head.next=swapPairs(newHead.next);//节点1指向后面n-2个节点完成反转后的头节点
        newHead.next=head;//新的头节点的连接节点1
        return newHead;
    }
//迭代
 public ListNode swapPairs(ListNode head) {
       if(head==null||head.next==null)//链表为空或者只有1个节点
            return head;
        ListNode dummy=new ListNode();
        dummy.next=head;
        ListNode tmp=dummy;
        while(tmp.next!=null&&tmp.next.next!=null){//有2个节点
            ListNode node1=tmp.next;//指向第1个节点
            ListNode node2=tmp.next.next;//指向第2个节点
            tmp.next=node2;//第2个节点作为头节点
            node1.next=node2.next;//第1个节点指向第3个节点
            node2.next=node1;//第2个节点指向第一个节点
            tmp=node1;//tmp往后移动一个节点
        }
        return dummy.next;
    }

4.13 K个一组逆置链表

k=2 1->2->3->4->5 变成 2->1->4->3->5

k=3 1->2->3->4->5 变成 3->2->1->4->5

不足k个的不反转

public ListNode reverseKGroup(ListNode head, int k) {
         if(k==0||k==1)//0个1组或1个一组相当于不用反转
            return head;
        ListNode cur=head;
        ListNode newHead=null;
        if(hasKNodes(cur,k))//链表长度>=k
            newHead=getKthNode(cur,k);
        else //链表长度<k 相当于不用反转
            return head;
        ListNode pre=null,tmp;
        while(cur!=null&&hasKNodes(cur, k)){
            int i=0;
            ListNode last=cur;//last保存当前组的第一个节点 反转之后last就是最后1个节点
            while(i<k){//逆置某一组  和单链表的反转代码类似
                tmp=cur.next;
                cur.next=pre;
                pre=cur;
                cur=tmp;
                i++;
            }
            last.next=getKthNode(cur,k);//逆置完后该组原来的第一个节点变成最后一个
            //最后一个节点连接下一组的第k个节点(下一组有k个则和第k个连接  不足就跟第一个节点连接)
            
        }
        return newHead;
    }
    //获取从cur开始往后数的第k个节点
    public ListNode getKthNode(ListNode cur,int k){
        if(cur==null)
            return null;
        int i=1;
        ListNode p=cur,pre=p;
        while(p!=null&&i<k){
            pre=p;
            p=p.next;
            i++;
        }
        return p==null?cur:p;//有k个则返回改组第k个节点  不足k个则返回改组第一个节点
    }
    //以cur节点开始是否还有k个节点?
    public boolean hasKNodes(ListNode cur,int k){
        int i=0;
        while(cur!=null){
            i++;
            if(i==k)
                break;
            cur=cur.next;
        }
        return i==k;
    }

4.13 约瑟夫环问题

N个人围成一圈,编号为1-N, 从某个人开始从1报数,报到M的人出局,然后从下一个人开始从1报数,直到只剩下一个人

 public static ListNode getJosephusPosition(int N,int M,ListNode head){
        //head是一个环形链表中的某个节点
        ListNode p=head;
        for(int cnt=N;cnt>1;cnt--){
            for(int i=2;i<=M-1;i++){//找到待删除节点上一个节点  一开始p就指向某个节点(报数1) 因此只需要从2开始报数
                p=p.getNext();
            }
            //前面报数了M-1次 p指向待删除节点的上一个节点
            p.setNext(p.getNext().getNext());
            p=p.getNext();//从待删除的人的下一个人开始报数
        }
        return p;
    }

4.14 复制带随机指针的链表

image-20220427185213891

先根据已有节点的值创建一个具有相同值的节点,建立原有节点和新节点的映射,先不考虑next和random指针

创建完映射之后,对于原始节点X,对应的新创建的节点Y=map.get(X) Y.next就是map.get(X.next) Y.random就是map.get(X.random) X.next和X.random都是指向原有的节点,原有的节点是map中的键,因此都对应一个新创建的节点

	HashMap<Node,Node> map;
    public Node copyRandomList(Node head) {
        map=new HashMap<>();
        Node cur=head;
        while(cur!=null){
            map.put(cur,new Node(cur.val));
            cur=cur.next;
        }
        cur=head;
        while(cur!=null){
            Node cloneNode=map.get(cur);
            cloneNode.next=map.get(cur.next);
            cloneNode.random=map.get(cur.random);
            cur=cur.next;
        }
        return map.get(head);
    }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CodePanda@GPF

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值