1 算法描述
美团外卖的品牌代言人A先生最近正在进行音乐研究。他有两段音频,每段音频是一个表示音高的序列。现在A先生想要在第二段音频中找出与第一段音频最相近的部分。
具体地说,就是在第二段音频中找到一个长度和第一段音频相等且是连续的子序列,使得它们的difference 最小。两段等长音频的 difference 定义为:
difference = SUM((a[i] - b[i])的2次方)(1 ≤ i ≤ n),其中SUM()表示求和
其中 n 表示序列长度,a[i], b[i]分别表示两段音频的音高。现在A先生想要知道,difference的最小值是多少?数据保证第一段音频的长度小于等于第二段音频的长度。
2 算法分析
本章节的算法分析包括两个步骤:用例分析与逻辑分析。
2.1 用例分析
名称 | 输入 | 分析步骤 | 输出 |
一般用例 | 第一段音频的序列:[1,2] 第二段音频的序列:[4,3,2,1] | dif1=[(4-1)的2次方+(3-2)的2次方] =10 dif2=[(3-1)的2次方+(2-2)的2次方] =4 dif3=[(2-1)的2次方+(1-2)的2次方] =2 | dif的最小值等于2 |
2.2 逻辑分析
算法主流程分析:
-
第二段音频的窗口函数的滑动步长是1,窗口长度等于第一段音频的长度
-
从第一步骤中获取子序列,与第二段音频输入计算difference函数,保存结果
-
对比第二步骤的计算结果,取最小值
3 算法目标
算法运行的目标是最小化系统资源开销(包括处理器资源开销与内存资源开销)以及最小耗时(算法运行时长)。
4 算法设计
4.1 数据结构设计
名称 | 类型 | 长度 | 描述 |
第一段音频序列 | 一维数组 | 输入序列的长度 | 保存原始输入的音频序列 |
第二段音频序列 | 一维数组 | 输入序列的长度 | 保存原始输入的音频序列 |
窗口索引列表 | 二维数组 | 窗口数 | 第一维是保存窗口列表。 第二维是保存窗口对第二段音频的索引,数组长度2,索引0标识窗口的起始索引,索引1标识窗口的结束索引 |
Difference函数计算结果 | 一维数组 | 窗口数 | 每个元素标识音频序列对比的计算结果 |
4.2 算法逻辑设计
4.2.1 输入条件设计
- 第一段音频序列以及第二音频序列是整数数列
- 第一段序列的长度小于第二段音频序列的长度
4.2.2 mainFunc函数主流程设计
- 标准化第一段音频输入序列、第二音频输入序列
- 使用窗口函数winFunc输出窗口列表
- 遍历每个窗口,使用diff函数计算对比的结果,保存计算结果
- 使用min函数从计算结果中获取最小值
4.2.3 winFunc函数流程设计
- 确定窗口函数的窗口长度等于第一段音频的序列长度
- 确定窗口函数的窗口滑动的步长等于1
- 遍历第二段音频序列,输出窗口列表
4.2.4 diffFunc函数流程设计
- 从窗口列表中获取到每一个窗口
- 使用difference算法定义计算每个窗口与第一段音频序列的结果
- 输出计算结果
4.2.5 minFunc函数流程设计
- 遍历结果列表
- 获取最小值,输出最小值
4.3 并行计算设计
使用Java Lamda表达式实现并行化流式计算,提高计算效率。
5 算法实现
5.1 程序语言
本程序分别使用Java语言。
5.2 程序示例
5.2.1 串行版
5.2.1.1 mainFunc函数
5.2.1.2 winFunc函数
5.2.1.3 diffFunc函数
5.2.1.4 minFunc函数
5.2.2 并行版
5.2.2.1 mainFunc函数
5.2.2.2 winFunc函数
5.2.2.3 diffFunc函数
6 算法验证
本算法验证测试使用代码覆盖率测试方法。
6.1 测试用例
用例描述 | 输入数列 | 输出数列 | 输出值 | 覆盖函数 |
数列相等 | 第一音频:1,2 第二音频:1,2 | 0 | 所有函数 | |
数列长度相等 | 第一音频:1,2 第二音频:3,4 | 8 | 所有函数 | |
长度不同的随机数列 | 随机长度 | 随机值 | 所有函数 |
6.2 执行用例
测试用例通过率100%,代码覆盖率100%。
7 算法效率
7.1 时间复杂度
由代码可知该算法的时间复杂度等于O(N的多次方)。
7.2 空间复杂度
该算法使用一维数组,其空间复杂度是常数。
7.3 性能测试
输入数列长度 | 输出数列长度 | 耗时 |
1000 | 59毫秒 | |
10000 | 120毫秒 | |
100000 | 615毫秒 | |
1000000 | 4696毫秒 |
由以上的性能测试可知,每增加一个数量级,耗时线性增长。
7.4 算法优化
已启动多线程并行的处理。