链表算法操作5大经典集合

链表反转

定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。

示例:

输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/fan-zhuan-lian-biao-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

解题

  1. 直接将链表看成两部分,一部分是已经反转的,一部分是待反转的。

    可以如下:

    分割开,每次需要使用三个指针,一个是已经反转的部分,一个是待反转部分,一个是待反转部分的下一个。

    代码如下:

    public ListNode reverseList(ListNode head) {
            ListNode pre = null, cur = head;
    
            while (cur != null) {
                ListNode tmp = cur.next;
    
                cur.next = pre;
                pre = cur;
                cur = tmp;
            }
    
            return pre;
        }
    
  2. 递归解法

    遍历到链表尾部,再将尾结点的下一个指向前一个结点即可。

    这个不好画图,简单说明如下:

    先找到链表尾。

    由于一般会在head.next为空结束,说以当前节点会是head,那么我们有了当前节点,以及当前节点后的结点,只需要设置next结点即可。

    public ListNode reverseList2(ListNode head) {
            if (head == null || head.next == null) {
                return head;
            }
    
            ListNode resHead = reverseList2(head.next);
            head.next.next = head;
            head.next = null;
    
            return resHead;
        }
    

合并有序链表

输入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是递增排序的。

示例1:

输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/he-bing-liang-ge-pai-xu-de-lian-biao-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

解法

用伪头结点,将小元素链接到后面,最后返回伪头结点的next即可。

public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        ListNode resHead = new ListNode();
        ListNode res = resHead;

        ListNode p1 = l1, p2 = l2;

        while (p1 != null &&
                p2 != null) {

            if (p1.val <= p2.val) {
                res.next = p1;
                p1 = p1.next;
            } else {
                res.next = p2;
                p2 = p2.next;
            }

            res = res.next;
        }

        res.next = p1 != null?
                    p1:
                    p2;

        return resHead.next;
    }

链表的中间结点

给定一个带有头结点 head 的非空单链表,返回链表的中间结点。

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

示例 1:

输入:[1,2,3,4,5]
输出:此列表中的结点 3 (序列化形式:[3,4,5])
示例 2:

输入:[1,2,3,4,5,6]
输出:此列表中的结点 4 (序列化形式:[4,5,6])
由于该列表有两个中间结点,值分别为 3 和 4,我们返回第二个结点。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/middle-of-the-linked-list
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

解法

  1. 快慢指针,快指针到达尾部,返回慢指针对应的值即可。

    T:n。S:1;

    public ListNode middleNode(ListNode head) {
            if (head == null || head.next == null) {
            return head;
            }
    
            ListNode slow = head, fast = head.next;
    
        while (fast != null) {
                slow = slow.next;
    
                fast = fast.next == null?
                        null:
                    fast.next.next;
            }
    
            return slow;
        }
    
  2. 用数组存储,直接用数据的中点索引即可。

    T:n;S:n

    if (head == null || head.next == null) {
                return head;
            }
    
            ListNode[] listNodes = new ListNode[100];
            int i = 0;
    
            ListNode p = head;
            while (p != null) {
                listNodes[i] = p;
                p = p.next;
                i++;
            }
    
            return (i & 1) == 1 ?
                    listNodes[i / 2] :
                    listNodes[i / 2 - 1];
    
  3. 单指针。扫描一遍得到链表长,再扫描一遍取中点。

    T:n。S:1

    public ListNode middleNode3(ListNode head) {
            int len = 0;
            ListNode p1 = head, p2 = head;
    
            while (p1 != null) {
                p1 = p1.next;
                len ++;
            }
    
            int i = len / 2;
            while (i-- >= 0) {
                p2 = p2.next;
            }
    
            return p2;
        }
    

LRU

设计和构建一个“最近最少使用”缓存,该缓存会删除最近最少使用的项目。缓存应该从键映射到值(允许你插入和检索特定键对应的值),并在初始化时指定最大容量。当缓存被填满时,它应该删除最近最少使用的项目。

它应该支持以下操作: 获取数据 get 和 写入数据 put 。

获取数据 get(key) - 如果密钥 (key) 存在于缓存中,则获取密钥的值(总是正数),否则返回 -1。
写入数据 put(key, value) - 如果密钥不存在,则写入其数据值。当缓存容量达到上限时,它应该在写入新数据之前删除最近最少使用的数据值,从而为新的数据值留出空间。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/lru-cache-lcci
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

解法

使用hashmap + 双向链表结点实现,再使用两个哨兵头尾结点简化编程。

public class LruHashMap {
    private Map<Integer, BANode> map;
    private int capacity;
    private int size;
    private BANode head;
    private BANode tail;

    public LruHashMap(int capacity) {
        this.capacity = capacity;
    }

    public void put(int key, int value) {
        if (isEmpty()) {
            init();
        }

        BANode n = map.get(key);

        // 有值则修改,放到头部
        if (n != null) {
            n.value = value;
            afterAccess(n);
        } else {
            // 无值则新建,放到头部
            n = new BANode(key, value);

            map.put(key, n);
            moveToHead(n);
            size++;

            // 判断是否超出容量
            afterInsertion();
        }
    }

    private void afterInsertion() {
        if (size > capacity) {
            BANode pre = tail.before;
            BANode realPre = tail.before.before;

            map.remove(tail.before.key);
            
            tail.before = realPre;
            realPre.after = tail;

            pre.after = null;
            pre.before = null;
            size--;
        }
    }

    public int get(int key) {
        if (isEmpty()) {
            return -1;
        }
        
        BANode n = map.get(key);
        if (n == null) {
            return -1;
        }

        afterAccess(n);
        return n.value;
    }

    /**
    * 被访问之后,将其前后结点连接起来,将被访问结点移至头部
    */
    private void afterAccess(BANode n) {
        BANode af = n.after;
        BANode be = n.before;

        af.before = be;
        be.after = af;

        n.before = null;
        n.after = null;

        moveToHead(n);
    }

    private void moveToHead(BANode n) {
        head.after.before = n;
        n.after = head.after;
        head.after = n;
        n.before = head;
    }

    private boolean isEmpty() {
        return size == 0;
    }
    
    private void init() {
        map = new HashMap<>();
        head = new BANode();
        tail = new BANode();
        head.after = tail;
        tail.before = head;
    }
}

链表环判断

给定一个链表,判断链表中是否有环。

为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。

示例 1:

输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/linked-list-cycle
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

解法

快慢指针,当快指针等于慢指针,表明有环

public boolean hasCycle(ListNode head) {
        if (head == null || head.next == null) {
            return false;
        }

        ListNode slow = head, fast = head.next;

        while (slow != fast) {
            if (fast == null || fast.next== null) {
                return false;
            }

            slow = slow.next;
            fast = fast.next.next;
        }

        return true;
    }

链表环长度

这个没找到题目,我自己验证了几个示例,如果有问题还请指出。

也是快慢指针的思路,记录第一次相遇时的计数器,再记录第二次的,相减就是环长。(因为可能会很凑巧直接碰到,所以没有办法通过一次的碰面确定环长。)

public static int getCircleLen(Node head) {
        if (head == null || head.next == null) {
            return 0;
        }

        Node slow = head, fast = head.next;
        int firstMeet = -1;
        int count = 0;

        while (firstMeet == -1 || slow != fast) {
            if (fast == null || fast.next == null) {
                return 0;
            }

            if (slow == fast) {
                firstMeet = count;
            }

            count++;
            slow = slow.next;
            fast = fast.next.next;
        }

        return count - firstMeet;
    }

猜你喜欢

转载自blog.csdn.net/qq_42254247/article/details/107740897