【问题标题】:How can we efficiently find the minimum integer from concatenating integers in an array?我们如何有效地从连接数组中的整数中找到最小整数?
【发布时间】:2014-08-20 04:31:26
【问题描述】:

问题:给定一个可以包含重复项的正整数数组,找到通过连接整数获得的最小数。例如:[3, 32, 321] 返回321323

除了尝试所有 n!串联排列,我似乎找不到解决这个问题的好方法。我知道下面有一个很好的解决方案,但我无法理解为什么它是正确的(如果你想尝试解决这个问题,请停止阅读这里):

我阅读了一个解决方案,我们可以使用比较器对数组进行排序,该比较器通过比较串联 mn 和串联 nm 的数值来比较两个数字 mn,以及排序数组将是给出最小数字的串联,但我不知道为什么这是真的。有什么想法吗?

【问题讨论】:

    标签: performance algorithm sorting data-structures concatenation


    【解决方案1】:

    您可以使用类似于冒泡排序的方法来解决这个问题。

    • 首先,我们注意到最终结果的长度是固定的。

    • 其次,结果是你可以创建的最小的字典序字符串(因为所有可能的字符串的长度都是固定的,所以最小的字典序也是最小的个数)。

    假设我们有两个数字nm,如果nm

    所以我们继续交换号码,直到没有东西可以交换,这就是最终的答案

    【讨论】:

    • "因为如果我们有一个字符串是 m...n,所以我们总是可以通过将它换成 n...m 来获得一个更小的字符串。"你有正确的直觉,但这种说法一般不成立。考虑 m = 12 和 n = 1 和 ... = 3。
    • @DavidEisenstat 你是对的,缺少一些东西。由于...中的所有元素也应该遵守规则,所以我们有最终的结果......仍然需要改进:)
    【解决方案2】:

    这更像是一个数学问题而不是计算机问题。

    定义串联操作为C(a1, ... an),其中(a1, ... an)是一个有序数组。那么C就满足了

    C(a1, ... an) = C( C(a1, ... ax), C(a(x + 1), ... an) )

    对于任何 1

    使用数学归纳法,将 F 定义为处理 n 个变量数组的函数。

    F(2) = min(C(a1, a2), C(a2, a1)) 定义明确。

    对于 F(n),给定 F(n - 1) 被最小化,问题就变成了寻找最佳位置以使 F(n) 最小化。那么就很容易证明了

    • 对于 C(ax, an) > C(an, ax), C(a1, ... ax, an, a(x+1), ... a(n - 1) 的任何 ax ))

    • 对于 C(ax, an) C(a1, ... a(x - 1), an, ax, a(x+1), ... a(n - 1))。

    所以最好的解决方案是在 C(ax, an) C(an, a(x + 1)) 处插入 an。因此,您描述的算法成立。

    【讨论】:

    • 当字符串的长度不同时为什么应该有最佳子结构似乎一点也不明显。
    • @DavidEisenstat 学习数学 :)
    【解决方案3】:

    如果它们都是单个数字,解决方案很简单:对它们进行排序并为结果中最重要的数字(最左边的数字)选择最小的数字。例如在 3 之前选择 2,因为 23 小于 32。

    类似的多位数字,按最左边的数字排序。例如在 45 之前选择 123,因为 12345 小于 45123。

    最棘手的部分是领带。如果最左边的数字相同,则比较第二个数字,然后是第三个数字......例如在 34 之前选择 321,因为 32134 小于 34321。

    最后,如果出现平局而您的数字用完了怎么办?在 3 之前选择 321,因为 3213 小于 3321。

    因此,使用 Java 作为一门语言,使用这些规则编写一个比较器(请注意,您可能最好将数字视为字符串,而不是数字),排序,然后连接。其他语言也类似。

    比较器的 Java 代码: See also Gist with a main() for testing 请注意,我能够重组和简化规则。这段代码可能可以更好地优化速度。 (注意:假设所有的 int 都首先转换为 Strings 以提高效率)

    public class SO25396760 implements Comparator<String> {
    
       @Override
       public int compare(String s1, String s2) {
          int minLength = Math.min(s1.length(), s2.length());
          int result = s1.substring(0, minLength).compareTo(s2.substring(0, minLength));
          if (result != 0)
             return result;
    
          int lenDiff = s1.length() - s2.length();
          if (lenDiff == 0)
             return 0;  // Strings are equal
    
          if (lenDiff < 0)
             return compare(s1, s2.substring(minLength));
          else
             return compare(s1.substring(minLength), s2);
       }
    }
    

    当输入为 {"321","3","35", "321334", "333", "2"};排序后的数组是 [2, 321, 321334, 3, 333, 35] 并且连接的值为 2321321334333335。通过检查测试这是最小的。

    【讨论】:

    • 我不认为你的做法是正确的,例如我们有321,3,35和321334,所以为了知道应该放在前面,你需要生成所有的可能性:)
    • 如果这些是带字母的单词,例如AT、ADD、BE、BUSY,是否需要生成所有 24 种可能性?不,因为以 A 开头的单词总是在以 B 开头的单词之前。您最多有 4 种可能性,通过比较关系的第二个字母,您得出的解决方案只有一种可能性。
    • 您如何比较案例 3,33,333,3333,...?关键是你的算法,我们不确定我们需要多深才能比较两个元素。如果您可以为比较器编写伪代码会更清楚。
    • 已添加代码(Java 比较器,不确定 OP 想要什么语言...)
    • 充其量这个比较器是一种相当冗长的方式来获得与问题中描述的相同的顺序;没有其他订单有效(请参阅我的回答)。
    【解决方案4】:

    &lt;' 成为有问题的比较器,这样m &lt;' n 当且仅当 mn &lt; nm假设&lt;'strict weak ordering, 关于为什么按&lt;' 排序的其他答案是正确的 好主意。这是一个仔细的证明。假设相反,两个 (可能不相邻的)输入字符串m &lt;' n 乱序,因此 输出看起来像

    ...n...p...m...
    

    通过归纳nm 之间的距离,我们证明了 输出不是最小的。在基本情况下,当nm 相邻时,我们 可以交换它们。由于mn &lt; nm,新的输出更小。归纳地, 让p 成为出现在nm 之间的输出中的输入字符串。 如果p &lt;' m,那么p &lt;' n 通过传递性。如果p &lt;' n,那么我们调用 np 的归纳假设,它们比 nm。如果n &lt;' p,那么m &lt;' p 通过传递性。如果m &lt;' p,那么我们 在pm 上调用归纳假设,它们比 nm。如果这些情况都不适用,那么 np 是 无与伦比,pm 是无与伦比的。通过传递性 不可比性,nm 是不可比的,这与 事实上n &lt;' m,所以前面四种情况之一适用。

    因此确定了最小输出中输入字符串的顺序 最多置换等价块内的等价字符串。自从 m 等价于 n 意味着 mn = nm,我们得到相同的输出 不管排列如何,因此这个输出是最小的。

    对我来说,正确性证明中有趣的部分是证明&lt;' 首先是严格的弱排序。事实上,除非 我们排除了会造成严重破坏的空字符串。对于所有非空 字符串m,设key(m)为由m组成的无限字符串 经常无限重复。表明&lt;' 是严格的弱排序 在非空字符串上,足以证明 m &lt;' n 当且仅当 key(m) &lt; key(n).

    首先假设m &lt;' n。然后mn &lt; nm。以|m| 为长度 m,我们有 (m^|n|)(n^|m|) &lt; (n^|m|)(m^|n|) 重复 不等式mn &lt; nm 的应用。自从 |m^|n|| = |m||n| = |n^|m||,我们得出结论m^|n| &lt; n^|m|,因此 那个key(m) &lt; key(n)

    现在假设m &lt;' n 不成立。如果nm &lt; mn,那么我们认为 在此之前key(n) &lt; key(m) 因此key(m) &lt; key(n) 没有 抓住。否则,mn = nm。由于(m^|n|)(n^|m|) = (n^|m|)(m^|n|),我们 得出结论m^|n| = n^|m|,因此得出key(m) = key(n)

    【讨论】:

    • 虽然正确的排序对我来说只是“显而易见”,但这是一个很好的证明。至于空字符串,它不会影响排序:您可以将空字符串放在结果中的任何位置,而不会产生任何影响。因此可以排除。 (此外,这个特定问题涉及数字,没有空字符串表示。)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-01-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-11-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多