链表算法例题

对于链表问题,返回结果为头结点时,通常需要先初始化一个预先指针 pre,该指针的下一个节点指向真正的头结点head。使用预先指针的目的在于链表初始化时无可用节点值,而且链表构造过程需要指针移动,进而会导致头指针丢失,无法返回结果。

链表总结

剑指Offer18.删除链表的节点

给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。

返回删除后的链表的头节点。


  • pre指针为要删除节点的前一个值
  • cur指针为要删除的值
class Solution {
    public ListNode deleteNode(ListNode head, int val) {

        ListNode dump=new ListNode(0);
        dump.next =head;
        ListNode pre = dump;
        ListNode cur = head;
        while(cur.val != val){
            pre = cur;
            cur=cur.next;
        }
        pre.next=cur.next;
        return dump.next;
    }
}

剑指Offer22.链表中倒数第k个节点

输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。

例如,一个链表有 6 个节点,从头节点开始,它们的值依次是 1、2、3、4、5、6。这个链表的倒数第 3 个节点是值为 4 的节点。


双指针

  • 初始化:双指针都指向头节点 head​
  • 前指针 fast先向前走 k 步
  • 双指针共同移动:每轮都向前走一步,直到fast走过链表尾部节点
class Solution {
    public ListNode getKthFromEnd(ListNode head, int k) {

        ListNode slow =head, fast = head;
        for(int i=0; i < k; i++){
            fast=fast.next;
        }
        while(fast != null){
            slow=slow.next;
            fast=fast.next;
        }
        return slow;
    }
}

删除链表的倒数第N个节点

我们弹出栈的第 n 个节点就是需要删除的节点,并且目前栈顶的节点就是待删除节点的前驱节点。

class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode dummy = new ListNode(0, head);
        Deque<ListNode> stack = new LinkedList<ListNode>();
        ListNode cur = dummy;
        while (cur != null) {
            stack.push(cur);
            cur = cur.next;
        }
        for (int i = 0; i < n; ++i) {
            stack.pop();
        }
        ListNode prev = stack.peek();
        prev.next = prev.next.next;
        ListNode ans = dummy.next;
        return ans;
    }
}

双指针

class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode dummy = new ListNode(0, head);
        ListNode first = head;
        ListNode second = dummy;
        for (int i = 0; i < n; ++i) {
            first = first.next;
        }
        while (first != null) {
            first = first.next;
            second = second.next;
        }
        second.next = second.next.next;
        ListNode ans = dummy.next;
        return ans;
    }
}

剑指Offer06.从尾到头打印链表(栈)

输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。


class Solution {
    public int[] reversePrint(ListNode head) {

        Deque<ListNode>  stack = new LinkedList<>();
        ListNode temp =head;
        while(temp != null){
            stack.push(temp);
            temp=temp.next;
        }
        int size =stack.size();
        int[] res = new int[size];
        for(int i=0; i<size; i++){
            res[i]=stack.pop().val;
        }
        return res;

    }
}

2.两数相加

给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。

请你将两个数相加,并以相同形式返回一个表示和的链表。

你可以假设除了数字 0 之外,这两个数都不会以 0 开头。


  • 将两个链表看成是相同长度的进行遍历,如果一个链表较短则在前面补 0,比如 987 + 23 = 987 + 023 = 1010
  • 答案链表处相应位置的数字为 (n1+n2+carry) mod 10
  • 进位为 (n1+n2+carry)/10
  • 如果两个链表全部遍历完毕后,进位值为 1,则在新链表最前方添加节点 1
/**
 * 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 addTwoNumbers(ListNode l1, ListNode l2) {
        ListNode pre = new ListNode(0);
        ListNode cur =pre;
        int carry =0;
        while(l1 != null || l2 != null){
            int n1 =l1==null ?0 : l1.val;//如果一个链表较短则在前面补 0
            int n2 =l2==null ?0 : l2.val;
            int sum =n1+n2+carry;

            carry = sum/10;
            sum =sum %10;
            cur.next=new ListNode(sum);
            cur =cur.next;

            if(l1!=null){
                l1=l1.next;
            }

            if(l2!=null){
                l2=l2.next;
            }
        }
        if (carry==1){
            cur.next=new ListNode(carry);
        }
        return pre.next;
    }
}

class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        ListNode head = null, tail = null;
        int carry = 0;
        while (l1 != null || l2 != null) {
            int n1 = l1 != null ? l1.val : 0;
            int n2 = l2 != null ? l2.val : 0;
            int sum = n1 + n2 + carry;
            if (head == null) {
                head = tail = new ListNode(sum % 10);
            } else {
                tail.next = new ListNode(sum % 10);
                tail = tail.next;
            }
            carry = sum / 10;
            if (l1 != null) {
                l1 = l1.next;
            }
            if (l2 != null) {
                l2 = l2.next;
            }
        }
        if (carry > 0) {
            tail.next = new ListNode(carry);
        }
        return head;
    }
}

合并两个有序链表

class Solution {
        public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
            ListNode dump = new ListNode(0);
            ListNode cur = dump;
            while (l1 != null && l2 != null) {
                if (l1.val < l2.val) {
                    cur.next = l1;
                    l1 = l1.next;
                } else {
                    cur.next = l2;
                    l2 = l2.next;
                }
                cur = cur.next;
            }
            //然后把那个不为空的链表挂到新的链表中
            cur.next = l1 == null ? l2 : l1;
            return dump.next;
        }
    }
//递归
public class Solution {
public ListNode Merge(ListNode list1,ListNode list2) {
       if(list1 == null){
           return list2;
       }
       if(list2 == null){
           return list1;
       }
       if(list1.val <= list2.val){
           list1.next = Merge(list1.next, list2);
           return list1;
       }else{
           list2.next = Merge(list1, list2.next);
           return list2;
       }       
   }
}

合并K个升序链表

给你一个链表数组,每个链表都已经按升序排列。

请你将所有链表合并到一个升序链表中,返回合并后的链表。

/**
 * 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 mergeKLists(ListNode[] lists) {
        ListNode ans =null;
        for(int i=0; i < lists.length; i++){
            ans=merge(ans,lists[i]);
        }
        return ans;
    }

    public ListNode merge(ListNode a, ListNode b){
        if(a==null || b == null){
            return  a==null? b:a;
        }
        ListNode dump =new ListNode(0);
        ListNode cur =dump;
        while(a!=null && b!= null){
            if(a.val < b.val){
                cur.next=a;
                a=a.next;
            }else{
                cur.next=b;
                b=b.next;
            }
            cur=cur.next;
        }
        cur.next=(a==null? b: a);
        return dump.next;
    }   
}

反转链表

class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode pre =null;
        ListNode cur =head;
        while(cur != null){
            ListNode next = cur.next;
            cur.next=pre;
            pre=cur;//两个指针后移
            cur=next;
        }
        //此时,pre指向头节点
        return pre;
    }
}

回文链表

//1、复制链表值到数组列表中。
//2、使用双指针法判断是否为回文。
class Solution {
    public boolean isPalindrome(ListNode head) {
        List<Integer> list = new ArrayList<>();
        ListNode cur =head;
        while(cur != null){
            list.add(cur.val);
            cur=cur.next;
        }
        int left=0;
        int right=list.size()-1;
        while(left < right){
            if(!list.get(left).equals(list.get(right))){
                return false;
            }
            left++;
            right--;
        }
        return true;
    }
}

方法er:快慢指针

  1. 找到前半部分链表的尾节点:慢指针一次走一步,快指针一次走两步,快慢指针同时出发。当快指针移动到链表的末尾时,慢指针恰好到链表的中间。通过慢指针将链表分为两部分。若链表有奇数个节点,则中间的节点应该看作是前半部分。
  2. 反转后半部分链表。
  3. 判断是否回文:当后半部分到达末尾则比较完成,可以忽略计数情况中的中间节点。
  4. 恢复链表:再反转一次恢复链表本身。
  5. 返回结果。
public boolean isPalindrome(ListNode head) {

        if (head == null) {
            return true;
        }
        ListNode fisrtEnd = endOfFirstHalf(head);
        ListNode secodHead = reverse(fisrtEnd.next);

        ListNode p1 = head;
        ListNode p2 = secodHead;

        while (p2 != null) {
            if (p1.val == p2.val) {
                p1 = p1.next;
                p2 = p2.next;
            } else {
                return false;
            }
        }
        // 还原链表并返回结果
        fisrtEnd.next = reverse(secodHead);
        return true;
    }

    public ListNode reverse(ListNode head) {
        ListNode pre = null;
        ListNode cur = head;
        while (cur != null) {
            ListNode next = cur.next;
            cur.next = pre;
            pre = cur;
            cur = next;
        }
        return pre;
    }


    public ListNode endOfFirstHalf(ListNode head) {
        ListNode slow = head;
        ListNode fast = head;
        while (fast.next != null && fast.next.next != null) {
            slow = slow.next;
            fast = fast.next.next;
        }
        return slow;
    }

环形链表

//快慢指针
//定义两个指针,一快一满。慢指针每次只移动一步,而快指针每次移动
//两步。初始时,慢指针在位置 head,而快指针在位置 head.next。这
//样一来,如果在移动的过程中,快指针反过来追上慢指针,就说明该链
//表为环形链表。否则快指针将到达链表尾部,该链表不为环形链表。
public class Solution {
    public boolean hasCycle(ListNode head) {
        if(head == null || head.next == null){
            return false;
        }
        ListNode slow = head;
        ListNode fast = head.next;
        while(slow != fast){
            if(fast == null || fast.next == null){
                return false;
            }
            slow=slow.next;
            fast=fast.next.next;
        }
        return true;
    }
}

剑指Offer52.两个链表的第一个公共节点

输入两个链表,找出它们的第一个公共节点。
在这里插入图片描述
两个链表长度分别为L1+C、L2+C, C为公共部分的长度。

  • 第一个人走了L1+C步后,回到第二个人起点走L2步;
  • 第2个人走了L2+C步后,回到第一个人起点走L1步;
  • 当两个人走的步数都为L1+L2+C时这两个家伙就相爱了
/**
     * 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) {
            ListNode A = headA;
            ListNode B = headB;
            while (A != B) {
//                if (A != null) {
//                    A = A.next;
//                } else {
//                    A = headB;
//                }
//                if (B != null) {
//                    B = B.next;
//                } else {
//                    B = headA;
//                }
                A = A != null ? A.next : headB;
                B = B != null ? B.next : headA;
            }
            return A;
        }
    }

876.链表的中间结点

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

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


获取中间元素的问题。设有两个指针 fast 和 slow,初始时指向头节点。每次移动时,fast向后走两次,slow向后走一次,直到 fast 无法向后走两次。这使得在每轮移动之后。fast 和 slow 的距离就会增加一。设链表有 n 个元素,那么最多移动 n/2 轮。当 n 为奇数时,slow 恰好指向中间结点,当 n 为 偶数时,slow 恰好指向中间两个结点的靠前一个。

在这里插入图片描述

class Solution {
    public ListNode middleNode(ListNode head) {

        ListNode slow =head;
        ListNode fast =head;
        while(fast != null && fast.next != null){
            slow=slow.next;
            fast=fast.next.next;
        }
        return slow;
    }
}

142.环形链表II

给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。


哈希表

我们遍历链表中的每个节点,并将它记录下来;一旦遇到了此前遍历过的节点,就可以判定链表中存在环。

public class Solution {
    public ListNode detectCycle(ListNode head) {
        ListNode pos = head;
        Set<ListNode> visited = new HashSet<ListNode>();
        while (pos != null) {
            if (visited.contains(pos)) {
                return pos;
            } else {
                visited.add(pos);
            }
            pos = pos.next;
        }
        return null;
    }
}

双指针

从相遇点到入环点的距离加上 n-1 圈的环长,恰好等于从链表头部到入环点的距离。

1、fast = slow ,第一次相遇

  • 设链表共有 a+b个节点,其中 链表头部到链表入口 有 a 个节点(不计链表入口节点), 链表环有 b个节点
  • fast 走的步数是slow步数的 2倍,即 f = 2s
  • fast 比 slow多走了 n 个环的长度,即 f = s + nb( 双指针都走过 a 步,然后在环内绕圈直到重合,重合时 fast 比 slow 多走 环的长度整数倍)
  • 以上两式相减得:s = nb,f=2nb,即fast和slow 指针分别走了 2n,n 个 环的周长
  • 所有 走到链表入口节点时的步数 是:k=a+nb(先走 a 步到入口节点,之后每绕 1 圈环( b步)都会再次到入口节点)。
  • slow 指针走过的步数为 nb步。因此,我们只要想办法让 slow 再走 a 步停下来,就可以到环的入口。

2、第二次相遇

  • slow指针 位置不变 ,将fast指针重新 指向链表头部节点 ;slow和fast同时每轮向前走 1 步,此时 f = 0,s = nb
  • 当 fast 指针走到f = a 步时,slow 指针走到步s = a+nb,此时 两指针重合,并同时指向链表环入口 。
public class Solution {
    public ListNode detectCycle(ListNode head) {
        ListNode fast = head, slow = head;
        while (true) {
            if (fast == null || fast.next == null) return null;
            fast = fast.next.next;
            slow = slow.next;
            if (fast == slow) break;
        }
        fast = head;
        while (slow != fast) {
            slow = slow.next;
            fast = fast.next;
        }
        return fast;
    }
}

143.重排链表

给定一个单链表 L 的头节点 head ,单链表 L 表示为:

L0 → L1 → … → Ln-1 → Ln
请将其重新排列后变为:

L0 → Ln → L1 → Ln-1 → L2 → Ln-2 → …

不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换

输入: head = [1,2,3,4]
输出: [1,4,2,3]

/**
 * 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; }
 * }
 */
public void reorderList(ListNode head) {

        if (head == null || head.next == null || head.next.next == null) {
            return;
        }
        ListNode slow = head;
        ListNode fast = head;
        while (fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
        }
        ListNode newHead = slow.next;

        newHead = reverse(newHead);
        slow.next = null;

        ListNode p1 = head;
        ListNode p2 = newHead;
        ListNode temp1 = null;
        ListNode temp2 = null;
        while (p1 != null && p2 != null) {
            temp1 = p1.next;
            temp2 = p2.next;

            p1.next = p2;
            p1 = temp1;
            p2.next = p1;
            p2 = temp2;
        }

    }

    public ListNode reverse(ListNode head) {
        ListNode pre = null;
        ListNode cur = head;

        while (cur != null) {
            ListNode next = cur.next;
            cur.next = pre;
            pre = cur;
            cur = next;
        }
        return pre;
    }

LRU

package com.example.test;

import java.util.HashMap;
import java.util.Map;

/**
 * LRUCache(int capacity) 以正整数作为容量 capacity 初始化 LRU 缓存
 * int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。
 * void put(int key, int value) 如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字-值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。
 *
 * @param
 * @param
 * @return
 */
public class LRUCache {


    private Map<Integer, DLinkedNode> cache = new HashMap<>();
    private int size;
    private int capacity;
    private DLinkedNode head, tail;


    //初始化 LRU 缓存
    public LRUCache(int capacity) {
        this.size = 0;
        this.capacity = capacity;
        //使用伪头部和伪尾部节点
        head = new DLinkedNode();
        tail = new DLinkedNode();
        head.next = tail;
        tail.pre = head;
    }

    public int get(int key) {
        DLinkedNode node = cache.get(key);
        if (node == null) {
            return -1;
        }
        moveToHead(node);
        return node.value;
    }

    public void put(int key, int value) {
        DLinkedNode node = cache.get(key);
        if (node == null) {
            DLinkedNode newNode = new DLinkedNode(key, value);
            addHead(newNode);
            cache.put(key, newNode);
            size++;
            if (size > capacity) {
                DLinkedNode tail = removeTail();
                cache.remove(tail.key);
                size--;
            }
        } else {
            node.value = value;
            moveToHead(node);
        }

    }

    //链表头部添加节点
    public void addHead(DLinkedNode node) {
        node.pre = head;
        node.next = head.next;//head.next=tail;
        head.next.pre = node;//尾节点的pre指针指向待添加节点
        head.next = node;//
    }

    private void removeNode(DLinkedNode node) {
        node.pre.next = node.next;
        node.next.pre = node.pre;
    }

    public void moveToHead(DLinkedNode node) {
        removeNode(node);
        addHead(node);
    }

    public DLinkedNode removeTail() {
        DLinkedNode res = tail.pre;
        removeNode(res);
        return res;
    }

    //创建一个双向链表
    class DLinkedNode {
        int key;
        int value;
        DLinkedNode pre;
        DLinkedNode next;

        public DLinkedNode() {

        }

        public DLinkedNode(int key, int value) {
            this.key = key;
            this.value = value;
        }
    }

}

25.K个一组翻转链表

给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。

k 是一个正整数,它的值小于或等于链表的长度。

如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。


public static ListNode reverseKGroup(ListNode head, int k) {
        if (head == null || head.next == null){
            return head;
        }
        //定义一个假的节点。
        ListNode dummy=new ListNode(0);
        //假节点的next指向head。
        // dummy->1->2->3->4->5
        dummy.next=head;
        //初始化pre和end都指向dummy。pre指每次要翻转的链表的头结点的上一个节点。end指每次要翻转的链表的尾节点
        ListNode pre=dummy;
        ListNode end=dummy;

        while(end.next!=null){
            //循环k次,找到需要翻转的链表的结尾,这里每次循环要判断end是否等于空,因为如果为空,end.next会报空指针异常。
            //dummy->1->2->3->4->5 若k为2,循环2次,end指向2
            for(int i=0;i<k&&end != null;i++){
                end=end.next;
            }
            //如果end==null,即需要翻转的链表的节点数小于k,不执行翻转。
            if(end==null){
                break;
            }
            //先记录下end.next,方便后面链接链表
            ListNode next=end.next;
            //然后断开链表
            end.next=null;
            //记录下要翻转链表的头节点
            ListNode start=pre.next;
            //翻转链表,pre.next指向翻转后的链表。1->2 变成2->1。 dummy->2->1
            pre.next=reverse(start);
            //翻转后头节点变到最后。通过.next把断开的链表重新链接。
            start.next=next;
            //将pre换成下次要翻转的链表的头结点的上一个节点。即start
            pre=start;
            //翻转结束,将end置为下次要翻转的链表的头结点的上一个节点。即start
            end=start;
        }
        return dummy.next;
    }
    //链表翻转
    // 例子:   head: 1->2->3->4
    public static ListNode reverse(ListNode head) {
        //单链表为空或只有一个节点,直接返回原单链表
        if (head == null || head.next == null){
            return head;
        }
        //前一个节点指针
        ListNode preNode = null;
        //当前节点指针
        ListNode curNode = head;
        //下一个节点指针
        ListNode nextNode = null;
        while (curNode != null){
            nextNode = curNode.next;//nextNode 指向下一个节点,保存当前节点后面的链表。
            curNode.next=preNode;//将当前节点next域指向前一个节点   null<-1<-2<-3<-4
            preNode = curNode;//preNode 指针向后移动。preNode指向当前节点。
            curNode = nextNode;//curNode指针向后移动。下一个节点变成当前节点
        }
        return preNode;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

德玛西亚!!

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值