通过重新排列列表构建可能的最大数字

Posted

技术标签:

【中文标题】通过重新排列列表构建可能的最大数字【英文标题】:Constructing the largest number possible by rearranging a list 【发布时间】:2013-01-10 00:35:43 【问题描述】:

假设我有一个正整数数组;我想操纵顺序,以便结果数组的串联是可能的最大数字。例如[97, 9, 13] 导致99713[9,1,95,17,5] 导致 9955171。我不确定答案。

【问题讨论】:

【参考方案1】:

sorted(x, cmp=lambda a, b: -1 if str(b)+str(a) < str(a)+str(b) else 1)

【讨论】:

这似乎有效,并且比大型列表的详尽排列要快得多。 是的,这可行,但与其他 cmp 解决方案一样,不适用于 Python 3。 (当然,除非你使用functools.cmp_to_key(),这会作弊)。 这个答案如此简单却又如此有效。您能否介绍一下您是如何得出这个结论的? @MarkRansom 首先我们必须澄清这个问题是关于找到字典上最大的字符串;我认为提到整数纯粹是因为该问题已在一些著名公司的采访中使用过,也许他们认为不这么快给出提示会很好。双重问题,词汇。最小字符串,是所谓的 Lyndon 词的精确实例,它处理给定字母表闭包的总排序。更多有趣和详细的信息可以在“单词组合”的第一部分,J. Karhumäki【参考方案2】:

直观地说,我们可以看到,一位数的反向排序会导致最大的数字:

>>> ''.join(sorted(['1', '5', '2', '9'], reverse=True))
'9521'

所以反向排序应该可以工作。当输入中有多位 sn-ps 时,就会出现问题。在这里,直觉再次让我们在95 之前排序9171 之前排序,但为什么会这样呢?同样,如果它们的长度相同,那么如何对它们进行排序就很清楚了:

95 < 99
96 < 97
14 < 17

然后,诀窍是“扩展”较短的数字,以便可以将它们与较长的数字进行比较,并且可以按字典顺序自动排序。实际上,您需要做的就是重复 sn-p 到超出最大长度:

比较995:比较9999595,因此999是第一位的。 比较117:比较1111717,因此1717是第一位的。 比较13213:比较1321321313,因此132132 是第一位的。 比较232341:比较23232323412341,因此2341是第一位的。

这是可行的,因为 python 只需要比较两个 sn-ps,直到它们在某个地方有所不同;在比较两个 sn-ps 时,我们需要跳过(重复的)匹配前缀,以确定它们需要采用的顺序才能形成最大数。

你只需要重复一个sn-p直到它比输入中最长的sn-p * 2长,以保证你在比较两个sn-ps时能找到第一个不匹配的数字。

您可以使用 sorted()key 参数来执行此操作,但您需要先确定 sn-ps 的最大长度。使用该长度,您可以“填充”排序键中的所有 sn-ps,直到它们长于最大长度:

def largestpossible(snippets):
    snippets = [str(s) for s in snippets]
    mlen = max(len(s) for s in snippets) * 2  # double the length of the longest snippet
    return ''.join(sorted(snippets, reverse=True, key=lambda s: s*(mlen//len(s)+1)))

s*(mlen//len(s)+1) 用自身填充 sn-p,使其长度超过 mlen

这给出了:

>>> combos = 
...     '12012011': [1201, 120, 1],
...     '87887': [87, 878],
...     '99713': [97, 9, 13],
...     '9955171': [9, 1, 95, 17, 5],
...     '99799713': [97, 9, 13, 979],
...     '10100': [100, 10],
...     '13213': [13, 132],
...     '8788717': [87, 17, 878],
...     '93621221': [936, 21, 212],
...     '11101110': [1, 1101, 110],
... 
>>> def test(f):
...     for k,v in combos.items():
...         print ' ->  ()'.format(v, f(v), 'correct' if f(v) == k else 'incorrect, should be '.format(k))
... 
>>> test(largestpossible)
[97, 9, 13] -> 99713 (correct)
[1, 1101, 110] -> 11101110 (correct)
[936, 21, 212] -> 93621221 (correct)
[13, 132] -> 13213 (correct)
[97, 9, 13, 979] -> 99799713 (correct)
[87, 878] -> 87887 (correct)
[1201, 120, 1] -> 12012011 (correct)
[100, 10] -> 10100 (correct)
[9, 1, 95, 17, 5] -> 9955171 (correct)
[87, 17, 878] -> 8788717 (correct)

请注意,此解决方案是 a) 短 3 行,b) 也适用于 Python 3,而不必诉诸 functools.cmp_to_key(),并且 c) 不会暴力破解解决方案(这是 itertools.permutations 选项所做的)。

【讨论】:

[101, 10110]。我不明白“用自己填充 sn-p”的想法——如果自身引入的填充大于具有相同前缀的另一个术语的填充,则 ISTM 比较将误入歧途。 @DSM:一切都是为了匹配前缀; 101 是一个匹配前缀,当你重复这些模式足够远时,你会发现它们的分歧点。对于您的反例,您找到了一种方法来克服我重复过去 mlen 的限制,因为 101101 仍然是 1011010110 的前缀,而重复较短的模式 3 次会显示差异:101101101 可针对1011010110。所以我对我需要重复元素多远的分析需要重新思考。我怀疑我需要在这里 加倍 mlen,然后将其用作上限。 @DSM: 加倍mlen 确实可以解决这个问题;您确实需要重复最长的模式才能找到它是如何开始重复的;连接 78778 时,在某处形成 778787,并通过将 both 模式重复到最小长度,您可以为 key 方法生成两种形式并选择更大的。 @DSM:现在密钥更长了也没关系;重要的是前缀匹配; 下一个不匹配的数字确定排序。如果您正在匹配完全重复的模式(234234324),则顺序不再重要,[234, 234234][234234, 234] 的结果相同。 FWIW 我似乎无法用我的套件中的任何测试来打破当前版本,它尝试了五六种不同的方法来寻找反例..【参考方案3】:

提示一:你连接的是字符串,而不是整数。 提示二:itertools.permutations().

【讨论】:

但不要按数字排序。 它所需要的只是一个字典顺序,其中9 &lt; 8 &lt; 7 ... &lt; 1 &lt; 0 请展示一个与max( int(''.join(x)) for x in itertools.permutations( str(x) for x in seq ) )给出相同答案的排序解决方案。 @ypercube:不幸的是,事情没那么简单。不是零件的可变长度。 9 应该在 95 之前,但 17 应该在 1 之前。我相信对第一个数字进行排序,然后对后面的数字(更高、缺失、更低)进行评分会起作用,但不是 100% 确定的。【参考方案4】:
import itertools
nums =  ["9", "97", "13"]
m = max(("".join(p) for p in itertools.permutations(nums)), key = int)

您可以按照提示使用 itertools.permutations,并在将它们与 join 函数连接后使用 max 函数的 key 参数(它告诉将哪个函数应用于每个元素以确定最大值)。

以字符串开头更容易。

【讨论】:

【参考方案5】:

我不喜欢这种蛮力方法。对于大型集合,它需要大量的计算。

您可以为 sorted 内置方法编写自己的比较函数,该函数将根据您在函数中输入的任何逻辑返回任何对的排序参数。

示例代码:

def compareInts(a,b):
    # create string representations
    sa = str(a)
    sb = str(b)

    # compare character by character, left to right
    # up to first inequality
    # if you hit the end of one str before the other, 
    # and all is equal up til then, continue to next step
    for i in xrange(min(len(sa), len(sb))):
        if sa[i] > sb[i]:
            return 1
        elif sa[i] < sb[i]:
            return -1

    # if we got here, they are both identical up to the length of the shorter
    # one.
    # this means we need to compare the shorter number again to the 
    # remainder of the longer
    # at this point we need to know which is shorter
    if len(sa) > len(sb): # sa is longer, so slice it
        return compareInts(sa[len(sb):], sb)
    elif len(sa) < len(sb): # sb is longer, slice it
        return compareInts(sa, sb[len(sa):])
    else:
        # both are the same length, and therefore equal, return 0
        return 0



def NumberFromList(numlist):
    return int(''.join(''.format(n) for n in numlist))

nums = [97, 9, 13, 979]
sortednums = sorted(nums, cmp = compareInts, reverse = True)
print nums # [97, 9, 13, 979]
print sortednums # [9, 979, 97, 13]
print NumberFromList(sortednums) # 99799713

【讨论】:

【参考方案6】:

嗯,总是有蛮力的方法......

from itertools import permutations
lst = [9, 1, 95, 17, 5]

max(int(''.join(str(x) for x in y)) for y in permutations(lst))
=> 9955171

或者这个,@Zah 的答案的改编版,它接收整数列表并返回一个整数,如问题中所述:

int(max((''.join(y) for y in permutations(str(x) for x in lst)), key=int))
=> 9955171

【讨论】:

【参考方案7】:

您可以通过一些巧妙的排序来做到这一点。

如果两个字符串的长度相同,则选择两个字符串中较大的一个。很简单。

如果它们的长度不同,请弄清楚如果将最佳组合附加到较短的组合会产生什么结果。由于较短的后面的所有内容都必须等于或小于它,因此您可以通过将短的附加到自身来确定这一点,直到它与较长的大小相同。一旦它们的长度相同,您就可以像以前一样进行直接比较。

如果第二个比较结果相等,您已经证明较短的字符串不可能比较长的字符串更好。取决于它与它配对的内容,它仍然可能会变得更糟,所以应该先出现更长的那个。

def compare(s1, s2):
    if len(s1) == len(s2):
        return -1 if s1 > s2 else int(s2 > s1)
    s1x, s2x = s1, s2
    m = max(len(s1), len(s2))
    while len(s1x) < m:
        s1x = s1x + s1
    s1x = s1x[:m]
    while len(s2x) < m:
        s2x = s2x + s2
    s2x = s2x[:m]
    return -1 if s1x > s2x or (s1x == s2x and len(s1) > len(s2)) else 1

def solve_puzzle(seq):
    return ''.join(sorted([str(x) for x in seq], cmp=compare))

>>> solve_puzzle([9, 1, 95, 17, 5])
'9955171'
>>> solve_puzzle([97, 9, 13])
'99713'
>>> solve_puzzle([936, 21, 212])
'93621221'
>>> solve_puzzle([87, 17, 878])
'8788717'
>>> solve_puzzle([97, 9, 13, 979])
'99799713'

这应该比遍历所有排列更有效。

【讨论】:

失败例如[936、21、212]。 对于[87, 17, 878][97, 9, 13, 979] @RussellBorogove 和 MartijnPieters,正如我所说,我没有解决问题,你的两个例子都有这种情况。我有一个解决办法,现在只需测试一下。 这涵盖了我目前拥有的所有测试用例。 新的:[1201, 120, 1],你和我的都失败了。【参考方案8】:
import itertools
def largestInt(a):
    b = list(itertools.permutations(a))
    c = []
    x = ""
    for i in xrange(len(b)):
        c.append(x.join(map(str, b[i])))
    return max(c)

【讨论】:

以上是关于通过重新排列列表构建可能的最大数字的主要内容,如果未能解决你的问题,请参考以下文章

重新排列后用另一个列表中的数字替换一个列表

从可用数字中形成最大数字?

如何从数字列表中获取所有可能的排列并存储在数据框中?

一道笔试题-给定一个正整数序列,请尝试将它们重新排列使得排列的结果最大。

leetcode 179. 最大数 解题报告

当矩阵在 C 中实现为列表时重新排列矩阵行