Q6-LeetCode179- 最大数

27 阅读4分钟

一 实现思路

1 思维层面

1 这道题的难点是能相出 a,b 之间的比较规则f,以及怎么证明 f具体传递性,即 如果 aΘb, bΘc, 那么 aΘc

2 如果f传递性成立,那么就意味着我们可以按序 两两比较元素,而不用暴力比较 所有其他成员

3 f的传递性证明如下:

S1 定义 x⊕y

定义一种非负整数集合上的二元运算,记作 ⊕,它表示将两个数拼接后的结果

令 s(x) 表示大于非负整数 x 的最小的十的整次幂(即当 x>0 时 s(x)=10 ^ (log10x + 1),

特别地,s(0)=10)

那么 x⊕y = x×s(y)+y, 比如 12⊕456 = 12*1000 + 456 = 12456


S2 定义一个非负整数集合上的二元关系,记作 Θ

当一个数x 排在数y 前面更优时,(即 x⊕y≥y⊕x),我们认为 xΘy。


S3 证明 Θ关系的 传递性:即 如果 aΘb 且 bΘc,那么 aΘc 也成立。传递性证明如下:

由 aΘb 且 bΘc 可知:

(p1): a × s(b)+b ≥ b×s(a)+a

(p2): b× s(c)+c ≥ c×s(b)+b

分别给 p1 和 p2 左右2边 加上 -a-b 和 -b-c,移项整理得:

(p3): a × (s(b)−1) ≥ b × (s(a)−1)

(p4): b × (s(c)−1) ≥ c × (s(b)−1)

两式的左右两边均非负,因此由两式相乘可得:

(p5): a × b × (s(b)−1) × (s(c)−1) ≥ b × c × (s(a)−1) × (s(b)−1)

不等式两边都有 b,根据 b 是否为 0 分类讨论:

  • 当 b = 0 时:

    • 将 b = 0 代入(p2)的 bΘc 可知:c ≥ c×10,即 c = 0
    • 当 c = 0 时,有 a × s(c)+c ≥ c × s(a) + a(因为10a >= a必然成立),恰符合 aΘc 的定义
  • 当 b > 0 时:

    • 此时 b × (s(b)−1)) > 0 必然成立,所以P5的 不等式两边同时除以 b×(s(b)−1))
    • 化简得:a × (s(c)−1) ≥ c × (s(a)−1),恰符合 aΘc 的定义。

综上,有 aΘc

4.1 证明了传递性后,只是说明这个排序规则是合理的,确保了可以使用排序算法,原因是 满足了“全序关系”的3个性质

  • 反身性 a Θ a:

    • a⊕a ≥ a⊕a ==> a×s(a) + a ≥ a×s(a) + a, 显然是成立的
  • 传递性 若 aΘb 且 bΘc,则aΘc:

    • 上述已证明
  • 完全性:对任意a,b,必有 aΘb 或 bΘa:

    • 即 要么 a⊕b ≥ b⊕a,要么 b⊕a > a⊕b,显然成立,因为a,b 都是非负整数,相关运算结果 必然是可比较的

这三个性质确保了可以使用任何比较排序算法


但是 还需要证明:按这个规则排序得到的序列 就是 最优解

4.2 反证法步骤:

假设: 存在一个序列P是最优解(能得到最大数)

但P不完全遵守规则Θ

推理:

由于P不完全遵守规则Θ

必然存在至少一对相邻元素(s1, s2),其中s1在s2前面

但它们违反规则Θ,即 (k1)s2⊕s1 > s1⊕s2

关键性质:

对于任意序列[..., x, y, ...],整个序列拼接后的值可以表示为:[...⊕x⊕y⊕...]

当且仅当x,y相邻时,交换x,y的结果等价于:[...⊕y⊕x⊕...]

矛盾推导:

设序列P是最优解 = [..., s1, s2, ...],

将s1,s2交换后得到新序列P' = [..., s2, s1, ...]

由于(k1)s2⊕s1 > s1⊕s2,且s1,s2相邻,其他元素位置不变

所以P'拼接后的值 > P拼接后的值

这与P是最优解矛盾

结论: 原假设不成立

因此最优解必须完全遵守规则Θ

即:按规则Θ排序得到的序列就是最优解

2 JS里Array.sort() 的排序规则

1 遵循以下规则:

  • 如果返回值 < 0,表示 a 排在 b 前面, 如 (a,b) => a-b是 升序排列
  • 如果返回值 > 0,表示 b 排在 a 前面, 如 (a,b) => b-a是 降序排列
  • 如果返回值 = 0,保持原来的顺序 ​

3 参考实现

01-官方实现和传递性证明

02-比较规则传递性以及算法正确性的证明

二 代码实现

1 方法1: 排序 时间复杂度:O(nlognlogm); 空间复杂度: O(logn)

function largestNumber(nums: number[]): string {
  // 数学排序规则(这里^表示的数学上的幂运算,而不是异或符):
  //     b * 10 ^ a + a - (a * 10 ^ b.length + b) 降序排列

  // 易错点1:由于此题是大数比较,直接用数字比较可能会溢出,所以这里用字符串比较
  // 这里期望是降序,所以字符串的返回值期望是正数
  nums.sort((a,b) => +`${b}${a}` - +`${a}${b}`)

  // 易错点2:如果排序后的数组第一个元素是0,有可能存在前导0情况
  // 所以这里直接返回0
  if (nums[0] === 0) return '0'
  return nums.join('')
};