开始对LeetCode中链表相关问题进行刷题🤔
206. Reverse Linked List
题目描述:
反转一个单链表。
示例:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
解题思路:
1 → 2 → 3 → 4→ 5→ Ø,改成 Ø ← 1 ← 2 ← 3←4←5
定义一个前结点pre和当前结点cur,在遍历列表时,将当前节点的 next 指针改为指向前面一个元素。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode reverseList(ListNode head) {
ListNode pre = null; //前一个节点
ListNode cur = head;
while (cur != null) {
ListNode nextNode = cur.next;
cur.next = pre; //指针指向前一个结点
pre = cur;
cur = nextNode;
}
return pre;
}
}
21. Merge Two Sorted Lists
题目描述:
合并两个已排序的链表,并将其作为一个新列表返回。新列表应该通过将前两个列表的节点拼接在一起来创建。
Input: 1->2->4, 1->3->4
Output: 1->1->2->3->4->4
解题思路:
利用递归,比较两个链表第一个结点的大小,确定头结点的位置 ,头结点确定后,继续在剩下的结点中选出下一个结点去链接到第二步选出的结点后面,然后在继续重复上述步骤,直到有链表为空。
class Solution {
public 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;
}
}
}
83. Remove Duplicates from Sorted List
题目描述:
给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。
Example 1:
Input: 1->1->2
Output: 1->2
Example 2:
Input: 1->1->2->3->3
Output: 1->2->3
解题思路:
可以使用一个临时指针node,需要注意头指针“head”不应该直接移动或更新,因为它指向列表的头部,你需要在最后返回这个头部。所有操作都应该使用“node”来防止丢失头部。
class Solution {
public ListNode deleteDuplicates(ListNode head) {
if (head == null){
return null;
}
ListNode node = head; //使用临时指针
while (node.next != null) {
if (node.val == node.next.val) {
node.next = node.next.next;
}else{
node = node.next;
}
}
return head;
}
}
也可以使用递归:
class Solution {
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;
}
}
24. Swap Nodes in Pairs
题目描述:
定一个链表,交换每两个相邻节点并返回其头部。
不能修改列表节点中的值,只能更改节点本身。
Given 1->2->3->4, you should return the list as 2->1->4->3.
解题思路:
本题同样可以使用递归或者按照迭代,两种方式运行速度和内存都差不多。
迭代:
// 1->2->3->4 class Solution { public ListNode swapPairs(ListNode head) { ListNode node = new ListNode(0); node.next = head; ListNode temp = node; while (temp.next != null && temp.next.next != null) { ListNode l1 = temp.next; ListNode l2 = temp.next.next; ListNode next = l2.next; temp.next = l2; l2.next = l1; l1.next = next; temp = l1; } return node.next; //head此时已被改变 } }
递归:
class Solution { public ListNode swapPairs(ListNode head) { if (head == null || head.next == null) { return head; } ListNode second = head.next; ListNode third = second.next; second.next = head; head.next = swapPairs(third); return second; } }
19.Remove Nth Node From End of List
题目描述:
给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。
Example:
Given linked list: 1->2->3->4->5, and n = 2.
After removing the second node from the end, the linked list becomes 1->2->3->5.
解题思路:
题目那里描述可否用一次遍历就完成,刚开始我只想到用两次遍历,后来发现还可以使用双指针实现一次遍历就解题:
两次遍历:
删除从列表开头数起的第 (L - n + 1) 个结点,其中 L 是列表的长度。第一次遍历中先算出链表长度,第二次遍历删除该数,注意添加一个临时结点作为辅助,该结点位于列表头部。该结点用来简化某些极端情况,例如列表中只含有一个结点,或需要删除列表的头部。class Solution { public ListNode removeNthFromEnd(ListNode head, int n) { ListNode temp= new ListNode(0); //临时结点 temp.next = head; ListNode node = head; int length = 0; //链表长度 while (node != null) { node = node.next; length++; } length -= n; node = temp; while (length > 0) { length--; node = node.next; } node.next = node.next.next; return temp.next; } }
一次遍历:
使用两个指针,第一个指针fast先开始移动n步,然后第二个指针slow从头开始移动,此时第一个指针也一起移动,这样就可以使得在移动过程中两个指针保持n步的距离,直到fast到达最后一个结点。此时slow将指向从最后一个结点数起的第 n 个结点,我们重新链接slow所引用的结点的 next 指针指向该结点的下下个结点。class Solution { public ListNode removeNthFromEnd(ListNode head, int n) { ListNode fast = head; while (n-- > 0) { fast = fast.next; } if (fast == null) return head.next; //防止极端情况 ListNode slow = head; while (fast.next != null) { fast = fast.next; slow = slow.next; } slow.next = slow.next.next; return head; } }
725. Split Linked List in Parts
题目描述:
给定一个头结点为 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.前面部分的长度大于等于后面部分的长度。
解题思路:
首先求出链表长度,然后看链表长度是否平均分配,对其求余数即可,然后用数组来保存每一个切割那部分,最终将切断上一部分的最后一个节点和下一部分的第一个节点之间的链接。
class Solution {
public ListNode[] splitListToParts(ListNode root, int k) {
ListNode node = root;
int length = 0; //链表长度
while (node != null) {
length++;
node = node.next;
}
node = root;
int mod = length % k; //哪一个多余
int size = length / k; //分成几部分
ListNode result [] = new ListNode[k]; //用数组保存
for (int i = 0; node != null && i < k; i++) {
result[i] = node;
int cursize = size + (mod-- > 0 ? 1:0);
for (int j = 0; j < cursize - 1; j++) {
node = node.next;
}
ListNode next = node.next;
node.next = null;
node = next;
}
return result;
}
}
总结:
在链表中要理解每个节点和每个节点是相连的,如何来求链表长度,时刻要注意使用临时指针来代替头部分或者其他部分;用两个指针(一个快指针,一个慢指针)求链表中的中间结点;同时中也要灵活运用递归和迭代。
对链表刷题告一段落,开始新的tag刷题😀