创建一个基础工具类来放ListNode及定义其一些常用方法

  1. package com.shiers.leetcode;
  2. /**
  3. * Demo class
  4. *
  5. * @author shierS
  6. * @date 2021/4/25
  7. */
  8. public class ListNode {
  9. public int val;
  10. public ListNode next;
  11. public ListNode() {
  12. }
  13. public ListNode(int val) {
  14. this.val = val;
  15. }
  16. public ListNode(int val, ListNode next) {
  17. this.val = val;
  18. this.next = next;
  19. }
  20. //初始化一个List
  21. public static ListNode initList(int[] arr){
  22. if (arr.length < 1){
  23. return null;
  24. }
  25. ListNode head = new ListNode(arr[0]);
  26. ListNode my = head;
  27. for (int i = 1; i < arr.length; i++) {
  28. ListNode thisListNode = new ListNode(arr[i]);
  29. my.next = thisListNode;
  30. my = thisListNode;
  31. }
  32. return head;
  33. }
  34. //输出一个List
  35. public static void printlist(ListNode head){
  36. while (head != null){
  37. System.out.println(head.val);
  38. head = head.next;
  39. }
  40. }
  41. }

160.相交链表、找出两个链表的交点✅

编写一个程序,找到两个单链表相交的起始节点。要求时间复杂度为 O(N),空间复杂度为 O(1)。如果不存在交点则返回 null。
✅链表 - 图1
思路:
设 A 的长度为 a + c,B 的长度为 b + c,其中 c 为尾部公共部分长度,可知 a + c + b = b + c + a。
当访问 A 链表的指针访问到链表尾部时,令它从链表 B 的头部开始访问链表 B;同样地,当访问 B 链表的指针访问到链表尾部时,令它从链表 A 的头部开始访问链表 A。这样就能控制访问 A 和 B 两个链表的指针能同时访问到交点。
如果不存在交点,那么 a + b = b + a,以下实现代码中 l1 和 l2 会同时为 null,从而退出循环。

  1. /**
  2. * Definition for singly-linked list.
  3. * public class ListNode {
  4. * int val;
  5. * ListNode next;
  6. * ListNode(int x) {
  7. * val = x;
  8. * next = null;
  9. * }
  10. * }
  11. */
  12. public class Solution {
  13. public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
  14. ListNode lnA = headA,lnB = headB;
  15. while(lnA != lnB){
  16. lnA=(lnA==null?headB:lnA.next);
  17. lnB=(lnB==null?headA:lnB.next);
  18. }
  19. return lnA;
  20. }
  21. }

扩展:
如果只是判断是否存在交点,那么就是另一个问题。有两种解法:

  • 把第一个链表的结尾连接到第二个链表的开头,看第二个链表是否存在环;
  • 或者直接比较两个链表的最后一个节点是否相同。

    206. 反转链表

    给你单链表的头节点head,请你反转链表,并返回反转后的链表。
    ✅链表 - 图2

    解法一:头插法

    思路:
    head指针,指向原列表的头节点,后续用于操作的节点指针
    创建一个新的节点newHead,开始指向null,该节点始终用来指向新链表的头节点
    创建一个next指针,用来备份head指针在原链表中的下一个节点
    步骤1、创建next指针备份head的下一个节点
    步骤2、操纵head节点指向newHead节点的next(也就是指向新链表的头节点)
    image.png
    步骤3、使得newHead指向head节点
    步骤4、head节点指向next指向的节点
    image.png
    重复操作即可
  1. public static class ListNode {
  2. int val;
  3. ListNode next;
  4. ListNode() {
  5. }
  6. ListNode(int val) {
  7. this.val = val;
  8. }
  9. ListNode(int val, ListNode next) {
  10. this.val = val;
  11. this.next = next;
  12. }
  13. }
  14. public static ListNode reverseList(ListNode head) {
  15. ListNode newHead = new ListNode(-1);
  16. while (head != null) {
  17. ListNode next = head.next;
  18. head.next = newHead.next;
  19. newHead.next = head;
  20. head = next;
  21. }
  22. return newHead.next;
  23. }

解法二:递归

思路:
image.png
使用递归要注意,reverseList(next)递归完成之后,就默认其中部分完成了所有操作,即已经反转了,只需要将当前位置的头节点反转完成即可(即递归完之后的代码步骤)
千万不要试着取理解每一次递归的具体操作,很容易将自己绕晕😂😂😂(过来人经验:走火入魔很痛苦),知道这个以后发现递归真的超好用,顿悟了!

  1. public static ListNode reverseList(ListNode head) {
  2. if (head == null || head.next == null) { //递归出口
  3. return head;
  4. }
  5. ListNode next = head.next;
  6. ListNode newHead = reverseList(next); //next之后经过递归已经完成反向
  7. next.next = head;
  8. head.next = null;
  9. return newHead; 返回头节点
  10. }

21. 合并两个有序链表

将两个升序链表合并为一个新的升序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
✅链表 - 图6

解法一:暴力破解

时间复杂度 O(M+N)
空间复杂度 O(1)
空间换时间,多了一个哑节点的空间,一次遍历通过
image.png

  1. public static ListNode mergeTwoLists(ListNode l1, ListNode l2) {
  2. ListNode head = new ListNode(-1);//哑节点
  3. ListNode prev = head;
  4. while (l1 != null && l2 != null) {
  5. if (l1.val < l2.val) {
  6. prev.next = l1;
  7. l1 = l1.next;
  8. } else {
  9. prev.next = l2;
  10. l2 = l2.next;
  11. }
  12. prev = prev.next;
  13. }
  14. // 任一为空,直接连接另一条链表
  15. if (l1 == null) {
  16. prev.next = l2;
  17. }
  18. if (l2 == null) {
  19. prev.next = l1;
  20. }
  21. return head;
  22. }

解法二:递归

时间复杂度 O(M+N)
空间复杂度 O(M+N) 最坏情况下需要叠加栈
image.png

  1. public static ListNode mergeTwoLists(ListNode l1, ListNode l2) {
  2. if (l1 == null) return l2;
  3. if (l2 == null) return l1;
  4. if (l1.val < l2.val) {
  5. l1.next = mergeTwoLists(l1.next, l2);
  6. return l1;
  7. } else {
  8. l2.next = mergeTwoLists(l1, l2.next);
  9. return l2;
  10. }
  11. }

83. 删除排序链表中的重复元素

存在一个按升序排列的链表,给你这个链表的头节点head,请你删除所有重复的元素,使每个元素只出现一次
返回同样按升序排列的结果链表。
✅链表 - 图9

解法一:暴力

  1. class Solution {
  2. public ListNode deleteDuplicates(ListNode head) {
  3. if(head == null || head.next == null){
  4. return head;
  5. }
  6. ListNode temp = head;//temp指针始终指向已经删除重复元素节点链表的尾节点
  7. ListNode next = head.next;//该指针循环遍历原来链表
  8. while(next != null){
  9. if(temp.val != next.val){
  10. temp.next = next;
  11. temp = temp.next;
  12. }
  13. next = next.next;
  14. }
  15. temp.next = null;//最后给尾节滞空,防止链表最后有多个相同元素
  16. return head;
  17. }
  18. }

解法二:递归

  1. class Solution {
  2. public ListNode deleteDuplicates(ListNode head) {
  3. if(head == null || head.next == null){
  4. return head;
  5. }
  6. head.next = deleteDuplicates(head.next);
  7. return head.val == head.next.val?head.next:head;
  8. }
  9. }

19. 删除链表的倒数第 N 个结点

给你一个链表,删除链表的倒数第 n个结点,并且返回链表的头结点。
进阶:你能尝试使用一趟扫描实现吗?
✅链表 - 图10
思路:

image.png

  1. class Solution {
  2. public ListNode removeNthFromEnd(ListNode head, int n) {
  3. if(head == null || head.next == null){
  4. return null;
  5. }
  6. ListNode dummyHead = new ListNode(-1); //设置哨兵防止删除数组第一个元素!!!
  7. dummyHead.next = head;
  8. ListNode prev = dummyHead;
  9. ListNode tail = dummyHead;
  10. int flag = 0;
  11. while(tail != null){
  12. if(flag > n){ //保持俩个指针距离位n+1
  13. prev = prev.next;
  14. }
  15. tail = tail.next;
  16. flag++;
  17. };
  18. ListNode temp = prev.next;
  19. prev.next = prev.next.next;
  20. temp.next = null;//删除节点滞空!
  21. return dummyHead.next;//记得跳过哨兵
  22. }
  23. }

优化:

  1. class Solution {
  2. public ListNode removeNthFromEnd(ListNode head, int n) {
  3. ListNode fast = head;
  4. while( n-- > 0 ){
  5. fast = fast.next;
  6. }
  7. if(fast == null) return head.next;
  8. ListNode slow = head;
  9. while( fast.next != null ){
  10. slow = slow.next;
  11. fast = fast.next;
  12. }
  13. slow.next = slow.next.next;
  14. return head;
  15. }
  16. }

24. 两两交换链表中的节点

给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
✅链表 - 图12
思路:
image.png

  1. public static ListNode swapPairs(ListNode head) {
  2. ListNode node = new ListNode(-1); //设置哨兵节点
  3. node.next = head;
  4. ListNode first = node;
  5. while (first.next != null && first.next.next != null) {
  6. ListNode second = first.next;
  7. ListNode third = first.next.next;
  8. //交换
  9. first.next = third;
  10. second.next = third.next;
  11. third.next = second;
  12. //移动first指针
  13. first = second;
  14. }
  15. return node.next;
  16. }

445. 两数相加 II

给你两个 非空 链表来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储一位数字。将这两数相加会返回一个新的链表。
你可以假设除了数字 0 之外,这两个数字都不会以零开头。

进阶:
如果输入链表不能修改该如何处理?换句话说,你不能对列表中的节点进行翻转。

示例:
输入:(7 -> 2 -> 4 -> 3) + (5 -> 6 -> 4) 输出: 7 -> 8 -> 0 -> 7
解释: 7243 + 564 = 7807

利用栈结构实现

  1. public static ListNode addTwoNumbers(ListNode l1, ListNode l2) {
  2. ListNode node = new ListNode(-1);
  3. ListNode index = node;
  4. Stack s0 = new Stack<Integer>();
  5. Stack s1 = new Stack<Integer>();
  6. Stack s2 = new Stack<Integer>();
  7. //放入元素
  8. while (l1 != null) {
  9. s1.push(l1.val);
  10. l1 = l1.next;
  11. }
  12. while (l2 != null) {
  13. s2.push(l2.val);
  14. l2 = l2.next;
  15. }
  16. int num = 0;//进位存储
  17. //取出元素并相加
  18. while (!s1.empty() && !s2.empty()) {
  19. int temp = num + (int) s1.pop() + (int) s2.pop();
  20. num = temp / 10;//获取进位值
  21. temp = temp % 10;//获取个位数
  22. s0.push(temp);
  23. }
  24. //将剩余链表种的值取出
  25. while (!s1.empty()){
  26. int temp = num + (int) s1.pop();
  27. num = temp / 10;//获取进位值
  28. temp = temp % 10;//获取个位数
  29. s0.push(temp);
  30. }
  31. while (!s2.empty()){
  32. int temp = num + (int) s2.pop();
  33. num = temp / 10;//获取进位值
  34. temp = temp % 10;//获取个位数
  35. s0.push(temp);
  36. }
  37. //判断是否还有进位
  38. if(num != 0){
  39. s0.push(num);
  40. }
  41. //生成新链表
  42. while (!s0.empty()){
  43. index.next = new ListNode((int)s0.pop());
  44. index = index.next;
  45. }
  46. return node.next;
  47. }

优化

  1. public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
  2. Stack<Integer> l1Stack = buildStack(l1);
  3. Stack<Integer> l2Stack = buildStack(l2);
  4. ListNode head = new ListNode(-1);
  5. int carry = 0;
  6. while (!l1Stack.isEmpty() || !l2Stack.isEmpty() || carry != 0) {
  7. int x = l1Stack.isEmpty() ? 0 : l1Stack.pop();
  8. int y = l2Stack.isEmpty() ? 0 : l2Stack.pop();
  9. int sum = x + y + carry;
  10. ListNode node = new ListNode(sum % 10);
  11. node.next = head.next;
  12. head.next = node;
  13. carry = sum / 10;
  14. }
  15. return head.next;
  16. }
  17. private Stack<Integer> buildStack(ListNode l) {
  18. Stack<Integer> stack = new Stack<>();
  19. while (l != null) {
  20. stack.push(l.val);
  21. l = l.next;
  22. }
  23. return stack;
  24. }

234. 回文链表

请判断一个链表是否为回文链表。
示例 1:
输入: 1->2
输出: false
示例 2:
输入: 1->2->2->1
输出: true
进阶:
你能否用 O(n) 时间复杂度和 O(1) 空间复杂度解决此题?

进阶以后难度为中等

解法一:数组列表

思路:
将链表存入数组列表中,利用双向指针比较元素是否相等

复杂度分析
时间复杂度:O(n),其中 n 指的是链表的元素个数。
第一步: 遍历链表并将值复制到数组中,O(n)。
第二步:双指针判断是否为回文,执行了 O(n/2) 次的判断,即 O(n)。
总的时间复杂度:O(2n) = O(n)。
空间复杂度:O(n),其中 n 指的是链表的元素个数,我们使用了一个数组列表存放链表的元素值。

注意: get()方法对127以内的整形数值会直接封装成值返回,但是对大于127的值会返回对象,因此你的比较是进行对象地址的比较,实际上是不对的。你得用equals()方法比较。 1、首先Integer是对象,比较两个对象是否相等不能使用==,应该使用equals 2、让我们查看jdk源码,在使用==比较Integer类型时,默认会缓存 -128至127(包括-128和127),如果超过这个范围,则会new,所以两个对象的地址不一样,==则会返回false。 3、如果非要使用==,-128至127(包括-128和127)范围内可以使用==比较,返回true,反之false。

  1. public static boolean isPalindrome(ListNode head) {
  2. //结束条件,链表只有一个或为空也是回文
  3. if(head == null || head.next == null) return true;
  4. List<Integer> list = new ArrayList<Integer>();
  5. // 将链表的值复制到数组中
  6. while(head != null){
  7. list.add(head.val);
  8. head = head.next;
  9. }
  10. // 使用双指针判断是否回文
  11. int before = 0;
  12. int back = list.size()-1;
  13. while(before < back){
  14. if(!list.get(before).equals(list.get(back))){
  15. return false;
  16. }
  17. before++;
  18. back--;
  19. }
  20. return true;
  21. }

解法二:利用栈

  1. // 利用栈,和数组链表一样
  2. public static boolean isPalindrome(ListNode head) {
  3. if (head == null || head.next == null) return true;
  4. Stack<Integer> stack = new Stack<>();
  5. ListNode node = head;
  6. // 把链表存入栈中
  7. while (node != null) {
  8. stack.push(node.val);
  9. node = node.next;
  10. }
  11. //在来一次和栈中元素比较
  12. node = head;
  13. while (node != null) {
  14. if (node.val != stack.pop()) {
  15. return false;
  16. }
  17. node = node.next;
  18. }
  19. return true;
  20. }

解法三:递归

该解法超出时间限制😂

  1. //递归
  2. public static boolean isPalindrome(ListNode head) {
  3. //结束条件,链表只有一个或为空也是回文
  4. if(head == null || head.next == null) return true;
  5. ListNode tail = head.next;
  6. ListNode temp = head;
  7. while (tail.next != null){
  8. temp = tail;
  9. tail = tail.next;
  10. }
  11. //while循环完毕后 tail指向最后一个元素
  12. //判断首尾元素是否相等
  13. if(head.val != tail.val) return false;
  14. //删除首尾相同元素的节点
  15. head = head.next;
  16. //删除尾部
  17. temp.next = null;
  18. //递归调用,判断去掉首尾元素的剩余链表
  19. return isPalindrome(head);
  20. }

方法四:进阶最优——反转链表

未命名绘图.png

  1. // 最优:反转链表
  2. public static boolean isPalindrome(ListNode head) {
  3. if (head == null || head.next == null) return true;
  4. // 1、利用快慢指针找到中间节点
  5. ListNode fast = head;
  6. ListNode slow = head;
  7. ListNode prev = null; //指向slow的之前一个
  8. while (fast != null && fast.next != null) {
  9. fast = fast.next.next;
  10. prev = slow;
  11. slow = slow.next;
  12. }
  13. // 2、切断链表
  14. if (fast == null) { // 偶数情况
  15. prev.next = null; // 切断
  16. } else { // 奇数情况
  17. slow = slow.next; // 跳过中间节点
  18. prev.next = null;// 切断
  19. }
  20. // 3、反转链表
  21. ListNode newslow = reverseList(slow);
  22. // 4、比较
  23. while (head != null) {
  24. if (head.val != newslow.val) {
  25. return false;
  26. }
  27. head = head.next;
  28. newslow = newslow.next;
  29. }
  30. return true;
  31. }
  32. //206.反转链表
  33. public static ListNode reverseList(ListNode head) {
  34. ListNode node = new ListNode(-1);
  35. while (head != null) {
  36. ListNode next = head.next;
  37. head.next = node.next;
  38. node.next = head;
  39. head = next;
  40. }
  41. return node.next;
  42. }

725. 分隔链表

给定一个头结点为 root 的链表, 编写一个函数以将链表分隔为 k 个连续的部分。
每部分的长度应该尽可能的相等: 任意两部分的长度差距不能超过 1,也就是说可能有些部分为 null。
这k个部分应该按照在链表中出现的顺序进行输出,并且排在前面的部分的长度应该大于或等于后面的长度。
返回一个符合上述规则的链表的列表。
举例: 1->2->3->4, k = 5 // 5 结果 [ [1], [2], [3], [4], null ]

示例 1: 输入: root = [1, 2, 3], k = 5 输出: [[1],[2],[3],[],[]] 解释: 输入输出各部分都应该是链表,而不是数组。 例如, 输入的结点 root 的 val= 1, root.next.val = 2, oot.next.next.val = 3, 且 root.next.next.next = null。 第一个输出 output[0] 是 output[0].val = 1, output[0].next = null。 最后一个元素 output[4] 为 null, 它代表了最后一个部分为空链表。
示例 2: 输入: root = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], k = 3 输出: [[1, 2, 3, 4], [5, 6, 7], [8, 9, 10]] 解释: 输入被分成了几个连续的部分,并且每部分的长度相差不超过1.前面部分的长度大于等于后面部分的长度。

  1. public static ListNode[] splitListToParts(ListNode root, int k) {
  2. int length = 0;
  3. ListNode node = root;
  4. // 获取链表元素个数
  5. while (node != null) {
  6. length++;
  7. node = node.next;
  8. }
  9. ListNode[] arrListNode = new ListNode[k];
  10. //计算分隔链表位置
  11. int mod = length % k; // 取余计算出前几个子链表长度需要+1
  12. int size = length / k; // 子链表平均长度
  13. node = root;
  14. for (int i = 0; node != null && i < k ; i++) {
  15. ListNode son = node;
  16. arrListNode[i] = son; // 对数组赋值子链表头部
  17. int curSize = size + (mod-- > 0 ? 1 : 0); // 子链表长度
  18. for (int j = 0; j < curSize-1; j++) { //走到子链表尾部
  19. node = node.next;
  20. }
  21. //切断链表
  22. ListNode next = node.next;
  23. node.next = null;
  24. node = next;
  25. }
  26. return arrListNode;
  27. }

328. 奇偶链表

给定一个单链表,把所有的奇数节点和偶数节点分别排在一起。请注意,这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性。
请尝试使用原地算法完成。你的算法的空间复杂度应为 O(1),时间复杂度应为 O(nodes),nodes 为节点总数。

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

示例 2: 输入: 2->1->3->5->6->4->7->NULL 输出: 2->3->6->7->1->5->4->NULL

说明: 应当保持奇数节点和偶数节点的相对顺序。 链表的第一个节点视为奇数节点,第二个节点视为偶数节点,以此类推。

解法一:

一开始写出来的,和方法二一样,比较笨和繁琐

  1. public static ListNode oddEvenList1(ListNode head) {
  2. if (head == null || head.next == null) {
  3. return head;
  4. }
  5. ListNode node = head.next.next; //设置遍历节点指针
  6. ListNode j = head; //奇数链表尾指针
  7. ListNode o = head.next; //偶数链表尾指针
  8. while (node != null && node.next != null) {
  9. ListNode next = node.next;
  10. // 将偶数链表链接到node下一个节点
  11. o.next = next;
  12. o = next;
  13. // 将node节点插入到奇数链表尾部
  14. node.next = j.next;
  15. j.next = node;
  16. j = node;
  17. node = next.next;//node移动俩位到下一个 奇数 节点上
  18. }
  19. //如果链表长度为奇数,需要处理最后一个奇数节点
  20. if (node != null) {
  21. ListNode next = node.next;
  22. o.next = next;
  23. node.next = j.next;
  24. j.next = node;
  25. }
  26. return head;
  27. }

解法二:分离节点后合并

image.png

  1. public static ListNode oddEvenList(ListNode head) {
  2. if (head == null || head.next == null) {
  3. return head;
  4. }
  5. ListNode jtail = head;//奇链表尾节点
  6. ListNode ohead = head.next; //偶列表头节点
  7. ListNode otail = ohead; //偶列表尾节点
  8. while (otail != null && otail.next != null){
  9. jtail.next = otail.next;
  10. jtail = jtail.next;
  11. otail.next = otail.next.next;
  12. otail = otail.next;
  13. }
  14. //链接奇偶链表
  15. jtail.next = ohead;
  16. return head;
  17. }