本回复末尾有一句俏皮话,解释如下:-)
系数数组包含:第一行(索引中的第 0 行)有 M 个元素,第二行(第 1 行)有 (M-1) 个元素,依此类推,总共有 M+(M-1)+…+ 1=M(M+1)/2个元素。
从最后开始工作会稍微容易一些,因为系数数组always最后一行(M-1 行)有 1 个元素,倒数第二行(M-2 行)有 2 个元素,M-3 行有 3 个元素,依此类推。最后 K 行占据系数数组的最后 K(K+1)/2 个位置。
现在假设您在系数数组中得到了一个索引 i。大于 i 的位置有 M(M+1)/2-1-i 个元素。拨打这个号码 i';你想找到最大的 k 使得 k(k+1)/2 ≤ i'。这个问题的形式正是你痛苦的根源——据我所知,你无法避免求平方根:-)
Well let's do it anyway: k(k+1) ≤ 2i' means (k+1/2)2 - 1/4 ≤ 2i', or equivalently k ≤ (sqrt(8i'+1)-1)/2. Let me call the largest such k as K, then there are K rows that are later (out of a total of M rows), so the row_index(i,M) is M-1-K. As for the column index, K(K+1)/2 elements out of the i' are in later rows, so there are j'=i'-K(K+1)/2 later elements in this row (which has K+1 elements in total), so the column index is K-j'. [Or equivalently, this row starts at (K+1)(K+2)/2 from the end, so we just need to subtract the starting position of this row from i: i-[M(M+1)/2-(K+1)(K+2)/2]. It is heartening that both expressions give the same answer!]
(伪)代码[使用 ii 而不是 i',因为某些语言不允许使用像 i' 这样的名称的变量;-)]:
row_index(i, M):
ii = M(M+1)/2-1-i
K = floor((sqrt(8ii+1)-1)/2)
return M-1-K
column_index(i, M):
ii = M(M+1)/2-1-i
K = floor((sqrt(8ii+1)-1)/2)
return i - M(M+1)/2 + (K+1)(K+2)/2
当然,您可以通过替换 ii 和 K 的表达式将它们变成直线。您可能必须小心精度误差,但有一些方法可以找到不需要浮点计算的整数平方根。另外,引用 Knuth 的话:“当心上述代码中的错误;我只是证明了它的正确性,没有尝试过。”
如果我可以斗胆做进一步的评论:简单地将所有值保留在 M×M 数组中最多将占用两倍的内存,并且根据您的问题,与算法改进相比,2 的因子可能微不足道,并且可能是值得用可能昂贵的平方根计算来换取更简单的表达式。
[编辑:顺便说一句,你可以证明 Floor((sqrt(8ii+1)-1)/2) = (isqrt(8ii+1)-1)/2 其中 isqrt(x)=floor(sqrt(x))是整数平方根,除法是整数除法(截断;C/C++/Java 等中的默认设置)——所以如果您担心精度问题,您只需要担心实现正确的整数平方根。]