有一个字符串数组, 将数组中的每一个字符串按照字母序进行排序; 然后再将整个字符串数组按照字典序进行排序; 时间复杂度是多少?
假设数组中最长的字符串长度为 s, 有 n 个字符串。
对每一个字符串按照字母序进行排序: n * slogs
对整个字符串数组按照字典序进行排序: s * nlogn
(比如比较 'abc' 和 'abd' 两个字符串的顺序, 需要比较到第 s 位字母, 所以需要乘上 3。)
排序默认为 O(nlogn) 的复杂度
如下是 选择排序 中的代码片段:
var chooseSort = function(arr) {for (let x = 0; x < arr.length; x++) {let min = xfor (let y = x + 1; y < arr.length; y++) {if (arr[y] < arr[min]) {min = y}}let tmp = arr[x]arr[x] = arr[min]arr[min] = tmp}return arr}
分析其时间复杂度
(n - 1) + (n - 2) + (n - 3) + ... + 0 = (n - 1 + 0) * n / 2 ≈ O(n^2)
需注意的是并不是所有有两重遍历循环的算法时间复杂度都是 O(n^2)
。
function demo1(n) {for (let i = 0; i < n; i++) {for (let j = 0; j < 30; j++) {...}}}
在 demo1 函数中, 因为内层循环的次数为常量 30, 所以其时间复杂度为 30 * n ≈ O(n)
function demo2(n) {for (let i = 0; i < n; i += i) {for (let j = 0; j < n; j++) {...}}}
在 demo2 函数中, 外层循环循环的次数 , 所以其复杂度也不是 O(n^2) 而是 O(logN)。
如下是 二分查找 中的代码片段:
// arr 为指定数组, target 为目标元素function binarysearch(arr, target) {let left = 0let right = arr.length - 1while (left <= right) {const middlePoint = Math.floor((left + right) / 2)let middle = arr[middlePoint]if (middle > target) {right = middlePoint - 1} else if (middle < target) {left = middlePoint + 1} else {return middle}}return '数组中目标元素不存在'}
分析其时间复杂度。
假设数据总量为 N, 因为二分查找每次会减少一半的数据, 所以经过 1 次后, 数据剩下为 N / 2, 经过 2 次后, 数据剩下为 N / 2^2, 二分查找的极限是最后剩下 1 个数据, 假设经过 m 次后, 达到极限, N / 2^m = 1, 即 , 所以时间复杂度为 logN;
题目: 现在有一个 1~1000 区间中的正整数, 需要你猜下这个数字是几, 你只能问一个问题: 大了还是小了?问需要猜几次才能猜对?
答: 根据二分查找的时间复杂度为 logN, 所以题目可以转化为求 log1000 的值, 2^10 = 1024, 所以最多猜 10 次就能猜对;
以 JavaScript 这门语言为例, 书写如下函数
function main() {for (let i = 1; i <= 10; i++) {let n = Math.pow(10, i)console.time('start')let sum = 0for (let x = 0; x < n; x++) {sum += x}console.log(`10^${i}`)console.timeEnd('start')}}
运行结果为:
10^1start: 0.14892578125ms10^2start: 0.071044921875ms10^3start: 0.074951171875ms10^4start: 0.232177734375ms10^5start: 4.281982421875ms10^6start: 0.97509765625ms10^7start: 8.960205078125ms10^8start: 90.760986328125ms10^9start: 887.587890625ms10^10start: 13403.979248046875ms
可以得出如下结论:
使用 JavaScript 如果想让其在 1 s 内完成任务, 数据规模的要求如下:
思路: 将数据规模乘 2, 观察对应时间的增长关系。
function testComplexity(fn) {for (let i = 10, i < 15, i++) {const total = Math.pow(2, i)const mockArr = new Array(total)console.time('test')/* fn 为算法函数, 这里仅仅作为 demo 演示 */fn(mockArr)console.end('test')}}
单次递归算法的时间复杂度 = 深度 * 递归函数的时间复杂度
在单次递归算法调用中, 深度就为递归函数执行次数
/* 求 1 + 2 + 3 + ... + n */function sum(n) {if (n === 0) return 0return n + sum(n - 1)}
比如在 sum 函数中, 深度为 n, 递归函数的时间复杂度为 1, 所以该函数的时间复杂度为 O(n)。
递归中进行多次递归调用, 建议画图来辅助分析。
function f(n) {if (n === 0) return 0return f(n - 1) + f(n - 1)}
等差数列前 n 项和公式:
* Sn = [n*(a1+an)]/2* Sn = a1*n + [n*(n-1)*d]/2* Sn = d/2*n²+(a1-d/2)*n
等比数列前 n 项和公式:
排列组合公式: