TurboQuant

上周还有个"大事儿"忘记聊了。

周末有同学在群里聊到,自己又买了两条内存,我才想起来这事儿:内存降价了。

降价,自然是相对于涨价来说的。

自从 2025 年下半年以来,内存就一路高歌猛进。

现在的"降价",其实是相对于高位来说的,更准确的叫法,应该是"回撤"。

至于此前为什么涨价,原因很简单,供求关系失衡了。

由于 AI 服务器对内存的需求,是普通服务器的 8~10 倍,而全世界各个国家都在搭建自己的 AI 算数中心,尤其是美国。

大量的海外内存订单涌入,导致内存厂商放弃生产"消费级的 DDR4",转而生产 HBM(高带宽内存)和 DDR5 了。

结果,前些天,谷歌发布了新压缩算法 TurboQuant,可将大型语言模型运行时的键值缓存内存占用减少 60% 以上。

于是,内存价格开始跳水。

但这事儿,你要问我怎么看的话。

我只能建议:如果确实是有购买内存的需求,现在是个不错的买入时机,但如果是想买来囤着,那没必要。

我这么说,不是看好,内存价格还会持续走低。

恰恰相反,虽然 TurboQuant 很高效,但还不足马上拉平这供求关系,未来内存价格肯定还会上涨。

大家想想 2025 年初,DeepSeek 发布的时候,因为新架构,导致算力要求下降,当时也有鬼故事说,英伟达的芯片卖不出去了,甚至一度导致英伟达股票下跌。

结果呢,该买还得买,大家对算力的要求,是无止境的,内存也是类似。

按这个逻辑,我认为内存价格,还会回到高位,囤着,似乎也没有毛病?

这也就是我前面说的,内存和芯片之间,是"类似",而不是"一样"。

芯片,国内造不了,但内存,国内能造。

这就是最核心的区别,任何世界级的供求关系失衡,东大这一超级制造工厂,都能填满空缺,只是时间问题。

所以如果是在这个位置,因为某个算法的出圈,就去抄底内存,我担心可能熬不到明年。

...

聊到这里,来一道和「字节跳动」相关的算法题。

题目描述

平台:LeetCode

题号:400

给你一个整数 ,请你在无限的整数序列 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, ...] 中找出并返回第  位数字。

示例 1:

输入:n = 3

输出:3

示例 2:

输入:n = 11

输出:0

解释:第 11 位数字在序列 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, ... 里是 0 ,它是 10 的一部分。

提示:

模拟

我们知道,对于长度为 的数字的范围为 (共 个),总长度为:

因此我们可以先对 进行不断试减(更新 ),确定下来目标数字 的长度为多少,假设为

然后直接计算出长度 的最小值为 ,由于范围内的数长度都是 ,因此我们可以直接定位到目标数字 为何值。

根据目标值 必然满足关系式:

变形可得:

进行最后一次的试减(更新 ),若恰好有 ,说明答案为 的最后一位,可由 x % 10 取得;若大于 ,说明答案是 的第 位(十进制表示,从左往右数),可由 (x + 1) / (int) (Math.pow(10, len - n)) % 10 取得。

Java 代码:

class Solution {
    public int findNthDigit(int n) {
        int len = 1;
        while (len * 9 * Math.pow(10, len - 1) < n) {
            n -= len * 9 * Math.pow(10, len - 1);
            len++;
        }
        long s = (long) Math.pow(10, len - 1);
        long x = n / len - 1 + s;
        n -= (x - s + 1) * len;
        return n == 0 ? (int) (x % 10) : (int) ((x + 1) / Math.pow(10, len - n) % 10);
    }
}

C++ 代码:

class Solution {
public:
    int findNthDigit(int n) {
        int len = 1;
        while (len * 9 * pow(10, len - 1) < n) {
            n -= len * 9 * pow(10, len - 1);
            len++;
        }
        long s = static_cast<long>(pow(10, len - 1));
        long x = n / len - 1 + s;
        n -= (x - s + 1) * len;
        return n == 0 ? x % 10 : (x + 1) / static_cast<long>(pow(10, len - n)) % 10;
    }
};

Python 代码:

class Solution:
    def findNthDigit(self, n: int) -> int:
        lenv = 1
        while lenv * 9 * (10 ** (lenv - 1)) < n:
            n -= lenv * 9 * (10 ** (lenv - 1))
            lenv += 1
        s = 10 ** (lenv - 1)
        x = n // lenv - 1 + s
        n -= (x - s + 1) * lenv
        return x % 10 if n == 0 else (x + 1) // 10 ** (lenv - n) % 10

TypeScript 代码:

function findNthDigit(n: number): number {
    let len = 1;
    while (len * 9 * Math.pow(10, len - 1) < n) {
        n -= len * 9 * Math.pow(10, len - 1);
        len++;
    }
    const s = Math.pow(10, len - 1);
    let x = Math.floor(n / len) - 1 + s;
    n -= (x - s + 1) * len;
    return n === 0 ? x % 10 : Math.floor((x + 1) / Math.pow(10, len - n)) % 10;
};
  • 时间复杂度:
  • 空间复杂度:
补充

看到评论区,不少小伙伴对上述推导理解感到困难。我们可以换个思路,从更加纯粹直接的角度进行分析。

首先和上述解法一样,我们还是需要先对 进行第一阶段的试减(更新 ),得到目标数字所在的数值的长度 是多少。

然后根据 我们可以算得起点值 (例如 时, 时, )。

由于每个数长度都是 ,因此我们可以用剩余的数 ,得到从起点的偏移量是多少(并将偏移量累加更新到 ),然后对 做第二阶段的试减,减去的值就是

如果试减后结果恰好为 ,那么答案为当前 的最后一个数字;否则(试减结果大于 ),则是 中(十进制表示,从左往右数)的第 个数字。

Java 代码:

class Solution {
    public int findNthDigit(int n) {
        int len = 1;
        while (len * 9 * Math.pow(10, len - 1) < n) {
            n -= len * 9 * Math.pow(10, len - 1);
            len++;
        }
        long s = (long) Math.pow(10, len - 1);
        s += n / len - 1;
        n -= len * (n / len);
        return n == 0 ? (int) (s % 10) : (int) ((s + 1) / Math.pow(10, len - n) % 10);
    }
}
  • 时间复杂度:
  • 空间复杂度:

最终你会发现,两种做法是完全等价的,只是对应了不同的角度描述而已。

最后

巨划算的 LeetCode 会员优惠通道目前仍可用 ~

使用福利优惠通道 leetcode.cn/premium/?promoChannel=acoier,年度会员 有效期额外增加两个月,季度会员 有效期额外增加两周,更有超大额专属 🧧 和实物 🎁 福利每月发放。

我是宫水三叶,每天都会分享算法知识,并和大家聊聊近期的所见所闻

欢迎关注,明天见。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐