算法学习-链表,经典的数据结构得细品(持续更新中)


本文写于笔者的另一篇文章 算法学习-数组的相关操作中,那篇还没更完,这篇就开始挖坑了。在数组上删除重复项是一种重要的题型,同样,在链表上也会进行相关的操作。因此,本文就开始将链表的相关题目进行整理汇总,希望能够记录自己的刷题路径。

链表操作基础知识

本文参考Carl的代码随想录

1. 虚拟头节点
在面对链表的创建、删除等操作的时候,需要灵活使用,即就在链表前插入一个不存值的节点,不影响取值但是可以简化很多指针操作。

ListNode dummyHead=new ListNode(0);
ListNode cur=dummyHead;
cur.next=head;

2. 链表从零开始的创建操作:

  • 尾插法,从dummyHead.next开始往后不断增加,虽然按照后面中间插入中的循环插入写法也可以,但是这里直接插入,不用考虑后面的节点,可以简化很多代码
ListNode dummyHead=new ListNode(0);
ListNode cur=dummyHead;

cur.next=new ListNode(newVal);
cur=cur.next;
  • 头插法,先定义head=nullnewNode.next=head插入,不断更新head
ListNode head=null;
ListNode newNode=new ListNode(newVal);
newNode.next=head;
head=node;

3. 链表的中间插入操作:
找到要插入节点的前一个节点pre

ListNode newNode=new ListNode(newVal);
newNode.next=pre.next;
pre.next=newNode;

如果是要在原链表中循环插入的话,需要记录插入位置的下一个位置

ListNode temp=pre.next; // 记录插入位置的下一个位置

ListNode newNode=new ListNode(newVal); //正常插入操作
newNode.next=pre.next;
pre.next=newNode;
pre=temp;

4. 链表的删除操作:
找到要删除节点的前一个节点pre

pre.next=pre.next.next;

5. 链表的指针改变
很多题目中,对链表结构的改变就只需要修改「原链表指针指向」

//反转链表伪代码
while(cur!=null){
    ListNode temp=cur.next;
    cur.next=pre;
    pre=cur;
    cur=temp;
}

6. 针对while遍历条件的编写,需要因题而异

while(cur!=null)... 最后停止在链表后,常出现在遍历全部链表、反转链表的题中

while(cur.next!=null)... 最后停止在链表最后一个节点,当面对删除操作时,这样子的定义会更有利于删除

while(fast!=null&&fast.next!=null)... 出现在去除重复值或者走两步的题中

while(fast.next!=null&&fast.next.next!=null)... 常出现在走两步找中心的题目中,奇偶情况下都可以slow.next找到后半部分

while(ptr->next != head)...循环链表的题目

6. 当前工作指针cur初始化不一样,需要因题而异

ListNode cur=dummyHead;
或者
ListNode cur=head;

相关题目

21.删除链表的倒数第n个结点

快慢指针法,看到删除链表,先建立dummyhead; 快指针先提前走n步,然后快慢指针同步走,结束条件需要脑中模拟一下,如果fast和slow都初始化为原先的head,快指针走到null,slow刚好走到倒数第n个节点,因此再有个pre记录一下倒数第n个节点的前一个节点就可以了。

/**
 * 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 removeNthFromEnd(ListNode head, int n) {
        ListNode dummyhead=new ListNode(-1);
        dummyhead.next=head;
        ListNode fast=head;
        ListNode slow=head;
        ListNode pre=dummyhead;
        while(n-->0){
            fast=fast.next;
        }
        while(fast!=null){
            fast=fast.next;
            slow=slow.next;
            pre=pre.next;
        }
        pre.next=slow.next;
        return dummyhead.next;
    }
}
876.链表的中间结点

参考题解,尤其注意while(fast!=null&&fast.next!=null)的编写。遍历时,快指针走两步,慢指针走一步。

while(fast!=null&&fast.next!=null)在偶数节点情况下找到的是后面的中间节点,while(fast.next!=null&&fast.next.next!=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 middleNode(ListNode head) {
        ListNode fast=head;
        ListNode slow=head;
        while(fast!=null&&fast.next!=null){
            fast=fast.next.next;
            slow=slow.next;
        }
        return slow;
    }
}
143.重排链表

这题涉及到的知识点非常多,参考题解,总体思路是中间分割,后半部分反转链表,然后合并链表。

先中间分割链表,while(fast.next!=null&&fast.next.next!=null)在偶数节点情况下找到的是前面的中间节点,奇数情况下是正中间节点,在偶数找到前面的中间的节点的情况下,我们都可以通过mid.next取到后半部分链表;然后反转后半部分链表;合并链表是原地操作的,没有新建链表,将后半部分反转的链表插入到前半部分链表中去。

/**
 * 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 void reorderList(ListNode head) {
        ListNode mid= split(head);
        ListNode headb= reverse(mid.next);
        //这一步很关键,要将两个链表分开来,这样后面重新插入结尾才不会出错
        mid.next=null;
        merge(head,headb);
    }
    public ListNode split(ListNode head){
        ListNode slow=head;
        ListNode fast=head;
        while(fast.next!=null&&fast.next.next!=null){
            fast=fast.next.next;
            slow=slow.next;
        }
        return slow;
    }
    public ListNode reverse(ListNode head){
        ListNode pre=null;
        ListNode cur=head;
        while(cur!=null){
            ListNode temp=cur.next;
            cur.next=pre;
            pre=cur;
            cur=temp;
        }
        return pre;
    }
    public void merge(ListNode heada,ListNode headb){
        ListNode cura=heada;
        ListNode curb=headb;
        while(curb!=null){
            ListNode nexta=cura.next;
            ListNode nextb=curb.next;
            curb.next=nexta;
            cura.next=curb;
            cura=nexta;
            curb=nextb;
        }
    }
}
77.链表排序

链表的归并排序,需要实现「找中间节点」以及两个排序链表的合并函数,数组的归并排序需要O(N)的空间,链表改变指向不需要额外的空间。

/**
 * 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 sortList(ListNode head) {
        if(head==null) return null;
        if(head.next==null) return head;
        // 划分成两半
        ListNode mid=split(head);
        ListNode list2=mid.next;
        mid.next=null;

        //归并排序
        ListNode left=sortList(head);
        ListNode right=sortList(list2);
        return merge(left,right);
    }

    // 找到中间节点进行划分
    public ListNode split(ListNode head){
        ListNode fast=head;
        ListNode slow=head;
        while(fast.next!=null&&fast.next.next!=null){
            fast=fast.next.next;
            slow=slow.next;
        }
        return slow;
    }

    // 合并两个已经排序的列表
    public ListNode merge(ListNode list1,ListNode list2){
        ListNode dummyhead=new ListNode(-1);
        ListNode cur=dummyhead;
        while(list1!=null&&list2!=null){
            if(list1.val<=list2.val){
                cur.next=list1;
                list1=list1.next;
            }else{
                cur.next=list2;
                list2=list2.next;
            }
            cur=cur.next;
        }
        cur.next=list1==null?list2:list1;
        return dummyhead.next;
    }   
}
27.回文链表

从后向前找到链表中心点,偶数找到左边中心点,然后将后半部分链表反转,最后比较后小半部分是否和从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 boolean isPalindrome(ListNode head) {
        ListNode fast=head;
        ListNode slow=head;
        //找到中心点
        while(fast.next!=null&&fast.next.next!=null){
            slow=slow.next;
            fast=fast.next.next;
        }

        //后半部分反转
        ListNode pre=null;
        ListNode cur=slow.next;
        while(cur!=null){
            ListNode temp=cur.next;
            cur.next=pre;
            pre=cur;
            cur=temp;
        }
        
        //后半部分偏小,比较反转的后半部分和前半部分是否回文
        while(pre!=null){
            if(pre.val!=head.val) return false;
            pre=pre.next;
            head=head.next;
        }
        return true;
    }
}
23.两个链表的第一个重合节点

参考题解,本质上就是要一直找到两个链表「地址相同的两个节点」,但两链表节点数不一样,没法对应。可以尝试拼接的做法,让p1遍历完链表A之后开始遍历链表B,让p2遍历完链表B之后开始遍历链表A,这样相当于「逻辑上」两条链表接在了一起,这样子就让相交节点前面长度补齐了。即使最后两链表没有相交,则p1=p2=null退出循环。两条拼接链表都只遍历一次。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode p1=headA;
        ListNode p2=headB;
        while(p1!=p2){
            p1=p1==null?headB:p1.next;
            p2=p2==null?headA:p2.next;
        }
        return p1;
    }
}
24.反转链表
/**
 * 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) {
        ListNode pre=null;
        ListNode cur=head;
        while(cur!=null){
            ListNode temp=cur.next;
            cur.next=pre;
            pre=cur;
            cur=temp;
        }
        return pre;
    }
}

递归法参考,用递归栈保存了前一个节点,方便在回溯的时候反转。

/**
 * 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) {
        return dfs(head,null);
    }

    // 返回值都是链表头节点,只是在回溯的过程中改变指向
    // dfs表示反转后面的节点,同时返回最终的答案
    public ListNode dfs(ListNode head,ListNode pre){
        if(head==null) return pre;
        ListNode res=dfs(head.next,head);
        head.next=pre; 
        return res;
    }
}
28.展平多级双向链表

遍历第一级节点,将后面的节点逐层拼接到第一级上,做法是将head.child!=null的节点cur的孩子节点头尾改变指向,放到第一级上去,然后继续遍历cur.next(已经将第二级拼接上来改变结构了)。

/*
// Definition for a Node.
class Node {
    public int val;
    public Node prev;
    public Node next;
    public Node child;
};
*/

class Solution {
    public Node flatten(Node head) {
        Node cur=head;
        while(cur!=null){
            //可能后面级插入上来的child也不为null
            if(cur.child!=null){
                //保存cur的下一个节点,便于插入后的拼接
                Node temp=cur.next;
                //改变cur的指向,双向改变
                Node childnode=cur.child;
                cur.next=childnode;
                childnode.prev=cur;
                cur.child=null;
                //找到child的最后一个节点
                Node last=cur;
                while(last.next!=null){
                    last=last.next;
                }
                //将child最后一个节点连接到第一级上
                last.next=temp;
                if(temp!=null) temp.prev=last;
            }
            //同时可以查看后面拼接上来的节点的child
            cur=cur.next;
        }
        return head;
    }
}
29.排序的循环链表

参考题解,考虑到五种情况,while(ptr->next != head)是循环链表循环查找的方式。从头开始查找插入的位置ptr,当查到1.val在当前节点ptr下一个点中间 2.最大值最小值 3.循环到头结束(1个元素或者都是相同元素),就在ptr后面插入元素,实际上就是找到链表插入基本操作里的pre节点了。

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

    public Node() {}

    public Node(int _val) {
        val = _val;
    }

    public Node(int _val, Node _next) {
        val = _val;
        next = _next;
    }
};
*/

class Solution {
    public Node insert(Node head, int insertVal) {
        //列表为空
        if(head==null){
            Node newNode=new Node(insertVal);
            newNode.next=newNode;
            return newNode;
        }
        Node ptr=head;
        //循环到头结束
        while(ptr.next!=head){
            //val在当前节点ptr和下一个点中间
            if(insertVal>=ptr.val&&insertVal<=ptr.next.val) break;
            //在链表首尾相连处找到最大值最小值
            if(ptr.val>ptr.next.val&&(insertVal>=ptr.val||insertVal<=ptr.next.val)) break;
            ptr=ptr.next;
        }

        Node newNode=new Node(insertVal);
        newNode.next=ptr.next;
        ptr.next=newNode;
        return head;
    }
}
25.链表中的两数相加

先用栈将两个链表的数字反转存储,然后双对象双指针的加法模板+头插法构建链表

/**
 * 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 addTwoNumbers(ListNode l1, ListNode l2) {
        Stack<Integer> st1=new Stack<>();
        Stack<Integer> st2=new Stack<>();
        while(l1!=null){
            st1.push(l1.val);
            l1=l1.next;
        }
        while(l2!=null){
            st2.push(l2.val);
            l2=l2.next;
        }
        int carry=0;
        ListNode anshead=null;
        while(!st1.isEmpty()||!st2.isEmpty()){
            int digitA=st1.isEmpty()?0:st1.peek();
            int digitB=st2.isEmpty()?0:st2.peek();
            int sum=digitA+digitB+carry;
            int res=sum%10;
            carry=sum/10;

            ListNode newNode=new ListNode(res);
            newNode.next=anshead;
            anshead=newNode;

            if(!st1.isEmpty()) st1.pop();
            if(!st2.isEmpty()) st2.pop();
        }
        if(carry!=0){
            ListNode newNode=new ListNode(carry);
            newNode.next=anshead;
            anshead=newNode;
        }
        return anshead;
    }
}
83.删除排序链表中的重复元素

相比于数组去重26.删除有序数组中的重复项中的不重复就是nums[right]覆盖nums[++left],重复就right++忽略处理;链表中需要把握前一个节点为cur,同时其后一个节点cur.next,这两个节点的关系,逐个往后推,cur、cur.next前后相等则删除后一个节点cur.next,不同则cur继续往前不改变。
迭代做法:

/**
 * 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) return null;
        ListNode cur=head;
        //只有一个节点时不满足条件直接返回
        while(cur!=null&&cur.next!=null){
            if(cur.val==cur.next.val){
                //前后相同删除cur.next
                cur.next=cur.next.next;
            }else{
                cur=cur.next;
            }
        }
        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 deleteDuplicates(ListNode head) {
        if(head==null) return null;
        if(head.next==null) return head;
        ListNode left;
        ListNode right;
        left=head;
        right=head.next;
        while(right!=null){
            if(right.val!=left.val){
                left=left.next;
                left.val=right.val;
            }
            right=right.next;
        }
        left.next=null;
        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 deleteDuplicates(ListNode head) {
        //base情况
        if(head==null||head.next==null) return head;
        if(head.next.val!=head.val){
            head.next=deleteDuplicates(head.next);
        }else{
            //head->head.next(move)->...->null
            ListNode move=head.next;
            //直到move为null或者已经不和head.val一样
            while(move!=null&&move.val==head.val){
                move=move.next;
            }
            head.next=deleteDuplicates(move);
        }
        return head;
    }
}

另一种写法:
关键在于递归的base情况,就是链表尾部,以及递归返回的节点,是链表已经处理过重复后的头部节点。

/**
 * 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) {
        return dfs(head);
    }

    // 从左到右递归,从右到左递归返回
    public ListNode dfs(ListNode head){
        if(head==null||head.next==null) return head;
        while(head!=null&&head.next!=null&&head.val==head.next.val) // 要一删到底,防止[1,1,1]的情况返回1,1
            head.next=head.next.next;
        head.next=dfs(head.next);
        return head;
    }
}
82.删除排序链表中的重复元素II

使用了递归和迭代两种做法,参考负雪明烛大佬的题解
递归解法:

/**
 * 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) {
        //base情况
        if(head==null||head.next==null) return head;

        //递归情况
        if(head.next.val!=head.val){
            head.next=deleteDuplicates(head.next);
            return head;
        }else{
            //head->head.next(move)->...->null
            ListNode move=head.next;
            //直到move为null或者已经不和head.val一样
            while(move!=null&&move.val==head.val){
                move=move.next;
            }
            //head不保留
            return deleteDuplicates(move);
        }
    }
}

迭代解法:
这里关键是需要将出现重复的节点都删除,像上一题83.删除排序链表中的重复元素,出现重复节点的下面节点删除是容易的,是因为我们用cur节点可以很方便地找到cur.next然后改变cur的指向,但这一题需要连着cur一起删了,因此引入了pre节点。

当有重复节点的时候,先cur=cur.next往后走,如果到达最后一个再判断,pre节点的指向以及移动是本题的难点,当没有重复节点的时候pre=pre.next,否则改变指向pre.next=cur.next,如何判断是否有重复节点,则判断是否pre.next==cur,即中间有没有空档。

/**
 * 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) {
        ListNode dummyHead=new ListNode(0);
        dummyHead.next=head;
        ListNode pre=dummyHead;
        ListNode cur=head;
        while(cur!=null){
            //指向重复节点的最后一个
            while(cur.next!=null&&cur.next.val==cur.val){
                cur=cur.next;
            }
            //pre节点的移动
            if(pre.next==cur) pre=pre.next;
            else pre.next=cur.next;
            cur=cur.next;
        }
        return dummyHead.next;
    }
}

后面二刷的时候还有一种解法:
碰到相同的元素,就先找到重复元素的最后一个,然后进行删除。

/**
 * 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) {
        ListNode dummyHead=new ListNode(-1);
        dummyHead.next=head;
        ListNode pre=dummyHead;
        ListNode cur=head;
        while(cur!=null&&cur.next!=null){
            if(cur.val==cur.next.val){
                while(cur!=null&&cur.next!=null){ // 这里是将重复的元素都找到
                    if(cur.val==cur.next.val){
                        cur=cur.next;
                    }else{
                        break;
                    }
                }
                // 然后进行删除
                cur=cur.next; //这里卡了下bug,注意先要移动一下cur
                pre.next=cur; 
            }else{
                pre=pre.next;
                cur=cur.next;
            }
        }
        return dummyHead.next;
    }
}
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 ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        int carry=0;
        ListNode dummyHead=new ListNode(0);
        ListNode cur=dummyHead;
        while(l1!=null||l2!=null){
            int digitA=l1!=null?l1.val:0;
            int digitB=l2!=null?l2.val:0;
            int sum=digitA+digitB+carry;
            int digit=sum%10;
            carry=sum/10;
            cur.next=new ListNode(digit);
            cur=cur.next;
            if(l1!=null) l1=l1.next;
            if(l2!=null) l2=l2.next;
        }
        if(carry!=0) cur.next=new ListNode(carry);
        return dummyHead.next;
    }
}
445.两数相加II

这题链表头存的是最高位,为了从最低位开始加起,需要先拿链表反存一下,由于先算出来的是最低位结果,又需要采用头插法存储结果。

/**
 * 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 addTwoNumbers(ListNode l1, ListNode l2) {
        Stack<Integer> st1=new Stack<>();
        Stack<Integer> st2=new Stack<>();
        while(l1!=null){
            st1.push(l1.val);
            l1=l1.next;
        }
        while(l2!=null){
            st2.push(l2.val);
            l2=l2.next;
        }
        int carry=0;
        ListNode head=null;
        while(!st1.isEmpty()||!st2.isEmpty()){
            int digitA=!st1.isEmpty()?st1.peek():0;
            int digitB=!st2.isEmpty()?st2.peek():0;
            int sum=digitA+digitB+carry;
            int digit=sum%10;
            carry=sum/10;

            ListNode newNode=new ListNode(digit);
            newNode.next=head;
            head=newNode;

            if(!st1.isEmpty())st1.pop();
            if(!st2.isEmpty())st2.pop();
        }
        if(carry!=0){
            ListNode newNode=new ListNode(carry);
            newNode.next=head;
            head=newNode;
        }
        return head;
    }
}
817.链表组件

判断连续段与断开需要掌握。

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def numComponents(self, head: Optional[ListNode], nums: List[int]) -> int:
        s=set()
        for n in nums:
            s.add(n)
        res=0
        # 每次指向当前cur的前一个值,初始化为-1,
        # 用于判断当前值和前面的值是否是断开的关系(当前值在nums中,前一个值不nums中)
        pre=-1
        cur=head
        while cur!=None:
            if (pre==-1 or pre not in s) and cur.val in s:
                res+=1
            pre=cur.val
            cur = cur.next
        return res
            
21.合并两个有序链表

双指针+链表从零开始创建

class Solution {
    public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        ListNode cura=list1;
        ListNode curb=list2;
        ListNode dummyhead=new ListNode(-1);
        ListNode cur=dummyhead;
        while(cura!=null&&curb!=null){
            if(cura.val<=curb.val){
                cur.next=cura;
                cura=cura.next;
            }else{
                cur.next=curb;
                curb=curb.next;
            }
            cur=cur.next;
        }
        // 拼接上没插入完的部分
        cur.next=cura==null?curb:cura;
        return dummyhead.next;
    }
}
23.合并K个升序链表

归并的思想,排序已经排好了,每次问题的规模是所有链表的长度加起来为N,但是层数减少了,变为logk,总体时间复杂度为logkN。

/**
 * 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 mergeKLists(ListNode[] lists) {
        return dfs(lists,0,lists.length-1);
    }

    public ListNode dfs(ListNode[] lists,int i,int j){
        if(i>j) return null;
        if(i==j) return lists[i];
        int mid=(i+j)/2;
        ListNode head1=dfs(lists,i,mid);
        ListNode head2=dfs(lists,mid+1,j);
        ListNode ans=merge(head1,head2);
        return ans;
    }

    public ListNode merge(ListNode head1, ListNode head2){
       ListNode cura=head1;
       ListNode curb=head2;
       ListNode dummyhead=new ListNode(-1);
       ListNode cur=dummyhead;
       while(cura!=null&&curb!=null){
           if(cura.val<=curb.val){
               cur.next=cura;
               cura=cura.next;
           }else{
               cur.next=curb;
               curb=curb.next;
           }
           cur=cur.next;
       } 
       cur.next=cura==null?curb:cura;
       return dummyhead.next;
    }
}

链表中的环问题

看到别人面试的连环问:

  1. 怎么判断链表相交
  2. 怎么判断链表有环
  3. 怎么找到环入口地址?为什么这么找?怎么证明
  4. 怎么判断两个有环链表是否相交?
22.链表中环的入口节点

快慢指针的运用,快指针每次走两步,慢指针每次走一步,能相遇则一定有环否则无环。设链表共有 a+b 个节点,其中 链表头部到链表入口 有 a 个节点(不计链表入口节点), 链表环 有 b 个节点,快指针步数fast=2*slowfast=slow+n*b两者相遇得slow=n*b。所有指针走到链表入口节点时的步数是k=a+nb,在慢指针走了nb步的情况下,再走a步就可以了,这正是链表头开始走,可以和慢指针slow碰头的步数。

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode detectCycle(ListNode head) {
       ListNode fast=head;
       ListNode slow=head;
       ListNode ans=null;
       // 当fast能够结束就说明无环,所以如果不存在环,一遍就能结束,
       // 如果存在环,自然内部 if(fast==slow)会拦住
       while(fast!=null&&fast.next!=null){
           fast=fast.next.next;
           slow=slow.next;
           if(fast==slow){
               ans=head;
               ListNode cur2=fast;
               while(ans!=cur2){
                   ans=ans.next;
                   cur2=cur2.next;
               }
               // ans==cur2
               break;
           }
       }
       // 无环返回null
       return ans;

    }
}

链表相交问题

链表相交

快慢指针+双指针,本质上需要理解,这是两个有穷的链表,两个链表相交说明有一个地址相同的相交链表节点,该节点以后所有的节点这两个链表都是相同的。但是两个链表长度是不同的,相交以后的长度一定相同,所以可以先遍历两个链表找出长度,再让快指针的一方先补齐长度,接着同时移动比较指针。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        int len1=0;
        int len2=0;
        ListNode cura=headA;
        ListNode curb=headB;
        while(cura!=null){ // 统计长度
            len1++;
            cura=cura.next;
        }
        while(curb!=null){
            len2++;
            curb=curb.next;
        }

        if(len1<len2){ // 让a是长链,准备查找相交节点
            cura=headB;
            curb=headA;
            int temp=len1;
            len1=len2;
            len2=temp;
        }else{
            cura=headA;
            curb=headB;
        }

        for(int i=0;i<len1-len2;i++){
            cura=cura.next;
        }
        for(int i=0;i<len2;i++){
            if(cura==curb){
                return cura;
            }else{
                cura=cura.next;
                curb=curb.next;
            }
        }
    
        return null; // 没有链表相交
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

互联网民工蒋大钊

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

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

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

打赏作者

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

抵扣说明:

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

余额充值