代码随想录算法训练营第三天 | LeetCode203 移除链表元素、LeetCode707设计链表、LeetCode206反转链表

1. LeetCode203 移除链表元素

链表基础知识

  1. 链表在Java中的定义:
/**
 * 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; }
  }
  1. 最后的程序返回,返回head,即链表的头指针即可
  2. 链表中移除元素的操作:更换前一个节点指针指向的下一节点,Java中的垃圾回收机制会将移除的那个元素的内存进行回收

题目

在这里插入图片描述

代码实现

class Solution {
    public ListNode removeElements(ListNode head, int val) {
            while((head != null) && (head.val == val)){
                head = head.next;
            }           
            ListNode cur = head;           
            while ((cur != null) && (cur.next != null)){
                if (cur.next.val == val){
                    cur.next = cur.next.next;
                }else {
                    cur = cur.next;
                }
            }           
            return head;
    }
}

思路

  1. 整体思路:如果下一个节点的值等于target,则将指针指向下下个节点。前提:头结点的值不为target,所以需要对头结点提前进行处理。
  2. 第一个while,操作针对的情况是,如果头结点的值即为target,那么需要把当前节点扔掉,将下一个节点定义为头节点。因为要取当前节点的val及next,所以要保证当前节点不为空,否则会报空指针异常的错误
  3. 用while,不用if的原因:考虑一个链表为[7,7,7,4],target值为7。如果用了if,头节点变为下一节点,依然是target值,需要去除。移除节点值为target值的这个操作是持续的,所以要用while。
  4. 第一个while操作完成后,保证了头节点的值不为target值,则此时可以放心地进行下一步操作:如果下一个节点的值等于target,则将指针指向下下个节点。但是因为我们返回需要返回头节点,因此头节点不可变,因此重新定义一个节点current,不断往后遍历,指向当前遍历的节点,进行后续的操作。
  5. 第二个while:后续要取当前节点的值和当前节点的下一节点的值,所以这两个都不能为空。如果当前节点的下一节点值为target值,则指向下下节点;如果当前节点的下一节点值不为target值,则移动current到下一个节点,继续进入到while中进行检查。
  6. 最后,返回头节点。

优化

在第一个while结束后,最好加一个检查head是否为空,如果为空,可以直接返回head了,就不用再进行下面的操作了。

二解:使用虚拟头节点

代码:

class Solution {
    public ListNode removeElements(ListNode head, int val) {
            ListNode dummy = new ListNode(-1, head);
              ListNode cur = dummy;
              
              if (head == null){
                  return head;
              }
              
              while ((cur != null) && (cur.next != null)){
                    if (cur.next.val == val){
                        cur.next = cur.next.next;
                    }else {
                        cur = cur.next;
                    }
              }
              
              return dummy.next;
    }
}

思路:
在头节点前面定义一个dummy head虚拟头节点,虚拟头节点的下一节点指向原来的头节点,这样就不用对头节点的值是否为target值进行特殊考虑了。同样定义一个current指针,对所有节点一视同仁进行操作。最后返回dummy.next,而不是原来的头节点(万一原来头节点的值就是target值呢)。

遍历链表的时候要新定义一个指针来遍历!很重要!
因为要返回头节点,如果用头节点遍历的话,头节点的值都改了,还怎么返回头节点。

2. LeetCode707 设计链表

题目

在这里插入图片描述

代码实现

    class ListNode{
        int val;
        ListNode next;
        ListNode(){}
        ListNode(int val){
            this.val = val;
        }
        ListNode(ListNode next){
            this.next = next;
        }
        ListNode(int val, ListNode next){
            this.val = val;
            this.next = next;
        }
    }

    class MyLinkedList {
        ListNode dummy;
        int size;

        public MyLinkedList() {
            this.size = 0;
            this.dummy = new ListNode(-1);
        }

        public int get(int index) {
            if (index >= size){
                return -1;
            }
            ListNode cur = dummy;
            for (int i = 0; i < index; i++){
                cur = cur.next;
            }
            return cur.next.val;
        }

        public void addAtHead(int val) {
            ListNode newNode = new ListNode(val);
            if (dummy.next != null) {
                newNode.next = dummy.next;
            }
            dummy.next = newNode;
            size++;
            return;
        }

        public void addAtTail(int val) {
            ListNode newNode = new ListNode(val);
            ListNode cur = dummy;
            for (int i = 0; i < size; i++){
                cur = cur.next;
            }
            cur.next = newNode;
            size++;
            return;
        }

        public void addAtIndex(int index, int val) {
            if (index > size){
                return;
            }
            if (index == size){
                addAtTail(val);
                return;
            }
            ListNode cur = dummy;
            ListNode newNode = new ListNode(val);
            for (int i = 0; i < index; i++){
                cur = cur.next;
            }
            newNode.next = cur.next;
            cur.next = newNode;
            size++;
            return;
        }

        public void deleteAtIndex(int index) {
            if (index >= size){
                return;
            }
            ListNode cur = dummy;
            for (int i = 0; i < index; i++){
                cur = cur.next;
            }
            cur.next = cur.next.next;
            size--;
            return;
        }
    }

/**
 * 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. 上面代码中少考虑了index < 0的情况,后续二刷可以加上。
  3. 因为力扣不需要自己定义输入输出,今天摸索了好久这道题的链表的定义。最终得出结论如下:java中没有指针的概念,所以对于每个ListNode,有两个属性。其一为自身值,其二为next,而next也是ListNode类的一个实例,这样不断传承下去。ListNode在构造时,可以无参(既无自身值,又无next),也可以有参(有自身值,无next;或有自身值,有next)
  4. 针对本题,MyLinkedList需要定义两个属性,一个是list的size,一个是list里构造的虚拟头节点,这两个属性不仅要在class里定义,还要在class的构造函数里进行初始化。今天最纠结的一个问题,系统怎么读出来这个List的?怎么准确的知道第二个节点是头节点的?而不是把虚拟头节点当做头节点?想了半天,想明白了,应该是循环调用了public int get(int index)这个函数,从index=0 获取到 index=size-1。这样就可以解释也没有定义头节点,但是输出list的时候的第一个节点是真实的头节点而不是虚拟头节点了。

3. LeetCode206 反转链表

题目

在这里插入图片描述

代码实现

/**
 * 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) {
        if (head == null){
            return head;
        }
        ListNode pre = null;
        ListNode cur = head;
        while (cur != null){
            ListNode temp = cur.next;
            cur.next = pre;
            pre = cur;
            cur = temp;
        }
        return pre;
    }  
}

思路

在这里插入图片描述
这道题本来也想仿照之前两道题定义一个dummy head,最后没成功。又去看了卡哥讲的视频,消化了半天,自己画了个图,才理解。

  1. 为什么要定义pre并初始化为null?因为链表是以null结束的,反转之后头节点的next必须是null,所以定义一个pre指针,并初始化为null。
  2. 整体思路:定义一个current指针用来遍历,定义一个pre指针用来遍历新的next节点们。current指针一开始指向头节点。也即双指针法。每次循环中,提前定义一个temp指针指向cur.next,然后就可以放心地改变cur.next为pre了。为了不断地向后遍历,再将pre指针移到cur所指的位置,cur指针移到temp(也即原来cur指向的next)的位置,重复上述过程。
  3. 只要没有User user = new User(),单纯的User user = exist,这种都是新加一个user引用,指向已有的exist对象。new的话才是先开辟一个内存存储新对象。

总结

今天前两题目都需要:遍历链表的时候要新定义一个current指针来遍历!很重要!最后一道题双指针法。也相当于要新定义一个current和pre指针指向已有节点,进行遍历。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值