LeetCode每日一题(java version)--(链表专栏)

本文介绍了LeetCode中关于链表的一系列问题,包括相交链表、链表反转、合并两个有序链表、删除排序链表中的重复元素、删除链表的倒数第N个节点、两两交换链表中的节点、两数相加II、回文链表和分隔链表等。每个问题都提供了解题思路和时间复杂度分析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

160 相交列表(Easy)

//编写一个程序,找到两个单链表相交的起始节点。 // // 如下面的两个链表: // // // // 在节点 c1 开始相交。 // // // // 示例 1: // // // // 输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, s //kipB = 3 //输出:Reference of the node with value = 8 //输入解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1 //,8,4,5]。在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。 // // // // // 示例 2: // // // // 输入:intersectVal = 2, listA = [0,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = // 1 //输出:Reference of the node with value = 2 //输入解释:相交节点的值为 2 (注意,如果两个链表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [0,9,1,2,4],链表 B 为 [3,2,4 //]。在 A 中,相交节点前有 3 个节点;在 B 中,相交节点前有 1 个节点。 // // // // // 示例 3: // // // // 输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2 //输出:null //输入解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。由于这两个链表不相交,所以 intersectVal 必须为 0,而 // skipA 和 skipB 可以是任意值。 //解释:这两个链表不相交,因此返回 null。 // // // // // 注意: // // // 如果两个链表没有交点,返回 null. // 在返回结果后,两个链表仍须保持原有的结构。 // 可假定整个链表结构中没有循环。 // 程序尽量满足 O(n) 时间复杂度,且仅用 O(1) 内存。 // // Related Topics 链表 // 👍 837 👎 0

解题思路:

当访问 A 链表的指针访问到链表尾部时,令它从链表 B 的头部开始访问链表 B;同样地,当访问 B 链表的指针访问到链表尾部时,令它从链表 A 的头部开始访问链表 A。这样就能控制访问 A 和 B 两个链表的指针能同时访问到交点。

如果不存在交点,那么 a + b = b + a,以下实现代码中 l1 和 l2 会同时为 null,从而退出循环。

 public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
     ListNode l1 = headA, l2 = headB;
     while (l1 != l2) {
         l1 = (l1 == null) ? headB : l1.next;
         l2 = (l2 == null) ? headA : l2.next;
     }
     return l1;
 }

时间复杂度:O(n)

空间复杂度:O(1)

206 链表反转(Easy)

//反转一个单链表。 // // 示例: // // 输入: 1->2->3->4->5->NULL //输出: 5->4->3->2->1->NULL // // 进阶: //你可以迭代或递归地反转链表。你能否用两种方法解决这道题? // Related Topics 链表 // 👍 1263 👎 0

解题思路:

  • 迭代

  • 递归

    •  reverseList: head=1
           reverseList: head=2
               reverseList: head=3
                   reverseList:head=4
                       reverseList:head=5 
                           终止返回
                       cur = 5
                       4.next.next->4,即5->4
                   cur=5
                   3.next.next->3,即4->3
               cur = 5
               2.next.next->2,即3->2
           cur = 5
           1.next.next->1,即2->1
           
           最后返回cur
  •  

 class Solution {
     public ListNode reverseList(ListNode head) {
         ListNode prev = null;
         ListNode curr = head;
         while (curr != null) {
             ListNode nextTemp = curr.next;
             curr.next = prev;
             prev = curr;
             curr = nextTemp;
         }
         return prev;
     }
 }
 ​

时间复杂度:O(n)

空间复杂度:O(1)

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

时间复杂度:O(n)

空间复杂度:O(n)

21 合并两个有序链表(Easy)

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

解题思路:

  • 递归

  • 迭代

 class Solution {
     public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
         if (l1 == null) {
             return l2;
         } else if (l2 == null) {
             return l1;
         } else if (l1.val < l2.val) {
             l1.next = mergeTwoLists(l1.next, l2);
             return l1;
         } else {
             l2.next = mergeTwoLists(l1, l2.next);
             return l2;
         }
 ​
     }
 }

时间复杂度:O(n+m)

空间复杂度:O(n+m) 递归调用栈空间造成的消耗

 class Solution {
     public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
         ListNode prehead = new ListNode(-1);
 ​
         ListNode prev = prehead;
         while (l1 != null && l2 != null) {
             if (l1.val <= l2.val) {
                 prev.next = l1;
                 l1 = l1.next;
             } else {
                 prev.next = l2;
                 l2 = l2.next;
             }
             prev = prev.next;
         }
 ​
         // 合并后 l1 和 l2 最多只有一个还未被合并完,我们直接将链表末尾指向未合并完的链表即可
         prev.next = l1 == null ? l2 : l1;
 ​
         return prehead.next;
     }
 }

时间复杂度:O(n+m)

空间复杂度:O(1)

83 删除排序链表中的重复元素 (Easy)

//给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。 // // 示例 1: // // 输入: 1->1->2 //输出: 1->2 // // // 示例 2: // // 输入: 1->1->2->3->3 //输出: 1->2->3 // Related Topics 链表 // 👍 408 👎 0

解题思路:

  • 由于输入的列表已排序,因此我们可以通过将结点的值与它之后的结点进行比较来确定它是否为重复结点。如果它是重复的,我们更改当前结点的 next 指针,以便它跳过下一个结点并直接指向下一个结点之后的结点。

  • 使用递归

 public ListNode deleteDuplicates(ListNode head) {
     ListNode current = head;
     while (current != null && current.next != null) {
         if (current.next.val == current.val) {
             current.next = current.next.next;
         } else {
             current = current.next;
         }
     }
     return head;
 }

时间复杂度:O(n)

空间复杂度:O(1)

 public ListNode deleteDuplicates(ListNode head) {
     if (head == null || head.next == null) return head;
     head.next = deleteDuplicates(head.next);
     return head.val == head.next.val ? head.next : head;
 }

时间复杂度:O(n)

空间复杂度:O(n)

19 删除链表的倒数第N个节点(Medium)

//给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。 // // 示例: // // 给定一个链表: 1->2->3->4->5, 和 n = 2. // //当删除了倒数第二个节点后,链表变为 1->2->3->5. // // // 说明: // // 给定的 n 保证是有效的。 // // 进阶: // // 你能尝试使用一趟扫描实现吗? // Related Topics 链表 双指针 // 👍 1018 👎 0

解题思路:

  • 两次遍历:删除从头开始第(L-n+1)个节点,首先要知道链表的长度

    • 首先我们将添加一个哑结点作为辅助,该结点位于列表头部。哑结点用来简化某些极端情况,例如列表中只含有一个结点,或需要删除列表的头部。在第一次遍历中,我们找出列表的长度 L。然后设置一个指向哑结点的指针,并移动它遍历列表,直至它到达第(L−n) 个结点那里。我们把第 (L−n) 个结点的 next 指针重新链接至第 (L−n+2) 个结点,完成这个算法

  • 一次遍历算法,使用两个指针

 

 public ListNode removeNthFromEnd(ListNode head, int n) {
     ListNode dummy = new ListNode(0);
     dummy.next = head;
     int length  = 0;
     ListNode first = head;
     while (first != null) {
         length++;
         first = first.next;
     }
     length -= n;
     first = dummy;
     while (length > 0) {
         length--;
         first = first.next;
     }
     first.next = first.next.next;
     return dummy.next;
 }

时间复杂度:O(L) 首先计算了列表的长度 L,其次找到了第(L-n)个点,执行了2L-n步

空间复杂度:O(1)

 public ListNode removeNthFromEnd(ListNode head, int n) {
     ListNode dummy = new ListNode(0);
     dummy.next = head;
     ListNode first = dummy;
     ListNode second = dummy;
     // Advances first pointer so that the gap between first and second is n nodes apart
     for (int i = 1; i <= n + 1; i++) {
         first = first.next;
     }
     // Move first to the end, maintaining the gap
     while (first != null) {
         first = first.next;
         second = second.next;
     }
     second.next = second.next.next;
     return dummy.next;
 }

时间复杂度:O(L)

空间复杂度:O(1)

24 两两交换链表中的节点 (Medium)

//给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。 // // 你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。 // // // // 示例: // // 给定 1->2->3->4, 你应该返回 2->1->4->3. // // Related Topics 链表 // 👍 647 👎 0

解题思路:

  • 递归

    • 从链表的头节点 head 开始递归。 每次递归都负责交换一对节点。由 firstNode 和 secondNode 表示要交换的两个节点。 下一次递归则是传递的是下一对需要交换的节点。若链表中还有节点,则继续递归。 交换了两个节点以后,返回 secondNode,因为它是交换后的新头。 在所有节点交换完成以后,我们返回交换后的头,实际上是原始链表的第二个节点

  • 迭代

 

 class Solution {
     public ListNode swapPairs(ListNode head) {
 ​
         // If the list has no node or has only one node left.
         if ((head == null) || (head.next == null)) {
             return head;
         }
 ​
         // Nodes to be swapped
         ListNode firstNode = head;
         ListNode secondNode = head.next;
 ​
         // Swapping
         firstNode.next  = swapPairs(secondNode.next);
         secondNode.next = firstNode;
 ​
         // Now the head is the second node
         return secondNode;
     }
 }

时间复杂度:O(n) 链表的节点数量

空间复杂度:O(n) 递归过程使用的堆栈空间

 /**
  * Definition for singly-linked list.
  * public class ListNode {
  *     int val;
  *     ListNode next;
  *     ListNode(int x) { val = x; }
  * }
  */
 class Solution {
     public ListNode swapPairs(ListNode head) {
 ​
         // Dummy node acts as the prevNode for the head node
         // of the list and hence stores pointer to the head node.
         ListNode dummy = new ListNode(-1);
         dummy.next = head;
 ​
         ListNode prevNode = dummy;
 ​
         while ((head != null) && (head.next != null)) {
 ​
             // Nodes to be swapped
             ListNode firstNode = head;
             ListNode secondNode = head.next;
 ​
             // Swapping
             prevNode.next = secondNode;
             firstNode.next = secondNode.next;
             secondNode.next = firstNode;
 ​
             // Reinitializing the head and prevNode for next swap
             prevNode = firstNode;
             head = firstNode.next; // jump
         }
 ​
         // Return the new head node.
         return dummy.next;
     }
 }

445 两数相加 II (Medium)

 

//给你两个 非空 链表来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储一位数字。将这两数相加会返回一个新的链表。 // // 你可以假设除了数字 0 之外,这两个数字都不会以零开头。 // // // // 进阶: // // 如果输入链表不能修改该如何处理?换句话说,你不能对列表中的节点进行翻转。 // // // // 示例: // // 输入:(7 -> 2 -> 4 -> 3) + (5 -> 6 -> 4) //输出:7 -> 8 -> 0 -> 7 // // Related Topics 链表 // 👍 290 👎 0

 public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
     Stack<Integer> l1Stack = buildStack(l1);
     Stack<Integer> l2Stack = buildStack(l2);
     ListNode head = new ListNode(-1);
     int carry = 0;
     while (!l1Stack.isEmpty() || !l2Stack.isEmpty() || carry != 0) {
         int x = l1Stack.isEmpty() ? 0 : l1Stack.pop();
         int y = l2Stack.isEmpty() ? 0 : l2Stack.pop();
         int sum = x + y + carry;
         ListNode node = new ListNode(sum % 10);
         node.next = head.next;
         head.next = node;
         carry = sum / 10;
     }
     return head.next;
 }
 ​
 private Stack<Integer> buildStack(ListNode l) {
     Stack<Integer> stack = new Stack<>();
     while (l != null) {
         stack.push(l.val);
         l = l.next;
     }
     return stack;
 }

时间复杂度:O(max(m,n))

空间复杂度:O(m+n)

234 回文链表 (Easy)

//请判断一个链表是否为回文链表。 // // 示例 1: // // 输入: 1->2 //输出: false // // 示例 2: // // 输入: 1->2->2->1 //输出: true // // // 进阶: //你能否用 O(n) 时间复杂度和 O(1) 空间复杂度解决此题? // Related Topics 链表 双指针 // 👍 661 👎 0 解题思路:

我们可以分为以下几个步骤:

  • 找到前半部分链表的尾节点。

  • 反转后半部分链表。

  • 判断是否为回文。

  • 返回结果。

  • 执行步骤一,我们可以计算链表节点的数量,然后遍历链表找到前半部分的尾节点。

或者可以使用快慢指针在一次遍历中找到:慢指针一次走一步,快指针一次走两步,快慢指针同时出发。当快指针移动到链表的末尾时,慢指针到链表的中间。通过慢指针将链表分为两部分。

若链表有奇数个节点,则中间的节点应该看作是前半部分。

步骤二可以使用在反向链表问题中找到解决方法来反转链表的后半部分。

步骤三比较两个部分的值,当后半部分到达末尾则比较完成,可以忽略计数情况中的中间节点。

步骤四与步骤二使用的函数相同

 public boolean isPalindrome(ListNode head) {
     if (head == null || head.next == null) return true;
     ListNode slow = head, fast = head.next;
     while (fast != null && fast.next != null) {
         slow = slow.next;
         fast = fast.next.next;
     }
     if (fast != null) slow = slow.next;  // 偶数节点,让 slow 指向下一个节点
     cut(head, slow);                     // 切成两个链表
     return isEqual(head, reverse(slow));
 }
 ​
 private void cut(ListNode head, ListNode cutNode) {
     while (head.next != cutNode) {
         head = head.next;
     }
     head.next = null;
 }
 ​
 private ListNode reverse(ListNode head) {
     ListNode newHead = null;
     while (head != null) {
         ListNode nextNode = head.next;
         head.next = newHead;
         newHead = head;
         head = nextNode;
     }
     return newHead;
 }
 ​
 private boolean isEqual(ListNode l1, ListNode l2) {
     while (l1 != null && l2 != null) {
         if (l1.val != l2.val) return false;
         l1 = l1.next;
         l2 = l2.next;
     }
     return true;
 }

725 分隔链表(Medium)

//给定一个头结点为 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, \root.next.next.val = 3, 且 root.ne //xt.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.前面部分的长度大于等于后面部分的长度。 // // // // // 提示: // // // root 的长度范围: [0, 1000]. // 输入的每个节点的大小范围:[0, 999]. // k 的取值范围: [1, 50]. // // // // Related Topics 链表 // 👍 100 👎 0

解题思路:

如果链表有 N个结点,则分隔的链表中每个部分中都有 n/k个结点,且前 N%k 部分有一个额外的结点。我们可以用一个简单的循环来计算 N

 class Solution {
     public ListNode[] splitListToParts(ListNode root, int k) {
         ListNode cur = root;
         int N = 0;
         while (cur != null) {
             cur = cur.next;
             N++;
         }
 ​
         int width = N / k, rem = N % k;
 ​
         ListNode[] ans = new ListNode[k];
         cur = root;
         for (int i = 0; i < k; ++i) {
             ListNode head = new ListNode(0), write = head;
             for (int j = 0; j < width + (i < rem ? 1 : 0); ++j) {
                 write = write.next = new ListNode(cur.val);
                 if (cur != null) cur = cur.next;
             }
             ans[i] = head.next;
         }
         return ans;
     }
 }

时间复杂度:O(N+k)

空间复杂度:O(max(N,k))

328 奇偶链表 (Medium)

//给定一个单链表,把所有的奇数节点和偶数节点分别排在一起。请注意,这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性。 // // 请尝试使用原地算法完成。你的算法的空间复杂度应为 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 // // 说明: // // // 应当保持奇数节点和偶数节点的相对顺序。 // 链表的第一个节点视为奇数节点,第二个节点视为偶数节点,以此类推。 // // Related Topics 链表 // 👍 254 👎 0

解题思路:

将奇节点放在一个链表里,偶链表放在另一个链表里。然后把偶链表接在奇链表的尾部。

 class Solution{
     public ListNode oddEvenList(ListNode head){
         // 特判:头结点为 null,返回null
         // head是奇链表的头
         if (head == null) return null;
 ​
         // odd是奇链表的当前节点,先初始化为head(初始化为奇链表头)
         ListNode odd  = head;
         // even是偶链表的当前节点,初始化为第二个节点也就是head.next
         ListNode even = head.next;
         // evenHead是偶链表的头节点,初始化为链表第二个节点(初始化为奇链表头的下一个节点)
         ListNode evenHead = even;
 ​
         while (even != null && even.next != null){
             // 这里while退出判断条件还是画图一下才能理解(也就是官方题解的STEP2)
             odd.next = even.next;  // 相当于odd.next = odd.next.next(跳过一个偶数节点)
             odd = odd.next;        // odd向前前进一位
             even.next = odd.next;   // 奇链表的下一个节点就是偶链表的节点
             even = even.next;       // even向前前进一位
         }
         // while条件结束,把偶链表头指针拼接到奇链表的最后
         odd.next = evenHead;
         // 返回奇链表头就是返回整个奇偶排序后的链表
         return head;
     }
 }
  • 时间复杂度:O(n) 是链表的节点数,这里遍历了一遍链表

  • 空间复杂度:O(1) 这里

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值