专栏名称: 吴师兄学算法
和程序员小吴一起从初学者的角度学习算法,以动画的形式呈现解题的思路。每周四篇原创文章,期待你的鉴赏!
目录
相关文章推荐
河南新闻广播  ·  低至200元!价格大幅下调 ·  10 小时前  
河南新闻广播  ·  阳光正在赶来!河南即将开启升温模式 ·  10 小时前  
河南发布  ·  阳光正在赶来!河南即将开启升温模式 ·  13 小时前  
河南新闻广播  ·  即日起至5月底,严查! ·  昨天  
河南新闻广播  ·  下雪啦!今起河南多地将迎雨雪→ ·  2 天前  
51好读  ›  专栏  ›  吴师兄学算法

小米开源的项目,热榜第一!

吴师兄学算法  · 公众号  ·  · 2024-12-20 20:21

正文

大家好,我是吴师兄。

今天刷 GitHub 热榜时,看到一个熟悉的名字——小米。

一个名为“ha_xiaomi_home”的项目 ,短短一天时间收获了快 3000 的 star,甚至在昨天登顶。

作为程序员,我忍不住点进去看了看。

结果发现,这不仅仅是个技术项目,它背后藏着不少值得琢磨的东西。


Home Assistant 的穷人解法:智能家居的“第三条道路”

简单来说,这个小米开源项目的核心是为 Home Assistant 提供官方支持。

Home Assistant 是个啥?没听过的朋友可以简单理解为“穷人的智能家居中枢”。

它不像苹果 HomeKit 那么贵,也不像米家、华为鸿蒙那么封闭。

它更像是“家居界的安卓”: 开源、自由、可定制,只要你愿意折腾,几乎所有智能设备都能被接入和统一控制

小米开源的这款“Xiaomi Home Integration”组件, 让米家设备能无缝融入 Home Assistant ,从灯光、开关到摄像头、传感器,都能一次性接入。

有人说, 这简直是小米对智能家居爱好者的一次“精神大赦” :过去大家为了接入米家设备,只能靠民间开发的半成品,或者忍受各种 API 限制,现在终于迎来了官方的支持。


小米为什么这么干?

有人好奇,小米不是一直强调“全屋智能”的闭环系统吗?这次开源是不是“自毁长城”?其实仔细一想,这背后逻辑很清楚。

1. 开源,是为了抢开发者的心。

在技术圈里,开源就是个杀手锏。你开源了,说明你开放透明、技术硬核,也愿意倾听用户需求。 对于技术社区来说,开源比什么广告、KOL 安利都管用。

小米这么做,相当于在全世界开发者面前递了一张名片: “我们不仅是智能家居硬件的玩家,更是生态开放的推动者。”

2. 收割米家设备的剩余价值。

其实对于小米来说,最赚钱的不是这些设备本身,而是用户买设备后的生态粘性。就像手机配件利润大过手机本身,米家的逻辑也是一样。 开源后,即便用户不再用米家 App,而是转投 Home Assistant,小米的设备销售依然会保持活力。

3. 不与其余生态硬刚,而是“借力打力”。

苹果、华为都在搞全屋智能,Home Assistant 背后也有一帮爱折腾的发烧友。

而小米这一开源动作,很可能直接整合了 Home Assistant 社区的资源,把“生态之争”变成了“开放合作”。

你怎么看?欢迎在评论区聊聊你的观点!



回归我们公众号的主题, 每天掌握一道算法题

继续来一道和「小米校招」相关的算法原题。

图片

这题虽然是一个中等题,但从代码层面来说属于困难级别了,加上非常考察大家对于递归和迭代的掌握程度,一个不小心就会因为死循环而做错,所以这题在我眼中确实是一个困难题。

下面我们来看一下这道题目和对应的解法。

题目描述

给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表 示例 1:

img

输入:head = [4,2,1,3]
输出:[1,2,3,4]

示例 2:

img

输入:head = [-1,5,3,4,0]
输出:[-1,0,3,4,5]

示例 3:

输入:head = []
输出:[] 

提示:

  • 链表中节点的数目在范围 [0, 5 * 10^4]
  • -10^5 <= Node.val <= 10^5

进阶: 你可以在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序吗?

题目解析

题目要求时间空间复杂度分别为 O(nlogn) 和 O(1) ,根据时间复杂度和题目中的 排序 两个字,可以联想到 归并排序 快速排序

所以,本题有两种解法: 归并排序 快速排序

这里我们先用 归并排序 的思路进行处理,其中 合并 的基本操作如下:

  • 1、长度为 1 的链表和长度为 1 的链表合并后,形成一个长度为 2 的链表
  • 2、长度为 2 的链表和长度为 2 的链表合并后,形成一个长度为 4 的链表
  • 。。。

而由于合并过程中操作的是链表,所以需要有 断链 重新连接 的过程。

图片

具体操作步骤如下:

1、先获取链表长度,基于这个长度才能知道后续合并到什么时候截止

2、设置三个指针 prev curr next

其中, prev 表示已经排序好的链表的【尾节点】。

curr 一开始设置为准备排序的那些节点的【首节点】,然后向后移动,获取相应的节点,到达所有正在准备排序的那些节点的【尾节点】位置。

next 表示接下来需要排序的那些节点的【首节点】。

图片

3、断开 prev curr 的连接,再断开 curr next 的连接。

图片

4、把 curr 访问的这些节点划分为两个区域,区域的长度取决于此时进行到了长度为多少的链表进行合并操作,一个是左链表,一个是右链表,把这两个链表进行合并操作。

图片

5、合并成功之后, prev 移动到尾部, curr 来到 next 的位置,继续后面的归并操作。

6、这样一轮下来,已经把长度为 2 的链表和长度为 2 的链表合并,形成了一个长度为 4 的链表。

图片

7、接下来,只需要执行上述同样的操作,唯一的修改点在于合并的子链表长度变成了 4。

图片

参考代码

// 登录 AlgoMooc 官网获取更多算法图解
// https://www.algomooc.com
// 作者:程序员吴师兄
class Solution {
    public ListNode sortList(ListNode head) {

        // 获链表的总长度
        int length = 0;

        // 从链表的头节点开始访问
        ListNode node = head;

        // 利用 while 循环,可以统计出链表的节点个数,即长度
        while (node != null) {

            length++;

            node = node.next;

        }

        // 在原链表的头部设置一个虚拟头节点
        // 因为可能会操作到原链表的头节点
        // 设置了虚拟头节点后,原链表的头节点和原链表的其它节点地位一样
        ListNode dummyHead = new ListNode(0, head);

        // 利用 for 循环,执行合并的操作
        // 长度为 1 的链表和长度为 1 的链表合并后,形成一个长度为 2 的链表
        // 长度为 2 的链表和长度为 2 的链表合并后,形成一个长度为 4 的链表
        // 长度为 4 的链表和长度为 4 的链表合并后,形成一个长度为 8 的链表
        // 长度为 8 的链表和长度为 8 的链表合并后,形成一个长度为 16 的链表
        // 也有可能是,长度为 8 的链表和长度为 5 的链表合并后,形成一个长度为 13 的链表
        // 但是,每次合并过程中,子链表都会想要扩充为原来的两倍
        // 直到子链表想要扩充的长度超过了 length
        for (int subLength = 1; subLength < length; subLength *= 2) {

            // 整个归并过程分为三个步骤
            // 1、不停的划分,直到无法划分为止
            // 2、开始两两合并
            // 3、每次合并之后的结果都需要连接起来

            // 每次都把结果连接到 dummyHead,因此先记录一下
            // prev 表示已经排序好的链表的【尾节点】
            ListNode prev = dummyHead;
            
            // dummyHead 的后面节点才是原链表的节点,需要把它们进行划分
            // curr 表示所有正在准备排序的那些节点的【尾节点】
            ListNode curr = dummyHead.next;

            // 利用 while 循环,寻找出每次划分后子链表的头节点
            while (curr != null) {
                
                // 每次都是两个子链表开始合并

                // 1、先寻找出【左子链表】,长度为 subLength
                ListNode head1 = curr;

                // 通过 for 循环,找出 subLength 个节点来
                // curr 的索引为 0 ,需要再找 subLength - 1 个节点来
                for (int i = 1; i < subLength && curr.next != null; i++) {

                    curr = curr.next;

                }

                // 2、再寻找出【右子链表】,长度最多为 subLength,甚至有可能长度为 0






请到「今天看啥」查看全文