代码随想录day02--链表

发布于:2024-12-07 ⋅ 阅读:(125) ⋅ 点赞:(0)

移除链表元素

题目

地址:移除链表元素

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

思路是使用虚拟节点的方式比较简单一些,直接定义一个虚拟节点指向头结点,在链表中移除元素就是将指针域向后移动两部就可

代码

/**
 * 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 dumpNode = new ListNode();
        dumpNode.next = head;
        
        //定义当前节点为虚拟节点
        ListNode cur = dumpNode;
        while(cur.next != null){
            if(cur.next.val == val){
                //移除元素
                cur.next = cur.next.next;
            }else{
                cur = cur.next;
            }
        }
        //指向虚拟节点的下一个节点即头节点
        return dumpNode.next;
    }
}

设计链表

题目

地址:设计链表

在链表类中实现这些功能:

  • get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。
  • addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。
  • addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。
  • addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val 的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。
  • deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点。

707示例

代码

class MyLinkedList {
    //链表数量
    int size;
    //虚拟头节点
    ListNode head; 

    //初始化链表
    public MyLinkedList() {
        size = 0;
        head = new ListNode(0);
    }
    
    // 获取链表中下标为 index 的节点的值。如果下标无效,则返回 -1 
    public int get(int index) {
        if(index <0 || index >= size){
            return -1;
        }
        ListNode curNode = head;
        for(int i=0; i <= index; i++){
            curNode = curNode.next;
        }
        return curNode.val;
    }
    
    // 将一个值为 val 的节点插入到链表中第一个元素之前。在插入完成后,新节点会成为链表的第一个节点。
    public void addAtHead(int val) {
        addAtIndex(0,val);
    }
    
    //将一个值为 val 的节点追加到链表中作为链表的最后一个元素。
    public void addAtTail(int val) {
        addAtIndex(size,val);
    }
    
    //将一个值为 val 的节点插入到链表中下标为 index 的节点之前。如果 index 等于链表的长度,那么该节点会被追加到链表的 末尾。如果 index 比长度更大,该节点将 不会插入 到链表中。
    public void addAtIndex(int index, int val) {
        if(index > size){
            return;
        }
        index = Math.max(0,index);
        size++;

        ListNode preNode = head;
        for(int i=0;i < index ;i++){
            preNode = preNode.next;
        }
        ListNode toAdd = new ListNode(val);
        toAdd.next = preNode.next;
        preNode.next = toAdd;
    }
    
    // 如果下标有效,则删除链表中下标为 index 的节点。
    public void deleteAtIndex(int index) {
        if(index < 0 || index >= size){
            return;
        }
        size--;
        ListNode curNode = head;
        for(int i=0 ; i < index ; i++){
            curNode = curNode.next; 
        }
        curNode.next = curNode.next.next;
    }
}

class ListNode{
    int val;
    ListNode next;

    public ListNode(int val){
        this.val = val;
    }

    public ListNode(){}
}

/**
 * Your MyLinkedList object will be instantiated and called as such:
 * MyLinkedList obj = new MyLinkedList();
 * int param_1 = obj.get(index);
 * obj.addAtHead(val);
 * obj.addAtTail(val);
 * obj.addAtIndex(index,val);
 * obj.deleteAtIndex(index);
 */

反转链表

题目

地址:反转链表

题意:反转一个单链表。

示例: 输入: 1->2->3->4->5->NULL 输出: 5->4->3->2->1->NULL

img

代码

/**
 * 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 preNode = null;
        ListNode curNode = head;
        ListNode tempNode = null;

        while(curNode != null){
          //保存下一个节点
          tempNode = curNode.next;
          curNode.next = preNode;
          preNode = curNode;
          curNode = tempNode;
        }
        return preNode;
    }
}

双指针方法是最容易理解的,当然参考代码随想录,还有其他的两种方法就是递归方法和从后向前递归方法

递归:

class Solution {
    public ListNode reverseList(ListNode head) {
        return reverse(null, head);
    }

    private ListNode reverse(ListNode prev, ListNode cur) {
        if (cur == null) {
            return prev;
        }
        ListNode temp = null;
        temp = cur.next;// 先保存下一个节点
        cur.next = prev;// 反转
        // 更新prev、cur位置
        // prev = cur;
        // cur = temp;
        return reverse(cur, temp);
    }
}

从后向前递归

class Solution {
    ListNode reverseList(ListNode head) {
        // 边缘条件判断
        if(head == null) return null;
        if (head.next == null) return head;
        
        // 递归调用,翻转第二个节点开始往后的链表
        ListNode last = reverseList(head.next);
        // 翻转头节点与第二个节点的指向
        head.next.next = head;
        // 此时的 head 节点为尾节点,next 需要指向 NULL
        head.next = null;
        return last;
    } 
}

两两交换链表中的节点

题目

地址:两两交换链表中的节点

给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。

img

思路:

使用虚拟头结点

24.两两交换链表中的节点1

代码

/**
 * 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) {
        //使用虚拟头结点来操作
        ListNode dumpNode = new ListNode(-1);
        dumpNode.next = head;
        ListNode curNode = dumpNode;
        //定义三个临时节点
        ListNode firstNode;//两个节点中的第一个节点
        ListNode secondeNode;//两个节点中的第二个节点
        ListNode temp;两个节点后面的节点

        while(curNode.next!=null && curNode.next.next != null){
            temp = curNode.next.next.next;
            firstNode = curNode.next;
            secondeNode  =curNode.next.next;
            //进行交换
            curNode.next = secondeNode;
            secondeNode.next = firstNode;
            firstNode.next = temp;
            //下一轮循环
            curNode = firstNode;
        }
        return dumpNode.next;
    }
}

删除链表倒数第N个节点

题目

地址:删除链表倒数第N个节点

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

img

代码

/**
 * 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 dumpNode = new ListNode(0);
        dumpNode.next = head;

        //定义快慢指针
        ListNode fastIndex = dumpNode;
        ListNode slowIndex = dumpNode;

        //快慢指针只要相差n即可
        for(int i=0;i<=n;i++){
            fastIndex = fastIndex.next;
        }
        while(fastIndex != null){
            fastIndex = fastIndex.next;
            slowIndex = slowIndex.next;
        }

        if(slowIndex.next != null){
            slowIndex.next = slowIndex.next.next;
        }

        return dumpNode.next;
    }
}

链表相交

题目

地址:链表相交

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

img

简单来说,就是求两个链表交点节点的指针。 这里要注意,交点不是数值相等,而是指针相等。

代码

/**
 * 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){
            if(p1 == null){
                p1 = headB;
            }else{
                p1 = p1.next;
            }
            if(p2 == null){
                p2 = headA;
            }else{
                p2 = p2.next;
            }
        }
        return p1;
    }
}

环形链表II

题目

地址:环形链表II

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

为了表示给定链表中的环,使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。

说明:不允许修改给定的链表。

思路:

判断链表是否有环,可以使用快慢指针法,分别定义 fast 和 slow 指针,从头结点出发,fast指针每次移动两个节点,slow指针每次移动一个节点,如果 fast 和 slow指针在途中相遇 ,说明这个链表有环。

判断链表是否有环

可以使用快慢指针法,分别定义 fast 和 slow 指针,从头结点出发,fast指针每次移动两个节点,slow指针每次移动一个节点,如果 fast 和 slow指针在途中相遇 ,说明这个链表有环。

此时已经可以判断链表是否有环了,那么接下来要找这个环的入口了。

假设从头结点到环形入口节点 的节点数为x。 环形入口节点到 fast指针与slow指针相遇节点 节点数为y。 从相遇节点 再到环形入口节点节点数为 z。 如图所示:

img

那么相遇时: slow指针走过的节点数为: x + y, fast指针走过的节点数:x + y + n (y + z),n为fast指针在环内走了n圈才遇到slow指针, (y+z)为 一圈内节点的个数A。

因为fast指针是一步走两个节点,slow指针一步走一个节点, 所以 fast指针走过的节点数 = slow指针走过的节点数 * 2:

(x + y) * 2 = x + y + n (y + z)

两边消掉一个(x+y): x + y = n (y + z)

因为要找环形的入口,那么要求的是x,因为x表示 头结点到 环形入口节点的的距离。

所以要求x ,将x单独放在左面:x = n (y + z) - y ,

再从n(y+z)中提出一个 (y+z)来,整理公式之后为如下公式:x = (n - 1) (y + z) + z 注意这里n一定是大于等于1的,因为 fast指针至少要多走一圈才能相遇slow指针。

这个公式说明什么呢?

先拿n为1的情况来举例,意味着fast指针在环形里转了一圈之后,就遇到了 slow指针了。

当 n为1的时候,公式就化解为 x = z

这就意味着,从头结点出发一个指针,从相遇节点 也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是 环形入口的节点

也就是在相遇节点处,定义一个指针index1,在头结点处定一个指针index2。

让index1和index2同时移动,每次移动一个节点, 那么他们相遇的地方就是 环形入口的节点。

动画如下:

142.环形链表II(求入口)

那么 n如果大于1是什么情况呢,就是fast指针在环形转n圈之后才遇到 slow指针。

其实这种情况和n为1的时候 效果是一样的,一样可以通过这个方法找到 环形的入口节点,只不过,index1 指针在环里 多转了(n-1)圈,然后再遇到index2,相遇点依然是环形的入口节点。

代码

/**
 * 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 slowNode = head;
        ListNode fastNode = head;
        while(fastNode != null && fastNode.next != null){
            slowNode = slowNode.next;
            fastNode = fastNode.next.next;
            if(fastNode == slowNode){//有环
                ListNode indexNode1 = head;
                ListNode indexNode2 = slowNode;
                while(indexNode1 != indexNode2){
                    indexNode1 = indexNode1.next;
                    indexNode2 = indexNode2.next;
                }
                return indexNode1;
            }
        }
        return null;
    }
}

【ps】:所有的图片引用来自代码随想录,网址:代码随想录


网站公告

今日签到

点亮在社区的每一天
去签到