刷算法-链表

算法总结——链表

链表的基本原理

一条链表不需要一整块连续的内存空间存储元素。链表的元素可以分散在内存空间的任意地方,通过每个节点上的 next, prev 指针,将零散的内存块串联起来形成一个链式结构。

  • 数组的优点包括:

    1. 随机访问性强,可以快速查找元素,因为数组的元素是连续存储的,可以直接通过索引访问。
    2. 查找速度快,因为数组的查找操作不需要从头开始,而是直接定位到指定索引。

    数组的缺点包括:

    1. 对内存的要求高,需要连续的内存空间,这可能导致在内存碎片化严重的环境中难以使用。
    2. 插入和删除的效率比较低,因为这通常需要移动其他元素以保持数组的连续性。
    3. 大小固定,动态拓展性差,可能浪费内存,因为数组在定义时需要预先指定大小。
  • 链表的优点包括:

    1. 对内存的要求低,链表中的元素不需要连续存储在内存中。
    2. 大小可以不固定,内存的利用率比较高,适合动态添加或删除元素。
    3. 插入和删除方便,因为这只需要修改元素中的指针,不需要移动其他元素。

    链表的缺点包括:

    1. 查找效率低,不可以随机查找,每次查找必须从头开始。
    2. 不能直接访问任意位置的元素,需要从第一个元素开始遍历到目标元素。

单链表:

定义:
class ListNode {
    int val;
    ListNode next;
    ListNode(int x) { val = x; }
}

// 输入一个数组,转换为一条单链表
ListNode createLinkedList(int[] arr) {
    if (arr == null || arr.length == 0) {
        return null;
    }
    ListNode head = new ListNode(arr[0]);
    ListNode cur = head;
    for (int i = 1; i < arr.length; i++) {
        cur.next = new ListNode(arr[i]);
        cur = cur.next;
    }
    return head;
}
查找/修改:
// 遍历单链表
for (ListNode p = head; p != null; p = p.next) {
	//进行操作
    System.out.println(p.val);
}
增加:

分别为:链表头部、尾部、中间任意位置进行增加。

// 在单链表头部插入一个新节点 0 
ListNode newHead = new ListNode(0);
newHead.next = head;
head = newHead;
// 链表变成了 0 -> 1 -> 2 -> 3 -> 4 -> 5

// 在单链表尾部插入一个新节点 6
ListNode p = head;
// 先走到链表的最后一个节点
while (p.next != null) {
    p = p.next;
}
//  p 就是链表的最后一个节点
// 在 p 后面插入新节点
p.next = new ListNode(6);
// 链表变成了 0 -> 1 -> 2 -> 3 -> 4 -> 5 -> 6


// 在第 3 个节点后面插入一个新节点 66
// 先要找到前驱节点,即第 3 个节点
ListNode p = head;
for (int i = 0; i < 2; i++) {
    p = p.next;
}
// 此时 p 指向第 3 个节点
// 组装新节点的后驱指针
ListNode newNode = new ListNode(66);
newNode.next = p.next;
// 插入新节点
p.next = newNode;
// 链表变成了  0 -> 1 -> 2 -> 3 -> 66 -> 4 -> 5 -> 6

删除:

分别为:删除首结点、尾结点、中间任意结点

// 创建一条单链表
ListNode head = createLinkedList(new int[]{1, 2, 3, 4, 5});

// 删除头结点
head = head.next;
// 链表变成了 2 -> 3 -> 4 -> 5


// 删除尾节点
ListNode p = head;
// 找到倒数第二个节点
while (p.next.next != null) {
    p = p.next;
}
// 此时 p 指向倒数第二个节点
// 把尾节点从链表中摘除
p.next = null;
// 现在链表变成了 1 -> 2 -> 3 -> 4


// 删除第 4 个节点,要操作前驱节点
ListNode p = head;
for (int i = 0; i < 2; i++) {
    p = p.next;
}
// 此时 p 指向第 3 个节点,即要删除节点的前驱节点
// 把第 4 个节点从链表中摘除
p.next = p.next.next;
// 现在链表变成了 1 -> 2 -> 3 -> 5

双链表:

定义:
class DoublyListNode {
    int val;
    DoublyListNode next, prev;
    DoublyListNode(int x) { val = x; }
}

DoublyListNode createDoublyLinkedList(int[] arr) {
    if (arr == null || arr.length == 0) {
        return null;
    }
    DoublyListNode head = new DoublyListNode(arr[0]);
    DoublyListNode cur = head;
    // for 循环迭代创建双链表
    for (int i = 1; i < arr.length; i++) {
        DoublyListNode newNode = new DoublyListNode(arr[i]);
        cur.next = newNode;
        newNode.prev = cur;
        cur = cur.next;
    }
    return head;
}
查找/修改:
// 创建一条双链表
DoublyListNode head = createDoublyLinkedList(new int[]{1, 2, 3, 4, 5});

// 从头遍历双链表
for (DoublyListNode p = head; p != null; p = p.next) {
    //进行操作
    System.out.println(p.val);
}

// 从尾遍历双链表(假设我们有尾节点的引用 tail)
for (DoublyListNode p = tail; p != null; p = p.prev) {
    //进行操作
    System.out.println(p.val);
}
增加:

头插法、尾插法、中间插入

// 创建一条双链表
DoublyListNode head = createDoublyLinkedList(new int[]{1, 2, 3, 4, 5});
// 在双链表头部插入新节点 0
DoublyListNode newHead = new DoublyListNode(0);
newHead.next = head;
head.prev = newHead;
head = newHead;
// 现在链表变成了 0 -> 1 -> 2 -> 3 -> 4 -> 5


// 创建一条双链表
DoublyListNode head = createDoublyLinkedList(new int[]{1, 2, 3, 4, 5});
DoublyListNode tail = head;
// 先走到链表的最后一个节点
while (tail.next != null) {
    tail = tail.next;
}
// 在双链表尾部插入新节点 6
DoublyListNode newNode = new DoublyListNode(6);
tail.next = newNode;
newNode.prev = tail;
// 更新尾节点引用
tail = newNode;
// 现在链表变成了 1 -> 2 -> 3 -> 4 -> 5 -> 6



// 创建一条双链表
DoublyListNode head = createDoublyLinkedList(new int[]{1, 2, 3, 4, 5});
// 在第 3 个节点后面插入新节点 66
// 找到第 3 个节点
DoublyListNode p = head;
for (int i = 0; i < 2; i++) {
    p = p.next;
}
// 组装新节点
DoublyListNode newNode = new DoublyListNode(66);
newNode.next = p.next;
newNode.prev = p;
// 插入新节点
p.next.prev = newNode;
p.next = newNode;
// 现在链表变成了 1 -> 2 -> 3 -> 66 -> 4 -> 5


删除:
// 创建一条双链表
DoublyListNode head = createDoublyLinkedList(new int[]{1, 2, 3, 4, 5});
// 删除头结点
DoublyListNode toDelete = head;
head = head.next;
head.prev = null;
// 清理已删除节点的指针
toDelete.next = null;
// 现在链表变成了 2 -> 3 -> 4 -> 5


// 创建一条双链表
DoublyListNode head = createDoublyLinkedList(new int[]{1, 2, 3, 4, 5});
// 删除尾节点
DoublyListNode p = head;
// 找到尾结点
while (p.next != null) {
    p = p.next;
}
// 现在 p 指向尾节点
// 把尾节点从链表中摘除
p.prev.next = null;
// 把被删结点的指针都断开是个好习惯(可选)
p.prev = null;
// 现在链表变成了 1 -> 2 -> 3 -> 4


// 创建一条双链表
DoublyListNode head = createDoublyLinkedList(new int[]{1, 2, 3, 4, 5});
// 删除第 4 个节点
// 先找到第 3 个节点
DoublyListNode p = head;
for (int i = 0; i < 2; i++) {
    p = p.next;
}
// 现在 p 指向第 3 个节点,我们它后面那个节点摘除出去
DoublyListNode toDelete = p.next;
// 把 toDelete 从链表中摘除
p.next = toDelete.next;
toDelete.next.prev = p;
// 把 toDelete 的前后指针都置为 null 是个好习惯(可选)
toDelete.next = null;
toDelete.prev = null;
// 现在链表变成了 1 -> 2 -> 3 -> 5

虚拟头结点

使用虚拟头结点有以下几个主要的好处:

  1. 简化边界条件处理:当合并两个链表时,如果其中一个链表为空,需要特殊处理头结点的情况。使用虚拟头结点可以避免这种情况,统一处理所有节点的添加。
  2. 减少代码量和复杂度:虚拟头结点可以帮助我们简化代码逻辑,减少需要处理的边界条件。这样可以得到更加简洁、易读的代码。
  3. 统一操作:使用虚拟头结点,我们可以将所有节点的添加操作统一起来,不需要单独处理头结点。这样可以提高代码的可读性和可维护性。
  4. 返回结果更方便:使用虚拟头结点,我们最终返回的就是合并后链表的真实头结点,即 dummy.next。这比直接返回头结点更加简单。
  5. 适用于各种链表操作:虚拟头结点技巧不仅适用于合并两个有序链表,在其他链表操作中如反转链表、删除节点等也都能发挥作用。

力扣第 21 题「合并两个有序链表

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

/**
 * 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 mergeTwoLists(ListNode list1, ListNode list2) {
        ListNode res = new ListNode(-1), p = res;
        ListNode p1 = list1, p2 = list2;
        while (p1 != null && p2 != null) {
            if (p1.val > p2.val) {
                p.next = p2;
                p2 = p2.next;
            } else {
                p.next = p1;
                p1 = p1.next;
            }
            p = p.next;
        }
        if (p1 != null) {
            p.next = p1;
        }
        if (p2 != null) {
            p.next = p2;
        }
        return res.next;
    }
}
//runtime:0 ms
//memory:40.7 MB

双指针

力扣第 19 题「删除链表的倒数第 N 个结点

给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。(定义两个指针,让两个指针间位置相距n)

//leetcode submit region begin(Prohibit modification and deletion)

/**
 * 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 removeNthFromEnd(ListNode head, int n) {
        ListNode xn = new ListNode(-1);
        xn.next = head;
        ListNode p = xn;

        //实际操作的是xn链表,而非head链表
        ListNode p1 = xn;
        //注意循环终止位置
        for (int i = 0; i < n + 1; i++) {
            xn = xn.next;
        }
        while (xn != null) {
            xn = xn.next;
            p = p.next;
        }
        p.next = p.next.next;

        //返回时也要返回实际操作链表的引用
        return p1.next;
    }
}
//leetcode submit region end(Prohibit modification and deletion)

快慢指针

力扣第 876 题「链表的中间结点

给你单链表的头结点 head ,请你找出并返回链表的中间结点。

如果有两个中间结点,则返回第二个中间结点。

/**
 * 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 middleNode(ListNode head) {
        if(head == null) return null;
        ListNode k,m;
        //初始起点一样,进入判断后开始走,k要快两步
        m = head;
        k = head;
        while(k!=null && k.next!=null){
            m = m.next;
            k = k.next.next;
        }
        return m;

    }
}

//runtime:0 ms
//memory:40.3 MB

链表互补

力扣第 160 题「相交链表

给你两个单链表的头节点 headAheadB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null

//leetcode submit region begin(Prohibit modification and deletion)

/**
 * Definition for singly-linked list.
 * public class ListNode {
 * int val;
 * ListNode next;
 * ListNode(int x) {
 * val = x;
 * next = null;
 * }
 * }
 */
public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        if (headA == null || headB == null) {
            return null;
        }

        ListNode a = headA;
        ListNode b = headB;

        //当两个指针相等时(要么两个节点相等,要么都走到拼接后的链表末尾)
        while (a != b) {
            a = (a != null) ? a.next : headB;
            b = (b != null) ? b.next : headA;
        }
        return a;
    }
}
//leetcode submit region end(Prohibit modification and deletion)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值