力扣5674“构造字典序最大的合并字符串”解题报告
Contents
但算法的正确性证明并不那么显然。这里是证明过程。
1. 题目
题意很简单,给定两个字符串,要你把它们合并成一个,找出字典序最大的合并串。
比如给定两个字符串cabaa
和bcaaa
,字典序最大的合并串是cbcabaaaaa
。合并方式是:
|
|
2. 算法
贪心法。
将给定的两个串简称为“源串”(名为word1
和word2
),最终结果串称为“目标串”(名为merge
)。
再将word1
用掉某些开头的字符(添加到merge
)后剩余的部分(总是word1
的后缀串)称为word1
剩余串,命名为left1
。类似的有left2
。
每次选择left1
和left2
中字典序比较大的那个,选择其第一个字符,从原串的开头移除,并放到merge
中。
循环执行,直到两个源串都变成空串为止。
例如上面例子,算法步骤如下(加粗的是用于比较left1
和left2
字典序的前缀):
阶段 | left1 | left2 | merge |
---|---|---|---|
0 | cabaa | bcaaa | (empty) |
1 | abaa | bcaaa | c |
2 | abaa | caaa | cb |
3 | abaa | aaa | cbc |
4 | baa | aaa | cbca |
5 | aa | aaa | cbcab |
… | … | … | … |
10 | (empty) | (empty) | cbcabaaaaa |
3. 代码
|
|
4. 证明
将要证明的命题规范地说一遍,就是:
每次决定从left1还是left2的开头选取字符时,选择字典序比较大的串的首字符添加到
merge
,最终能获得最大字典序的merge
。
为了方便描述,在下面举个具体的例子。
假设left1
和left2
从[0]
到[s-1]
都相同,[s]
不同。不妨设left1[s]
是'x'
,left2[s]
是'y'
。
证明分两步。
第1步:证明在最大字典序合并方案(下称“最佳方案”)中,left2[s]
一定比left1[s]
先出现。
第2步:证明先走left2[0]
一定能到达最佳方案,而走left1[0]
不一定能到达最佳方案。
补充:比较left1
和left2
时,如果left1
是left2
的前缀,可以假想left1
后面有若干'\0'
。
4.1. 第一步,证明:在最大字典序合并方案(下称“最佳方案”)中,left[s]一定比left[s]先出现
反证法。
假设left1[s]
也就是'x'
先出现。即,合并串中出现left1[s]
时(left1
走完s+1
个字符),left2[s]
尚未出现(left2
走完的字符数k
满足k <= s
)。
|
|
在合并串中,前s + k
个字符,是由
|
|
构成的。根据定义,上下两串的[0]
到[k - 1]
是相同的。
对于假设中的合并方案,如果完全交换left1
和left2
的选择方案(即原方案中选择left1[0]
的时候改成选择left2[0]
,反之亦然),则merge[0]
到merge[s + k - 1]
将完全一致,区别仅仅是merge[s + k]
可以是’y’。
易见,根据字典序的定义,新方案的字典序比原方案大。这就证明了原假设错误。证毕。
4.2. 第二步,证明:先选left2[0]一定能得到最大字典序的merge,先选left1[0]不一定能得到最大字典序的merge
注意下图:
左上角是left1
和left2
都还没用过的情况。右下角是left1
用了k
个字符,left2
用了s + 1
个字符(最后一个字符是'y'
)。
可以将两个字符串的合并看做从左上角到右下角的一个路径,每次只能往右或往下走。
假设对于left1
和left2
,走完前s + k + 1
个字符的最佳方案是下图这样的:
容易发现,由于left1
和left2
前s
个字符相同(第s+1
个字符不同),那么前k
个字符必然相同。所以,经过对角线的路径,均可替换成其镜像路径,而不改变merge
的值。如下图。
显然左上角的起点在对角线上。
如果在遇到字符left2[s]
即'y'
之前,路径最后一次经过对角线的位置是在起点之外的某个地方,那么从起点开始第一步往下走(选择left1[0]
)还是往右走(选择left2[0]
)均不影响达到最佳路径。即往右走是可以的。
而,如果路径最后一次(也是唯一一次)经过对角线的路径就是起点,如下图:
那么第一步就一定要往右走(选择left2[0]
)。如果第一步往下走(选择left1[0]
),因为路径不经过对角线,那就再也不可能到达目标格了。所以第一步不能往下走。
证毕。
Author
LastMod 2021-02-08