Java 数据结构与算法之链表

作为一种基本数据结构,链表(Linked List)是一种物理存储单元上非连续的存储结构,元素的逻辑顺序是通过链表的指针来实现的。这种存储方式使链表具备插入数据复杂度 O(1) 查找复杂度 O(n) 的特性。链表一般分为三种:单向链表、双向链表、循环链表。

一、单向链表

单向链表是一种线性表,由节点(ListNode)组成,节点是由存储的对象(object)以及对下一个节点的引用(next)组成

package LinkedList;

public class ListNode {
    int val;
    ListNode next;

    ListNode(int x) {
        val = x;
    }
}

 区别于数组,我们无法直接访问链表里的元素,必须通过遍历来访问,复杂度是 O(n) 。

  • 插入:在链表尾部插入元素 O(n),在链表头部插入元素 O(1)。
    // insert 在链表尾部插入元素
    public ListNode insertListNode(ListNode head, int value) {
        ListNode target = new ListNode(value);
        if (null == head) {
            return target;
        }
        ListNode cur = head;
        while (cur.next != null) {
            cur = cur.next;
        }
        cur.next = target;
        return head;
    }

    // insert 在链表头部插入元素
    public ListNode insertHeadListNode(ListNode head, int value) {
        ListNode target = new ListNode(value);
        target.next=head;
        return target;
    }
  • 删除: 删除指定下标的元素 复杂度 O(n)   pre.next = cur.next 
    // delete
    public ListNode deleteListNode(ListNode head, int index) {
        if (index < 0) {
            return head;
        }
        ListNode temp = new ListNode(-1);
        temp.next = head;
        ListNode pre = temp;
        ListNode cur = head;
        int i = 0;
        while (cur != null) {
            if (i == index) {
                pre.next = cur.next;
                return temp.next;
            }
            pre = cur;
            cur = cur.next;
            i++;
        }
        return temp.next;
    }
  •  反转链表
    // Reverse
    public ListNode reverseListNode(ListNode head) {
        ListNode pre = null;
        ListNode cur = head;
        // 维护 pre  cur
        while (cur != null) {
            // 临时变量next
            ListNode next = cur.next;
            // 更新cur pre
            cur.next = pre;
            pre = cur;
            cur = next;
        }
        return pre;
    }
  • 在有序链表中删除重复元素:

83. Remove Duplicates from Sorted List 题解

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        }
        ListNode pre = head;
        ListNode cur = head.next;
        while (cur != null) {
            if (cur.val == pre.val) {
                pre.next = cur.next;
            } else {
                pre = cur;
            }
            cur = cur.next;
        }
        return head;
    }
}
  • 递归从尾到头输出链表:
    public void printReverse(ListNode head) {
        if (null != head) {
            printReverse(head.next);
            System.out.println(head.val);
        }
    }

Middle of the Linked List 求中间节点开始的链表,题解:慢指针移动一步,快指针移动两步。 

    class Solution {
        public ListNode middleNode(ListNode head) {
            ListNode slowhead = head;
            ListNode fasthead = head;
            while (fasthead.next != null && fasthead.next.next != null) {
                slowhead = slowhead.next;
                fasthead = fasthead.next.next;
            }
            if (fasthead.next == null)
                return slowhead;
            else {
                return slowhead.next;
            }
        }
    }

Linked List Cycle 判断链表是否有环,题解:慢指针移动一步,快指针移动两步 

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public boolean hasCycle(ListNode head) {
        ListNode slow = head;
        ListNode fast = head;
        while (fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
            if (slow == fast) {
                return true;
            }
        }
        return false;
    }
}

 类似的求倒数第k个元素,维护 slow 和 fast 两个指针,fast 比 slow 快 k 步,当 fast 到达尾部时,slow即为所求值

Linked List Cycle II 判断是否有环,如果有返回起点,如果没有返回null 题解:

    public ListNode detectCycle(ListNode head) {
        if (head == null || head.next == null) {
            return null;
        }
        ListNode slow = head;
        ListNode fast = head;
        while (fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
            // 如果有相等的,表示此链表由环
            if (slow == fast) {
                break;
            }
        }
        // 从起点head 和 第一次相遇点 同时向前走最终相遇于入口
        while (head != null && slow != null) {
            if (head == slow) {
                return slow;
            }
            head = head.next;
            slow = slow.next;
        }
        return null;
    }
    public ListNode sortList(ListNode head) {
        if (head == null) {
            return null;
        }
        ListNode cur = head;
        while (cur.next != null) {
            ListNode next = cur.next;
            while (next != null) {
                if (cur.val > next.val) {
                    int tmp = cur.val;
                    cur.val = next.val;
                    next.val = tmp;
                }
                next = next.next;
            }
            cur = cur.next;
        }
        return head;
    }
}

Reverse Nodes in k-Group 题解

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode reverseKGroup(ListNode head, int k) {
        if (k <= 1) {
            return head;
        }
        int count = 0;
        ListNode res = head;
        ListNode tmp = null;
        ListNode pre = tmp;
        while (head != null) {
            if (count == 0) {
                tmp = head;
            }
            count++;
            head = head.next;
            if (count == k) {
                count = 0;
                ListNode temp = reverse(tmp, k);
                if (pre == null) {
                    res = temp;
                } else {
                    pre.next = temp;
                }
                pre = tmp;
                // 第一个head作为头结点
            }
        }
        if (null != pre && null != tmp&&pre!=tmp) {
            pre.next = tmp;
        }
        return res;
    }

    private static ListNode reverse(ListNode head, int k) {
        ListNode pre = null;
        ListNode cur = head;
        if (head.next == null) {
            return head;
        }
        ListNode next = head.next;

        int count = 0;
        while (cur != null && count < k) {
            cur.next = pre;
            pre = cur;
            cur = next;
            if (cur == null) {
                break;
            }
            next = next.next;
            count++;
        }
        return pre;
    }
}

Merge k Sorted Lists 题解

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        Queue<ListNode> priorityQueue=new PriorityQueue<>((a,b)->{return a.val-b.val;});
        for (ListNode head:lists){
            while (head != null) {
                priorityQueue.offer(head);
                head=head.next;
            }
        }
        ListNode res=new ListNode(-1);
        ListNode pre=res;
        while (!priorityQueue.isEmpty()){
            ListNode cur=priorityQueue.poll();
            pre.next=cur;
            cur.next=null;
            pre=pre.next;
        }
        return res.next;
    }
}

总结

  • 调用节点前,一定要判断节点是否为空
  • 快慢指针时,判断条件为 while (fast != null && fast.next != null),检查 fast 和 fast.next 不为空
  • 快慢指针可以找环,可以找中间点,可以找相差k节点的链表
  • 回文链表:需要找到中点节点,反转半段,在遍历对比

双端链表:在链表内添加了对链表尾部的引用,方便操作尾部元素,可以用来实现队列

二、双向链表

双向链表,由节点(ListNode)组成,节点是由存储的对象(object),指向下一个节点的引用(next)以及指向上一个节点的引用(pre),所以双向链表可以从两个方向进行遍历。

package LinkedList;

public class DoubleEndListNode {
    int val;
    DoubleEndListNode next;
    DoubleEndListNode prev;

    DoubleEndListNode(int x) {
        val = x;
    }
}

三、循环链表

循环链表指的是在单向链表和双向链表的基础上,将最后一个节点指向头结点,实现循环链表。

还有很多经典例子,不断更新中

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值