本篇是有关Linked List的几道相关题型,涉及链表的一些基本的操作和技巧,这些之前的blog也有提到过。
Sort List
题目为把一个链表进行排序,要求时间复杂度为O(nlgn)。链表我们一般无法使用快排,因为它进行随机访问太耗时。回忆之前提到过的Merge Two Sorted List的解法,发现我们将链表一分为二然后再合并就好了,但是这里合并的两个链表都必须是有序的,因此我们需要使用分治法来解决来问题。
private ListNode findMiddle(ListNode head) {
ListNode slow = head, fast = head.next;
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
}
return slow;
}
private ListNode merge(ListNode head1, ListNode head2) {
ListNode dummy = new ListNode(0);
ListNode tail = dummy;
while (head1 != null && head2 != null) {
if (head1.val < head2.val) {
tail.next = head1;
head1 = head1.next;
} else {
tail.next = head2;
head2 = head2.next;
}
tail = tail.next;
}
if (head1 != null) {
tail.next = head1;
} else {
tail.next = head2;
}
return dummy.next;
}
public ListNode sortList(ListNode head) {
if (head == null || head.next == null) {
return head;
}
ListNode mid = findMiddle(head);
ListNode right = sortList(mid.next);
mid.next = null;
ListNode left = sortList(head);
return merge(left, right);
}
其中,sortList即为分治法,我们将链表一分为二,然后合并。
Partition List
题目是给定一个目标值和一个链表,将链表中数值小于目标值的节点移到左侧,将数值大于目标值的节点移到右侧(该目标值节点的右侧)。此题目如之前所介绍,由于我们不知道头结点会是哪个元素,因此我们需要设置dummynode作为Head。另外,从题目可以看出我们将链表中的节点按目标值分成两个链表,之后直接接在一起即可。
public ListNode partition(ListNode head, int x) {
if (head == null) {
return null;
}
ListNode leftDummy = new ListNode(0);
ListNode rightDummy = new ListNode(0);
ListNode left = leftDummy, right = rightDummy;
while (head != null) {
if (head.val < x) {
left.next = head;
left = head;
} else {
right.next = head;
right = head;
}
head = head.next;
}
right.next = null;
left.next = rightDummy.next;
return leftDummy.next;
}
Reorder List
题目:Given a singly linked list L: L0→L1→…→Ln-1→Ln, reorder it to: L0→Ln→L1→Ln-1→L2→Ln-2→… You must do this in-place without altering the nodes’ values. For example, Given {1,2,3,4}, reorder it to {1,4,2,3}.
这道题目的思路是,首先将链表从中间分开(可用之前的findMiddle函数),然后将从中间节点开头的链表倒转(可用之前的reverse函数),最后将他们merge在一起,这个merge区别于之前排序时使用的merge,比他简单很多,仅交替节点相接即可。这里仅给出merge的代码:
private void merge(ListNode head1, ListNode head2) {
int index = 0;
ListNode dummy = new ListNode(0);
while (head1 != null && head2 != null) {
if (index % 2 == 0) {
dummy.next = head1;
head1 = head1.next;
} else {
dummy.next = head2;
head2 = head2.next;
}
dummy = dummy.next;
index ++;
}
if (head1 != null) {
dummy.next = head1;
} else {
dummy.next = head2;
}
}
Linked List Cycle
题目为判断一个链表中是否有环。此题可以借鉴之前findMiddle的代码,用一个一次走两步的fast节点和一个一次仅走一步的slow节点。
public Boolean hasCycle(ListNode head) {
if (head == null || head.next == null) {
return false;
}
ListNode fast, slow;
fast = head.next;
slow = head;
while (fast != slow) {
if(fast==null || fast.next==null)
return false;
fast = fast.next.next;
slow = slow.next;
}
return true;
}
Linked List Cycle II
此题目在Linked List Cycle基础上,要返回环开始的节点。因此,可根据之前代码的基础上做一些修改。根据之前代码分析:如果存在环,fast节点要比slow节点多走一圈,那么再进一步想,slow现在的位置到环开始处的节点的距离其实就是头结点到环开始处的节点的距离。
public ListNode detectCycle(ListNode head) {
if (head == null || head.next==null) {
return null;
}
ListNode fast, slow;
fast = head.next;
slow = head;
while (fast != slow) {
if(fast==null || fast.next==null)
return null;
fast = fast.next.next;
slow = slow.next;
}
while (head != slow.next) {
head = head.next;
slow = slow.next;
}
return head;
}