LeetCode刷题
数据结构:
一、链表
160,相交链表,easy
206,反转链表,easy
21,合并两个有序链表,easy
83,删除排序链表中的重复元素,easy
83-Ⅱ.删除排序链表中的重复元素,middle
19,删除链表的倒数第N个节点,middle
24,两两交换链表中的节点,middle
445,两数相加Ⅱ,middle
234,回文链表,easy
725,分隔链表,middle
328,奇偶链表,middle
160,相交链表,easy
输入两个链表,找出它们的第一个公共节点。
如下面的两个链表:



- 方法:双指针。
- 思路:定义节点pA指向headA,节点pB指向headB。
- 如果两链表长度相同
- 如果两链表长度不同,先走完的指针指向另一个链表的头节点(如示例2的pB先走完,则指向headA),两指针再次出发,直到后走完的指针也走完了当前链表,使其指向另一链表(即pA走完,指向headB),两指针再次出发,直到找到交点或没有交点走完两链表。
- 代码:
public class Solution { public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode pA = headA; ListNode pB = headB;
if (pA == null || pB == null) { return null; }
while(pA != null && pB != null){ pA = pA.next; pB = pB.next; }
if(pA == null){ pA = headB; } else { pB = headA; }
while(pA != null && pB != null){ pA = pA.next; pB = pB.next; } if(pA == null){ pA = headB; } else { pB = headA; }
while(pA != pB){ pA = pA.next; pB = pB.next; } return pA; } }
|
简化:
public class Solution { public ListNode getIntersectionNode(ListNode headA, ListNode headB) { ListNode pA = headA; ListNode pB = headB; while(pA != pB){ pA = pA == null ? headB:pA.next; pB = pB == null ? headA:pB.next; } return pA; } }
|
206,反转链表,easy
反转一个单链表。
示例:
输入: 1->2->3->4->5->NULL 输出: 5->4->3->2->1->NULL
|
方法一:递归。
- 思路:先递归到底, 找到最后一个节点, 然后从最后一个节点开始, 把箭头方向掉转。

递归出口:链表为空或递归到链表的尾节点

递归体:假如归到节点2,
image-20201203164342559
代码:
class Solution { public ListNode reverseList(ListNode head) {
if(head == null || head.next == null) return head;
ListNode cur = reverseList(head.next); head.next.next = head; head.next = null;
return cur; } }
|
方法二:迭代。
- 思路:构建新链表,将原链表加入新链表,并调转顺序。

直到head = null
class Solution { public ListNode reverseList(ListNode head) { ListNode newHead = null; while(head != null){ ListNode temp = head.next; head.next = newHead; newHead = head; head = temp; } return newHead; } }
|
21,合并两个有序链表,easy
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例:
输入:1->2->4, 1->3->4 输出:1->1->2->3->4->4
|
- 方法一:迭代。
- 思路:建立新节点(值为0),再定义一个临时节点temp存储链表。比较 l1.val 与 l2.val 。如果 l1.val < l2.val,temp的下个节点设为l1,并将l1 向右移;如果 l1.val >= l2.val,对 l2 进行上述操作。如果一个链表全部存完为空,将另一个链表剩余节点加入temp.next。最后返回newHead.next。
- 代码:
class Solution { public ListNode mergeTwoLists(ListNode l1, ListNode l2) { if(l1 == null) return l2; if(l2 == null) return l1; ListNode newHead = new ListNode(0); ListNode temp = newHead; while(l1 != null && l2 != null){ if(l1.val < l2.val){ temp.next = l1; l1 = l1.next; } else if(l1.val >= l2.val){ temp.next = l2; l2 = l2.next; } temp = temp.next; if(l1 == null){ temp.next = l2; } if(l2 == null){ temp.next = l1; } } return newHead.next; } }
|
class Solution { public ListNode mergeTwoLists(ListNode l1, ListNode l2) { if(l1 == null) return l2; if(l2 == null) return l1;
ListNode newHead = new ListNode(0); if(l1.val < l2.val){ newHead = l1; newHead.next = mergeTwoLists(l1.next,l2); } else if(l1.val >= l2.val){ newHead = l2; newHead.next = mergeTwoLists(l1,l2.next); } return newHead; } }
|
83,删除排序链表中的重复元素,easy
给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。
示例 1:
示例 2:
输入: 1->1->2->3->3 输出: 1->2->3
|
- 方法一:直接法。
- 思路:定义辅助单指针temp = head,比较temp与temp.next的值,如果相同,
temp.next = temp.next.next
,即
如果不同,temp = temp.next
,指针右移。
class Solution { public ListNode deleteDuplicates(ListNode head) { ListNode temp = head; while(temp != null && temp.next != null){ if(temp.val == temp.next.val){ temp.next = temp.next.next; } else{ temp = temp.next; } } return head; } }
|
- 方法二:双指针。
- 思路:快慢指针。快指针用于探路,慢指针为结果链表指针。快指针如果与慢指针的值相等,快指针右移;不相等,保存到慢指针快指针再右移。
class Solution { public ListNode deleteDuplicates(ListNode head) { if(head == null) return null; ListNode right = head.next; ListNode left = head; while(right != null){ if(right.val != left.val){ left.next = right; left = left.next; } else{ right = right.next; } } left.next = null; return head; } }
|
- 方法三:递归。
- 思路:把链表看成 头节点->没有重复元素的排序链表,则比较头节点与子链表的头节点,如果相同,返回子链表的头节点;否则,返回head。
class Solution { public ListNode deleteDuplicates(ListNode head) {
if(head == null || head.next == null) return head;
ListNode newHead = deleteDuplicates(head.next); head.next = newHead;
return head.val == newHead.val ? newHead : head; } }
|
83-Ⅱ.删除排序链表中的重复元素,middle
给定一个排序链表,删除所有含有重复数字的节点,只保留原始链表中 没有重复出现 的数字。
示例 1:
输入: 1->2->3->3->4->4->5 输出: 1->2->5
|
示例 2:
输入: 1->1->1->2->3 输出: 2->3
|
- 方法一:递归。
- 思路:和上面那道题思路类似,但要判断头节点与后面元素是否相同。如果相同,找到第一个不重复的元素开始递归;如果不同,与上面题类似。
- 代码:
class Solution { public ListNode deleteDuplicates(ListNode head) {
if(head == null || head.next == null) return head; if(head.val == head.next.val){ while(head != null && head.next != null && head.val == head.next.val){ head = head.next; } return deleteDuplicates(head.next); } else{ ListNode newHead = deleteDuplicates(head.next); head.next = newHead;
return head; } } }
|
- 方法二:双指针-快慢指针。
- 思路:建立虚拟头节点dummy,双指针left指向dummy,right指向head,判断left.next 与 right.val是否相等。如果不相等,两指针右移;如果相等,right需要跳过所有重复数字,再令left的下一位为 right.next(第一个不重复的数字)。最后返回dummy之后的节点。
- 代码:
class Solution { public ListNode deleteDuplicates(ListNode head) {
if(head == null || head.next == null) return head;
ListNode dummy = new ListNode(-1); dummy.next = head; ListNode left = dummy; ListNode right = head; while(right != null && right.next != null){ if(left.next.val != right.next.val){ left = left.next; right = right.next; } else{ while(right != null && right.next != null && left.next.val == right.next.val){ right = right.next; } left.next = right.next; right = right.next; } } return dummy.next; } }
|
19,删除链表的倒数第N个节点,middle
给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。
示例:
给定一个链表: 1->2->3->4->5, 和 n = 2.
当删除了倒数第二个节点后,链表变为 1->2->3->5.
|
- 方法一:先求出链表长度,就可以找到要删除节点的前一个节点,再使他指向后一个节点即可。
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) { if(head == null || head.next == null) return null; ListNode temp = head;
int length = getLength(head); if(length == n) return head.next;
for(int i = 0; i < length - n - 1; i++){ temp = temp.next; } temp.next = temp.next.next; return head; } public int getLength(ListNode head){ int length = 1; while(head.next != null){ length++; head = head.next; } return length; } }
|
- 方法二:双指针-快慢指针。
- 思路:令快指针先走 n 步,慢指针再与快指针同时出发,直到快指针走到链表尾部,此时慢指针走到要删除节点的前一个节点,跳过即可。有些类似第160题.相交链表
- 代码:
class Solution { public ListNode removeNthFromEnd(ListNode head, int n) { public ListNode removeNthFromEnd(ListNode head, int n) { ListNode fast = head; ListNode slow = head; for(int i = 0; i < n; i++){ fast = fast.next; } if(fast == null){ return head.next; } while(fast.next != null){ fast = fast.next; slow = slow.next; } slow.next = slow.next.next; return head; } }
|
24,两两交换链表中的节点,middle
给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

示例 1:
输入:head = [1,2,3,4] 输出:[2,1,4,3]
|
示例 2:
示例 3:
- 方法一:递归。
- 思路:两个相邻节点看为一组,组间节点交换(递归实现),再调整组间顺序。
- 代码:
class Solution { public ListNode swapPairs(ListNode head) { if(head == null || head.next == null) return head; ListNode temp = head.next; head.next = swapPairs(head.next.next); temp.next = head;
return temp; } }
|
- 方法二:迭代。
- 思路:定义一个虚拟头节点dummy,三个指针:left、right用于进行交换,temp用于连接两次迭代之间的节点。
- 代码:
class Solution { public ListNode swapPairs(ListNode head) { if(head == null || head.next == null) return head; ListNode dummy = new ListNode(-1); dummy.next = head; ListNode left = dummy; ListNode right = dummy; ListNode temp = dummy; while(right != null && right.next != null && right.next.next != null){ left = left.next; right = right.next.next;
temp.next = right; left.next = right.next; right.next = left; temp = left; right = left; } return dummy.next; } }
|
445,两数相加Ⅱ,middle
给你两个 非空 链表来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储一位数字。将这两数相加会返回一个新的链表。
你可以假设除了数字 0 之外,这两个数字都不会以零开头。
进阶:
如果输入链表不能修改该如何处理?换句话说,你不能对列表中的节点进行翻转。
示例:
输入:(7 -> 2 -> 4 -> 3) + (5 -> 6 -> 4) 输出: 7 -> 8 -> 0 -> 7
|
- 题目说明:题目中说明最高位位于链表头部,即 7243 + 564 = 7807,最后输出的即7 -> 8 -> 0 -> 7。
- 方法一:借助栈。
- 思路:逆序先想到栈来存储链表节点的值,相加后再弹出,存储到一个新的链表。注意要记录进位,加到下一位。
- 图解:

- 代码:
class Solution { public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
Stack<Integer> stack1 = new Stack<>(); Stack<Integer> stack2 = new Stack<>(); while(l1 != null){ stack1.push(l1.val); l1 = l1.next; } while(l2 != null){ stack2.push(l2.val); l2 = l2.next; } int carry = 0; ListNode head = null; while(!stack1.isEmpty() || !stack2.isEmpty() || carry > 0){ int sum = carry; sum += stack1.isEmpty() ? 0 : stack1.pop(); sum += stack2.isEmpty() ? 0 : stack2.pop();
ListNode node = new ListNode(sum % 10); node.next = head; head = node;
carry = sum / 10; } return head; } }
|
注意:while条件里添加carry > 0是为了
也可以单独判断carry,如果stack1、stack2都为空了但还有进位,则创建新节点加入结果。
while(!stack1.isEmpty() || !stack2.isEmpty() ){ int sum = carry; sum += stack1.isEmpty() ? 0 : stack1.pop(); sum += stack2.isEmpty() ? 0 : stack2.pop();
ListNode node = new ListNode(sum % 10); node.next = head; head = node;
carry = sum / 10; } if(carry > 0){ ListNode node1 = new ListNode(carry); node1.next = head; head = node1; } return head; }
|
- 方法二:反转链表。
- 思路:因为要从链尾开始相加,所以先反转链表,两个链表从头开始相加节点的值,再将新的链表反转,得到结果。
- 代码:
class Solution { public ListNode addTwoNumbers(ListNode l1, ListNode l2) { if(l1 == null || l2 == null) return l1 == null ? l2 : l1; ListNode newl1 = reverse(l1); ListNode newl2 = reverse(l2); ListNode dummy = new ListNode(-1); ListNode cur = dummy; int carry = 0; while(newl1 != null || newl2 != null || carry > 0){ int sum = carry; sum += newl1 == null ? 0 : newl1.val; sum += newl2 == null ? 0 : newl2.val;
ListNode node = new ListNode(sum % 10); cur.next = node; cur = node;
carry = sum / 10; if(newl1 != null) newl1 = newl1.next; if(newl2 != null) newl2 = newl2.next; } ListNode res = reverse(dummy.next); return res; } public ListNode reverse(ListNode head){ if(head == null || head.next == null) return head; ListNode node = reverse(head.next); head.next.next = head; head.next = null; return node; } }
|
扩展:另一道类似题
image-20200805204018584.png
- 题目说明:逆序存储,即链尾为高位,相加得到结果仍要逆序输出。
- 代码:
class Solution { public ListNode addTwoNumbers(ListNode l1, ListNode l2) { if (l1 == null || l2 == null) return l1 == null ? l2 : l1; int carry = 0; ListNode dummy = new ListNode(-1); ListNode cur = dummy; while (l1 != null || l2 != null || carry > 0) { int sum = carry; sum += l1 == null ? 0 : l1.val; sum += l2 == null ? 0 : l2.val; ListNode node = new ListNode(sum % 10); cur.next = node; cur = node; carry = sum / 10; if(l1 != null) l1 = l1.next; if(l2 != null) l2 = l2.next; } return dummy.next; } }
|
234,回文链表,easy
请判断一个链表是否为回文链表。
示例 1:
示例 2:
- 方法一:借助栈。
- 思路:将链表节点值放入栈并统计链表长度,循环 l/2 次(只需比较前一半节点值),如果链表的头部值与栈弹出的值不相等,返回false;否则节点右移,最终返回true。
- 代码:
class Solution { public boolean isPalindrome(ListNode head) { if(head == null) return true; Stack<Integer> stack = new Stack<>(); ListNode temp = head; int length = 0; while(temp != null){ stack.push(temp.val); temp = temp.next; length++; } length /= 2; while(length-- > 0){ if(stack.pop() != head.val){ return false; } head = head.next; } return true; } }
|
- 方法二:将链表节点值转为集合存储,再使用双指针-头尾指针。
class Solution { public boolean isPalindrome(ListNode head) { List<Integer> list = new ArrayList<>(); while(head != null){ list.add(head.val); head = head.next; } int left = 0; int right = list.size() - 1; while(left < right){ if(!list.get(left).equals(list.get(right))) return false; else{ left++; right--; } } return true; } }
|
- 错误点:
if(list.get(left) != list.get(right))
错误
- Integer是对象,比较两个对象相等要用equals
- 使用 == 比较Integer类型时,默认缓存 -128 ~ 127,超过此范围会new新对象,两个对象地址不一样则返回false
- 方法三:快慢指针。
- 思路:找到中间节点,将后半段链表进行反转,再比较反转后的链表与原链表前半段的值。
- 代码:
class Solution { public boolean isPalindrome(ListNode head) { if(head == null) return true; ListNode middle = getMiddle(head); ListNode newHead = reverse(middle.next); while(newHead != null){ if(head.val != newHead.val){ return false; } else{ head = head.next; newHead = newHead.next; } } return true; } public ListNode getMiddle(ListNode head){ ListNode left = head; ListNode right = head; while(right.next != null && right.next.next != null){ left = left.next; right = right.next.next; } return left; } public ListNode reverse(ListNode head){ if(head == null || head.next == null) return head; ListNode temp = reverse(head.next); head.next.next = head; head.next = null; return temp; } }
|
725,分隔链表,middle
给定一个头结点为 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.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.前面部分的长度大于等于后面部分的长度。
|
方法一:拆分链表。
- 思路:分类讨论。
- 如果链表为空,返回 k 个空节点组成的节点数组。
- 如果链表长度l <= k,前 l 个元素是单个节点,后 l - k 个元素为空。
- 如果链表长度l > k,每部分至少有 l / k 个节点,前 l % k 部分节点数 + 1。再将原链表分部分存储在结果数组中,每部分为一个链表,部分之间要断开连接。
- 代码:
class Solution { public ListNode[] splitListToParts(ListNode root, int k) { if(root == null) return new ListNode[k]; ListNode[] res = new ListNode[k]; int length = getLength(root); if(length <= k){ for(int i = 0; i < length; ){ ListNode temp = root.next; root.next = null; res[i++] = root; root = temp; } for(int i = length; i < k; i++){ res[i] = null; } } else if(length > k){ int n = length / k; int m = length % k; int[] counts = new int[k]; for(int i = 0; i < k; i++){ counts[i] = m-- > 0 ? n + 1: n; } ListNode cur = root; for(int i = 0; i < k; i++){ res[i] = cur; for(int j = 0; j < counts[i] - 1; j++){ cur = cur.next; } ListNode temp = cur.next; cur.next = null; cur = temp; } } return res; } public int getLength(ListNode node){ int length = 0; while(node != null){ length++; node = node.next; } return length; } }
|
328,奇偶链表,middle
给定一个单链表,把所有的奇数节点和偶数节点分别排在一起。请注意,这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性。
请尝试使用原地算法完成。你的算法的空间复杂度应为 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
|
说明:
应当保持奇数节点和偶数节点的相对顺序。 链表的第一个节点视为奇数节点,第二个节点视为偶数节点,以此类推。
- 方法一:双指针,分离链表为奇链表、偶链表,再合并。
class Solution { public ListNode oddEvenList(ListNode head) {
if(head == null) return head; ListNode odd = head; ListNode evenHead = head.next; ListNode even = evenHead; while(even != null && even.next != null){ odd.next = odd.next.next; odd = odd.next; even.next = even.next.next; even = even.next; } odd.next = evenHead; return head; } }
|