【问题标题】:Lexicographic minimum permutation such that all adjacent letters are distinct字典最小排列,使得所有相邻的字母都是不同的
【发布时间】:2014-10-28 22:16:09
【问题描述】:

这是一项奖励学校任务,我们还没有接受任何教学,我也不是在寻找完整的代码,但是一些开始的提示会很酷。当我回到家时,我会发布我迄今为止在 Java 中所做的事情,但这是我已经完成的事情。

所以,我们必须做一个排序算法,例如将“AAABBB”排序到 ABABAB。最大输入大小为 10^6,这一切都必须在 1 秒内完成。如果有多个答案,按字母顺序排列的第一个是正确的。我开始测试不同的算法,甚至在没有考虑字母顺序要求的情况下对它们进行排序,只是为了看看结果如何。

第一版:

将 ascii 代码保存到 Integer 数组,其中 index 是 ascii 代码,值是该字符在 char 数组中出现的数量。 然后我选择了 2 个最高的数字,并开始将它们一个接一个地发送到新的字符数组,直到某个数字更高,然后我换到它。效果很好,但是顺序当然不对。

第二版:

遵循相同的想法,但不再选择出现次数最多的数字,而是按照它们在我的数组中的顺序选择索引。在输入类似于 CBAYYY 之前运行良好。算法将其排序为 ABCYYY 而不是 AYBYCY。当然,我可以尝试为那些 Y 找到一些空闲位置,但那时它开始花费太长时间。

【问题讨论】:

  • 我会说你绝对必须首先计算每个字符。 (如果一个字符的长度超过 2,你也可以停在那里,因为不存在有效的解决方案。)
  • 请正式定义任务更好,你的例子还不够。另外 - 你的字母表大小是多少?
  • “排序”一个字符数组,因此彼此之间没有相同的字符 - 这是不是排序,而是重新排列。误导性标题。排序会将相同的字符放在一起。
  • @icza 由于决胜局是按字母顺序排列的,因此也涉及到排序。

标签: java arrays algorithm sorting


【解决方案1】:

一个有趣的问题,一个有趣的调整。是的,这是一种排列或重新排列,而不是一种排序。不,引用的问题不是重复的。

算法。

  1. 计算字符频率。
  2. 按字母顺序从最低的两个字符中输出交替字符。
  3. 每个都用完后,移到下一个。
  4. 在某些时候,最高频率的字符将恰好是剩余字符的一半。此时切换为输出所有该字符,并按字母顺序与其他剩余字符交替输出。

需要注意避免非一错误(输入字符的奇数与偶数)。否则,仅编写代码并使其正常工作是一项挑战。


请注意,有一种特殊情况,其中字符数是奇数,并且一个字符的频率从(一半加 1)开始。在这种情况下,您需要从算法中的第 4 步开始,依次输出所有一个字符与其他字符。

另请注意,如果一个字符包含超过一半的输入,那么对于这种特殊情况,没有解决方案是可能的。这种情况可以通过检查频率预先检测到,或者在执行期间当尾部由一个字符组成时检测到。检测这种情况不是规范的一部分。


由于不需要排序,因此复杂度为 O(n)。每个字符被检查两次:一次是在计数时,一次是在添加到输出时。其他一切都摊销了。

【讨论】:

  • cccba的情况如何?正确答案是 cacbc,但我相信你的答案是 abccc
  • @PhamTrung 不,第 2 步被跳过,因为第 4 步直接适用(顺便说一句,它不完全是一半,如果长度为奇数,它可以向上取整)
  • 它可能会改进答案(+1 btw)以指出没有排列满足不相邻相同字符要求的条件,即最高频率大于 n/2 的上限。跨度>
  • @hardmath:谢谢,你是对的,我应该涵盖的。见编辑。
【解决方案2】:

我的想法如下。通过正确的实现,它几乎可以是线性的。

首先建立一个函数来检查解决方案是否可行。它应该非常快。像最常见的字母 > 所有字母的 1/2 之类的东西,如果可以是第一个,请考虑。

然后,当还有剩余的字母时,取与前面不同的字母顺序的第一个字母,并使进一步的解决方案成为可能。

【讨论】:

  • 你从A开始,但是发现BB的其余部分不能合法写。所以你从 B 开始。然后你写 A,然后是 B。导致 BAB。
  • 此方法对 AAABBBXXXXXX 失败。
  • 不,它没有。你从 A 开始,检查 2A3B5X 是否可以完成。那么你不能从A开始,所以你尝试B,你发现2A2B5X仍然可以完成。然后你尝试 A,你意识到 a2B5X 做不到,所以你尝试 B,同样的结论,所以你尝试 X。等等... ABXAXAXBXBX
【解决方案3】:

正确的算法如下:

  1. 构建输入字符串中字符的直方图。
  2. 将 CharacterOccurrences 放入 PriorityQueue / TreeSet 中,按出现次数最多、字母顺序最低的顺序排列
  3. 有一个 CharacterOccurrence 类型的辅助变量
  4. PQ 不为空时循环

    1. 把PQ的头拿下来留着
    2. 将头部的字符添加到输出中
    3. 如果设置了辅助变量 => 将其重新添加到 PQ 中
    4. 将保留的头部存储在辅助变量中,减少 1 次出现,除非出现次数最终为 0(然后取消设置)
  5. 如果输出的大小 == 输入的大小,这是可能的,你有你的答案。否则是不可能的。

复杂度为 O(N * log(N))

【讨论】:

  • 这是不正确的。 Alpha 排序应该优先于频率,它不会交替,也不会处理 AABBBZZZZ。
【解决方案4】:

制作一个字符频率的双向表:character->countcount->character。记录一个optional<Character>,它存储了最后一个字符(或者一个都没有)。存储总字符数。

如果 (total number of characters-1)

否则,请按字母顺序使用不是最后一个字符输出的最早字符。

记录最后一个字符输出,减少总字符数和使用字符数。

当我们还有字符时循环。

【讨论】:

    【解决方案5】:

    虽然this question 不是完全重复,但my answer 的部分给出了用于枚举所有排列的算法,尽可能少的相邻相等字母可以调整为仅返回最小值,因为它的最优性证明需要每个递归调用都会产生至少一个排列。测试代码之外的更改范围是按排序顺序尝试键并在找到第一个命中后中断。 下面代码的运行时间是多项式的(如果我为更好的数据结构而烦恼,则为 O(n)),因为与它的祖先不同,它没有枚举所有可能性。

    david.pfx's 回答暗示了逻辑:贪婪地取最少的字母,但不会消除所有可能性,但正如他所说,细节是微妙的。

    from collections import Counter
    from itertools import permutations
    from operator import itemgetter
    from random import randrange
    
    
    def get_mode(count):
        return max(count.items(), key=itemgetter(1))[0]
    
    
    def enum2(prefix, x, count, total, mode):
        prefix.append(x)
        count_x = count[x]
        if count_x == 1:
            del count[x]
        else:
            count[x] = count_x - 1
        yield from enum1(prefix, count, total - 1, mode)
        count[x] = count_x
        del prefix[-1]
    
    
    def enum1(prefix, count, total, mode):
        if total == 0:
            yield tuple(prefix)
            return
        if count[mode] * 2 - 1 >= total and [mode] != prefix[-1:]:
            yield from enum2(prefix, mode, count, total, mode)
        else:
            defect_okay = not prefix or count[prefix[-1]] * 2 > total
            mode = get_mode(count)
            for x in sorted(count.keys()):
                if defect_okay or [x] != prefix[-1:]:
                    yield from enum2(prefix, x, count, total, mode)
                    break
    
    
    def enum(seq):
        count = Counter(seq)
        if count:
            yield from enum1([], count, sum(count.values()), get_mode(count))
        else:
            yield ()
    
    
    def defects(lst):
        return sum(lst[i - 1] == lst[i] for i in range(1, len(lst)))
    
    
    def test(lst):
        perms = set(permutations(lst))
        opt = min(map(defects, perms))
        slow = min(perm for perm in perms if defects(perm) == opt)
        fast = list(enum(lst))
        assert len(fast) == 1
        fast = min(fast)
        print(lst, fast, slow)
        assert slow == fast
    
    
    for r in range(10000):
        test([randrange(3) for i in range(randrange(6))])
    

    【讨论】:

      【解决方案6】:

      您首先计算数组中的每个字母数:

      例如,您有 3 - A、2 - B、1 - C、4 - Y、1 - Z。

      1)然后你每次放最低的一个(就是A),就可以放了。

      所以你开始:

      一个

      那么你不能再放A,所以你放B:

      AB

      然后:

      ABABACYZ

      如果您还有至少 2 种字符,这些方法将有效。但在这里你仍然有 3 Y。

      2) 要放置最后一个字符,您只需从第一个 Y 开始,然后在开始方向插入 2 上的一个。(我不知道这些是否是用英语表达的好方法)。

      所以 ABAYBYAYCYZ。

      3) 然后你取 Y 之间的子序列所以 YBYAYCY 并且你对 Y 之间的字母进行排序:

      BAC => ABC

      然后你到达​​p>

      阿巴亚比西兹

      这应该是您问题的解决方案。

      要做所有这些事情,我认为LinkedList 是最好的方法

      希望对你有帮助:)

      【讨论】:

      • 当然你必须检查所有字母:字母编号
      • 这个答案是错误的。如果 A=3 B=4 C=2 Z=8,它将失败。而且没有任何理由使用链表。
      • 我使用了链表,因为有很多插入。但是确实有问题。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-25
      • 2021-12-30
      • 2014-10-30
      • 1970-01-01
      相关资源
      最近更新 更多