【数据结构-2】单向链表

学习目标:

掌握单向链表


学习内容:

  1. 单向链表
  2. 带哨兵的单向链表
  3. 力扣相关练习

链表

链表是一种物理存储上非连续,数据元素的逻辑顺序通过链表中的指针链接次序,实现的一种线性存储结构。


单向链表

1 定义链表

public class SinglyLinkList {
    private Node head = null; //头指针

    private static class Node {
        int value; //值
        Node next; //下一个节点

        public Node(int value, Node next) {
            this.value = value;
            this.next = next;
        }
    }
}

2 头插元素

    public void addFirst(int value) {
        head = new Node(value, head);
    }

3 尾插元素

	//查找链表最后一个元素
	 private Node findLast() {
	       if (head == null)//空链表
	           return null;
	
	       Node p = head;
	       while (p.next != null) {
	           p = p.next;
	       }
	       return p;
	   }

    public void addLast(int value){
        Node last = findLast();

        if(last == null){ //空链表直接头插
            addFirst(value);
            return;
        }

        last.next = new Node(value,null);
    }

4 循环遍历

4.1 基于while遍历

    //基于while
    public void loop(Consumer<Integer> consumer) {
        Node p = head;
        while (p != null) {
            consumer.accept(p.value);
            p = p.next;
        }
    }

4.2基于for遍历

    //基于for
    public void loop2(Consumer<Integer> consumer) {
        for (Node p = head; p != null; p = p.next) {
            consumer.accept(p.value);
        }
    }

4.3 基于iterator遍历

    //基于iterator
    @Override
    public Iterator<Integer> iterator() {
        return new Iterator<Integer>() {
            Node p = head;
            @Override
            public boolean hasNext() {
                return p!=null;
            }

            @Override
            public Integer next() {
                int value = p.value;
                p = p.next;
                return value;
            }
        };
    }

4.4 递归遍历

    public void loop(Node node) {
        recursion(head);
    }

    private void recursion(Node node) {
        if (node == null) return;
        System.out.println(node.value);
        recursion(node.next);
    }

5 根据索引查询元素

    private Node findNode(int index) {
        int i = 0;
        for (Node p = head; p != null; p = p.next, i++) {
            if (i == index) {
                return p;
            }
        }
        return null;
    }
    
    public int get(int index) {
        Node node = findNode(index);
        if (node == null) {
            throw new IllegalArgumentException(String.format("index [%d]", index));
        }
        return node.value;
    }

6 根据索引位置插入元素

    public void insert(int index, int value) {
        if (index == 0) addFirst(value);
        Node prev = findNode(index - 1); //找上一个节点
        if (prev == null) { //找不到上一个节点
            throw new IllegalArgumentException(String.format("index [%d] 不合法", index));
        }
        prev.next = new Node(value, prev.next);
    }

7 删除第一个元素

    public void removeFirst(){
        if(head==null){
            throw new IllegalArgumentException(String.format("index 不合法"));
        }
        head = head.next;
    }

8 按索引删除元素

    public void remove(int index) {
        if (index == 0) removeFirst();
        Node prev = findNode(index - 1); //上一个节点
        if (prev == null) {
            throw new IllegalArgumentException(String.format("index [%d] 不合法", index));
        }
        Node removed = prev.next;
        if (removed == null) {
            throw new IllegalArgumentException(String.format("index [%d] 不合法", index));
        }
        prev.next = removed.next;
    }

优化单向链表-哨兵节点

在基础的实现方法中需要频繁地判断节点是否为空,添加哨兵节点后,链表中至少会有一个哨兵节点,因此可以省去许多判空操作。

1 定义链表

需要添加一个哨兵节点,节点的value无所谓,head要指向哨兵节点。

public class SinglyLinkList {
    private Node head = new Node(666,null); //头指针指向哨兵节点

    private static class Node {
        int value; //值
        Node next; //下一个节点

        public Node(int value, Node next) {
            this.value = value;
            this.next = next;
        }
    }
}

2 头插元素

    public void addFirst(int value) {
        insert(0,value);
    }

3 尾插元素

	//查找链表最后一个元素
	 private Node findLast() {
	       Node p = head;
	       while (p.next != null) {
	           p = p.next;
	       }
	       return p;
	   }

    public void addLast(int value){
        Node last = findLast();
        last.next = new Node(value,null);
    }

4 循环遍历

添加哨兵节点后,遍历起点要从head变为head.next,即哨兵节点。

4.1 基于while遍历

    //基于while
    public void loop(Consumer<Integer> consumer) {
        Node p = head.next;
        while (p != null) {
            consumer.accept(p.value);
            p = p.next;
        }
    }

4.2 基于for遍历

    //基于for
    public void loop2(Consumer<Integer> consumer) {
        for (Node p = head.next; p != null; p = p.next) {
            consumer.accept(p.value);
        }
    }

4.3 基于iterator遍历

    //基于iterator
    @Override
    public Iterator<Integer> iterator() {
        return new Iterator<Integer>() {
            Node p = head.next;
            @Override
            public boolean hasNext() {
                return p!=null;
            }

            @Override
            public Integer next() {
                int value = p.value;
                p = p.next;
                return value;
            }
        };
    }

5 根据索引查询元素

    private Node findNode(int index) {
        int i = -1;
        for (Node p = head; p != null; p = p.next, i++) {
            if (i == index) {
                return p;
            }
        }
        return null;
    }
    
    public int get(int index) {
        Node node = findNode(index);
        if (node == null) {
            throw new IllegalArgumentException(String.format("index [%d]", index));
        }
        return node.value;
    }

6 根据索引位置插入元素

    public void insert(int index, int value) {
        Node prev = findNode(index - 1); //找上一个节点
        if (prev == null) { //找不到上一个节点
            throw new IllegalArgumentException(String.format("index [%d] 不合法", index));
        }
        prev.next = new Node(value, prev.next);
    }

7 删除第一个元素

    public void removeFirst(){
        remove(0);
    }

8 按索引删除元素

    public void remove(int index) {
        Node prev = findNode(index - 1); //上一个节点
        if (prev == null) {
            throw new IllegalArgumentException(String.format("index [%d] 不合法", index));
        }
        Node removed = prev.next;
        if (removed == null) {
            throw new IllegalArgumentException(String.format("index [%d] 不合法", index));
        }
        prev.next = removed.next;
    }

力扣链表相关练习

1.反转单向链表

解法一:新建链表逐个存取

    public static ListNode reverseList1(ListNode o1) {
        ListNode n1 = null;
        ListNode p = o1;
        while (p != null) {
            n1 = new ListNode(p.val, n1);
            p = p.next;
        }
        return n1;
    }

解法二:递归改节点的next

思路:首先找到最后一个节点,接着在退出递归的过程中依次完成相邻节点逆序

 public static ListNode reverseList2(ListNode p) {
        if (p == null) {
            return null;
        }
        
        //找到最后一个节点
        if (p.next == null) {
            return p;
        }
        ListNode last = reverseList2(p.next);

        //和其下一个节点相邻节点逆序
        p.next.next = p;
        p.next = null;
        return last;
    }

解法三:

思路:逐渐将旧链表的第二个元素头插

    public static ListNode reverseList3(ListNode o1) {
        if (o1 == null || o1.next == null) return o1;
        ListNode n1 = o1;
        while (o1.next != null) {
            ListNode p = o1.next;
            o1.next = p.next;
            p.next = n1;
            n1 = p;
        }
        return n1;
    }

解法四:

思路:不断将旧链表头部头插到新链表

    public static ListNode reverseList4(ListNode o1){
        ListNode n1 = null;
        while(o1!=null){
            ListNode o2 = o1.next;
            o1.next = n1;
            n1 = o1;
            o1 = o2;
        }
        return n1;
    }

2.根据值删除链表节点

解法一:遍历

    public static ListNode removeElements(ListNode head, int val) {
        //哨兵节点
        ListNode s = new ListNode(-1, head);
        ListNode p1 = s;
        ListNode p2 = s.next;
        while (p2 != null) {
            if (p2.val == val) {
                p1.next = p2.next;
                p2 = p2.next;
            } else {
                p1 = p1.next;
                p2 = p2.next;
            }
        }
        return s.next;
    }

解法二:递归

思路:

  • 如果p == val ,返回p.next递归的结果
  • 如果p != val ,返回p拼接上p.next递归的结果
    public static ListNode removeElements2(ListNode p,int val){
        if(p==null){
            return null;
        }
        if(p.val==val){
            return removeElements2(p.next,val);
        }else {
            p.next = removeElements2(p.next,val);
            return p;
        }

    }

3.删除倒数第n个节点

解法一:遍历

思路:快慢指针

    public static ListNode removeNthFromEnd(ListNode p, int n) {
        ListNode s = new ListNode(-1,p);//哨兵
        ListNode p1 = s;
        ListNode p2 = s;
        int t = n + 1; //p1,p2指针的距离
        while(t>0){
            p2 = p2.next;
            t--;
        }
        while(p2!=null){
            p2 = p2.next;
            p1 = p1.next;
        }
        p1.next = p1.next.next;

        return s.next;
    }

解法二:递归

    public static ListNode removeNthFromEnd(ListNode p, int n) {
        //哨兵用来处理删除首结点的情况
        ListNode s =new ListNode(-1,p);
        recursion(s,n);
        return s.next;
    }

    private static int recursion(ListNode p, int n) {
        if (p == null) return 0;
        int nth = recursion(p.next, n);
        if (nth == n) {
            p.next = p.next.next;
        }
        return nth + 1;
    }

4.删除有序链表的重复节点(保留重复的第一个节点)

方法1:遍历

    public static ListNode deleteDuplicates(ListNode p){
        ListNode p1 = p;
        ListNode p2 = p.next;
        while(p2!=null){
            if(p1.val==p2.val) {
                p1.next = p2.next;
                p2 = p2.next;
            }else{
                p1 = p1.next;
                p2 = p2.next;
            }
        }
        return p;
    }

方法2:递归

    public static ListNode deleteDuplicates2(ListNode p){
        ListNode s = new ListNode(-1,p);
        recursion(s);
        return s.next;
    }

    public static int recursion(ListNode p){
        if(p==null){
            return 0;
        }
        int nextValue = recursion(p.next);
        if(p.val == nextValue){
            p.next = p.next.next;
        }
        return p.val;
    }

5.有序链表去重(重复元素都不留)

方法一:遍历

    public static ListNode deleteDuplicates2(ListNode p) {
        if (p == null || p.next == null) {
            return p;
        }
        ListNode s = new ListNode(-1, p);
        ListNode p1 = s;
        ListNode p2;
        ListNode p3;
        while ((p2 = p1.next)!= null && (p3 = p2.next) != null) {
            if (p2.val == p3.val) {
                while ((p3 = p3.next) != null && p3.val == p2.val) {

                }
                p1.next = p3;
            } else {
                p1 = p1.next;
            }
        }
        return s.next;
    }

方法二:递归

    public static ListNode deleteDuplicates(ListNode p) {
        if (p == null || p.next == null) {
            return p;
        }
        if (p.val == p.next.val) {
            ListNode x = p.next.next;
            while (x != null && x.val == p.val) {//x定位到第一个不等于p.val的节点
                x = x.next;
            }
            return deleteDuplicates(x);
        } else {
            p.next = deleteDuplicates(p.next);//去重后拼接
            return p;
        }
    }

6.合并两个有序链表

解法一:遍历

    public static ListNode mergeTwoLists(ListNode p1, ListNode p2) {
        ListNode s = new ListNode(-1, null);
        ListNode q = s;
        while (p1 != null || p2 != null) {
            if (p1 == null) {
                q.next = p2;
                return s.next;
            }
            if (p2 == null) {
                q.next = p1;
                return s.next;
            }
            if (p1.val <= p2.val) {
                q.next = p1;
                p1 = p1.next;
            } else {
                q.next = p2;
                p2 = p2.next;
            }
            q = q.next;
        }
        return null;
    }

解法二:递归

    public static ListNode mergeTwoLists2(ListNode p1, ListNode p2) {
        if (p1 == null)
            return p2;
        if (p2 == null)
            return p1;
        if (p1.val < p2.val) {
            p1.next = mergeTwoLists2(p1.next, p2);
            return p1;
        } else {
            p2.next = mergeTwoLists2(p1, p2.next);
            return p2;
        }
    }

7.合并多个有序链表

利用分治法

    public static ListNode mergeTwoLists(ListNode p1, ListNode p2) {
        ListNode s = new ListNode(-1, null);
        ListNode q = s;
        while (p1 != null || p2 != null) {
            if (p1 == null) {
                q.next = p2;
                return s.next;
            }
            if (p2 == null) {
                q.next = p1;
                return s.next;
            }
            if (p1.val <= p2.val) {
                q.next = p1;
                p1 = p1.next;
            } else {
                q.next = p2;
                p2 = p2.next;
            }
            q = q.next;
        }
        return null;
    }

    public static ListNode mergeKLists(ListNode[] lists) {
        if (lists.length == 0) {
            return null;
        }
        return split(lists, 0, lists.length - 1);
    }

    //返回合并后的链表,i和j分别代表左右边界
    private static ListNode split(ListNode[] lists, int i, int j) {
        if(i==j){ //数组内只有一个链表
            return lists[i];
        }
            
        int m = (i + j) >>> 1;
        ListNode left = split(lists, i, m);
        ListNode right = split(lists, m + 1, j);
        return mergeTwoLists(left, right);
    }

8.查找链表的中间节点

    public ListNode middleNode(ListNode head) {
        ListNode p1 = head;
        ListNode p2 = head;
        while (p2 != null && p2.next != null) {
            p1 = p1.next;
            p2 = p2.next;
            p2 = p2.next;
        }
        return p1;
    }

9.判断是否是回文列表

    //判断回文链表
    /*
     * 1.找到中间节点
     * 2.中间点后半个链表反转
     * 3.反转后链表和元链表比较*/
    public static boolean isPalindrome(ListNode head) {
        ListNode middle = middleNode(head);
        ListNode newHead = reverse(middle);

        while (newHead!=null){
            if(newHead.val!=head.val){
                return false;
            }
            head = head.next;
            newHead = newHead.next;
        }
        return true;
    }

    private static ListNode reverse(ListNode o1){
        ListNode n1 = null;
        while(o1!=null){
            ListNode o2 = o1.next;
            o1.next = n1;
            n1 = o1;
            o1 = o2;
        }
        return n1;
    }

    private static ListNode middleNode(ListNode head) {
        ListNode p1 = head;
        ListNode p2 = head;
        while (p2 != null && p2.next != null) {
            p1 = p1.next;
            p2 = p2.next;
            p2 = p2.next;
        }
        return p1;
    }

找中间点和反转可以优化为同一步内完成,进一步提升效率

10. 判断链表是否有环

佛洛依德的龟与兔

第一阶段:

  • 龟走一步,兔走两步
  • 兔子走到终点则不存在环
  • 兔子追上龟则判断存在环

第二阶段:

  • 从一次相遇开始,龟回到起点,兔位置保持不变
  • 兔和龟一次都走一步
  • 再次相遇时,地点就是环的入口

第一阶段:判断是否有环

    public static boolean hasCycle(ListNode head) {
        ListNode h = head; //兔
        ListNode t = head; //龟
        while (h != null && h.next != null) {
            h = h.next.next;
            t = t.next;
            if (h == t) {
                return true;
            }
        }
        return false;
    }

第二阶段:判断环入口

    public static ListNode detectCycle(ListNode head) {
        ListNode h = head; //兔
        ListNode t = head; //龟
        while (h != null && h.next != null) {
            h = h.next.next;
            t = t.next;
            if (h == t) {
                //进入第二阶段
                t = head;
                while (true) {
                    if (t == h) {
                        return t;
                    }
                    h = h.next;
                    t = t.next;
                }
            }
        }
        return null;
    }
  • 10
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值