Algorithm:148. 排序链表
Review: 童子军规则
Tip/Tech: 归并排序
Share: 豌豆是每个人最喜欢的基于植物的蛋白质
Algorithm
148. 排序链表
https://leetcode-cn.com/problems/sort-list/comments/
这题其实对于知道归并排序的朋友来说一点也不陌生,反而很好理解,就是利用归并的思想来吧整个链表进行排序,至于归并排序其实有很多的代码的模板的,如果不知道的朋友可以去网上找找更过的资料,我这里这就不多说了,如果可以,我以后会专门总结一篇有关于归并排序的文章的。
因为思想都知道了,所以我们就先来看看效率那么高的解法。
归并排序的重点就是三个。
(1)找中点
(2)分开数组分别
(3)合并,也就是排序的步骤
如果你记不住什么时间复杂度,空间复杂度,但是你要记住怎么写的话,这三步曲就是最简单的实现了。
这里的题目,用最简单的归并思想就能完成了,具体的解答的注释我都放在代码里了。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode sortList(ListNode head) {
if (head == null) {
return head;
}
ListNode left = head;
ListNode right = head;
// 找最后一个点
while (right.next != null) {
right = right.next;
}
mergeSort(left, right);
return head;
}
public void mergeSort(ListNode left, ListNode right) {
// 退出的条件就是,如果两个点相同,那么就是单点的情况了,恭喜你,可以退出了。
if (left == right) {
return;
}
ListNode low = left;
ListNode fast = left.next;
// 找中点,low到时候就是中点了。
while (fast != right && fast.next != right) {
low = low.next;
fast = fast.next.next;
}
// 分开数组
mergeSort(left, low);
mergeSort(low.next, right);
// 合并数组
merge(left, low, right);
}
// 合并数组,其实真正的作用是排序。
public void merge(ListNode left, ListNode mid, ListNode right) {
ListNode rightPos = mid.next;
ListNode leftPos = left;
List<Integer> list = new LinkedList<>();
//
while (leftPos != mid.next && rightPos != right.next) {
if (leftPos.val <= rightPos.val) {
list.add(leftPos.val);
leftPos = leftPos.next;
} else {
list.add(rightPos.val);
rightPos = rightPos.next;
}
}
// 判断哪个节点还没到最后
ListNode start = leftPos;
ListNode end = mid;
if (rightPos != right.next) {
start = rightPos;
end = right;
}
// 将剩余的数据拷贝到临时数组list中
while (start != end.next) {
list.add(start.val);
start = start.next;
}
// 根据排序好的数据进行替换
ListNode tempIndex = left;
for (int i = 0, lenList = list.size(); i < lenList; ++i) {
tempIndex.val = list.get(i);
tempIndex = tempIndex.next;
}
}
}
但是以上不是最好的做法,首先就不满足题目要求的,要在常数的空间复杂度中来完成。
以下是目前看到的最好的解法,用的是网友angus-liu的解法:https://leetcode-cn.com/u/angus-liu
class Solution {
/**
* 参考:Sort List——经典(链表中的归并排序) https://www.cnblogs.com/qiaozhoulin/p/4585401.html
*
* 归并排序法:在动手之前一直觉得空间复杂度为常量不太可能,因为原来使用归并时,都是 O(N)的,
* 需要复制出相等的空间来进行赋值归并。对于链表,实际上是可以实现常数空间占用的(链表的归并
* 排序不需要额外的空间)。利用归并的思想,递归地将当前链表分为两段,然后merge,分两段的方
* 法是使用 fast-slow 法,用两个指针,一个每次走两步,一个走一步,知道快的走到了末尾,然后
* 慢的所在位置就是中间位置,这样就分成了两段。merge时,把两段头部节点值比较,用一个 p 指向
* 较小的,且记录第一个节点,然后 两段的头一步一步向后走,p也一直向后走,总是指向较小节点,
* 直至其中一个头为NULL,处理剩下的元素。最后返回记录的头即可。
*
* 主要考察3个知识点,
* 知识点1:归并排序的整体思想
* 知识点2:找到一个链表的中间节点的方法
* 知识点3:合并两个已排好序的链表为一个新的有序链表
*/
public ListNode sortList(ListNode head) {
return head == null ? null : mergeSort(head);
}
private ListNode mergeSort(ListNode head) {
if (head.next == null) {
return head;
}
ListNode p = head, q = head, pre = null;
while (q != null && q.next != null) {
pre = p;
p = p.next;
q = q.next.next;
}
pre.next = null;
ListNode l = mergeSort(head);
ListNode r = mergeSort(p);
return merge(l, r);
}
ListNode merge(ListNode l, ListNode r) {
ListNode dummyHead = new ListNode(0);
ListNode cur = dummyHead;
while (l != null && r != null) {
if (l.val <= r.val) {
cur.next = l;
cur = cur.next;
l = l.next;
} else {
cur.next = r;
cur = cur.next;
r = r.next;
}
}
if (l != null) {
cur.next = l;
}
if (r != null) {
cur.next = r;
}
return dummyHead.next;
}
}
从上面的代码可以看到,直接一个pre.next = null;
就把链表一次性分成了两半。不能说不强啊。。哈哈哈 。。。当然这里还有不能算最好的解法,因为依然无法满足题目中说的要常数空间复杂度。
最好的就是把递归变成迭代。
Review
The Boy Scout Rule
童子军规则:试着让这个世界变得比你发现它的时候更好一点。
童子军有一个规则:'总是离开露营地比你找到它更清洁。如果你在地上发现一团糟,无论是谁造成了混乱,你都要清理它。实际上,该规则的原始形式是由侦察之父罗伯特·斯蒂芬森·史密斯·巴登 - 鲍威尔撰写的,“试着让这世界变得比你发现它好一点。”
如果你发现了一个人的代码不够好,那么你就要自觉的修改,让代码变得更好的。
Tip/Tech
这个里主要是复习了归并排序的写法:基本上了解了归并的思想,这个还是很好理解的:
// 递归调用函数
private static void mergeSortInternally(int[] a, int p, int r) {
// 递归终止条件
if (p >= r) return;
// 取p到r之间的中间位置q,防止(p+r)的和超过int类型最大值
int q = p + (r - p)/2;
// 分治递归
mergeSortInternally(a, p, q);
mergeSortInternally(a, q+1, r);
// 将A[p...q]和A[q+1...r]合并为A[p...r]
merge(a, p, q, r);
}
private static void merge(int[] a, int p, int q, int r) {
int i = p;
int j = q+1;
int k = 0; // 初始化变量i, j, k
int[] tmp = new int[r-p+1]; // 申请一个大小跟a[p...r]一样的临时数组
while (i<=q && j<=r) {
if (a[i] <= a[j]) {
tmp[k++] = a[i++]; // i++等于i:=i+1
} else {
tmp[k++] = a[j++];
}
}
// 判断哪个子数组中有剩余的数据
int start = i;
int end = q;
if (j <= r) {
start = j;
end = r;
}
// 将剩余的数据拷贝到临时数组tmp
while (start <= end) {
tmp[k++] = a[start++];
}
// 将tmp中的数组拷贝回a[p...r]
for (i = 0; i <= r-p; ++i) {
a[p+i] = tmp[i];
}
}
Share
The Mighty Pea Is Everybody’s New Favorite Plant-Based Protein
豌豆是每个人最喜欢的基于植物的蛋白质
http://www.sohu.com/a/319707193_430392
这则新闻里面提到了一个股票beyond meat,这个股票发行价为25美元,但是到了现在,你也看到了,这个股价,一级已经涨了将近6倍了,可以说这个是牛股。这个公司是干啥的呢?
这个股票就是靠着开发“植物肉”,肉制品不是从动物身上来的,而是主要用大豆蛋白做的。
目测是日后这些个项目会在中国大火起来!!!