[toc]
时间和空间算法复杂度
- 用 O 表示
O(1)<O(logn)<O(n)<O(nlogn)<O(n²)<O(n³)<O(2ⁿ)<O(n!)
for(i=1; i<=n; ++i)
{
xxx;
sss;
...
}
- 假设一行代码的时间为1,以上代码时间就为 1+x*n 当其中x为常量 n无限大时 记得出时间复杂度O(n)
常见的时间复杂度(从小往大):
T(n) = O(f(n))
- 常量 O(1)
var i = 1;
var j = 2;
- 对数 O(logN)
var i = 1;
while(i<n)
{
i = i * 2;
}
循环x次 i达到n n=2^x => x = log2n
线性 O(n) 如上for循环
线性对数 O(nlogN)
for(j=1; j<n; j++)
{
i = 1;
while(i<n)
{
i = i * 2;
}
}
n*O(logN) => O(nlogN)
- 平方 O(n^2) 嵌套的两个for循环
- 立方 O(n^3)
- N次方 O(n^N)
- 指数 O(2^n)
空间复杂度
固定部分: 指令空间(即代码空间)、数据空间(常量、简单变量)等所占的空间,这部分属于静态空间
可变空间:动态分配的空间,以及递归栈所需的空间等,这部分的空间大小与算法有关
算法占用的内存包括:代码所占空间、输入输出数据占用空间、变量占用的空间
算法的空间复杂度:是计算整个算法的辅助空间单元的个数
S(n) = O(f(n))
常用的
O(1) 常量级 执行时,变量所需要的临时空间不会变化,没有空间的改变
var i = 1
var j = 2
var x = i + j
- O(n) 递归 每次调用都新建了变量
fun(n) {
var a = 1
if(){
console.log(n)
} else {
fun(n-1);
}
}
判断偶数 不用 %
function isEven(num) {
// 判断是不是整数
const isI = (num|0) === num
if(!isI) {
return false
}
const n = num/2
return parseInt(n) === Math.round(n)
}
判断质数
质数是指在大于1的自然数中,除了1和它本身以外不再有其他因数的自然数
function isPrime(n) {
if(typeof n !== 'number' || !Number.isInteger(n)){
return false
}
/* 偶数中 除了2其它都是 */
if(n===2) {
return true
}
if(n<2 || n%2 === 0){
return false
}
const sqrt = Math.sqrt(n)
/* 这里只循环奇数即可 */
for (let i = 3; i < sqrt; i+=2) {
if (n%i === 0) {
return false
}
}
return true
}
将数字改为: 1,234,567,890
function formatNumber(str) {
var num = str.split('').reverse().reduce((a, c, i) => {
return `${i%3===0 ? `${c},` : `${c}`}${a}`
})
return num
}
console.log(formatNumber('1234567890')); // 1,234,567,890
斐波拉契数列
爬楼梯问题: 一次走一层或两层 要到80层 有多少种可能 fn = f(n-1) + f(n-2)
f0 = 0; f1 = 1; fn = f(n-1) + f(n-2); n>=2
function fabonacci(n) {
let a = b = 1
for(let i=0; i<n; i++) {
[a,b] = [b, a+b]
}
return a
}
- 利用生成器
function* fibonacci() {
let [current, v] = [0, 1]
console.log('斐波拉契数列(0-100): ');
console.log(current);
while (true) {
[current, v] = [v, current + v]
yield current
}
}
for (const v of fibonacci()) {
if(v > 100) break
console.log(v);
}
不利用变量交换两个数
var a = 1, b = 2;
a = a + b;
b = a - b;
a = a - b;
// 或者
a = a ^ b;
b = a ^ b;
a = a ^ b;
数组相关
数组去重
- 利用对象表形式
var arr=[],hash={},newArr=[];
for(var i=0;i<arr.length;i++) {
if(!hash[arr[i]]) {
hash[arr[i]] = true;
newArr.push(arr[i])
}
}
console.log(newArr) // 去重后的数组
- 利用Set
var a = [1,2,3,1,4,4,4]
var b = new Set(a)
/* 一定要将Set转为数组 */
console.log([...b]); // [1,2,3,4]
- 利用 reduce
function uniq(arr) {
return arr.reduce((r, c) => r.includes(c) ? r : r.concat(c), [])
}
计算数组中任意两个数的和等于数组中某个值的组数
- 先将所有不同的值保存为对象的键值
- 再循环用和,减去数组中的每一个值,将差值作为键值,判断是否能获取到对应值
const sum = 10;
const arr = [3, 2, 1, 7, 8, 1, 9, 1, 6];
const hashObj = {};
let count = 0;
let repeat = 0;
for (let i = 0; i < arr.length; i++) {
const val = arr[i];
/* 计算相同值个数 */
if (hashObj[val]) {
repeat++
} else {
hashObj[val] = 1;
}
}
for (let j = 0; j < arr.length; j++) {
if (hashObj[(sum - arr[j])]) {
count++;
}
}
/* 相加的原因是: 计算前面的两个1时都匹配了后面的相同的9;但是当计算9时,只匹配了前面的两个1中的其中一个 */
const result = (count + repeat) / 2
console.log('count: ', count);
console.log('repeat: ', repeat);
console.log(`总共有 ${result} 组数的和为10`);
获取数组中 top three
- 一次循环,三次判断该值是否落在,前三值的区间
function getTopThree(arr) {
var f = 0, fi = 0;
var s = 0, si = 0;
var t = 0, ti = 0;
for (let i = 0; i < arr.length; i++) {
const e = arr[i];
if (e >= f) {
t = s;
ti = si
s = f;
si = fi
f = e;
fi = i
} else if (e >= s) {
t = s;
ti = si
s = e;
si = i;
} else if (e >= t) {
t = e
ti = i
}
}
// 返回前三的值及其下标
return [[f, s, t], [fi, si, ti]]
}
判断一个数是否在数组某两个数之间,或包含在数组中
- 一次循环,判断大于或是等于即可
function findValueBetweenInArray() {
var a = [1.2,1.434,3,5.6,7.9]
var target = 5
var index = [0, 0]
var have = false
if(a[0] > target || target>a[a.length-1]) {
console.log('不在数组中');
return
}
for (let i = 0; i < a.length; i++) {
if(a[i] > target) {
index = [i-1,i]
break;
}
if(a[i] === target){
index[0] = i
have = true
}
}
if(have) {
console.log(`有该数,下标${index[0]}`);
} else {
console.log(`在${a[index[0]]}与${a[index[1]]}之间`);
}
}
获取指定数组中的某一范围内的值
function getRangeValue(orange, startValue, endValue) {
const length = orange.length
let startIndex = 0, endIndex = length-1;
startValue = startValue || orange[0];
endValue = endValue || orange[endIndex];
/* 获取起始值下标 */
for (let i = 0,len=orange.length; i < len; i++) {
if(orange[i] >= startValue) { startIndex = i; break; }
}
/* 获取结尾值下标 */
for (let j = startIndex,len=orange.length; j < len; j++) {
if(orange[j] >= endValue) { endIndex = j; break; }
}
return orange.slice(startIndex, endIndex+1)
}
返回某一指定范围内固定步长的数组
function getRange(start, end, steep) {
const arr = [];
let val = start;
while(val < end) {
arr.push(val)
val += steep
}
return arr
}
获取一组数中的波峰
频谱相关
function findPeak(arr) {
// 满足一阶导数为0,并且满足二阶导数为负;而波谷点,则满足一阶导数为0,二阶导数为正
const peak = []
const len = arr.length
const tempArr = []
// 一阶求导,获取每个点的斜率
for (let i = 0; i < len; i++){
if (arr[i + 1] - arr[i]>0) {
// 表示向上倾斜,斜率为正
tempArr[i] = 1;
} else if (arr[i + 1] - arr[i] < 0){
// 表示向下倾斜,斜率为负
tempArr[i] = -1;
} else {
// 出现了水平线,或是超过了数组边界
tempArr[i] = 0;
}
}
// 处理水平的点位
for (let i = len - 1; i >= 0; i--) {
// 判断最后一个数是不是0
if (tempArr[i] == 0 && i == len - 1){
tempArr[i] = 1;
} else if (tempArr[i] == 0){
if (tempArr[i + 1] >= 0) {
// 将水平位置的点斜率设置为向上
tempArr[i] = 1;
} else{
// 将水平位置的点斜率设置为向下
tempArr[i] = -1;
}
}
}
for (i = 0; i < len; i++){
// 当 i+1 为波峰时,其对应的值减去 i 个值必为 -2
// 当 i+1 为波谷时,其对应的值减去 i 个值必为 2 注意除去最后一个数被误判位波谷的情况
if (tempArr[i + 1] - tempArr[i] == -2) peak.push(arr[i + 1]);
}
return peak
}
数组 转 树结构
let arr = [
{id: 1, name: 'n1', pid: 0},
{id: 2, name: 'n2', pid: 1},
{id: 3, name: 'n3', pid: 1},
{id: 4, name: 'n4', pid: 3},
{id: 5, name: 'n5', pid: 4},
]
let tree = {}
const map = {}
/* 处理数据 方便后面采用对象方式获取数据 */
arr.forEach(val => { map[val.id] = val })
arr.forEach(val => {
/* 利用对象引用类型特性,进行赋值 */
/* 这里从子一级反向找父一级,更容易确认对应关系,只会有一个父 */
const father = map[val.pid]
if(father) {
(father.children || (father.children = [])).push(val)
} else {
tree= val
}
})