代码随想录Day2

题目(LeetCode 707)

设计链表的实现。您可以选择使用单链表或双链表。单链表中的节点应该具有两个属性:val 和 next。val 是当前节点的值,next 是指向下一个节点的指针/引用。如果要使用双向链表,则还需要一个属性 prev 以指示链表中的上一个节点。假设链表中的所有节点都是 0-index 的。

实现如下功能:

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

提示:

0 <= index, val <= 1000
请不要使用内置的 LinkedList 库。
get, addAtHead, addAtTail, addAtIndex 和 deleteAtIndex 的操作次数不超过 2000。

思路:
主要考察点是链表的基础操作,包括删除,添加,查找
注意添加操作的顺序.先连接新节点的next再把前一节点和新节点连起来。这样可以避免无法找到新节点的后一节点的问题(后一节点需要通过前一节点.next来获取)

 代码:

复制代码
package com.dwj.LeetCode.LinkedList;

/**
 * 在链表类中实现这些功能:
 *
 * get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。
 * addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。
 * addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。
 * addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val  的节点。如果 index 等于链表的长度,
 * 则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。
 * deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点。
 */
public class DesignLinkedList {
    //单链表
    class ListNode {
        int val;
        ListNode next;
        ListNode(){}
        ListNode(int val) {
            this.val=val;
        }
    }
    class MyLinkedList {
        //size存储链表元素的个数
        int size;
        //虚拟头结点
        ListNode head;

        //初始化链表
        public MyLinkedList() {
            size = 0;
            head = new ListNode(0);
        }

        //获取第index个节点的数值
        public int get(int index) {
            //如果index非法,返回-1
            if (index < 0 || index >= size) {
                return -1;
            }
            ListNode currentNode = head;
            //包含一个虚拟头节点,所以查找第 index+1 个节点
            for (int i = 0; i <= index; i++) {
                currentNode = currentNode.next;
            }
            return currentNode.val;
        }

        //在链表最前面插入一个节点
        public void addAtHead(int val) {
            addAtIndex(0, val);
        }

        //在链表的最后插入一个节点
        public void addAtTail(int val) {
            addAtIndex(size, val);
        }

        // 在第 index 个节点之前插入一个新节点,例如index为0,那么新插入的节点为链表的新头节点。
        // 如果 index 等于链表的长度,则说明是新插入的节点为链表的尾结点
        // 如果 index 大于链表的长度,则返回空
        public void addAtIndex(int index, int val) {
            if (index > size) {
                return;
            }
            if (index < 0) {
                index = 0;
            }
            //因为新插入元素,表长度+1
            size++;
            //找到要插入节点的前驱
            ListNode pred = head;
            for (int i = 0; i < index; i++) {
                pred = pred.next;
            }
            ListNode toAdd = new ListNode(val);
            toAdd.next = pred.next;
            pred.next = toAdd;
        }

        //删除第index个节点
        public void deleteAtIndex(int index) {
            if (index < 0 || index >= size) {
                return;
            }
            size--;
            ListNode pred = head;
            for (int i = 0; i < index; i++) {
                pred = pred.next;
            }
            pred.next = pred.next.next;
        }
    }


}
package com.dwj.LeetCode.LinkedList;

/**
 * 在链表类中实现这些功能:
 *
 * get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。
 * addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。
 * addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。
 * addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val  的节点。如果 index 等于链表的长度,
 * 则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。
 * deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点。
 */
public class DesignLinkedList {
    //单链表
    class ListNode {
        int val;
        ListNode next;
        ListNode(){}
        ListNode(int val) {
            this.val=val;
        }
    }
    class MyLinkedList {
        //size存储链表元素的个数
        int size;
        //虚拟头结点
        ListNode head;

        //初始化链表
        public MyLinkedList() {
            size = 0;
            head = new ListNode(0);
        }

        //获取第index个节点的数值
        public int get(int index) {
            //如果index非法,返回-1
            if (index < 0 || index >= size) {
                return -1;
            }
            ListNode currentNode = head;
            //包含一个虚拟头节点,所以查找第 index+1 个节点
            for (int i = 0; i <= index; i++) {
                currentNode = currentNode.next;
            }
            return currentNode.val;
        }

        //在链表最前面插入一个节点
        public void addAtHead(int val) {
            addAtIndex(0, val);
        }

        //在链表的最后插入一个节点
        public void addAtTail(int val) {
            addAtIndex(size, val);
        }

        // 在第 index 个节点之前插入一个新节点,例如index为0,那么新插入的节点为链表的新头节点。
        // 如果 index 等于链表的长度,则说明是新插入的节点为链表的尾结点
        // 如果 index 大于链表的长度,则返回空
        public void addAtIndex(int index, int val) {
            if (index > size) {
                return;
            }
            if (index < 0) {
                index = 0;
            }
            //因为新插入元素,表长度+1
            size++;
            //找到要插入节点的前驱
            ListNode pred = head;
            for (int i = 0; i < index; i++) {
                pred = pred.next;
            }
            ListNode toAdd = new ListNode(val);
            toAdd.next = pred.next;
            pred.next = toAdd;
        }

        //删除第index个节点
        public void deleteAtIndex(int index) {
            if (index < 0 || index >= size) {
                return;
            }
            size--;
            ListNode pred = head;
            for (int i = 0; i < index; i++) {
                pred = pred.next;
            }
            pred.next = pred.next.next;
        }
    }


}
复制代码

 

Leetcode 206:

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

示例 1

 

 

 思路:双指针,把后一节点不断指向前一节点。

           一个pre,一个cur,一个暂存temp(提前存一下cur的后一节点,方便移动)

递归法比较难理解,以后专门抽个时间集中理解一下递归法。

复制代码
package com.dwj.LeetCode.LinkedList;

import java.util.List;

/**
 * 翻转链表,双指针写法
 */
public class ReverseLinkedList {
    class solution {
        public ListNode reverseList(ListNode head) {
            ListNode prev = null;
            ListNode cur = head;
            ListNode temp = null;
            while (cur != null) {
                //首先把后一节点存起来,后面移动用
                temp = cur.next;
                //连接翻转
                cur.next = prev;
                //位置前进
                prev = cur ;
                cur = temp;
            }
            return prev ;
        }
    }

}
复制代码

leetcode 24 

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

 

 

 思路:

如果要交换 3、4。我们需要拿到2才行。这很重要,对元素的操作,一定要考虑前后节点的连贯性,不能臆想。

所以,虚拟头结点很适合这种需要考虑头尾连接的问题。这一题的终止条件也显而易见是,2.next.next = null,也就是不再满足两两交换的时候,结束。

代码:

复制代码
package com.dwj.LeetCode.LinkedList;

public class ReverseNearByNode {
    /**
     * 给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。
     * 你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
     * Leetcode 24
     */
    // 虚拟头结点
    class Solution {
        public ListNode swapPairs(ListNode head) {

            ListNode dummyNode = new ListNode(0);
            dummyNode.next = head;
            ListNode prev = dummyNode;

            while (prev.next != null && prev.next.next != null) {
                ListNode temp = head.next.next; // 缓存 next
                prev.next = head.next;          // 将 prev 的 next 改为 head 的 next
                head.next.next = head;          // 将 head.next(prev.next) 的next,指向 head
                head.next = temp;               // 将head 的 next 接上缓存的temp
                prev = head;                    // 步进1位
                head = head.next;               // 步进1位
            }
            return dummyNode.next;
        }
    }
    // 递归版本
    class Solution2 {
        public ListNode swapPairs(ListNode head) {
            // base case 退出提交
            if(head == null || head.next == null) return head;
            // 获取当前节点的下一个节点
            ListNode next = head.next;
            // 进行递归
            ListNode newNode = swapPairs(next.next);
            // 这里进行交换
            next.next = head;
            head.next = newNode;

            return next;
        }
    }
}
复制代码

 

LeetCode 19 删除链表倒数第n个节点

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

思路:

问题的关键在于,如何找到倒数第n个节点。可以无脑两个for循环,但是不推荐

这题可以用快慢指针来巧妙的解决。让快指针先走n步,然后慢指针再走,走到快指针为null,慢指针的位置也就是倒数n的位置了。

执行删除操作,需要找到节点的prev,所以,快指针可以先走一步,这样慢指针就慢一步,正好是prev的位置。

代码:

复制代码
package com.dwj.LeetCode.LinkedList;

/**
 * 删除链表倒数第n个元素
 * 给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
 */
public class DeleteDescNode {
    public ListNode removeNthFromEnd(ListNode head, int n){
        ListNode dummyNode = new ListNode(0);
        dummyNode.next = head;

        ListNode fastIndex = dummyNode;
        ListNode slowIndex = dummyNode;

        //只要快慢指针相差 n 个结点即可
        for (int i = 0; i < n  ; i++){
            fastIndex = fastIndex.next;
        }

        while (fastIndex.next != null){
            fastIndex = fastIndex.next;
            slowIndex = slowIndex.next;
        }

        //此时 slowIndex 的位置就是待删除元素的前一个位置。
        //具体情况可自己画一个链表长度为 3 的图来模拟代码来理解
        slowIndex.next = slowIndex.next.next;
        return dummyNode.next;
    }
}
复制代码

 

LeetCode 142. 环形链表 II

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

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。

 

 思路:

首先,要判断是否形成了环形链表。可以通过快慢指针来判断,快慢指针的步长差为1的时候,快指针以每次循环靠近1的速度追慢指针,如果形成环形链表,一定可以追得上。

然后就是寻找入口。寻找入口是个比较脑筋急转弯,小学奥数难度的思考题。

 

假设 ,慢指针走了x+y后才遇到了快指针。

快指针每次走2步,慢指针每次走1步

快指针走的长度换算成慢指针是   (x+y)* 2    

可以写出如下等式:

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

  简化一下等式:

 x = (n-1)(y+z) + z

所以,如果n=1。  x=z

也就是说,如果从相遇点出发,x的距离和z的距离是相同的,他们以同样的步幅移动,一定会在入口处相遇。

我们就可以记录下相遇的位置和头结点的位置。

让他们同步幅移动,相遇点即为环形入口处。

 

代码:

复制代码
package com.dwj.LeetCode.LinkedList;

/**
 * 142 环形链表II
 * 给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
 */
public class CircleLinkedList {
    public class Solution {
        public ListNode detectCycle(ListNode head) {
            ListNode slow = head;
            ListNode fast = head;
            while (fast != null && fast.next != null) {
                slow = slow.next;
                fast = fast.next.next;
                if (slow == fast) {// 有环
                    ListNode index1 = fast;
                    ListNode index2 = head;
                    // 两个指针,从头结点和相遇结点,各走一步,直到相遇,相遇点即为环入口
                    while (index1 != index2) {
                        index1 = index1.next;
                        index2 = index2.next;
                    }
                    return index1;
                }
            }
            return null;
        }
    }
}
复制代码

 

 

 总结:

链表的操作,很多情况需要考虑到前后节点的连贯性,切记不能只考虑头结点情况,或者只考虑中间节点的情况。

像插入,删除操作,都需要视情况获取前后节点。如果存在这种需要考虑前后节点是否是头结点的情况,可以使用虚拟头结点进行操作。

快慢指针,双指针。  不同节奏的两个指针可以用来做很多事情。用来看是否形成环,查找倒数第n个节点,真的很妙。

在进行删除,交换等操作的时候,多考虑一下,是否会形成空指针和无线循环的情况。对于终止条件的定义多数情况要三思,想一想是否考虑了前后节点的情况。

 

upup,继续努力,明年秋招,一定要拿到offer!

posted @   NobodyHero  阅读(23)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 25岁的心里话
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!
· 零经验选手,Compose 一天开发一款小游戏!
点击右上角即可分享
微信分享提示