用笨办法应对充满不确定性的未来

【ARTS 周刊】第二期:靡不有初,鲜克有终

2023.07.15

零:碎碎念

参与 ARTS 打卡的人其实很多,既有完成 100 天挑战的也有半途而废的。我也不知道自己是否能够完成本次挑战,如果能够做到那是更好的了。以下是我想到的能够帮助我完成这一目标的方法:

  1. 结果驱动:如果能够完成这一打卡挑战,首先我能够沉淀下来一系列文章,其次是能够养成关注技术、分享好文章的习惯,并且能够持续分享下去。最后能够完成挑战本身也是一件值得骄傲的事情,我还可以将其拓展到 200 天、1000 天的挑战,作为持续学习的一部分,可以一直持续下去。
  2. 调整优先级:起床碰电脑的第一件事就是打开本期的 ARTS 文档,然后逐步往里面填充素材,并从周一开始优化文档,在周五的上午发布本周周刊。
  3. 有深度的写作:首先每天开始动笔写 10 分钟,快速启动写作状态,习惯会让我接着写下一个个 10 分钟,这样快到周末的时候就有了比较丰富的素材。其次我的第一优先级还是质量,用篇幅合适的长文能够比较全面地介绍一个或者两个知识点,并且有一定的深度。

希望运用这三个方法能够让写作这个项目更好的运转下去。

一、Algorithm

每周至少做一个算法题。

计划每天做两道新题目,复习之前的题目,这样可以有更好的刷题效果。

本周算法是链表中的经典题目。

反转单链表 206. Reverse Linked List

分析:

本题比较直接,顺着链表不断得将当前节点的 next 指针改成指向前驱节点,这样逐步将整条链表都反转过来。

JavaScript 版:

const reverseList = function(head){
    let current = head;
    let next = null;
    let prev = null;
    while(current){
        next = current.next;
        current.next = prev;
        prev = current;
        current = next;
     }
}

Python 版:

class Solution(object):
    def reverseList(self, head):
        prev, current = None, head
        while current:
            current.next, prev, current = prev, current, current.next
        return prev

时间复杂度都是 O(n),因为需要遍历一次链表,空间复杂度是 O(1),因为只需要两个变量来保存

不同的是如果是 JavaScript,需要第三个变量才能实现交换两个变量的值,而 Python 由于有多重赋值特性可以实现一行代码完成三个赋值操作,所以可以只用声明两个变量。

两两交换链表中的节点 24. Swap Nodes in Pairs

分析:

题目目的是将链表中的节点每两个节点,两两交换。比反转链表复杂的点在于关注要反转的节点数量。

思考:

先考虑针对本题的解。先设一个起始节点,再顺着链表往前走两步,然后从头节点开始两两交换。

JavaScript:

var swapPairs = function(head){
    let dumyNode = new ListNode(0);
    dumyNode.next = head;
    let current = dumyNode;
    while(current.next && current.next.next){
        let node1 = current.next;
        let node2 = current.next.next;

        current.next = node2;
        node1.next = node2.next;
        node2.next = node1
        current = node1
    }
    return dumyNode.next
}

Python:

class Solution(object):
    def swapPairs(self, head):
        """
        :type head: ListNode
        :rtype: ListNode
        """
        dummyNode = ListNode(0)
        dummyNode.next = head
        current = dummyNode
        while current.next and current.next.next:
            node1, node2 = current.next,current.next.next

            current.next = node2
            node1.next, node2.next = node2.next, node1

            current = node1
        return dummyNode.next

这两种方法的时间复杂度是 O(n),因为只需要遍历一次链表,空间复杂度是 O(1),使用了固定个数的变量。

每 K 个节点一组反转链表 25. Reverse Nodes in k-Group

分析:

跟前两题不同的一轮的循环节点数量是变量控制的。

可以先向前找后继节点,找到第 K 个节点。然后在将第 0 个到第 K 个节点之间的节点全部反转。

实现方式可以是借助堆栈 stack,或者是双指针法来寻找需要反转的子链

JavaScript 版本使用双指针

function reverse(head, tail) {
    let prev = tail.next;
    let p = head;
    while (prev != tail) {
        let nex = p.next;
        p.next = prev;
        prev = p;
        p = nex;
    }
    return [tail, head];
}

function reverseKGroup(head, k) {
    let dummy = new ListNode(0);
    dummy.next = head;
    let pre = dummy;

    while (head != null) {
        let tail = pre;
        // quick jump to border
        for (let i = 0; i < k; i++) {
            tail = tail.next;
            if (tail == null) {
                return dummy.next;
            }
        }
        let nex = tail.next;
        [head, tail] = reverse(head, tail);
        pre.next = head;
        tail.next = nex;
        pre = tail;
        head = tail.next;
    }

    return dummy.next;
}

时间复杂度是 O(n),虽然看起来有两层嵌套的 while 循环,但是第一层并不是遍历每个节点,最终每个节点都只被访问了一次。

空间复杂度是 O(1),因为只使用了常数个变量

Python 版本使用 Stack:

class Solution(object):
    def reverseKGroup(self, head, k):
        stack = []
        dummy = ListNode(0)
        ptr = dummy
        while True:
            count = 0
            temp = head
            while temp and count < k:
                stack.append(temp)
                temp = temp.next
                count += 1
            if count != k:
                ptr.next = head
                break
            while stack:
                ptr.next = stack.pop()
                ptr = ptr.next
            ptr.next = temp
            head = temp
        return dummy.next

时间复杂度是 O(n),空间复杂度是 O(k),因为增加了一个堆栈用来存储每 k 个需要翻转的节点。

二、Review

每周阅读并点评至少一篇英文文章。

看别人写的文章并点评,本周文章是 Is React Having An Angular.js Moment?

最近开始用 Next.js 来写业余项目,发现 React 官方文档已经优先推荐使用 Next.js,而 Next.js 中组件会默认是从服务端渲染,除非是手动声明了 =use client=。

这带来了很多问题,其中最严重的是需要重新理解它的执行机制,包括参数获取、网络请求等都有所不同。

比如不再能够在 useEffect 中去获取数据。

很多 UI 库构建在标准的 hooks 之上,现在可能需要寻找跟 React 服务端组件兼容的版本。

那我们为什么需要服务端 React 呢,跟之前前后端未分离时的 JSP 模板有什么区别呢?

看起来并非一个新鲜的概念,可能区别在于可以由前端人员来掌控全过程…

我的看法是可以先去学习尝试一下,就像 React 函数式组件和 hooks 刚出的那会,有些人会觉得 Class 组件已经挺好用了为什么还要再造个轮子呢,不熟悉的人 useEeffect 使用不合适的第二个参数还容易引起死循环。

给一些时间让它更成熟吧。

三、Tip

每周学习并分享至少一个技术技巧。

问题:

解决两个使用 Taro 编写的单页应用工程之间相互跳转时应用状态会丢失的问题。

分析:

场景是有两个页面 A 和 B,分别属于两个 Taro 工程,两者在发布时会产出 H5 版本,分别部署在同一个域名下的两个子目录中。

我们先访问页面 A,在点击页面 A 中的按钮,跳转到页面 B。然后点击返回按钮,回退到页面 A。此时页面 A 会重新渲染,但是丢失了之前保留的状态。

可以绘制出时序图如下:

跨工程时从页面B回退到页面A时会重新执行React初始化过程

跨工程时从页面B回退到页面A时会重新执行React初始化过程

Taro 工程打包成 H5 之后,在同一个工程范围内跳转再回退是没有这个问题的,因为路由库将页面状态给保存起来了,由于是同一个工程内,实际上并没有发生刷新 html 的情况,回退后可以从已经保存的状态中重新渲染出 Dom。

但是在跨工程的场景中,情况就有所不同,首先这两个工程的页面在浏览器端内存是不共享的,从页面 B 回退到页面 A 相当于重新执行整个生命周期。

解决办法:

  1. 采用打开新标签的形式跳转到页面 B,要回退到 A 页面时直接将页面 B 关闭。

    这种方法的成本比较小,体验上略有下降,在 PC 端还是移动端会有一个“跳出”的感觉。对体验要求非常高的情况下可以无压力的使用。

  2. 探索如何将页面状态包括 state 里的和 Dom 里的给保存起来,然后在回退时还原

    这个方法探索成本比较高,但是体验上是最好的。

选择:对于这块业务可以先选择方案一,然后再研究方案二。

也就是在页面 A 跳转到页面 B 时,拼装好目标页面链接,然后执行 =window.open=,在回退时直接使用

window.close()

四、Share

每周分享至少一篇有观点和思考的技术文章。最好是自己写的文章。

本期分享的是 Tauri 从零到一:使用 Tauri 开发一个 ChatGPT 工具

这篇文章主要尝试讲述 Tauri 从零开发一个应用应该要做什么,并写了一个简单的 ChatGPT 工具,当然这个工具比较简单,只是一个 小小的 Demo 。

后续还会尝试出一个 Tauri 系列,记录下自己开发工具过程中的心得。

本周的分享就到这里,祝大家生活愉快。