【问题标题】:How to remove kth element in O(1) time complexity如何在 O(1) 时间复杂度中删除第 k 个元素
【发布时间】:2020-08-07 16:56:22
【问题描述】:

给定一个字符串,我需要删除最小字符并返回已删除字符的索引总和。 假设字符串是 'abcab' 我需要在索引 1 处删除第一个 a。 我们只剩下'bcab'。现在再次删除剩余字符串中最小且位于索引 3 处的 a 我们只剩下'bcb'。 以同样的方式在索引 1 处删除 b,然后在索引 2 处再次从 'cb' 中删除 b,最后删除 c

所有索引的总和为 1+3+1+2+1=8 问题很简单,但我们需要在 O(n) 中完成。为此,我需要删除 O(1) 中的第 k 个元素。在 python del list[index] 中,时间复杂度为 O(n)。 如何使用 python 在恒定时间内删除

编辑

这是一个确切的问题

给定一个大小为 N 的字符串 S。假设 count 等于 0。

您的任务是通过执行以下操作 N 次删除字符串 S 的所有 N 个元素

• 在单个操作中,选择 S 中按字母顺序最小的字符,例如,从 S 中删除并将其索引添加到计数中。如果 c 等多个字符存在,则选择索引最小的那个。

打印count的值。

注意考虑基于 1 的索引

解决T测试用例的问题

输入格式

输入的第一行包含一个整数 T,表示测试用例的数量 • 每个测试用例的第一行包含一个整数 N,表示字符串 S 的大小

• 每个测试用例的第二行包含一个字符串 S

输出格式

为每个测试用例打印一行,其中包含一个表示计数值的整数

1

• S 只包含小写英文字母

所有测试用例的 N 总和不超过 10

示例输入 1

5

abcab

样本输出1

8

解释

操作按以下顺序进行

当前字符串 S= abcab', s 的字母顺序最小的字符是 'a 因为 a 出现了 2 次,我们选择第一次出现。它的索引 1 将被添加到计数中,而 a 将被删除。因此,S变成bcab

a will.be 从 5 (bcab) 中删除,3 will.be 添加到 count

第一次出现的 b 将从 (bcb) 中删除,并将 1 添加到 count。

b 将从 s (cb) 中删除,并将 2 添加到 count

c 将从 5 (c) 中删除,并将 1 添加到 count

【问题讨论】:

  • 字符串是否限于字符a - z
  • 是的,它仅限于 a-z

标签: python-3.x algorithm data-structures time-complexity


【解决方案1】:

如果您重复删除第一次出现的最小字符的过程,那么每个字符的索引(当您删除它时)是原始字符串中前面 较大 个字符的数量加上一。

因此,您真正需要做的是为每个字符找出前面较大字符的数量,然后将所有这些计数相加。

只有 26 个字符,因此您可以使用 26 个计数器来执行此操作。

【讨论】:

    【解决方案2】:

    请链接到原始问题陈述,或者复制/粘贴它所说的内容,而不是试图解释它。照原样,你所要求的是不可能的。

    忘记删除:如果您的要求是可能的,那么排序会更糟O(n)(删除最少剩余n 次,每次O(1) 成本),但众所周知,比较基于排序的排序不能比最坏的情况更好O(n log n)

    打赌:最初的问题陈述并不要求您删除任何内容,而是返回如同您已删除的结果。

    通过一次输入

    综合各种想法,一个字符的最终索引比之前看到的较大字符的数量多一。因此,可以使用O(1) 存储和O(n) 时间从左到右传递输入,同时不删除任​​何内容:

    def crunch(s):
        neq = [0] * 26
        result = 0
        orda = ord('a')
        for ch in map(ord, s):
            ch -= orda
            result += sum(neq[i] for i in range(ch + 1, 26)) + 1
            neq[ch] += 1
        return result
    

    对于您的原件:

    >>> crunch('abcab')
    8
    

    但也可以一次处理一个字符的任意迭代:

    >>> from itertools import repeat, chain
    >>> crunch(chain(repeat('y', 1000000), 'xz'))
    2000002
    

    x 最初位于(基于 1 的)索引 1000001,占结果的一半。然后从概念上删除一百万个“y”中的每一个,每个都位于索引 1。最后,“z”位于索引 1,总计 2000002。

    【讨论】:

    • 我不能给你链接,因为它是在现在已经结束的比赛中被问到的。如果不在 O(1) 中,我们可以比 O(n) 更好地删除第 k 个元素。这个问题和我上面说的一样。没有必要删除任何东西,那么我们还有什么其他方法?
    • 已经回答了您提出的问题:不可能。我不会反复拔牙 ;-) 试图激起您重建 actual 问题陈述。例如,您后来补充说,它被限制为 26 个可能的值。这很重要!将其添加到原始问题中,并删除不相关的内容(例如,在进行过程中实际上删除值的看似无用的要求)。
    • 值得注意的是,即使这是单遍,它也需要与多遍算法相当的指令数,因为平均而言,在主循环的每次迭代中,内循环将执行 25/ 2 = 12.5 步。在实践中,由于更好的缓存局部性,这将比 26 遍算法工作得更好,尤其是对于长字符串。
    • 确实,它可以处理太大而无法放入磁盘的字符串,更不用说在 RAM 中了 ;-)
    • 我已经添加了确切的问题。请检查。
    【解决方案3】:

    看起来您只对索引的结果总和感兴趣,不需要逐步模拟此算法。 在这种情况下,您可以通过以下方式计算结果: 对于从az 的每个字母:

    1. 将已删除字母的计数器设置为 0
    2. 遍历字符串,如果遇到当前字母,则将current_index - already_removed_counter 添加到结果中。 2a。如果您遇到当前或更早(较小)的字母,请增加计数器,因为它已被删除

    时间复杂度为 26 * O{n},即 O{n}。

    【讨论】:

    • 很好的解决方案。我懒得想出如何处理早期删除的字符的想法,所以我选择了一种更简单的方法。但事实证明,避免删除比我更容易!我已经更新了我的伪代码来整合你的想法。
    【解决方案4】:

    由于字符串中只有 26 个不同的字符,我们可以分别取出每个字符并线性遍历字符串以查找其所有出现的地方。记下找到多少章的计数器。每次发现给定字符的出现时,都会显示其索引减少计数器。在切换到新角色之前,删除前一个角色的所有出现 - 这可以在线性时间内完成。

    res = 0
    for c in 'a' .. 'z'
      cnt = 0
      for idx = 1 .. len(s)
        if s[idx] = c
          print idx - cnt
          res += idx - cnt
          cnt++
      removeAll(s, c)
    return res
    

    在哪里

    removeAll(s,c):
      i = 1
      cnt = 0
      n = len(s)
      while (i < n) 
        if s[i + cnt] = c
          cnt++
          n--
        else
          s[i] = s[i + cnt]
        i++
      len(s) = n
    

    它打印总和的元素以更好地说明正在发生的事情。

    编辑: 基于 Igor 回答的更新版本,不需要实际删除元素。复杂度相同,即O(n)

    res = 0
    for c in 'a' .. 'z'
      cnt = 0
      for idx = 1 .. len(s)
        if s[idx] <= c
          if s[idx] = c
            print idx - cnt
            res += idx - cnt
          cnt++
    return res
    

    【讨论】:

      猜你喜欢
      • 2021-06-21
      • 1970-01-01
      • 1970-01-01
      • 2010-10-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-09-03
      • 2014-01-09
      相关资源
      最近更新 更多