链表有很多不同的题目,这里写那些由一个链表切分成两个或多个链表的问题。
题目 | 简介 |
---|---|
86. Partition List | 比x小的放前,大的放后 |
328. Odd Even Linked List | 隔一个串一个 |
143. Reorder List | 头一个,尾一个(翻转后半list) |
234. Palindrome Linked List | 回文链表(翻转后半list) |
148. Sort List | 快速排序quick sort |
(有些答案代码和分析是抄的别人的,但实在是年代久远没有留具体链接,如有侵犯知识产权问题还请联系我删掉,谢谢啦)
86. Partition List
Input: head = 1->4->3->2->5->2, x = 3
Output: 1->2->2->4->3->5 比x小的放前面,大于等于x的放后面。
有点像撸一遍list,有一个标准“比x小”,把元素分成两类,分别构建一个新的list。构建新list要用small和large两个dummy node,“curS.next = head; curS = curS.next;” -->这个是典型的构建新list的基本操作。
最后把两个list衔接起来。
class Solution {
public ListNode partition(ListNode head, int x) {
ListNode small = new ListNode(-1);//相当于dummy node
ListNode large = new ListNode(-1);
ListNode curS = small, curL = large;
while (head != null) {
if (head.val < x) {
curS.next = head;
curS = curS.next;
} else {
curL.next = head;
curL = curL.next;
}
head = head.next;
}
curS.next = large.next;
curL.next = null;
return small.next;
}
}
328. Odd Even Linked List
Input: 1->2->3->4->5->NULL
Output: 1->3->5->2->4->NULL,奇数位在前,偶数位在后。
86的标准是“比x小”,这里328是“奇数位”。以前我总觉得多个串纠缠不容易想清楚,其实可以放弃想原来的链表的link们,直接认为和86类似,构建新链表。类似于86的dummy,这里把1叫做head(oddHead), 把2叫做eHead(evenHead),然后构建1的时候借助2的next,构建2的时候借助1的next。
class Solution {
public ListNode oddEvenList(ListNode head) {
if (head == null || head.next == null) {return head;}
ListNode eHead = head.next;
ListNode odd = head, even = eHead;
while (even != null && even.next != null) {
odd.next = even.next;//构建1的时候借助2的next
odd = odd.next;
even.next = odd.next;//构建2的时候借助1的next
even = even.next;
}
odd.next = eHead;
return head;
}
}
上面是不需要翻转列表的,下面是需要翻转部分列表的。
143. Reorder List
Given a singly linked list L: L0→L1→…→Ln-1→Ln,
reorder it to: L0→Ln→L1→Ln-1→L2→Ln-2→…
头一个,尾一个,头一个,尾一个……
Given 1->2->3->4->5, reorder it to 1->5->2->4->3.
尾部的需要逆序访问,这个在链表是不可能的,所以必须翻转链表。
于是分三步:
- 找到中点
- 翻转后一半链表
- merge前半和后半两个链表
有个cheatsheet背一下:
dummy->1->2->3->4->null
dummy->1->2->3->4->5->null
fast和slow都初始化指向dummy。
- while (fast != null && fast.next != null): //slow:2 in 4, 3 in 5
- while (fast != null && fast.next != null && fast.next != null): //slow:2 in 4, 2 in 5
在step 3合并两个链表时,因为若链表长为奇数,前一半可能比后一半长,于是“衔接‘前一半’的一个元素”这个操作会多一步,“衔接‘后一半’的一个元素”这个需要检查if (pre != null)。
class Solution {
public void reorderList(ListNode head) {
//step 1: 找到中点
ListNode dummy = new ListNode(-1);
dummy.next = head;
ListNode fast = dummy, slow = dummy;
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
} //slow:2 in 4, 3 in 5
//step 2: 翻转后一半链表
ListNode pre = null;
ListNode cur = slow.next;
slow.next = null; //cut off
while (cur != null) {
ListNode temp = cur.next;
cur.next = pre;
//挪指针
pre = cur;
cur = temp;
} //此时,pre是翻转的后一半的新head
//step 3: merge前半和后半两个链表:head and pre
//head:1->2->3,pre=5->4
ListNode ptr = head;
while (ptr != pre) {
//衔接“前一半”的一个元素
ListNode temp1 = ptr.next;
ptr.next = pre;
ptr = temp1; //挪指针
//衔接“后一半”的一个元素
if (pre != null) {
ListNode temp2 = pre.next;
pre.next = temp1;
pre = temp2; //挪指针
}
}
}
}
234. Palindrome Linked List
Input: 1->2->2->1 判断链表是否是“回文链表”
Output: true
同样是需要翻转后一半的链表。这部分代码和上面143一样。唯一区别是这里234不需要merge两个子链表,而是只比较一下元素即可。
class Solution {
public boolean isPalindrome(ListNode head) {
if (head == null || head.next == null) {return true;}
//step 1: 找到中点
ListNode dummy = new ListNode(-1);
dummy.next = head;
ListNode fast = dummy, slow = dummy;
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
}//slow:2 in 4, 3 in 5
//step 2: 翻转后一半链表
ListNode pre = null;
ListNode cur = slow.next;
slow.next = null; //cut off
while (cur != null) {
ListNode temp = cur.next;
cur.next = pre;
//挪指针
pre = cur;
cur = temp;
}
//step 3: 比较两个子链表(head和pre)
//head:1->2->3, pre:1->2
while (head != null && pre != null) {
if (head.val != pre.val) {return false;}
head = head.next;
pre = pre.next;
}
return true;
}
}
还可以Recursive做。
这么做的核心逻辑是什么呢?实际上就是把链表节点放入一个栈,然后再拿出来,这时候元素顺序就是反的,只不过我们利用的是递归函数的堆栈而已。算法的时间和空间复杂度都是 O(N)。
如果我想正序打印链表中的val值,可以在前序遍历位置写代码;反之,如果想倒序遍历链表,就可以在后序遍历位置操作:借助二叉树后序遍历的思路,不需要显式反转原始链表也可以倒序遍历链表。
//recursive
class Solution {
ListNode left; //左侧指针
boolean isPalindrome(ListNode head) {
left = head;
return traverse(head);
}
boolean traverse(ListNode right) {
if (right == null) {return true;}
boolean result = traverse(right.next);
// 后序遍历代码
result = result && (right.val == left.val);
left = left.next;
return result;
}
}
148. Sort List
Input: 4->2->1->3
Output: 1->2->3->4
Sort a linked list 时间 O(n log n),空间O(1)。
时间 O(n log n),方法有quick sort,Merge sort。
这里用Merge sort。分三步(就是个模板,背过就好啦):
- 链表切成两半
- 对每个half排序
- merge两个链表
//merge sort
class Solution {
public ListNode sortList(ListNode head) {
if (head == null || head.next == null) {return head;}
//step 1: 链表切成两半
ListNode pre = null, fast = head, slow = head;
while (fast != null && fast.next != null) {//3 in 4,3 in 5
pre = slow;
fast = fast.next.next;
slow = slow.next;
}
pre.next = null; //cut-off
//step 2: 对每个half排序
ListNode l1 = sortList(head);
ListNode l2 = sortList(slow);
//step 3: merge两个链表
return merge(l1, l2);
}
private ListNode merge(ListNode l1, ListNode l2) {
ListNode dummy = new ListNode(-1);
ListNode cur = dummy;
while (l1 != null && l2 != null) {
if (l1.val <= l2.val) {
cur.next = l1;
l1 = l1.next;
} else {
cur.next = l2;
l2 = l2.next;
}
cur = cur.next;
}
if (l1 != null) {
cur.next = l1;
}
if (l2 != null) {
cur.next = l2;
}
return dummy.next;
}
}