《代码随想录》刷题笔记——链表篇【java实现】

链表节点定义

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;
    }
}

*移除链表节点

题目链接:https://leetcode.cn/problems/reverse-linked-list/

【思路】

有时候增加一个虚拟节点,可以让后面的操作都一致

public static ListNode removeElements(ListNode head, int val) {
    
    
    if (head == null) {
    
    
        return null;
    }

    // 增加一个虚拟节点
    ListNode virtualNode = new ListNode();
    virtualNode.next = head;

    ListNode cur = virtualNode;
    ListNode next = cur.next;
    while (next != null) {
    
    
        if (next.val == val) {
    
    
            cur.next = next.next;
        } else {
    
    
            cur = next;
        }
        next = cur.next;
    }

    return virtualNode.next;
}

*反转链表

双指针法

  • 时间复杂度: O(n)
  • 空间复杂度: O(1)

在这里插入图片描述

public static ListNode reverseList(ListNode head) {
    
    
    ListNode cur = head;
    ListNode pre = null;
    ListNode temp = null;
    while (cur != null) {
    
    
        temp = cur.next;
        cur.next = pre;
        pre = cur;
        cur = temp;
    }
    return pre;
}

递归法

  • 时间复杂度: O(n)
  • 空间复杂度: O(n)

这里的逻辑和双指针法的逻辑是一样的,只是实现方式是递归,那为什么空间复杂度更高呢?

答:因为递归本质上是一种函数调用,在每一次递归调用时,都会在函数栈中分配一段内存空间来保存函数的局部变量、返回地址以及参数等信息。因此,递归实现相对于循环实现的空间复杂度更高,会占用更多的内存空间。尤其是在递归深度较大的情况下,可能会导致栈溢出等问题。而使用循环实现通常不需要使用额外的栈空间,因此循环实现的空间复杂度比递归实现要低

public static ListNode reverseList1(ListNode head) {
    
    
    return reverse(null, head);
}

public static ListNode reverse(ListNode pre, ListNode cur) {
    
    
    if (cur != null) {
    
    
        ListNode temp = cur.next;
        cur.next = pre;
        pre = reverse(cur, temp);
    }
    return pre;
}

*两两交换链表中的节点

https://leetcode.cn/problems/swap-nodes-in-pairs/

  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

在这里插入图片描述

public static ListNode swapPairs(ListNode head) {
    
    
    // 如果链表中没有元素 或者 只有一个元素,直接返回就行
    if (head == null || head.next == null) {
    
    
        return head;
    }

    // 增加一个虚拟头节点
    ListNode virtualNode = new ListNode();
    virtualNode.next = head;
    ListNode last = virtualNode;
    ListNode cur = head;
    ListNode next = cur.next;
    ListNode temp = null;

    while (next != null) {
    
    
        // 交换
        temp = next.next;
        cur.next = next.next;
        next.next = cur;
        last.next = next;

        // 指针移动到新的位置
        last = cur;
        cur = temp;
        if (cur != null) {
    
    
            next = cur.next;
        } else {
    
    
            break;
        }
    }
    return virtualNode.next;
}

*删除链表的倒数第N个节点

https://leetcode.cn/problems/remove-nth-node-from-end-of-list/submissions/

快慢指针法

在这里插入图片描述

/**
 * 让快指针比慢指针先移动 n 步
 *
 * @param head
 * @param n
 * @return
 */
public static ListNode removeNthFromEnd(ListNode head, int n) {
    
    
    ListNode virtual = new ListNode();
    virtual.next = head;
    // 让快指针先移动到n个节点
    ListNode fast = virtual;
    for (int i = 0; i < n; i++) {
    
    
        if (fast.next != null) {
    
    
            fast = fast.next;
        }
    }

    // slow和fast一起移动,等fast到达最后一个节点的时候,slow也就到达了要删除的节点前面的节点
    ListNode slow = virtual;
    while (fast.next != null) {
    
    
        slow = slow.next;
        fast = fast.next;
    }

    // 执行删除操作
    slow.next = slow.next.next;
    return virtual.next;
}
  • 时间复杂度: O(n)
  • 空间复杂度: O(1)

*链表相交

https://leetcode.cn/problems/intersection-of-two-linked-lists-lcci/

注意点

并不是判断val相等,而是hashcode相等

暴力求解

直接两重循环,循环两个链表

  • 时间复杂度:O(n^2)
  • 空间复杂度:O(1)

进阶解法

利用题目中的信息,已经是链表的尾部才有可能相交,可以先让一条链表前进 两条链表长度差值 的位置,最后再两条链表一起前进

在这里插入图片描述

public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
    
    
    ListNode cur1 = headA;
    ListNode cur2 = headB;
    int sizeA = 0;
    int sizeB = 0;

    // 统计链表A的长度
    while (cur1 != null) {
    
    
        sizeA += 1;
        cur1 = cur1.next;
    }
    // 统计链表B的长度
    while (cur2 != null) {
    
    
        sizeB += 1;
        cur2 = cur2.next;
    }
    // 回到初始位置
    cur1 = headA;
    cur2 = headB;

    // 先让元素较多的链表的指针移动一段距离
    if (sizeA > sizeB) {
    
    
        // 链表A的元素较多,先走一段距离
        for (int i = 0; i < (sizeA - sizeB); i++) {
    
    
            cur1 = cur1.next;
        }
    } else if (sizeA < sizeB) {
    
    
        for (int i = 0; i < (sizeB - sizeA); i++) {
    
    
            cur2 = cur2.next;
        }
    }

    // 两个链表同时走动
    while (cur1 != null && cur2 != null) {
    
    
        // 注意,需要的是hash值
        if (cur1.hashCode() == cur2.hashCode()) {
    
    
            return cur1;
        }
        cur1 = cur1.next;
        cur2 = cur2.next;
    }
    return null;
}
  • 时间复杂度:O(n + m)
  • 空间复杂度:O(1)

*环形链表

https://leetcode.cn/problems/linked-list-cycle-ii/

在这里插入图片描述

public static ListNode detectCycle1(ListNode head) {
    
    
    if (head == null) {
    
    
        return null;
    }

    ListNode slow = head;
    ListNode fast = head;
    ListNode node2 = null;

    // 先找到相交点
    while (slow != null && fast.next != null) {
    
    
        // slow移动一步
        slow = slow.next;
        // fast移动两步
        fast = fast.next.next;

        if (slow.hashCode() == fast.hashCode()) {
    
    
            // slow 和 fast相交
            node2 = slow;

            // 让node1从起点出发,node2从相交点出发,当node1和node2相交的时候,相交时的节点即环的入口
            ListNode node1 = head;
            while (true) {
    
    
                if (node1.hashCode() == node2.hashCode()) {
    
    
                    return node1;
                } else {
    
    
                    node1 = node1.next;
                    node2 = node2.next;
                }
            }
        }
    }
    return null;
}
  • 时间复杂度: O(n)。因为快慢指针相遇前,指针走的次数小于链表长度;快慢指针相遇后,两个node指针走的次数也小于链表长度,因此走的总次数小于 2n
  • 空间复杂度: O(1)

猜你喜欢

转载自blog.csdn.net/laodanqiu/article/details/132700388