数据结构专练-链表

本文介绍了多种链表操作的算法,包括删除排序链表中的重复元素、判断链表环的入口节点、链表排序、链表中两数相加及合并两个排序链表。此外,还探讨了复杂链表的复制方法。这些算法均具有高效的时间复杂度和空间复杂度。
摘要由CSDN通过智能技术生成

82. 删除排序链表中的重复元素 II

给定一个已排序的链表的头 head , 删除原始链表中所有重复数字的节点,只留下不同的数字 。返回 已排序的链表 。

class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        if (head == null) {
            return head;
        }
        ListNode dummy = new ListNode(0, head);
        ListNode cur = dummy;

        // 比较的是: cur.next.val 和 cur.next.next.val 的值
        while (cur.next != null && cur.next.next != null) {
            if (cur.next.val == cur.next.next.val) {
                int x = cur.next.val;
                while (cur.next != null && cur.next.val == x) {
                    // 跳过重复的元素
                    cur.next = cur.next.next;
                }
            } else {
                cur = cur.next;
            }
        }
        return dummy.next;
    }
}
  • 时间:O(n)
  • 空间:O(1)

剑指 Offer II 022. 求链表中环的入口节点(判断链表中是否有环)

给定一个链表,返回链表开始入环的第一个节点。 从链表的头节点开始沿着 next 指针进入环的第一个节点为环的入口节点。如果链表无环,则返回 null。

为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意,pos 仅仅是用于标识环的情况,并不会作为参数传递到函数中。

说明:不允许修改给定的链表。

思路:当发现 slow 与 fast 相遇时,我们再额外使用一个指针 ptr。起始,它指向链表头部;随后,它和 slow 每次向后移动一个位置。最终,它们会在入环点相遇。

具体结论的得出过程见:https://leetcode-cn.com/problems/c32eOV/solution/lian-biao-zhong-huan-de-ru-kou-jie-dian-vvofe/

public class Solution01 {
    // 判断链表中是否有环
    public boolean hasCycle(ListNode head) {
        if (head == null) {
            return false;
        }
        ListNode slow = head;
        ListNode fast = head.next;
        while (fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
            if (fast == slow) {
                return true;
            }
        }
        return false;
    }
}

public class Solution {
    // 有环,并且找到入环的节点
    public ListNode detectCycle(ListNode head) {
        if (head == null) {
            return null;
        }
        ListNode slow = head, fast = head;
        while (fast != null) {
            slow = slow.next;
            if (fast.next != null) {
                fast = fast.next.next;
            } else {
                return null;
            }
            if (slow == fast) {
                ListNode ptr = head;
                while (ptr != slow) {
                    slow = slow.next;
                    ptr = ptr.next;
                }
                return ptr;
            }
        }
        return null;
    }
}
  • 时间:O(n)
  • 空间:O(1)

剑指 Offer II 077. 链表排序

https://leetcode-cn.com/problems/7WHec2/
给定链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表 。

// 自底向上归并排序
class Solution {
    public ListNode sortList(ListNode head) {
        if (head == null) {
            return head;
        }
        // 求得 length 长度
        int length = 0;
        ListNode node = head;
        while (node != null) {
            length++;
            node = node.next;
        }
        // dummyHead ->  head
        //    prev   ->  curr
        ListNode dummyHead = new ListNode(0, head);
        for (int subLength = 1; subLength < length; subLength <<= 1) {
            ListNode prev = dummyHead, curr = dummyHead.next;
            while (curr != null) {
                ListNode head1 = curr;
                for (int i = 1; i < subLength && curr.next != null; i++) {
                    curr = curr.next;
                }
                ListNode head2 = curr.next;
                curr.next = null;
                curr = head2;
                for (int i = 1; i < subLength && curr != null && curr.next != null; i++) {
                    curr = curr.next;
                }
                ListNode next = null;
                if (curr != null) {
                    next = curr.next;
                    curr.next = null;
                }
                //合并 subLength
                ListNode merged = merge(head1, head2);
                prev.next = merged;
                while (prev.next != null) {
                    prev = prev.next;
                }
                curr = next;
            }
        }
        return dummyHead.next;
    }

    public ListNode merge(ListNode head1, ListNode head2) {
        ListNode dummyHead = new ListNode(0);
        ListNode temp = dummyHead, temp1 = head1, temp2 = head2;
        while (temp1 != null && temp2 != null) {
            if (temp1.val <= temp2.val) {
                temp.next = temp1;
                temp1 = temp1.next;
            } else {
                temp.next = temp2;
                temp2 = temp2.next;
            }
            temp = temp.next;
        }
        if (temp1 != null) {
            temp.next = temp1;
        } else if (temp2 != null) {
            temp.next = temp2;
        }
        return dummyHead.next;
    }
}
  • 时间:O(NlogN)
  • 空间:O(1)

剑指 Offer II 025. 链表中的两数相加

使用栈或者逆置链表,依次相加

class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        Stack<Integer> stack1 = new Stack<>();
        Stack<Integer> stack2 = new Stack<>();
        ListNode curr01 = l1;
        while (curr01 != null) {
            stack1.push(curr01.val);
            curr01 = curr01.next;
        }
        ListNode curr02 = l2;
        while (curr02 != null) {
            stack2.push(curr02.val);
            curr02 = curr02.next;
        }
        int carry = 0;
        ListNode newHead = new ListNode(-1);
        ListNode headNext = null;
        // 这里判断 carry != 0 是因为 可能存在 [5] [6] 这种情况
        while (!stack1.isEmpty() || !stack2.isEmpty() || carry != 0) {
            int a = stack1.isEmpty() ? 0 : stack1.pop();
            int b = stack2.isEmpty() ? 0 : stack2.pop();
            int curr = a + b + carry;
            // 一种简洁写法:
            // carry = cur / 10;
            // cur %= 10;
            // 然后直接 new ListNode && 头插即可
            if (curr >= 10) {
                headNext = new ListNode(curr % 10);
                carry = 1;
            } else {
                headNext = new ListNode(curr);
                carry = 0;
            }
            // 头插
            headNext.next = newHead.next;
            newHead.next = headNext;
        }
        return newHead.next;
    }
}
  • 时间:O(max(m+n))
  • 空间:O(m+n)

剑指 Offer 25. 合并两个排序的链表

class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        if (l1 == null && l2 != null) {
            return l2;
        }
        if (l1 != null && l2 == null) {
            return l1;
        }
        if (l1 == null && l2 == null) {
            return null;
        }
        ListNode newHead = new ListNode(-1);
        ListNode curr = newHead;

        ListNode curr1 = l1;
        ListNode curr2 = l2;

        while (curr1 != null && curr2 != null) {
            if (curr1.val <= curr2.val) {
                // 尾插 :即可
                curr.next = curr1;
                curr1 = curr1.next;
            } else {
                curr.next = curr2;
                curr2 = curr2.next;
            }
            curr = curr.next;
        }
        curr.next = curr1 == null ? curr2 : curr1;
        return newHead.next;
    }
}
  • 时间:O(m+n)
  • 空间:O(1)

剑指 Offer 35. 复杂链表的复制

https://leetcode-cn.com/problems/fu-za-lian-biao-de-fu-zhi-lcof/

思路:

(1)方法一:回溯 + 哈希表

class Solution {
    Map<Node, Node> cachedNode = new HashMap<Node, Node>();

    public Node copyRandomList(Node head) {
        if (head == null) {
            return null;
        }
        if (!cachedNode.containsKey(head)) {
            Node headNew = new Node(head.val);
            cachedNode.put(head, headNew);
            headNew.next = copyRandomList(head.next);
            headNew.random = copyRandomList(head.random);
        }
        return cachedNode.get(head);
    }
}
  • 时间,空间都是 O(n)

(2)利用链表特性

class Solution {
    public copyRandomList.Node copyRandomList(copyRandomList.Node head) {
        if (head == null) {
            return null;
        }
        // 对于链表 A->B->C,我们可以将其拆分为 A -> A' -> B -> B' -> C -> C'
        // 最后整理即可
        for (copyRandomList.Node node = head; node != null; node = node.next.next) {
            copyRandomList.Node nodeNew = new copyRandomList.Node(node.val);
            nodeNew.next = node.next;
            node.next = nodeNew;
        }
        for (copyRandomList.Node node = head; node != null; node = node.next.next) {
            copyRandomList.Node nodeNew = node.next;
            nodeNew.random = (node.random != null) ? node.random.next : null;
        }
        copyRandomList.Node headNew = head.next;
        for (copyRandomList.Node node = head; node != null; node = node.next) {
            copyRandomList.Node nodeNew = node.next;
            node.next = node.next.next;
            nodeNew.next = (nodeNew.next != null) ? nodeNew.next.next : null;
        }
        return headNew;
    }
}
  • 时间:O(n)
  • 空间:O(1)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值