代码随想录-链表1

构造链表

public class ListNode {
    // 结点的值
    int val;

    // 下一个结点
    ListNode next;

    // 节点的构造函数(无参)
    public ListNode() {
    }

    // 节点的构造函数(有一个参数)
    public ListNode(int val) {
        this.val = val;
    }

    // 节点的构造函数(有两个参数)
    public ListNode(int val, ListNode next) {
        this.val = val;
        this.next = next;
    }
}

203.移除链表元素

力扣题目链接

基本思路

设置虚拟节点指向头节点,这样可以做到对每个节点进行一样的处理。设置虚拟节点后,需要一个指向虚拟节点的“指针”,用来对链表进行操作。

方法代码

class Solution {
    public ListNode removeElements(ListNode head, int val) {
        ListNode dummyNode = new ListNode();
        dummyNode.next = head;
        ListNode curr = dummyNode;
        while(curr.next != null){
            if(curr.next.val == val){
                //如果想要手动删除目标节点和它next节点的链接,需要设置temp,临时指向目标节点
                //java中不考虑
                curr.next = curr.next.next;
            }else{
                curr = curr.next;
            }
        }
        //不能返回head,因为head可能被删除
        //不能返回dummyNode,因为它是我们引入的虚拟节点,它指向处理后的链表
        return dummyNode.next;
    }
}
  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

注意点

  1. 注意判断循环结束的条件是,当前节点指向的地方为null
  2. 返回处理后的链表,指的是虚拟节点的next指向的链表

707.设计链表

力扣题目链接

基本思路

定义节点类,在链表类中,设置size和虚拟头节点

方法代码

//首先定义链表的节点类
class LinkNode{
    int val;
    LinkNode next;
    LinkNode(){}
    LinkNode(int val){
        this.val = val;
    }
    LinkNode(int val, LinkNode next){
        this.val = val;
        this.next = next;
    }
}

class MyLinkedList {
    int size;
    LinkNode dummy;  //dummy就是head

    public MyLinkedList() {
        size = 0;
        dummy = new LinkNode(0);
        // dummy.next = null;
    }
    //注意:假设链表中的所有节点下标从 0 开始。
    public int get(int index) {
        LinkNode curr = dummy;
        if(index >= size || index < 0){
            return -1;
        }
        for(int i = 0; i <= index; i++){
            curr = curr.next;
        }
        return curr.val;
    }
    
    public void addAtHead(int val) {
        LinkNode node = new LinkNode(val);
        node.next = dummy.next;
        dummy.next = node;
        size++;
    }
    
    public void addAtTail(int val) {
        LinkNode node = new LinkNode(val);
        LinkNode curr = dummy;
        for(int i = 0; i < size; i++){
            curr = curr.next;
        }
        // while(curr.next != null){
        //     curr = curr.next;
        // }
        curr.next = node;
        size++;
    }
    //注意:假设链表中的所有节点下标从 0 开始。
    public void addAtIndex(int index, int val) {
        if(index > size){
            return;
        }
        if(index < 0){
            index = 0;
        }
        LinkNode curr = dummy;
        LinkNode node = new LinkNode(val);
        for(int i = 0; i < index; i++){
            curr = curr.next;
        }
        //插入需要先连接后面的节点
        node.next = curr.next;
        curr.next = node;
        size++;
    }
    
    public void deleteAtIndex(int index) {
        //最后一个元素的index是size-1
        if(index < 0 || index >= size){
            return;
        }
        LinkNode curr = dummy;
        for(int i = 0; i < index; i++){
            curr = curr.next;
        }
        curr.next = curr.next.next;
        size--;
    }
}
  • 时间复杂度:涉及 index 的相关操作为 O(index), 其余为 O(1)
  • 空间复杂度:O(n)

注意点

  1. 假设链表中的所有节点下标从 0 开始,最后一个元素的index是size-1
  2. 插入需要先连接后面的节点

206.反转链表

力扣题目链接

基本思路

  1. 迭代,双指针
    1. 用两个指针分别指向前后两个节点,这样就可以做到反向连接
    2. 反向连接时,使用temp记录next的值,这样就不会丢失节点
  2. 递归

迭代

class Solution {
    public ListNode reverseList(ListNode head) {
        //迭代
        //cur是工作指针,用来遍历链表,pre在cur左边,和temp辅助遍历
        ListNode pre = null;
        ListNode cur = head;
        ListNode temp = null;
        while(cur != null){
            temp = cur.next; // 保存一下 cur的下一个节点,因为接下来要改变cur->next
            cur.next = pre;
            pre = cur;
            cur = temp;
        }
        //cur到最后指向null,表示遍历完所有节点,此时pre指向前一个元素,即最后一个元素,即翻转后的第一个节点
        return pre;
    }
}
  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

递归(从前往后翻转)

实际上是按照迭代的方法来实现

class Solution {
    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);
    }

    public ListNode reverseList(ListNode head) {
        //递归
        return reverse(null, head); //这里相当于按照迭代的方法赋初值
    }
}
  • 时间复杂度:O(n), 要递归处理链表的每个节点
  • 空间复杂度:O(n), 递归调用了 n 层栈空间

递归(从后往前翻转)

  1. 递归的三个条件:
    1. 大问题可以拆成两个子问题
    2. 子问题求解方式和大问题一样
    3. 存在最小子问题
class Solution {
    ListNode reverseList(ListNode head) {
	    //递的过程
        // 边缘条件判断
        if (head == null || head.next == null){
	        return head;
        }
        // 递归调用,翻转第二个节点开始往后的链表
        //newHead就是原始链表最后一个节点
        ListNode newHead = reverseList(head.next);

		//归的过程
		//head节点就是子问题1
		//子链表就是子问题2
        // 翻转,每一层调用时的head都不一样,是这一层(顺序)子链表的头节点
        // 所以每次翻转一个节点的指针,其实就是把之前已经翻转过的子链表的最后一个节点的指针指向这一层的head
        // 这是通过head让next.next指向自己实现
        head.next.next = head;
        // 此时的 head 节点为这一层子链表的尾节点,next 需要指向 NULL
        head.next = null;
        return newHead;
    } 
}
  • 时间复杂度:O(n)
  • 空间复杂度:O(n)
posted @ 2024-12-04 21:07  xloading  阅读(23)  评论(0)    收藏  举报