【问题标题】:What is the best algorithm for removing every kth character in a string of length N?删除长度为 N 的字符串中的每个第 k 个字符的最佳算法是什么?
【发布时间】:2015-12-13 11:38:27
【问题描述】:

我知道有一种简单的 N 阶算法,我确信这是唯一可以使用的算法。还有其他的吗:

  1. 渐近更好
  2. 可流水线化,即 RAW、WAR 友好
  3. 多线程。

我确定 (1) 有一个,但我不太确定 (2) 和 (3)。如果您还想提及为什么这是一个很好的面试问题。我也很想知道。

【问题讨论】:

  • 如果您使用二叉搜索树来存储单个字符,其中节点的排序/顺序基于字符的索引,那么您可以渐近到O(log k)。但是常数因素会很多,很多更糟(由于指针遍历和树的一般缓存不友好)。
  • “如果您还想提及为什么这是一个很好的面试问题。我也很想知道这一点。” --> 因为最好的面试问题确实如此没有明确的答案,但正如你所拥有的那样,吸引你去探索替代方案。
  • 要退回什么?问题是要求返回第 k 个字符,还是删除第 k 个字符的字符串?
  • 字符串是否表示为字符数组?我认为面试官可能想被问到这样的问题,以开始讨论表示字符串/字符串集的其他方式。
  • 查看渐近复杂性问题的一种相对简单的方法是,无论您如何进行删除的细节,除了k - 1 之外的所有字符都必须移动到新位置。那是o(N - (k - 1)) = o(N)(小o),所以上限不能比这更好。由于您确实可以在 O(N) 操作中完成这项工作,因此这是您最好的上限。

标签: c string algorithm assembly


【解决方案1】:

显而易见的方法很容易就地完成

void remove_every_kth(char *s, size_t len, int k)
{
    // UNTESTED CODE, there might be an off-by-one or a wrong loop boundary
    if (k < len)
        return;

    const char *endp = s + len;
    size_t offset = 1;
    // we skip the s[i] = s[i] memmove at offset=0.
    for (s+=k-1 ; s + offset < endp-(k-1) ; s+=k-1) {
        // every iteration copies k-1 characters, and advances s by k-1
        memmove(s, s+offset, k-1);
        offset++;
    }
    size_t lastchunk = endp - (s+offset);  // some number (less than k) of bytes left in the input
    memmove(s, s+offset, lastchunk);
    s[lastchunk] = '\0';
}
// equivalently, we could have kept a pointer to the read position,
// like const char* read = s+offset;
// and incremented it by k, while incrementing s by k-1


    // for (int i=0 ; i < k ; i++)  // implicit length string
    //    if (!s[i]) return;    // check for length < k

由于k 是常数,您可以计算在哪里可以找到任何输出位置的输入字符。 out[i] = in[i + i/k]。没有什么依赖于数据的,所以如果你不需要就地执行它,这肯定是多线程的,并且你提前知道了字符串的长度。只需将必要的memcpy 调用分流到多个线程即可。 (我用memmove而不是char-pointer循环编写了简单的版本,以使这一点更明显,以及在中型到大型k中获得更好的性能。对于小型k来说可能很糟糕。)

对于就地多线程,如果k 很大,那么即使在长字符串的末尾,大部分复制的源和目标都在同一个块中。每个工作单元做:

  • 不要触摸第一个offset = chunk_number * chunk_size / k字节,前一个工作单元需要读取它们。
  • 将第二个 offset 字节保存到临时数组中。
  • memmove(chunk + offset, chunk + offset*2, chunk_size - offset)(即对前一个工作单元不需要的所有字节执行 memmove)。
  • 旋转等待 previous 块被处理它的线程标记为已完成。 (可能使用单独的数据结构,因为仅查看最后一个重叠位置的数据是行不通的。它可能会被相同的值覆盖。)
  • 从临时缓冲区复制到数据所属块的开头
  • 将工作块标记为已完成。

对于小的k,就地多线程是徒劳的,因为一个块中的大部分字节都需要被后面的块中的字节覆盖。 (非常大的块会有所帮助。)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-08-18
    • 1970-01-01
    • 1970-01-01
    • 2018-12-03
    • 2011-10-03
    • 2013-08-14
    • 2019-03-20
    • 1970-01-01
    相关资源
    最近更新 更多