Advertisement

43. Multiply Strings [Leetcode] [FFT] [python]

阅读量:

查看题目:https://leetcode.com/problems/multiply-strings/description/

粗略步骤:

  1. 将多项式从系数形式转换为点值形式
  2. 将两个多项式的点值形式相乘
  3. 将相乘后的点值形式转换为系数形式

1. 将多项式从系数表示转为点值表示

系数表示: A(x) = a_0 + a_1x + a_2x^2 + ··· + a_{n-1}x^{n-1}
点值表示:(x_0,A(x_0))、(x_1,A(x_1)、···)数个这样的点值对

这里我们巧妙地选择点值表示中的x值:
选择1的n次单位根

1的n次单位根:ω^n = 1,共有n个不同的根
ω^k_n = cos(2πk/n) + i * sin(2πk/n)
其中i^2 = -1

那么A(ω) = a_0 + a_1ω + a_2ω^2 + ··· + a_{n-1}ω^{n-1}

系数向量\vec a = (a_0, a_1, ··· , a_{n-1})
值向量\vec A = (A(ω_n^0), A(ω_n^1), ··· , A(ω_n^{n-1}))

\vec A = DFT(\vec a)
这里我们称为离散傅里叶变换

复制代码
    A1 = dft(a1)
    A2 = dft(a2)

2. 把两个多项式的点值表示相乘

复制代码
    for i in range(n):
    A[i] = A1[i]*A2[i]

3. 将相乘后的点值表示转为系数表示

离散傅里叶变换亦可表示为矩阵形式W \cdot \vec{a} = \vec{A}。 亦即,
\begin{bmatrix} 1&1&···&1\\ 1&\omega_n^1&···&(ω_n^1)^{n-1}\\ &&···&\\ 1&ω_n^{n-1}&···&(ω_n^{n-1})^{n-1}\end{bmatrix} * \begin{bmatrix} a_0\\ a_1\\ ···\\ a_{n-1}\end{bmatrix} = \begin{bmatrix} A(ω_n^0)\\ A(ω_n^1)\\ ···\\ A(ω_n^{n-1})\end{bmatrix}
其中,
\vec a = (a_0, a_1, ···, a_{n-1})^\top

\vec A = (A(ω_n^0), A(ω_n^1), ···, A(ω_n^{n-1}))^\top
其乘积结果为:
$\begin{cases}
a_k = (x_0 + x_2 + ··· + x_{n-2}) \

  • ω^{-k} (x_0i - x_2i + ··· + (-1)^{m+2}x_{2m)i}) \
  • ω^{-2k}(x_0 - x_2 + ··· + (-1)^m x_{2m})) \
    \quad (k=0, 1, ..., n-3)
    \end{cases}$

那么 \vec a = W^{-1} * \vec A
\vec a = DFT^{-1}(\vec A)
这里我们称离散傅里叶逆变换

复制代码
    a = idft(A)

a就是相乘后多项式的系数向量


快速傅里叶变换和逆变换

1. FFT

FFT(快速傅里叶变换)是对DFT的优化,减少了乘的次数

1的n次单位根有很好的性质:
ω_n^0 = 1
ω_n^{k+n} = ω_n^k
ω_n^{k+n/2} = -ω_n^k
ω_n^k = (ω_n^1)^k
ω_n^{mk} = ω_{n/m}^k

将多项式按奇偶次项分类后得到两个多项式 A_1(x)A_2(x)

\begin{aligned} &A_1(x) = a_0 + a_2 x^2 + \cdots +a_{n-3} x^{n-3}+a_{n-1} x^{n-1},\\ &A_2(x) =a_{1} x+a_{3} x^3+\cdots+a_{n-4} x^{n-4}+a_{n-5} x^{n-5}. \end{aligned}

则原多项式可表示为:

A(x)= A₁(x²)+xA₂(x²)

对于任意k=0, 1, ..., n/₂−₁时,

A(ω_nᵏ)= A₁(ω_n²ᵏ)+ ω_nᵏ A₂(ω_n²ᵏ)

接下来

有性质
A(ω_n^k) = A1(ω_{n/2}^k) + ω_n^kA2(ω_{n/2}^k)

有性质
A(ω_n^{k+n/2}) = A1(ω_n^{2k+n}) + ω_n^{k+n/2}A2(ω_n^{2k+n}) = A1(ω_n^{2k}) + ω_n^{k+n/2}A2(ω_n^{2k})

有性质
A(ω_n^{k+n/2}) = A1(ω_{n/2}^k) - ω_n^kA2(ω_{n/2}^k)

通过分治,递归处理。在求出A(ω_n^k)的同时,根据性质,也可以求出A(ω_n^{k+n/2})

2. IFFT

傅里叶变换为

有性质
W = \begin{bmatrix} 1&1&···&1\\ 1&ω_n^1&···&ω_n^{n-1}\\ &&···&\\ 1&ω_n^{n-1}&···&ω_n^{(n-1)(n-1)}\end{bmatrix}

傅里叶逆变换为
有公式W_n(ω)^{-1} = 1/nW_n(ω^{-1})
并且(ω_n^k)^{-1} = cos(2πk/n) - i * sin(2πk/n)

仅需对正变换的结果进行简单的调整即可完成傅里叶逆变换。应将频率变量ω替换为它的倒数即ω^{-1}。特别重要的是,在最终结果中必须进行除以n的操作。


Solution

复制代码
    class Solution:
    # opt为1表示傅里叶变换,-1表示逆变换
    def fft(self,num,opt):
        n = len(num)
        if n == 1:
            if opt == 1:
                return [complex(int(num[0]),0)]
            else:
                return [num[0]]
        #根据奇偶,对半分
        A1 = self.fft(num[::2],opt)
        A2 = self.fft(num[1::2],opt)
        w1 = complex(math.cos(2*math.pi/n),opt*math.sin(2*math.pi/n))
        w = complex(1,0)
        A = [0]*n
        for i in range(0, n//2):
            A[i] = A1[i] + w*A2[i]
            A[i+n//2] = A1[i] - w*A2[i]
            w = w * wn
        return A
    
    def multiply(self, num1, num2):
        length = len(num1) + len(num2)
        n = 1
        while n < length:
            n = n<<1
        # n即相乘后的位数,满足相乘后不会溢出,并且是2的幂次方
        A1 = self.fft(num1.zfill(n)[::-1],1)
        A2 = self.fft(num2.zfill(n)[::-1],1)
        A = [0]*n
        for i in range(n):
            A[i] = A1[i]*A2[i]
        Ans = self.fft(A,-1)
        rtn = ''
        #up用来进位
        up = 0
        for num in Ans:
            hre = int(num.real/n+0.5+up)
            if hre >= 10:
                up = int(hre/10)
                rtn += str(int(hre%10))
            else:
                up = 0
                rtn += str(hre)
        rtn = rtn[::-1].lstrip('0')
        # 避免在结果为0时返回空串
        if rtn == '':
            return '0'
        return rtn

需要注意的地方

  • 数字被以字符串形式输入,并且需要将反转后的字符串与相应的系数进行匹配。
  • 相乘操作可能导致结果的长度增加,并且结果的长度必须是2的幂次方。为了满足这一要求,在前面添加零来满足长度需求。
  • 计算得到最终的系数向量后,特别地,在处理过程中如果某个系数超过10,则需执行进位操作。

全部评论 (0)

还没有任何评论哟~