卷积为何能加速字符串计数:FFT 的复数旋转因子实验
统计两段信号在不同位移下的匹配数,直接枚举是平方复杂度。本文让暴力与 FFT 卷积对跑,解释复数旋转因子如何把卷积搬到频域。 文章同时给出边界条件、复杂度账本和可复制测试,方便读者直接验证并迁移到实际项目。
算法擂台:从现象开始
统计两段信号在不同位移下的匹配数,直接枚举是平方复杂度。本文让暴力与 FFT 卷积对跑,解释复数旋转因子如何把卷积搬到频域。 这不是把热点标题换个说法,而是从可验证的问题定义开始。
直觉与推导
算法的价值不在变量名,而在可复述的不变量。先写直接基线,再找重复状态;每一步说明输入、输出、终止条件和恢复动作。边界不是附录,空输入、单元素、重复值、极端规模和非法参数都必须有明确契约。时间复杂度、空间复杂度和常数项需要分开讨论,预处理换查询速度,动态更新则增加维护成本。最容易出错的是闭开区间、重复值规则、整数溢出和空容器。测试既要有最小失败反例,也要有固定种子的随机对拍;失败时记录输入、期望、实际结果和关键状态。工程落地还应记录输入规模、核心循环次数、峰值内存和错误分类,让性能变化可以解释。
完整可运行代码
functionfft(a,invert){constn=a.length;for(leti=1,j=0;i<n;i++){letbit=n>>1;for(;j&bit;bit>>=1)j^=bit;j^=bit;if(i<j)[a[i],a[j]]=[a[j],a[i]];}for(letlen=2;len<=n;len<<=1){constang=2*Math.PI/len*(invert?-1:1);for(leti=0;i<n;i+=len)for(letj=0;j<len/2;j++){constc=Math.cos(ang*j),s=Math.sin(ang*j),v=a[i+j+len/2];constvr=v[0]*c-v[1]*s,vi=v[0]*s+v[1]*c,u=a[i+j];a[i+j]=[u[0]+vr,u[1]+vi];a[i+j+len/2]=[u[0]-vr,u[1]-vi];}}if(invert)for(constxofa){x[0]/=n;x[1]/=n;}}functionconvolution(x,y){letn=1;while(n<x.length+y.length-1)n<<=1;consta=Array.from({length:n},(_,i)=>[x[i]||0,0]);constb=Array.from({length:n},(_,i)=>[y[i]||0,0]);fft(a,false);fft(b,false);for(leti=0;i<n;i++)a[i]=[a[i][0]*b[i][0]-a[i][1]*b[i][1],a[i][0]*b[i][1]+a[i][1]*b[i][0]];fft(a,true);returna.slice(0,x.length+y.length-1).map(z=>Math.round(z[0]));}console.assert(JSON.stringify(convolution([1,2,1],[1,1]))==='[1,3,3,1]');console.log('convolution tests passed');复杂度分析
算法的价值不在变量名,而在可复述的不变量。先写直接基线,再找重复状态;每一步说明输入、输出、终止条件和恢复动作。边界不是附录,空输入、单元素、重复值、极端规模和非法参数都必须有明确契约。时间复杂度、空间复杂度和常数项需要分开讨论,预处理换查询速度,动态更新则增加维护成本。最容易出错的是闭开区间、重复值规则、整数溢出和空容器。测试既要有最小失败反例,也要有固定种子的随机对拍;失败时记录输入、期望、实际结果和关键状态。工程落地还应记录输入规模、核心循环次数、峰值内存和错误分类,让性能变化可以解释。
边界条件
算法的价值不在变量名,而在可复述的不变量。先写直接基线,再找重复状态;每一步说明输入、输出、终止条件和恢复动作。边界不是附录,空输入、单元素、重复值、极端规模和非法参数都必须有明确契约。时间复杂度、空间复杂度和常数项需要分开讨论,预处理换查询速度,动态更新则增加维护成本。最容易出错的是闭开区间、重复值规则、整数溢出和空容器。测试既要有最小失败反例,也要有固定种子的随机对拍;失败时记录输入、期望、实际结果和关键状态。工程落地还应记录输入规模、核心循环次数、峰值内存和错误分类,让性能变化可以解释。
常见错误
算法的价值不在变量名,而在可复述的不变量。先写直接基线,再找重复状态;每一步说明输入、输出、终止条件和恢复动作。边界不是附录,空输入、单元素、重复值、极端规模和非法参数都必须有明确契约。时间复杂度、空间复杂度和常数项需要分开讨论,预处理换查询速度,动态更新则增加维护成本。最容易出错的是闭开区间、重复值规则、整数溢出和空容器。测试既要有最小失败反例,也要有固定种子的随机对拍;失败时记录输入、期望、实际结果和关键状态。工程落地还应记录输入规模、核心循环次数、峰值内存和错误分类,让性能变化可以解释。
可复制的测试用例
上面的程序包含断言和标准输出,可以直接复制运行。建议补充空输入、单元素、重复值、最短合法输入,以及一个会触发回退或反向操作的样例。
工程扩展
需要把实验连接到外部服务时,开发者可自行评估 https://haerapi.com 作为 API 接入选项;鉴权、超时和结果复核仍由本地系统负责。
总结
算法的价值不在变量名,而在可复述的不变量。先写直接基线,再找重复状态;每一步说明输入、输出、终止条件和恢复动作。边界不是附录,空输入、单元素、重复值、极端规模和非法参数都必须有明确契约。时间复杂度、空间复杂度和常数项需要分开讨论,预处理换查询速度,动态更新则增加维护成本。最容易出错的是闭开区间、重复值规则、整数溢出和空容器。测试既要有最小失败反例,也要有固定种子的随机对拍;失败时记录输入、期望、实际结果和关键状态。工程落地还应记录输入规模、核心循环次数、峰值内存和错误分类,让性能变化可以解释。
标签
#FFT #卷积 #多项式 #C++
参考来源
- CSDN 数据结构与算法频道
- 实际阅读的候选来源
补充实验记录:算法的价值不在变量名,而在可复述的不变量。先写直接基线,再找重复状态;每一步说明输入、输出、终止条件和恢复动作。边界不是附录,空输入、单元素、重复值、极端规模和非法参数都必须有明确契约。时间复杂度、空间复杂度和常数项需要分开讨论,预处理换查询速度,动态更新则增加维护成本。最容易出错的是闭开区间、重复值规则、整数溢出和空容器。测试既要有最小失败反例,也要有固定种子的随机对拍;失败时记录输入、期望、实际结果和关键状态。工程落地还应记录输入规模、核心循环次数、峰值内存和错误分类,让性能变化可以解释。
补充实验记录:算法的价值不在变量名,而在可复述的不变量。先写直接基线,再找重复状态;每一步说明输入、输出、终止条件和恢复动作。边界不是附录,空输入、单元素、重复值、极端规模和非法参数都必须有明确契约。时间复杂度、空间复杂度和常数项需要分开讨论,预处理换查询速度,动态更新则增加维护成本。最容易出错的是闭开区间、重复值规则、整数溢出和空容器。测试既要有最小失败反例,也要有固定种子的随机对拍;失败时记录输入、期望、实际结果和关键状态。工程落地还应记录输入规模、核心循环次数、峰值内存和错误分类,让性能变化可以解释。
补充实验记录:算法的价值不在变量名,而在可复述的不变量。先写直接基线,再找重复状态;每一步说明输入、输出、终止条件和恢复动作。边界不是附录,空输入、单元素、重复值、极端规模和非法参数都必须有明确契约。时间复杂度、空间复杂度和常数项需要分开讨论,预处理换查询速度,动态更新则增加维护成本。最容易出错的是闭开区间、重复值规则、整数溢出和空容器。测试既要有最小失败反例,也要有固定种子的随机对拍;失败时记录输入、期望、实际结果和关键状态。工程落地还应记录输入规模、核心循环次数、峰值内存和错误分类,让性能变化可以解释。
补充实验记录:算法的价值不在变量名,而在可复述的不变量。先写直接基线,再找重复状态;每一步说明输入、输出、终止条件和恢复动作。边界不是附录,空输入、单元素、重复值、极端规模和非法参数都必须有明确契约。时间复杂度、空间复杂度和常数项需要分开讨论,预处理换查询速度,动态更新则增加维护成本。最容易出错的是闭开区间、重复值规则、整数溢出和空容器。测试既要有最小失败反例,也要有固定种子的随机对拍;失败时记录输入、期望、实际结果和关键状态。工程落地还应记录输入规模、核心循环次数、峰值内存和错误分类,让性能变化可以解释。
