关于面试背景:这个问题不是众所周知的算法的常见问题(这不是 KMP 或 Dijsktra 的算法!),我认为绝对没有理由让某人期待一个开箱即用的答案。
即使您有尝试计算进位的想法,在您看到它有效之前,您也无法确定它是否有效。如果我没记错的话,有一个时间复杂度为 O(log10 N) 的解决方案。我专注于删除进位并从头开始构建算法(和实现):在某些边缘情况下可能仍然存在细微的错误。 (结果非常接近twin question 的接受答案。)
前言
每个回文数都可以写成两个镜像数之和吗?
想到的第一个想法是这样的数字是回文:
1 2 5
+ 5 2 1
-------
6 4 6
但这不是真的,因为我们知道当两位数之和大于或等于 10 时会有进位。
让我们从一个简单的问题开始:每个回文数都可以写成两个镜像数的和吗?
假设我们有N = D[2n]...D[n+1]D[n+1]...D[2n],其中D[k] 是第k 个数字。显然,我们可以这样写:
D[2n]-1 ... D[n+1]-1 1 ... 1
+ 1 ... 1 D[n+1]-1 ... D[2n]-1
-------------------------------------------
D[2n] D[n+1] D[n+1] D[2n]
但是如果位数是奇数呢? N = D[2n]...D[n+1]D[n]D[n+1]...D[2n]。我们可以这样写:
D[2n]-1 ... D[n+1]-1 D[n]/2 1 ... 1
+ 1 ... 1 D[n]/2 D[n+1]-1 ... D[2n]-1
--------------------------------------------------
D[2n] D[n+1] D[n] D[n+1] D[2n]
但这只有在D[n] 是偶数时才可以。
如果数字不是回文怎么办?
927 可以写成两个镜像数之和吗?想象一下我们有:
a b c
+ c b a
-------
9 2 7
单位数没有传入进位,因此c+a = 7 或c+a = 17。在这两种情况下,百位数字将是7(无进位)或8。个位和百位不够接近,无法将数字写成两个镜像数字之和。
让我们更具体一点。
让N = D[n+1]D[n]...D[0]拥有D[n+1] = 0 or 1(如果第一个数字不是1,我们在数字前面添加0)。
从小学开始,我们就知道如何将两个数字相加A[n]...A[0] 和B[n]...B[0]。进位是:
C[0] = 0
C[k+1] = (A[k] + B[k] + C[k]) // 10
数字是:
D[k] = (A[k] + B[k] + C[k]) % 10
D[n+1] = C[n+1])
如果N = A(n)...A(0) + A(0)...A(n) = M + N - M,我们可以特化上面的命题,因为A[k] = A(k)和B[k] = A(n-k)。我们有:
C[0] = 0
C[k+1] = (A[k] + A[n-k] + C[k]) // 10 (I)
还有:
D[k] = (A[k] + A[n-k] + C[k]) % 10
D[n+1] = C[n+1]
为了使这些公式更有用,我们可以用一种不太常见的方式重写数字公式:
A[k] + A[n-k] + C[k] = 10*C[k+1] + D[k] (II)
并将k 替换为n-k:
A[n-k] + A[k] + C[n-k] = 10*C[n-k+1] + D[n-k]
将两个公式放在一起,我们有:
D[k] - D[n-k] = C[k] - C[n-k] - 10*(C[k+1] - C[n-k+1]) (III)
N 不一定是回文,但我们在D[k] 和D[n-k] 之间仍然有很强的关系。
算法示意图
我们的想法是使用这种关系来计算进位。我们从外到内进行构建。如果进位不是 0 或 1,则无解。
如果所有进位都是 0 或 1,那么我们删除进位。如果剩下的“数”不是回文或中间有奇数,则无解。
否则我们可以像上面那样构建解决方案。
算法
我们将使用(I)和(III)从C[k+1]和C[n-k]进步到C[k]和C[n-k+1]。如果我们无法前进,即如果进位不是0 或1,那么假设N = A + (N - A) 是错误的。
如果n+2的位数是偶数,我们在k = n-k+1时停止。
否则,我们在k = n-k 时停止。
案例一:C[k+1] = 1 and C[n-k] = 0
如果D[n-k] = 9
我们知道A[k] + A[n-k] = 9,因为我们需要一个进位C[n-k] = 1 才能达到19。我们有C[n-k+1] = 0 (III)。命题 (III) 给了我们C[k],但我们已经知道C[k+1] = 1 是C[k] = 1。
如果D[n-k] <= 8
我们知道A[k] + A[n-k] <= 8 或A[k] + A[n-k] >= 10。但是我们有一个进位C[k+1] = 1:唯一的选择是A[k] + A[n-k] >= 10,因此是C[n-k+1] = 1。我们使用(III)计算C[k]:D[k] - D[n-k] = C[k],即C[k] = D[k] - D[n-k]。
案例2:C[k+1] = 1 and C[n-k] = 1
我们有A[k] + A[n-k] + C[k] >= 10,因此有A[k] + A[n-k] >= 9 和A[k] + A[n-k] + C[n-k] >= 10:C[n-k+1] = 1。我们使用 (III) 计算 C[k]:C[k] = D[k] - D[n-k] + 1。
案例3:C[k+1] = 0 and C[n-k] = 0
如果D[n-k] = 9
我们知道A[k] + A[n-k] = 9,因为我们需要一个进位C[n-k] = 1才能达到19。因此,C[n-k+1] = 0和C[k] = 0。
如果D[n-k] <= 8
我们知道A[k] + A[n-k] <= 8 或A[k] + A[n-k] >= 10。但是我们没有进位(C[k+1] = 0):唯一的选择是A[k] + A[n-k] <= 8,因此是C[n-k+1] = 0。我们使用(III)计算C[k]:D[k] - D[n-k] = C[k] + 10,即C[k] = D[k] - D[n-k]。
案例4:C[k+1] = 0 and C[n-k] = 1
如果D[n-k] = 9
我们知道A[k] + A[n-k] = 8 或A[k] + A[n-k] = 18。但是如果A[k] + A[n-k] = 18,我们将有C[k+1] = 1。因此,A[k] + A[n-k] = 8、C[n-k+1] = 0 以及使用 (III)、D[k] - D[n-k] = C[k] - 1,即 C[k] = D[k] - D[n-k] + 1。
如果D[n-k] = 0
我们知道A[k] + A[n-k] = 9 和C[n-k+1] = 1。从C[k+1] = 0 开始,我们就有了C[k] = 0。
如果1 <= D[n-k] <= 8
我们知道0 <= A[k] + A[n-k] <= 7 或10 <= A[k] + A[n-k] <= 17。由于C[k+1] = 0,我们知道0 <= A[k] + A[n-k] <= 7。因此我们有 C[n-k+1] = 0 和 C[k] = 0。
解压
使用(II)计算S(k) = A[k] + A[n-k]:
A[k] + A[n-k] = D[k] - C[k] + 10*C[k+1]
S(k) 可能大于9,但列表:[S(n), ..., S(0)] 必须是回文。
如果 是奇数个元素,那么我们需要中间元素是偶数。对于其他元素:
- 如果
S(k) = 0,则设置A[k] + A[n-k] = 0
- 如果
1 <= S(k) <= 10,则设置A[k] = S(k) - 1和A[n-k] = 1
- 如果
10 <= S(k) <= 18,则设置A[k] = S(k) - 9 和A[n-k] = 9
时间复杂度
时间复杂度为O(log10 N),因为我们将数字循环了三遍(可能是一到两次)。
在 Python 中的实现
实现充满了边缘情况。我测试了 10 到 10000 之间的数字,但可能仍然存在细微的错误。
def check_mirror_sum(N):
D = list(map(int, reversed(str(N))))
if D[-1] > 1:
D.append(0)
n = len(D) - 2
C = [None] * (n+2)
C[n+1], C[0] = D[n+1], 0
for k in range(n, 0, -1):
if k - (n-k+1) < 0:
break
if C[k+1] == 1:
if C[n-k] == 0: # case 1
if D[n-k] == 9:
e, f = 1, 0
else:
f = 1
e = D[k] - D[n-k]
else: # C[n-k] == 1, case 2
f = 1
e = D[k] - D[n-k] + 1
else: # C[k+1] == 0
if C[n-k] == 0: # case 3
if D[n-k] == 9:
e, f = 0, 0
else:
f = 0
e = D[k] - D[n-k]
else: # C[n-k] == 1, case 4
if D[n-k] == 9:
f = 1
e = D[k] - D[n-k] + 1
elif D[n-k] == 0:
e, f = 0, 1
else:
e, f = 0, 0
if not (0 <= e <= 1 and 0 <= f <= 1) or (k == n-k+1 and e != f):
return None
C[k], C[n-k+1] = e, f
S = [D[k] - C[k] + 10*C[k+1] for k in range(n+1)]
A = [None] * (n+1)
if (n+1) % 2 == 1:
if S[(n+1)//2] % 2 == 1: # odd middle "digit"
return None
A[(n+1)//2] = S[(n+1)//2] // 2
for k in range((n+1)//2):
if S[k] != S[n-k]: # not a palindrom!
return None
if S[k] == 0:
A[k], A[n-k] = 0, 0
elif S[k] <= 10:
A[k], A[n-k] = S[k] - 1, 1
else:
A[k], A[n-k] = S[k] - 9, 9
if A[0] == 0 or A[-1] == 0: # should not start or end with 0
return None
M = int("".join(map(str, A)))
return M
测试:
def control(N):
for i in range(1, N):
s = "".join(reversed(str(i)))
if s[0] != '0' and i + int(s) == N:
return i
return None
for N in range(10, 10000):
M = check_mirror_sum(N)
M2 = control(N)
assert (M is None) == (M2 is None)
if M is not None:
print("{} = {} + {}".format(N, M, N-M))
输出:
10 = 5 + 5
12 = 6 + 6
14 = 7 + 7
16 = 8 + 8
18 = 9 + 9
...
9779 = 8611 + 1168
9878 = 7891 + 1987
9889 = 8711 + 1178
9988 = 7991 + 1997
9999 = 8811 + 1188