数据结构与算法篇(刷题篇 - 链表)

目录

1. 反转链表(简单)

1.1. 题目描述

1.2. 解题思路

方法一:迭代(推荐使用)

方法二:递归(扩展思路)

方法三:使用栈解决

方法四:双链表求解

2. 链表内指定区间反转(中等)

2.1. 题目描述

2.2. 解题思路

方法一:头插法迭代(推荐使用)

方法二:递归(扩展思路)

3. 链表中的节点每k个一组翻转(中等)

3.1. 题目描述

3.2. 解题思路

方法一:递归(推荐使用)

方法二:模拟法

4. 排序链表(中等)

4.1. 题目描述

4.2. 解题思路

方法一:自顶向下归并排序

方法二:自底向上归并排序

5. 合并两个排序的链表(简单)

5.1. 题目描述

5.2. 解题思路

方法一:双指针迭代(推荐使用)

方法二:双指针递归(扩展思路)

6. 合并k个已排序的链表(较难)

6.1. 题目描述

6.2. 解题思路

方法一:归并排序思想(推荐使用)

方法二:优先队列(扩展思路)

7. 判断链表中是否有环(简单)

7.1. 题目描述

7.2. 解题思路

方法:双指针(推荐使用)

8. 链表中环的入口结点(中等)

8.1. 题目描述

8.2. 解题思路

方法:双指针(推荐使用)

9. 相交链表(简单)

9.1. 题目描述

9.2. 解题思路

方法一:哈希集合

方法二:双指针

10. 环形链表II(中等)

10.1. 题目描述

10.2. 解题思路

方法一:哈希表

方法二:快慢指针

11. 链表中倒数最后k个结点(简单)

11.1. 题目描述

11.2. 解题思路

方法一:快慢双指针(推荐使用)

方法二:先找长度再找最后k(扩展思路)

12. 两个链表的第一个公共结点(简单)

12.1. 题目描述

12.2. 解题思路

方法:双指针

13. 链表相加(二)(中等)

13.1. 题目描述

13.2 解题思路

方法:反转链表法(推荐使用)

14. 两数相加(中等)

14.1. 题目描述

14.2. 解题思路

方法一:模拟法

15. 单链表的排序(中等)

15.1. 题目描述

15.2. 解题思路

方法一:归并排序(推荐使用)

方法二:转化为数组排序(扩展思路)

16. 判断一个链表是否为回文结构(简单)

16.1. 题目描述

16.2. 解题思路

方法一:数组复制反转法(前置知识)

方法二:数组复制双指针(前置知识)

方法三:长度法找中点(推荐使用)

方法四:双指针找中点(推荐使用)

方法五:栈逆序(扩展思路)

17. 链表的奇偶重排(中等)

17.1. 题目描述

17.2. 解题思路

方法:双指针(推荐使用)

18. 删除有序链表中重复的元素-I(简单)

18.1. 题目描述

18.2. 解题思路

方法:遍历删除(推荐使用)

19. 删除有序链表中重复的元素-II(中等)

19.1. 题目描述

19.2. 解题思路

方法一:直接比较删除(推荐使用)

方法二:哈希表(扩展思路)

20. 删除链表的中间节点(中等)

20.1. 题目描述

20.2. 解题思路

方法一:快慢指针

21. 删除链表的倒数第n个节点(中等)

21.1. 题目描述

21.2. 解题思路

方法一:双指针(推荐使用)

方法二:长度统计法(思路扩展)

22. 从尾到头打印链表(简单)

22.1. 题目描述

22.2. 解题思路

23. 两两交换链表中的节点(中等)

23.1. 题目描述

23.2. 解题思路

方法一:递归

方法二:迭代

24. 复制带随机指针的链表(中等)

24.1. 题目描述

24.2. 解题思路

方法一:回溯 + 哈希表

方法二:迭代 + 节点拆分

25. 链表最大孪生和(中等)

25.1. 题目描述

25.2. 解题思路

快慢指针+ 翻转链表(头部翻转)


1. 反转链表(简单)

1.1. 题目描述

1.2. 解题思路

方法一:迭代(推荐使用)

public class Solution {
    public ListNode ReverseList(ListNode head) {
        //处理空链表
        if(head == null) 
            return null;
        ListNode cur = head;
        ListNode pre = null;
        while(cur != null){
            //断开链表,要记录后续一个
            ListNode temp = cur.next; 
            //当前的next指向前一个
            cur.next = pre; 
            //前一个更新为当前
            pre = cur; 
            //当前更新为刚刚记录的后一个
            cur = temp; 
        }
        return pre;
    }
}

方法二:递归(扩展思路)

public class Solution {
    public ListNode ReverseList(ListNode head) {
        //递归结束条件
        if(head == null || head.next == null)
            return head;
        //反转下一个
        ListNode newHead = ReverseList(head.next); 
        //逆转本级节点
        head.next.next = head; 
        //尾部设置空节点
        head.next = null; 
        return newHead;
    }
}

方法三:使用栈解决

import java.util.Stack;
public class Solution {
public ListNode ReverseList(ListNode head) {
    Stack<ListNode> stack= new Stack<>();
    //把链表节点全部摘掉放到栈中
    while (head != null) {
        stack.push(head);
        head = head.next;
    }
    if (stack.isEmpty())
        return null;
    ListNode node = stack.pop();
    ListNode dummy = node;
    //栈中的结点全部出栈,然后重新连成一个新的链表
    while (!stack.isEmpty()) {
        ListNode tempNode = stack.pop();
        node.next = tempNode;
        node = node.next;
    }
    //最后一个结点就是反转前的头结点,一定要让他的next
    //等于空,否则会构成环
    node.next = null;
    return dummy;
}
}
方法四:双链表求解

public ListNode ReverseList(ListNode head) {
    //新链表
    ListNode newHead = null;
    while (head != null) {
        //先保存访问的节点的下一个节点,保存起来
        //留着下一步访问的
        ListNode temp = head.next;
        //每次访问的原链表节点都会成为新链表的头结点,
        //其实就是把新链表挂到访问的原链表节点的
        //后面就行了
        head.next = newHead;
        //更新新链表
        newHead = head;
        //重新赋值,继续访问
        head = temp;
    }
    //返回新链表
    return newHead;
}

2. 链表内指定区间反转(中等)

2.1. 题目描述

2.2. 解题思路

方法一:头插法迭代(推荐使用)

Java实现代码:

import java.util.*;
public class Solution {
    public ListNode reverseBetween (ListNode head, int m, int n) {
        //加个表头
        ListNode res = new ListNode(-1); 
        res.next = head;
        //前序节点
        ListNode pre = res; 
        //当前节点
        ListNode cur = head; 
        //找到m
        for(int i = 1; i < m; i++){ 
            pre = cur;
            cur = cur.next;
        }
        //从m反转到n
        for(int i = m; i < n; i++){ 
            ListNode temp = cur.next;
            cur.next = temp.next;
            temp.next = pre.next;
            pre.next = temp;
        }
        //返回去掉表头
        return res.next; 
    }
}

方法二:递归(扩展思路)

Java实现代码:

import java.util.*;
public class Solution {
    ListNode temp = null;
    public ListNode reverse(ListNode head, int n){
        //只颠倒第一个节点,后续不管
        if(n == 1){ 
            temp = head.next;
            return head;
        }
        //进入子问题
        ListNode node = reverse(head.next, n - 1); 
        //反转
        head.next.next = head;
        //每个子问题反转后的尾拼接第n个位置后的节点
        head.next = temp; 
        return node;
    }
    public ListNode reverseBetween (ListNode head, int m, int n) {
        //从第一个节点开始
        if(m == 1) 
            return reverse(head, n);
        //缩减子问题
        ListNode node = reverseBetween(head.next, m - 1, n - 1); 
        //拼接已翻转
        head.next = node;
        return head;
    }
}

3. 链表中的节点每k个一组翻转(中等)

3.1. 题目描述

3.2. 解题思路

方法一:递归(推荐使用)

Java实现代码:

import java.util.*;
public class Solution {
    public ListNode reverseKGroup (ListNode head, int k) {
        //找到每次翻转的尾部
        ListNode tail = head;
        //遍历k次到尾部 
        for(int i = 0; i < k; i++){ 
            //如果不足k到了链表尾,直接返回,不翻转
            if(tail == null) 
                return head;
            tail = tail.next; 
        }
        //翻转时需要的前序和当前节点
        ListNode pre = null; 
        ListNode cur = head;
        //在到达当前段尾节点前
        while(cur != tail){ 
            //翻转
            ListNode temp = cur.next; 
            cur.next = pre;
            pre = cur;
            cur = temp;
        }
        //当前尾指向下一段要翻转的链表
        head.next = reverseKGroup(tail, k); 
        return pre;
    }
}

方法二:模拟法

class Solution {
    public ListNode reverseKGroup(ListNode head, int k) {
        ListNode hair = new ListNode(0);
        hair.next = head;
        ListNode pre = hair;

        while (head != null) {
            ListNode tail = pre;
            // 查看剩余部分长度是否大于等于 k
            for (int i = 0; i < k; ++i) {
                tail = tail.next;
                if (tail == null) {
                    return hair.next;
                }
            }
            ListNode nex = tail.next;
            ListNode[] reverse = myReverse(head, tail);
            head = reverse[0];
            tail = reverse[1];
            // 把子链表重新接回原链表
            pre.next = head;
            tail.next = nex;
            pre = tail;
            head = tail.next;
        }

        return hair.next;
    }

    public ListNode[] myReverse(ListNode head, ListNode tail) {
        ListNode prev = tail.next;
        ListNode p = head;
        while (prev != tail) {
            ListNode nex = p.next;
            p.next = prev;
            prev = p;
            p = nex;
        }
        return new ListNode[]{tail, head};
    }
}

4. 排序链表(中等)

4.1. 题目描述

4.2. 解题思路

方法一:自顶向下归并排序

class Solution {
    public ListNode sortList(ListNode head) {
        return sortList(head, null);
    }

    public ListNode sortList(ListNode head, ListNode tail) {
        if (head == null) {
            return head;
        }
        if (head.next == tail) {
            head.next = null;
            return head;
        }
        ListNode slow = head, fast = head;
        while (fast != tail) {
            slow = slow.next;
            fast = fast.next;
            if (fast != tail) {
                fast = fast.next;
            }
        }
        ListNode mid = slow;
        ListNode list1 = sortList(head, mid);
        ListNode list2 = sortList(mid, tail);
        ListNode sorted = merge(list1, list2);
        return sorted;
    }

    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;
    }
}

方法二:自底向上归并排序

class Solution {
    public ListNode sortList(ListNode head) {
        if (head == null) {
            return head;
        }
        int length = 0;
        ListNode node = head;
        while (node != null) {
            length++;
            node = node.next;
        }
        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;
                }
                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;
    }
}

5. 合并两个排序的链表(简单)

5.1. 题目描述

5.2. 解题思路

方法一:双指针迭代(推荐使用)

Java实现代码:

public class Solution {
    public ListNode Merge(ListNode list1,ListNode list2) {
        //一个已经为空了,直接返回另一个
        if(list1 == null) 
            return list2;
        if(list2 == null)
            return list1;
        //加一个表头
        ListNode head = new ListNode(0); 
        ListNode cur = head;
        //两个链表都要不为空
        while(list1 != null && list2 != null){ 
            //取较小值的节点
            if(list1.val <= list2.val){ 
                cur.next = list1;
                //只移动取值的指针
                list1 = list1.next; 
            }else{
                cur.next = list2;
                //只移动取值的指针
                list2 = list2.next; 
            }
            //指针后移
            cur = cur.next; 
        }
        //哪个链表还有剩,直接连在后面
        if(list1 != null) 
            cur.next = list1;
        else
            cur.next = list2;
        //返回值去掉表头
        return head.next; 
    }
}

方法二:双指针递归(扩展思路)

Java实现代码:

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;
        }
    }
}

6. 合并k个已排序的链表(较难)

6.1. 题目描述

6.2. 解题思路

方法一:归并排序思想(推荐使用)

图示:

Java实现代码:

import java.util.ArrayList;
public class Solution {
    //两个链表合并函数
    public ListNode Merge(ListNode list1, ListNode list2) { 
        //一个已经为空了,直接返回另一个
        if(list1 == null) 
            return list2;
        if(list2 == null)
            return list1;
        //加一个表头
        ListNode head = new ListNode(0); 
        ListNode cur = head;
        //两个链表都要不为空
        while(list1 != null && list2 != null){ 
            //取较小值的节点
            if(list1.val <= list2.val){ 
                cur.next = list1;
                //只移动取值的指针
                list1 = list1.next; 
            }else{
                cur.next = list2;
                //只移动取值的指针
                list2 = list2.next; 
            }
            //指针后移
            cur = cur.next; 
        }
        //哪个链表还有剩,直接连在后面
        if(list1 != null) 
            cur.next = list1;
        else
            cur.next = list2;
        //返回值去掉表头
        return head.next; 
    }
    
    //划分合并区间函数
    ListNode divideMerge(ArrayList<ListNode> lists, int left, int right){ 
        if(left > right) 
            return null;
        //中间一个的情况
        else if(left == right) 
            return lists.get(left);
        //从中间分成两段,再将合并好的两段合并
        int mid = (left + right) / 2; 
        return Merge(divideMerge(lists, left, mid), divideMerge(lists, mid + 1, right));
    }
    
    public ListNode mergeKLists(ArrayList<ListNode> lists) {
        //k个链表归并排序
        return divideMerge(lists, 0, lists.size() - 1);
    }
}

方法二:优先队列(扩展思路)

Java实现代码:

import java.util.*;
public class Solution {
    public ListNode mergeKLists(ArrayList<ListNode> lists) {
        //小顶堆
        Queue<ListNode> pq = new PriorityQueue<>((v1, v2) -> v1.val - v2.val); 
        //遍历所有链表第一个元素
        for(int i = 0; i < lists.size(); i++){ 
            //不为空则加入小顶堆
            if(lists.get(i) != null) 
                pq.add(lists.get(i));
        }
        //加一个表头
        ListNode res = new ListNode(-1); 
        ListNode head = res;
        //直到小顶堆为空
        while(!pq.isEmpty()){ 
            //取出最小的元素
            ListNode temp = pq.poll(); 
            //连接
            head.next = temp; 
            head = head.next;
            //每次取出链表的后一个元素加入小顶堆
            if(temp.next != null) 
                pq.add(temp.next);
        }
        //去掉表头
        return res.next; 
    }
}

7. 判断链表中是否有环(简单)

7.1. 题目描述

7.2. 解题思路

方法:双指针(推荐使用)

Java实现代码:

public class Solution {
    public boolean hasCycle(ListNode head) {
        //先判断链表为空的情况
        if(head == null) 
            return false;
        //快慢双指针
        ListNode fast = head; 
        ListNode slow = head;
        //如果没环快指针会先到链表尾
        while(fast != null && fast.next != null){ 
            //快指针移动两步
            fast = fast.next.next; 
            //慢指针移动一步
            slow = slow.next; 
            //相遇则有环
            if(fast == slow) 
                return true;
        }
        //到末尾则没有环
        return false; 
    }
}

8. 链表中环的入口结点(中等)

8.1. 题目描述

8.2. 解题思路

方法:双指针(推荐使用)

Java实现代码:

public class Solution {
    //判断有没有环,返回相遇的地方
    public ListNode hasCycle(ListNode head) {
        //先判断链表为空的情况
        if(head == null) 
            return null;
        //快慢双指针
        ListNode fast = head; 
        ListNode slow = head;
        //如果没环快指针会先到链表尾
        while(fast != null && fast.next != null){ 
            //快指针移动两步
            fast = fast.next.next; 
            //慢指针移动一步
            slow = slow.next; 
            //相遇则有环,返回相遇的位置
            if(fast == slow) 
                return slow;
        }
        //到末尾说明没有环,返回null
        return null; 
    }
    
    public ListNode EntryNodeOfLoop(ListNode pHead) {
        ListNode slow = hasCycle(pHead);
        //没有环
        if(slow == null) 
            return null;
        //快指针回到表头
        ListNode fast = pHead; 
        //再次相遇即是环入口
        while(fast != slow){ 
            fast = fast.next;
            slow = slow.next;
        }
        return slow;
    }
}

9. 相交链表(简单)

9.1. 题目描述

9.2. 解题思路

方法一:哈希集合

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        Set<ListNode> visited = new HashSet<ListNode>();
        ListNode temp = headA;
        while (temp != null) {
            visited.add(temp);
            temp = temp.next;
        }
        temp = headB;
        while (temp != null) {
            if (visited.contains(temp)) {
                return temp;
            }
            temp = temp.next;
        }
        return null;
    }
}

方法二:双指针

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        if (headA == null || headB == null) {
            return null;
        }
        ListNode pA = headA, pB = headB;
        while (pA != pB) {
            pA = pA == null ? headB : pA.next;
            pB = pB == null ? headA : pB.next;
        }
        return pA;
    }
}

10. 环形链表II(中等)

10.1. 题目描述

10.2. 解题思路

方法一:哈希表

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;
    }
}

方法二:快慢指针

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 (fast == slow) {
                ListNode ptr = head;
                while (ptr != slow) {
                    ptr = ptr.next;
                    slow = slow.next;
                }
                return ptr;
            }
        }
        return null;
    }
}

11. 链表中倒数最后k个结点(简单)

11.1. 题目描述

11.2. 解题思路

方法一:快慢双指针(推荐使用)

Java实现代码:

import java.util.*;
public class Solution {
    public ListNode FindKthToTail (ListNode pHead, int k) {
        int n = 0;
        ListNode fast = pHead; 
        ListNode slow = pHead;
        //快指针先行k步
        for(int i = 0; i < k; i++){  
            if(fast != null)
                fast = fast.next;
            //达不到k步说明链表过短,没有倒数k
            else 
                return slow = null;
        }
        //快慢指针同步,快指针先到底,慢指针指向倒数第k个
        while(fast != null){ 
            fast = fast.next;
            slow = slow.next;
        }
        return slow;
    }
}

方法二:先找长度再找最后k(扩展思路)

Java实现代码:

import java.util.*;
public class Solution {
    public ListNode FindKthToTail (ListNode pHead, int k) {
        int n = 0;
        ListNode p = pHead;
         //遍历链表,统计链表长度
        while(p != null){
            n++;
            p = p.next;
        }
        //长度过小,返回空链表
        if(n < k) 
            return null;
        p = pHead;
        //遍历n-k次
        for(int i = 0; i < n - k; i++) 
            p = p.next;
        return p;
    }
}

12. 两个链表的第一个公共结点(简单)

12.1. 题目描述

12.2. 解题思路

方法:双指针

Java实现代码:

public class Solution {
    public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
        ListNode l1 = pHead1, l2 = pHead2;
        while(l1 != l2){
            l1 = (l1==null)?pHead2:l1.next;
            l2 = (l2==null)?pHead1:l2.next;
        }
        return l1;
    }
}

13. 链表相加(二)(中等)

13.1. 题目描述

13.2 解题思路

方法:反转链表法(推荐使用)

Java实现代码:

import java.util.*;
public class Solution {
    //反转链表
    public ListNode ReverseList(ListNode pHead) { 
        if(pHead == null)
            return null;
        ListNode cur = pHead;
        ListNode pre = null;
        while(cur != null){
            //断开链表,要记录后续一个
            ListNode temp = cur.next;
            //当前的next指向前一个
            cur.next = pre; 
            //前一个更新为当前
            pre = cur; 
            //当前更新为刚刚记录的后一个
            cur = temp; 
        }
        return pre;
    }
    
    public ListNode addInList (ListNode head1, ListNode head2) {
        //任意一个链表为空,返回另一个
        if(head1 == null) 
            return head2;
        if(head2 == null)
            return head1;
        //反转两个链表
        head1 = ReverseList(head1); 
        head2 = ReverseList(head2);
        //添加表头
        ListNode res = new ListNode(-1); 
        ListNode head = res;
        //进位符号
        int carry = 0; 
        //只要某个链表还有或者进位还有
        while(head1 != null || head2 != null || carry != 0){ 
            //链表不为空则取其值
            int val1 = head1 == null ? 0 : head1.val; 
            int val2 = head2 == null ? 0 : head2.val;
            //相加
            int temp = val1 + val2 + carry; 
            //获取进位
            carry = temp / 10; 
            temp %= 10; 
            //添加元素
            head.next = new ListNode(temp); 
            head = head.next;
            //移动下一个
            if(head1 != null) 
                head1 = head1.next;
            if(head2 != null)
                head2 = head2.next;
        }
        //结果反转回来
        return ReverseList(res.next); 
    }
}

14. 两数相加(中等)

14.1. 题目描述

14.2. 解题思路

方法一:模拟法

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;
    }
}

15. 单链表的排序(中等)

15.1. 题目描述

15.2. 解题思路

方法一:归并排序(推荐使用)

Java实现代码:

import java.util.*;
public class Solution {
    //合并两段有序链表
    ListNode merge(ListNode pHead1, ListNode pHead2) { 
        //一个已经为空了,直接返回另一个
        if(pHead1 == null) 
            return pHead2;
        if(pHead2 == null)
            return pHead1;
        //加一个表头
        ListNode head = new ListNode(0); 
        ListNode cur = head;
        //两个链表都要不为空
        while(pHead1 != null && pHead2 != null){ 
            //取较小值的节点
            if(pHead1.val <= pHead2.val){ 
                cur.next = pHead1;
                //只移动取值的指针
                pHead1 = pHead1.next; 
            }else{
                cur.next = pHead2;
                //只移动取值的指针
                pHead2 = pHead2.next; 
            }
            //指针后移
            cur = cur.next; 
        }
        //哪个链表还有剩,直接连在后面
        if(pHead1 != null) 
            cur.next = pHead1;
        else
            cur.next = pHead2;
        //返回值去掉表头
        return head.next; 
    }
    
    public ListNode sortInList (ListNode head) {
        //链表为空或者只有一个元素,直接就是有序的
        if(head == null || head.next == null) 
            return head;
        ListNode left = head; 
        ListNode mid = head.next;
        ListNode right = head.next.next;
        //右边的指针到达末尾时,中间的指针指向该段链表的中间
        while(right != null && right.next != null){ 
            left = left.next;
            mid = mid.next;
            right = right.next.next;
        }
        //左边指针指向左段的左右一个节点,从这里断开
        left.next = null; 
        //分成两段排序,合并排好序的两段
        return merge(sortInList(head), sortInList(mid)); 
    }
}

方法二:转化为数组排序(扩展思路)

import java.util.*;
public class Solution {
    public ListNode sortInList (ListNode head) {
        ArrayList<Integer> nums = new ArrayList(); 
        ListNode p = head;
        //遍历链表,将节点值加入数组
        while(p != null){ 
            nums.add(p.val);
            p = p.next;
        }
        p = head;
        //对数组元素排序
        Collections.sort(nums); 
        //遍历数组
        for(int i = 0; i < nums.size(); i++){ 
            //将数组元素依次加入链表
            p.val = nums.get(i); 
            p = p.next;
        }
        return head;
    }
}

16. 判断一个链表是否为回文结构(简单)

16.1. 题目描述

16.2. 解题思路

方法一:数组复制反转法(前置知识)

Java实现代码:

import java.util.*;
public class Solution {
    public boolean isPail (ListNode head) {
        ArrayList<Integer> nums = new ArrayList();
        //将链表元素取出一次放入数组
        while(head != null){ 
            nums.add(head.val);
            head = head.next;
        }
        ArrayList<Integer> temp = new ArrayList();
        temp = (ArrayList<Integer>) nums.clone();
        //准备一个数组承接翻转之后的数组
        Collections.reverse(temp); 
        for(int i = 0; i < nums.size(); i++){
            int x = nums.get(i);
            int y = temp.get(i);
            //正向遍历与反向遍历相同
            if(x != y) 
                return false;
        }
        return true;
    }
}

方法二:数组复制双指针(前置知识)

Java实现代码:

import java.util.*;
public class Solution {
    public boolean isPail (ListNode head) {
        ArrayList<Integer> nums = new ArrayList();
        //将链表元素取出一次放入数组
        while(head != null){ 
            nums.add(head.val);
            head = head.next;
        }
        //双指针指向首尾
        int left = 0; 
        int right = nums.size() - 1;
        //分别从首尾遍历,代表正序和逆序
        while(left <= right){ 
            int x = nums.get(left);
            int y = nums.get(right);
            //如果不一致就是不为回文
            if(x != y) 
                return false;
            left++;
            right--;
        }
        return true;
    }
}

方法三:长度法找中点(推荐使用)

Java实现代码:

import java.util.*;
public class Solution {
    //反转链表指针
    ListNode reverse(ListNode head) { 
        //前序节点
        ListNode prev = null; 
        while(head != null){
            //断开后序
            ListNode next = head.next; 
            //指向前序
            head.next = prev; 
            prev = head;
            head = next;
        }
        return prev;
    }
    
    public boolean isPail (ListNode head) {
        ListNode p = head;
        int n = 0;
        //找到链表长度
        while(p != null){ 
            n++;
            p = p.next; 
        }
        //中点
        n = n / 2; 
        p = head;
        //遍历到中点位置
        while(n > 0){ 
            p = p.next;
            n--;
        }
        //中点处反转
        p = reverse(p);  
        ListNode q = head;
        while(p != null){
            //比较判断节点值是否相等
            if(p.val != q.val) 
                return false;
            p = p.next;
            q = q.next;
        }
        return true;
    }
}

方法四:双指针找中点(推荐使用)

Java实现代码:

import java.util.*;
public class Solution {
    //反转链表指针
    ListNode reverse(ListNode head) { 
        //前序节点
        ListNode prev = null; 
        while(head != null){
            //断开后序
            ListNode next = head.next; 
            //指向前序
            head.next = prev; 
            prev = head;
            head = next;
        }
        return prev;
    }
    
    public boolean isPail (ListNode head) {
        //空链表直接为回文
        if(head == null) 
            return true;
        //准备快慢双指针
        ListNode slow = head; 
        ListNode fast = head;
        //双指针找中点
        while(fast != null && fast.next != null){ 
            slow = slow.next;
            fast = fast.next.next;
        }
        //中点处反转
        slow = reverse(slow);  
        fast = head;
        while(slow != null){ 
            //比较判断节点值是否相等
            if(slow.val != fast.val) 
                return false;
            fast = fast.next;
            slow = slow.next;
        }
        return true;
    }
}

方法五:栈逆序(扩展思路)

Java实现代码:

import java.util.*;
public class Solution {
    public boolean isPail (ListNode head) {
        ListNode p = head;
        Stack<Integer> s = new Stack();
        //辅助栈记录元素
        while(p != null){ 
            s.push(p.val);
            p = p.next;
        }
        p = head;
        //正序遍历链表,从栈中弹出的内容是逆序的
        while(!s.isEmpty()){ 
            //比较是否相同
            if(p.val != s.pop()) 
                return false;
            p = p.next;
        }
        return true;
    }
}

17. 链表的奇偶重排(中等)

17.1. 题目描述

17.2. 解题思路

方法:双指针(推荐使用)

Java实现代码:

import java.util.*;
public class Solution {
    public ListNode oddEvenList (ListNode head) {
        //如果链表为空,不用重排
        if(head == null) 
            return head;
        //even开头指向第二个节点,可能为空
        ListNode even = head.next; 
        //odd开头指向第一个节点
        ListNode odd = head; 
        //指向even开头
        ListNode evenhead = even; 
        while(even != null && even.next != null){
            //odd连接even的后一个,即奇数位
            odd.next = even.next; 
            //odd进入后一个奇数位
            odd = odd.next; 
            //even连接后一个奇数的后一位,即偶数位
            even.next = odd.next; 
            //even进入后一个偶数位
            even = even.next; 
        } 
        //even整体接在odd后面
        odd.next = evenhead; 
        return head;
    }
}

18. 删除有序链表中重复的元素-I(简单)

18.1. 题目描述

18.2. 解题思路

方法:遍历删除(推荐使用)

Java实现代码:

import java.util.*;
public class Solution {
    public ListNode deleteDuplicates (ListNode head) {
        //空链表
        if(head == null) 
            return null;
        //遍历指针
        ListNode cur = head; 
        //指针当前和下一位不为空
        while(cur != null && cur.next != null){ 
            //如果当前与下一位相等则忽略下一位
            if(cur.val == cur.next.val) 
                cur.next = cur.next.next;
            //否则指针正常遍历
            else 
                cur = cur.next;
        }
        return head;
    }
}

19. 删除有序链表中重复的元素-II(中等)

19.1. 题目描述

19.2. 解题思路

方法一:直接比较删除(推荐使用)

Java实现代码:

import java.util.*;
public class Solution {
    public ListNode deleteDuplicates (ListNode head) {
        //空链表
        if(head == null) 
            return null;
        ListNode res = new ListNode(0);
        //在链表前加一个表头
        res.next = head; 
        ListNode cur = res;
        while(cur.next != null && cur.next.next != null){ 
            //遇到相邻两个节点值相同
            if(cur.next.val == cur.next.next.val){ 
                int temp = cur.next.val;
                //将所有相同的都跳过
                while (cur.next != null && cur.next.val == temp) 
                    cur.next = cur.next.next;
            }
            else 
                cur = cur.next;
        }
        //返回时去掉表头
        return res.next; 
    }
}

方法二:哈希表(扩展思路)

Java实现代码:

import java.util.*;
public class Solution {
    public ListNode deleteDuplicates (ListNode head) {
        //空链表
        if(head == null) 
            return null;
        Map<Integer,Integer> mp = new HashMap<>();
        ListNode cur = head;
        //遍历链表统计每个节点值出现的次数
        while(cur != null){ 
            if(mp.containsKey(cur.val))
                mp.put(cur.val, (int)mp.get(cur.val) + 1);
            else
                mp.put(cur.val,1);
            cur = cur.next;
        }
        ListNode res = new ListNode(0);
        //在链表前加一个表头
        res.next = head; 
        cur = res;
        //再次遍历链表
        while(cur.next != null){
            //如果节点值计数不为1 
            if(mp.get(cur.next.val) != 1) 
                //删去该节点
                cur.next = cur.next.next; 
            else
                cur = cur.next; 
        }
        //去掉表头
        return res.next; 
    }
}

20. 删除链表的中间节点(中等)

20.1. 题目描述

20.2. 解题思路

解题思路: 使用 slow 记录慢指针,fast 记录快指针。

当 fast 的再一次移动就结束时,说明 slow 的再一次移动也将到达中间点,

那么这个时候就可以直接用 slow.next = slow.next.next 来去掉中间节点。

方法一:快慢指针
class Solution {
    public ListNode deleteMiddle(ListNode head) {

        if (head == null || head.next == null) {
            return null;
        }

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

        slow.next = slow.next.next;

        return head;

    }
}

21. 删除链表的倒数第n个节点(中等)

21.1. 题目描述

21.2. 解题思路

方法一:双指针(推荐使用)

Java实现代码:

import java.util.*;
public class Solution {
    public ListNode removeNthFromEnd (ListNode head, int n) {
        //添加表头
        ListNode res = new ListNode(-1); 
        res.next = head;
        //当前节点
        ListNode cur = head; 
        //前序节点
        ListNode pre = res; 
        ListNode fast = head;
        //快指针先行n步
        while(n != 0){ 
            fast = fast.next;
            n--;
        }
        //快慢指针同步,快指针到达末尾,慢指针就到了倒数第n个位置
        while(fast != null){
            fast = fast.next;
            pre = cur;
            cur = cur.next;
        }
        //删除该位置的节点
        pre.next = cur.next; 
        //返回去掉头
        return res.next; 
    }
}

方法二:长度统计法(思路扩展)

Java实现代码:

import java.util.*;
public class Solution {
    public ListNode removeNthFromEnd (ListNode head, int n) {
        //记录链表长度
        int length = 0; 
        //添加表头
        ListNode res = new ListNode(-1); 
        res.next = head;
        //当前节点
        ListNode cur = head; 
        //前序节点
        ListNode pre = res; 
        //找到链表长度
        while(cur != null){ 
            length++;
            cur = cur.next;
        }
        //回到头部
        cur = head; 
        //从头遍历找到倒数第n个位置
        for(int i = 0; i < length - n; i++){ 
            pre = cur;
            cur = cur.next;
        }
        //删去倒数第n个节点
        pre.next = cur.next; 
        //返回去掉头节点
        return res.next; 
    }
}

22. 从尾到头打印链表(简单)

22.1. 题目描述

22.2. 解题思路

class Solution {
    public int[] reversePrint(ListNode head) {
        int count = 0;  //链表结点的个数
        ListNode p = head;
        while(p != null){  //统计链表中结点的个数
            count++;
            p = p.next;
        }
        int[] ans = new int[count]; //创建一个和结点个数等大的数组
        p = head;
        int index = count - 1; //作为索引填充数组(从后往前填充)
        while(p != null){
            ans[index] = p.val;
            p = p.next;
            index--;
        }
        return ans;
    }
}

23. 两两交换链表中的节点(中等)

23.1. 题目描述

23.2. 解题思路

方法一:递归

class Solution {
    public ListNode swapPairs(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        }
        ListNode newHead = head.next;
        head.next = swapPairs(newHead.next);
        newHead.next = head;
        return newHead;
    }
}

方法二:迭代

Java代码:

class Solution {
    public ListNode swapPairs(ListNode head) {
        ListNode dummyHead = new ListNode(0);
        dummyHead.next = head;
        ListNode temp = dummyHead;
        while (temp.next != null && temp.next.next != null) {
            ListNode node1 = temp.next;
            ListNode node2 = temp.next.next;
            temp.next = node2;
            node1.next = node2.next;
            node2.next = node1;
            temp = node1;
        }
        return dummyHead.next;
    }
}

24. 复制带随机指针的链表(中等)

24.1. 题目描述

24.2. 解题思路

方法一:回溯 + 哈希表

Java代码:

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);
    }
}

方法二:迭代 + 节点拆分

class Solution {
    public Node copyRandomList(Node head) {
        if (head == null) {
            return null;
        }
        for (Node node = head; node != null; node = node.next.next) {
            Node nodeNew = new Node(node.val);
            nodeNew.next = node.next;
            node.next = nodeNew;
        }
        for (Node node = head; node != null; node = node.next.next) {
            Node nodeNew = node.next;
            nodeNew.random = (node.random != null) ? node.random.next : null;
        }
        Node headNew = head.next;
        for (Node node = head; node != null; node = node.next) {
            Node nodeNew = node.next;
            node.next = node.next.next;
            nodeNew.next = (nodeNew.next != null) ? nodeNew.next.next : null;
        }
        return headNew;
    }
}

25. 链表最大孪生和(中等)

25.1. 题目描述

25.2. 解题思路

快慢指针+ 翻转链表(头部翻转)

传统方法:

  1. 先使用快慢指针找到中间节点 Using the Fast& Slow pointers to Find the middle point
  2. 翻转第二部分 Reverse the secondPart
  3. 通过同时遍历第一部分与反转后的第二部分得出最大结果 Add the values and store the maximum value

具体代码如下:

class Solution {
    
    public int pairSum(ListNode head) {

        ListNode fast = head;
        ListNode slow = head;

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

        ListNode prev = reverse(slow);
        ListNode head1 = head, head2 = prev;
        int res = 0;

        while (head2 != null) {
            res = Math.max(head1.val + head2.val, res);
            head1 = head1.next;
            head2 = head2.next;
        }
        return res;
    }

    private ListNode reverse(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        }
        
        ListNode curr = head;
        ListNode prev = null;
        
        while (curr != null) {
            ListNode next = curr.next;
            curr.next = prev;
            prev = curr;
            curr = next;
        }
        return prev;
    }
}

对原有思路进行改进:

可以在使用快慢指针时同时对第一部分(前一半)链表进行翻转操作 相较于原先 找到中间节点后翻转第二部分 更加简洁快速

具体代码如下:

class Solution {
    
    public int pairSum(ListNode head) {

        ListNode fast = head;
        ListNode slow = head;
        ListNode prev = null;

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

        int res = 0;
        while (slow != null) {
            res = Math.max(res, prev.val + slow.val);
            prv = prev.next;
            slow = slow.next;
        }
        return res;
    }
    
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

W哥教你学后端

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

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

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

打赏作者

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

抵扣说明:

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

余额充值