链表
LeetCode链表
2. 两数相加
给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。
如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。
您可以假设除了数字 0 之外,这两个数都不会以 0 开头。
示例:
输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 0 -> 8
原因:342 + 465 = 807
public ListNode addTwoNumbers(ListNode l1, ListNode l2) { ListNode dump = new ListNode(-1); ListNode temp = dump; //代表进位 int carry = 0; while(l1 != null || l2 != null) { int sum = carry; if(l1 != null) { sum += l1.val; l1 = l1.next; } if(l2 != null) { sum += l2.val; l2 = l2.next; } //如果当前位数大于等于10进位,反之不进位 if(sum >= 10) { carry = 1; sum -= 10; }else { carry = 0; } temp.next = new ListNode(sum); temp = temp.next; } if(carry == 1) { temp.next = new ListNode(1); } return dump.next; }
445. 两数相加Ⅱ
给定两个非空链表来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储单个数字。将这两数相加会返回一个新的链表。
你可以假设除了数字 0 之外,这两个数字都不会以零开头。
进阶:
如果输入链表不能修改该如何处理?换句话说,你不能对列表中的节点进行翻转。
示例:
输入: (7 -> 2 -> 4 -> 3) + (5 -> 6 -> 4)
输出: 7 -> 8 -> 0 -> 7
思路:这题和第2题很类似,可以把这题利用栈转为第二题,主要这题输出的是正向链表,所以可以利用头插法来添加链表,可以少去一次链表反转的操作。
public ListNode addTwoNumbers(ListNode l1, ListNode l2) { Stack<Integer> stack1 = new Stack<>(); Stack<Integer> stack2 = new Stack<>(); ListNode dump = new ListNode(-1); while (l1 != null) { stack1.push(l1.val); l1 = l1.next; } while (l2 != null) { stack2.push(l2.val); l2 = l2.next; } int carry = 0; while (!stack1.isEmpty() || !stack2.isEmpty()) { int sum = carry; if(!stack1.isEmpty()) { sum += stack1.pop(); } if(!stack2.isEmpty()) { sum += stack2.pop(); } //如果大于等于10,进位 if(sum >= 10) { carry = 1; sum -= 10; }else { carry = 0; } //使用头插法插入 ListNode next = dump.next; dump.next = new ListNode(sum); dump.next.next = next; } //说明还有进位 if(carry == 1) { ListNode next = dump.next; dump.next = new ListNode(1); dump.next.next = next; } return dump.next; }
876. 链表的中间节点(快慢指针)
给定一个带有头结点 head
的非空单链表,返回链表的中间结点。
如果有两个中间结点,则返回第二个中间结点。
示例 1:
输入:[1,2,3,4,5]
输出:此列表中的结点 3 (序列化形式:[3,4,5])
思路:快慢指针,慢指针跑一步,快指针跑两步
public ListNode middleNode(ListNode head) { if(head == null || head.next == null) { return head; } ListNode fast = head; ListNode slow = head; while(fast != null && fast.next != null) { slow = slow.next; fast = fast.next.next; } return slow; }
19. 删除链表的倒数第N个节点(双指针)
给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。
示例:
给定一个链表: 1->2->3->4->5, 和 n = 2.
当删除了倒数第二个节点后,链表变为 1->2->3->5.
说明:
给定的 n 保证是有效的。
进阶:
你能尝试使用一趟扫描实现吗?
public ListNode removeNthFromEnd(ListNode head, int n) { if(n <= 0) return null; ListNode dump = new ListNode(-1); dump.next = head; ListNode fast = dump; ListNode slow = dump;
//向前移动n+1步 while(n >= 0) { if(fast == null) { throw new RuntimeException("参数不合法"); } fast = fast.next; n--; } while(fast != null) { fast = fast.next; slow = slow.next; } slow.next = slow.next.next; return dump.next; }
141. 环形链表(快慢指针)
给定一个链表,判断链表中是否有环。
为了表示给定链表中的环,我们使用整数 pos
来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos
是 -1
,则在该链表中没有环。
示例 1:
输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
输入: head = [1,2], pos = 0
输出: true
解释: 链表中有一个环,其尾部连接到第一个节点。
public boolean hasCycle(ListNode head) { if(head == null || head.next == null) { return false; } ListNode fast = head; ListNode slow = head; while(fast != null && fast.next != null) { slow = slow.next; fast = fast.next.next; if(slow == fast) { return true; } } return false; }
141. 环形链表Ⅱ(快慢指针)
给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null
。
为了表示给定链表中的环,我们使用整数 pos
来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos
是 -1
,则在该链表中没有环。
说明: 不允许修改给定的链表。 ps:上题的基础上返回入环的第一个节点
public ListNode detectCycle(ListNode head) { if(head == null || head.next == null) { return null; } ListNode slow = head, fast = head; //如果是有环,则fast走了2nb步, slow走了nb步, 其中b为环的长度(假设时间为t,一个环的长度为b f=2t,s=t,f=s+nb) while(true) { if(fast == null || fast.next == null) { return null; } fast = fast.next.next; slow = slow.next; if(fast == slow) { break; } } fast = head; while(fast != slow) { fast = fast.next; slow = slow.next; } return fast; }
234. 回文链表(快慢指针)
请判断一个链表是否为回文链表。
示例 1:
输入: 1->2
输出: false
示例 2:
输入: 1->2->2->1
输出: true
进阶:
你能否用 O(n) 时间复杂度和 O(1) 空间复杂度解决此题?
public boolean isPalindrome(ListNode head) { if(head == null || head.next == null) { return true; } ListNode pre = null; ListNode fast = head; ListNode slow = head; ListNode next = null; while(fast != null && fast.next != null) { fast = fast.next.next; next = slow.next; slow.next = pre; pre = slow; slow = next; } //如果是偶数节点,fast = null, slow = 中间的后一个节点 //如果是基数节点,fast.next = null slow = 中间的一个节点 if(fast != null) slow = slow.next; while(slow != null) { if(pre.val != slow.val) { return false; } pre = pre.next; slow = slow.next; } return true; }
160. 相交链表(双指针)
编写一个程序,找到两个单链表相交的起始节点。
如下面的两个链表:
在节点 c1 开始相交。
输入解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。
解释:这两个链表不相交,因此返回 null。
public ListNode getIntersectionNode(ListNode headA, ListNode headB) { if(headA == null || headB == null) { return null; } ListNode pA = headA; ListNode pB = headB; while(pA != pB) { if(pA == null) { pA = headB; }else if(pB == null) { pB = headA; }else { pA = pA.next; pB = pB.next; } } return pA; }
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
public ListNode oddEvenList(ListNode head) {
if(head == null || head.next == null || head.next.next == null) {
return head;
}
//分别指向2条链表
ListNode odd = head, even = head.next;
while(even != null && even.next != null) {
ListNode tmp = odd.next;
odd.next = even.next;
even.next = even.next.next;
odd.next.next = tmp;
odd = odd.next;
even = even.next;
}
return head;
}
206. 反转链表
反转一个单链表。
示例:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
进阶:
你可以迭代或递归地反转链表。你能否用两种方法解决这道题?
解法一:递归解法
public ListNode reverseList(ListNode head) { if(head == null || head.next == null) { return head; } ListNode nextHead = reverseList(head.next); head.next.next = head; head.next = null; return nextHead; }
解法二:迭代解法
public ListNode reverseList(ListNode head) { if(head == null || head.next == null) { return head; } ListNode cur = head.next; ListNode pre = head; head.next = null; while(cur != null) { ListNode next = cur.next; cur.next = pre; pre = cur; cur = next; } return pre; }
92. 反转链表Ⅱ
反转从位置 m 到 n 的链表。请使用一趟扫描完成反转。
说明:
1 ≤ m ≤ n ≤ 链表长度。
示例:
输入: 1->2->3->4->5->NULL, m = 2, n = 4
输出: 1->4->3->2->5->NULL
解法一:递归解法
ListNode successor = null; //逆转前n个节点 public ListNode reverseN(ListNode head, int n) { if(n == 1) { successor = head.next; return head; } ListNode last = reverseN(head.next,n - 1); head.next.next = head; head.next = successor; return last; } public ListNode reverseBetween(ListNode head, int m, int n) { if(m == 1) { return reverseN(head,n); } head.next = reverseBetween(head.next,m - 1, n - 1); return head; }
解法二:迭代解法
利用一个冗余节点,另外头插法插入时候,从链表的后半部开始改。
public ListNode reverseBetween(ListNode head, int m, int n) { ListNode dump = new ListNode(-1); dump.next = head; ListNode pre = dump; for(int i = 1; i < m; i++) { pre = pre.next; } ListNode cur = pre.next; //采用头插法 for(int i = m; i < n; i++) { ListNode next = cur.next; cur.next = next.next; next.next = pre.next; pre.next = next; } return dump.next; }
24. 两两交换链表中的节点
给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
示例:
给定 1->2->3->4, 你应该返回 2->1->4->3.
解法一:递归解法
public ListNode swapPairs(ListNode head) { if(head == null || head.next == null){ return head; } ListNode next = head.next; head.next = swapPairs(next.next); next.next = head; return next; }
解法二:迭代解法
public ListNode swapPairs(ListNode head) { if(head == null || head.next == null) return head; ListNode dump = new ListNode(-1); ListNode pre = dump, p = head, q = head.next; while(q != null) { p.next = q.next; q.next = p; pre.next = q; pre = p; p = p.next; if(p == null) break; q = p.next; } return dump.next; }
25.K个一组翻转链表
给出一个链表,每 k 个节点一组进行翻转,并返回翻转后的链表。
k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那么将最后剩余节点保持原有顺序。
示例 :
给定这个链表:1->2->3->4->5
当 k = 2 时,应当返回: 2->1->4->3->5
当 k = 3 时,应当返回: 3->2->1->4->5
解法一:递归解法
public ListNode reverseKGroup(ListNode head, int k) { if(head == null || head.next == null) { return head; } //找到下一次翻转的头结点 ListNode tail = head; for (int i = 0; i < k; i++) { if(tail == null) return head; tail = tail.next; } //翻转前k个元素 ListNode newHead = reverse(head, tail); head.next = reverseKGroup(tail,k); return newHead; } /* 左闭又开区间 */ private ListNode reverse(ListNode head, ListNode tail) { ListNode pre = null; ListNode next = null; while (head != tail) { next = head.next; head.next = pre; pre = head; head = next; } return pre; }
解法二:迭代解法
public ListNode reverseKGroup(ListNode head, int k) { ListNode dump = new ListNode(-1); dump.next = head; int len = 0; int times = 0; len = lenListNode(head); //统计要循环的次数 times = len / k; ListNode pre = dump; ListNode cur = head; ListNode next; while(times-- > 0) { for(int i = 1; i < k; i++) { next = cur.next; cur.next = next.next; next.next = pre.next; pre.next = next; } pre = cur; cur = cur.next; } return dump.next; } //计算链表的长度 public int lenListNode(ListNode head) { int len = 0; while(head != null) { len++; head = head.next; } return len; }
203. 移除链表元素
删除链表中等于给定值 _val _的所有节点。
示例:
输入: 1->2->6->3->4->5->6, val = 6
输出: 1->2->3->4->5
public ListNode removeElements(ListNode head, int val) { if(head==null) return null; head.next=removeElements(head.next,val); if(head.val==val){ return head.next; }else{ return head; } }
83. 删除排序链表中的重复节点
给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。
public ListNode deleteDuplicates(ListNode head) { if(head == null || head.next == null) { return head; } ListNode node = deleteDuplicates(head.next); if(head.val == node.val) { head.next = node.next; } return head; }
82. 删除排序链表中的重复元素Ⅱ
给定一个排序链表,删除所有含有重复数字的节点,只保留原始链表中 _没有重复出现 _的数字。
示例 1:
输入: 1->2->3->3->4->4->5
输出: 1->2->5
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); } head.next = deleteDuplicates(head.next); return head; }
21. 合并2个有序链表
将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
解法一:递归解法
public ListNode mergeTwoLists(ListNode l1, ListNode l2) { if(l1 == null && l2 == null) { return null; } if(l1 == null) return l2; if(l2 == null) return l1; if(l1.val <= l2.val) { l1.next = mergeTwoLists(l1.next,l2); return l1; }else { l2.next = mergeTwoLists(l1,l2.next); return l2; } }
解法二:迭代解法
public ListNode mergeTwoLists(ListNode l1, ListNode l2) { if(l1 == null) return l2; if(l2 == null) return l1; ListNode dump = new ListNode(-1); ListNode p = dump; while(l1 != null || l2 != null) { if(l1 == null) { p.next = l2; break; } if(l2 == null) { p.next = l1; break; } if(l1.val < l2.val) { p.next = l1; l1 = l1.next; }else { p.next = l2; l2 = l2.next; } p = p.next; } return dump.next; }
148. 排序链表(归并排序)
在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序。
示例 1:
输入: 4->2->1->3
输出: 1->2->3->4
示例 2:
输入: -1->5->3->4->0
输出: -1->0->3->4->5
public ListNode sortList(ListNode head) { if(head == null || head.next == null){ return head; } //找到中点 ListNode fast = head.next; ListNode slow = head; while (fast != null && fast.next != null){ fast = fast.next.next; slow = slow.next; } ListNode temp = slow.next; slow.next = null; ListNode listLeftNode = sortList(head); ListNode listRightNode = sortList(temp); ListNode h = new ListNode(0); ListNode res = h; while (listLeftNode!=null && listRightNode != null){ if(listLeftNode.val <= listRightNode.val){ h.next = listLeftNode; listLeftNode = listLeftNode.next; h = h.next; }else { h.next = listRightNode; listRightNode = listRightNode.next; h = h.next; } } h.next = listLeftNode == null ? listRightNode : listLeftNode; return res.next; }
23. 合并K个排序链表(归并排序)
合并 k 个排序链表,返回合并后的排序链表。请分析和描述算法的复杂度。
public ListNode mergeKLists(ListNode[] lists){ if(lists == null || lists.length == 0){ return null; } return merge(lists,0,lists.length-1); } private ListNode merge(ListNode[] lists, int left, int right) { if(left == right) return lists[left]; int mid = left + (right - right)/2; ListNode l1 = merge(lists,left,mid); ListNode l2 = merge(lists,mid+1,right); return mergeTwoLists(l1,l2); } private ListNode mergeTwoLists(ListNode l1, ListNode l2) { if(l1 == null) return l2; if(l2 == null) return l1; if(l1.val < l2.val){ l1.next = mergeTwoLists(l1.next,l2); return l1; }else { l2.next = mergeTwoLists(l1,l2.next); return l2; } }