ARTS 2019 06 16 (35)

本文深入探讨了如何使用归并排序对链表进行高效排序,详细解释了归并排序的基本原理,包括寻找中点、分治策略及合并排序过程。文章提供了两种归并排序的代码实现,一种是递归方式,另一种则是更高效的迭代方式,同时强调了常数空间复杂度的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

https://97-things-every-x-should-know.gitbooks.io/97-things-every-programmer-should-know/content/en/thing_08/

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

https://www.bloomberg.com/news/articles/2019-05-14/the-mighty-pea-is-everybody-s-new-favorite-plant-based-protein

The Mighty Pea Is Everybody’s New Favorite Plant-Based Protein

豌豆是每个人最喜欢的基于植物的蛋白质

http://www.sohu.com/a/319707193_430392

这则新闻里面提到了一个股票beyond meat,这个股票发行价为25美元,但是到了现在,你也看到了,这个股价,一级已经涨了将近6倍了,可以说这个是牛股。这个公司是干啥的呢?
在这里插入图片描述
这个股票就是靠着开发“植物肉”,肉制品不是从动物身上来的,而是主要用大豆蛋白做的。

在这里插入图片描述
目测是日后这些个项目会在中国大火起来!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值