Day03 链表-移除链表元素+设计链表 +反转链表

2.linked list


    2.1 introduction


    A linked list is a linear data structure where elements are not stored contiguously in memory. Instead, they are linked together using pointers. 


    Each node in a linked list consists of two parts: a data field and a pointer field (which holds the address of the next node in the list). The pointer field of the last node in the list points to NULL (indicating that it is the end of the list).
    The first node in a linked list is called the head node, often referred to as 'head' in programming contexts. This head node serves as the entry point to the linked list, allowing traversal of the list starting from the beginning.
    
    The key characteristics of a linked list are:
        Dynamic Size: Linked lists can grow and shrink in size as needed, making them ideal for applications where the number of elements is not known in advance.
        Non-contiguous Memory Allocation: Unlike arrays, linked lists do not require contiguous memory allocation. This makes them more flexible but also slightly slower to access individual elements due to the need to traverse the list.
        Pointer-based Navigation: Navigation through a linked list is achieved by following the pointers from one node to the next. This allows for efficient insertion and deletion of elements without the need to shift other elements in the list.
        Circular and Doubly Linked Variants: Linked lists can be modified to form circular linked lists (where the last node points back to the first node) or doubly linked lists (where each node has two pointers: one to the next node and one to the previous node). These variants offer additional flexibility and capabilities.
        
    In summary, a linked list is a powerful data structure that provides flexibility in managing collections of elements, particularly when the size of the collection is not known in advance or when frequent insertions and deletions are required.
    
  

 Type of linked list


        #Single chain list
            What I just said is a single-linked list.
        #Doubly linked list(where each node has two pointers: one to the next node and one to the previous node)
            The pointer field in a singly linked list can only point to the next node of the node.
            Double-linked list: Each node has two pointer fields, one pointing to the next node and one pointing to the previous node. Double-linked lists can be queried both forward and backward.
        #Circular linked list (where the last node points back to the first node)
            A circular linked list, as the name suggests, is a linked list where the head and tail are connected.
            A circular linked list can be used to solve the Josephus ring problem.
    
  

 Storage method of linked list
      

 Arrays are distributed continuously in memory, but linked lists are not distributed continuously in memory.
        A linked list is a data structure in which nodes are linked together in memory through pointers in a pointer field.
        Therefore, the nodes in the linked list are not distributed continuously in memory, but are scattered at certain addresses in memory. The allocation mechanism depends on the memory management of the operating system.

    

2.2 203. Remove Linked List Elements


        Given the head of a linked list and an integer val, remove all the nodes of the linked list that has Node.val == val, and return the new head.
        key point: decide whether it is the head of the list
        
        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) {
            }
        }
        
        Q:how to make a linked list by ListNode class?
        public static void main(String[] args) {  
            // 创建链表的节点  
            ListNode head = new ListNode(2); // 头节点  
            head.next = new ListNode(3);  
            head.next.next = new ListNode(4);  
            head.next.next.next = new ListNode(5);  
            head.next.next.next.next = new ListNode(6);  
            head.next.next.next.next.next = new ListNode(7);        
            // 打印链表(假设我们有一个打印链表的方法)  
            printList(head);  
        }        
        // 辅助方法,用于打印链表  
        public static void printList(ListNode node) {  
            while (node != null) {  
                System.out.print(node.val + " -> ");  
                node = node.next;  
            }  
            System.out.println("null");  
        }  
        

答案:


        有两个方法可以解决从链表中删除元素(两个方法中的cur都是用于遍历列表的临时指针,不可以更改头节点):
        方法1:原链表删除元素。


            public ListNode removeElements(ListNode head, int val) {
                while (head != null && head.val == val) {
                    head = head.next;
                }
                // 已经为null,提前退出
                if (head == null) {
                    return head;
                }
                // 已确定当前head.val != val
                ListNode pre = head;
                ListNode cur = head.next;
                while (cur != null) {
                    if (cur.val == val) {
                        pre.next = cur.next;
                    } else {
                        pre = cur;
                    }
                    cur = cur.next;
                }
                return head;
            }
        ====================================================================
            public ListNode removeElements(ListNode head, int val) {
                while(head!=null && head.val==val){
                    head = head.next;
                }
                ListNode curr = head;
                while(curr!=null){
                    while(curr.next!=null && curr.next.val == val){
                        curr.next = curr.next.next;
                    }
                    curr = curr.next;
                }
                return head;
            }
        方法2:使用虚拟头节点


            public ListNode removeElements(ListNode head, int val) {
                if(head == null) return head;
                //因为删除可能涉及到头节点,所以设置一个val=-1,next=head的虚拟头节点,统一操作
                ListNode dummy = new ListNode(-1,head);
                ListNode cur = dummy;
                while (cur.next != null){
                    if(cur.next.val == val){
                        cur.next = cur.next.next;
                    }else{
                        cur = cur.next;
                    }
                }
                return dummy.next;
            }
    

tips:

    /*
        当你执行 curr.next = curr.next.next; 时,你实际上是在修改链表的结构,即删除了 curr.next 所指向的节点(这个节点的值等于 val)。
        这个操作是通过改变 curr 引用所指向的节点的 next 指针来实现的,而不是通过删除 curr 引用本身。

        curr = curr.next; 这行代码的作用是将 curr 引用移动到链表的下一个节点。这并不会删除任何节点,只是改变了 curr 引用所指向的对象。
        在这个上下文中,一旦 curr.next 被设置为 curr.next.next(即删除了一个节点),curr 就需要移动到下一个有效的节点上,以便继续检查链表中的剩余部分。

        这部分难以理解的问题:
        在cur = cur.next中,cur属性val和next为什么不会变成cur.next的val和next?
        解答:
        在 cur = cur.next; 这行代码中,cur 是一个变量,它持有对链表中某个节点的引用。当你执行这行代码时,你并没有改变 cur 之前所引用的节点的任何属性(如 val 或 next),你只是改变了 cur 变量本身所持有的引用,使其指向链表中的下一个节点。
        这里有几个关键点需要理解:
        变量与引用:在Java中,变量(如 cur)不是对象本身,而是对对象在内存中位置的引用(或者说是指针)。当你将 cur 设置为 cur.next 时,你实际上是在改变 cur 变量所持有的引用,使其指向 cur.next 所引用的对象。
        对象不变性:链表中的节点(即 ListNode 对象)一旦创建,它们的 val 和 next 属性(除非在代码中显式修改)就不会因为某个变量引用的改变而改变。这是因为 val 和 next 是节点对象的属性,而不是变量的属性。
        操作结果:执行 cur = cur.next; 后,cur 现在引用的是链表中的下一个节点。但是,之前的节点(即 cur.next 之前所引用的节点)仍然存在于内存中,除非没有其他引用指向它(这时它可能会被垃圾回收器回收)。然而,这个节点的 val 和 next 属性保持不变,因为它们与 cur 变量的变化无关。
        链表结构的变化:虽然 cur 变量的引用改变了,但如果你之前通过 cur 修改了链表的结构(比如通过 cur.next = cur.next.next; 删除了一个节点),那么这种修改是持久的,并且会影响到链表的其余部分。但是,这种修改是通过操作 cur 所引用的节点的 next 属性来实现的,而不是通过改变 cur 变量本身来实现的。
        综上所述,cur = cur.next; 这行代码只会改变 cur 变量所持有的引用,而不会改变链表中任何节点的 val 或 next 属性。如果你想要修改节点的属性,你需要直接操作该节点的属性(如 cur.val = newValue; 或 cur.next = newNextNode;),而不是改变引用变量的值。
         */
    


    2.3 707. Design Linked List


    Design your implementation of the linked list. You can choose to use a singly or doubly linked list.
    A node in a singly linked list should have two attributes: val and next. val is the value of the current node, and next is a pointer/reference to the next node.
    If you want to use the doubly linked list, you will need one more attribute prev to indicate the previous node in the linked list. Assume all nodes in the linked list are 0-indexed.
    Implement the MyLinkedList class:
    MyLinkedList() Initializes the MyLinkedList object.
    int get(int index) Get the value of the indexth node in the linked list. If the index is invalid, return -1.
    void addAtHead(int val) Add a node of value val before the first element of the linked list. After the insertion, the new node will be the first node of the linked list.
    void addAtTail(int val) Append a node of value val as the last element of the linked list.
    void addAtIndex(int index, int val) Add a node of value val before the indexth node in the linked list. If index equals the length of the linked list, the node will be appended to the end of the linked list. If index is greater than the length, the node will not be inserted.
    void deleteAtIndex(int index) Delete the indexth node in the linked list, if the index is valid.
    
    class MyLinkedList {
        public MyLinkedList() {        }        
        public int get(int index) {        }        
        public void addAtHead(int val) {        }        
        public void addAtTail(int val) {        }        
        public void addAtIndex(int index, int val) {        }        
        public void deleteAtIndex(int index) {        }
    }    
    
    /*
    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);
     */
    
        public void addAtHead(int val) {
            ListNode dummy = new ListNode(-1,head);
            ListNode cur = dummy;
            ListNode node = new ListNode(val);
            node.next = cur.next;
            cur.next = node;
            size++;
            this.head = dummy.next;        
        }
  

 答案:


    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(100);
        }

        //获取第index个节点的数值,注意index是从0开始的,第0个节点就是头结点
        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;
        }

        //在链表最前面插入一个节点,等价于在第0个元素前添加
        public void addAtHead(int val) {
            addAtIndex(0, val);
        }

        //在链表的最后插入一个节点,等价于在(末尾+1)个元素前添加
        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;
            }
            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--;
            if (index == 0) {
                head = head.next;
            return;
            }
            ListNode pred = head;
            for (int i = 0; i < index ; i++) {
                pred = pred.next;
            }
            pred.next = pred.next.next;
        }
    } 

tips:

重点:需要知道第n个节点的前一个节点的指针,才可以对第n个节点做操作。    
    //假如要删除第0个节点,那么第0个节点就是cur.next,无需对cur做操作,即无需进入循环。
    卡住的地方:
    初始化链表:head = new ListNode(100);此处的head就是之前的dummy。head实际的第0个值是100。所以在get(index)方法中,for循环i需要<=index,即index=0时也要循环一次。
    之前错误出在addAtHead,在最后未给实例的head属性赋值。那head就一直是null,所以出现了空指针。


    2.4 206. Reverse Linked List


    Given the head of a singly linked list, reverse the list, and return the reversed list.
    1.dual pointer


        public ListNode reverseList(ListNode head) {
            ListNode cur = head;
            ListNode pre = null;
            while(cur != null){
                ListNode temp = cur.next;
                cur.next = pre;
                pre = cur;
                cur = temp;
            }
            return pre;
        }
    2.recursion
        public ListNode reverseList(ListNode head) {
            return reverse(head,null);
        }        
        public ListNode reverse(ListNode cur, ListNode pre){
            if(cur == null)return pre;
            ListNode temp = cur.next;
            cur.next = pre;
            return reverse(temp,cur);
        }

  • 8
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值