一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第2天,点击查看活动详情。
今天开始正式进行算法题目,这里不仅仅会记录题目的解决方案,更重要的是记录不同方案的复杂度,甚至是性能分析。
1、题目分析
将数组旋转k步
举例:
原数组[1,2,3,4,5,6,7] => 旋转2步 => 目标数组 [6,7,1,2,3,4,5];
2、解题思路
如何通过程序将原数组变成目标数组呢?这里一般能想到两种思路(默认数组中元素均为数字):
思路一:
先将7拿出,然后插入到数组前,再将6拿出,再插入到数组前。
好的,这里你会想到什么?对的,想到的是利用pop弹出,再利用unshift向前插入;
解题:
function rotate1(arr:number[],key:number):number[]{
const length = arr.length;
if(length == 0) return arr;//如果输入变量中数组的长度为0,也就是传入一个空数组,那么则返回一个空数组
const step = Math.abs(key % arr.length) //1、abs表示取绝对值,如果传入的值是一个负数,那么会按照正数来进行处理,2、如果key和arr.length相等,那么则原数组不变,从而这里进行一步取余处理;3、如果key不是整数,那么在下方循环那么step就是NaN,NaN于任何值做比较都会返回false,下方循环就不会进入
for(let i=0; i<step; i++){
const p = arr.pop();
if(p != null){//这里p可能为0,所以如此判断
arr.unshift(p)
}
}
return arr;
}
功能测试
//功能测试
const oldArr1 = [1,2,3,4,5,6,7];
const newArr1 = rotate1(oldArr1, 5)
console.log(newArr1);//[3, 4, 5, 6, 7, 1, 2] 符合预期
复杂度分析
空间复杂度是O(n)
- 随着key和arr的增大,创建的变量p的数量也会随着增多,在变量不确定的情况下,新增的变量p的数量和占据的空间是不可数的,所以我认为其空间复杂度是O(n)
时间复杂度是O(n^2) - 明明只有一个循环,为什么时间复杂度会是O(n^2)?这是因为JS内置方法unshift本身就是一个时间复杂度为O(n)的方法,再加上其外面包裹了一个循环,所以其时间复杂度是O(n^2);
- shift,unshift,splice都要慎用,其时间复杂度都很大
数组是一个有序序列,向前插入一个元素,数组中之前所有元素的下标都会发生改变
而向数组后方插入一个元素,数组中之前所有元素的下标都不会会发生改变
更重要的是我们必须要知道,数组是需要的空间必须是一个连续的内存空间,它不能是分散的,这也就和链表形成了鲜明的对比,也是为什么两张图都使用了框将数据圈起来的原因
性能分析
const arrTest1 = [];
for(let i = 0; i< 100*10000; i++){
arrTest1.push(i)
}
console.time('rotate1')
rotate1(arrTest1,10000)
console.timeEnd('rotate1') //rotate1: 935.68701171875 ms
//console.time()和console.timeEnd()配合使用,只要其中的字段相同,那么就会打印出二者之间程序执行的时间 ,根据电脑配置不同,执行时间会存在差异
思路二:
根据事例观察得出,旋转k步就是在原数组从后向前数出k个元素并且记录此位置,以此位置为分界点分成两个数组[1,2,3,4,5]和[6,7],然后再将两个数组拼接起来。
这里想到的就是利用slice和concat。
解题:
function rotate2(arr:number[],key:number):number[]{
const length = arr.length;
if(length == 0) return arr;
const step = Math.abs(key % arr.length);//如果key不是整数,slice中会默认对key进行向下取整
const arrPart1 = arr.slice(-step); //slice()中的值是负数,那么将从后向前截取对应个数
const arrPart2 = arr.slice(0, length - step);
const arrPart3 = arrPart1.concat(arrPart2)
return arrPart3;
}
功能测试
//功能测试
const oldArr2 = [1,2,3,4,5,6,7];
const newArr2 = rotate2(oldArr2, 5)
console.log(newArr1);//[3, 4, 5, 6, 7, 1, 2] 符合预期
复杂度分析
空间复杂度是O(n)
- 因为arr和key发生改变,arrPart1,arrPart2,arrPart3三个数组随着里边元素增多,而导致占用的内存空间变大而让其不可数,那么它的空间复杂度是O(n) 时间复杂度是O(1)
- 因为无论arr和key如何发生改变,其都会主要执行
arr.slice(-step)等几种(10以内)时间复杂度为O(1)的程序,最终其时间复杂度是O(1)
性能分析
const arrTest2 = [];
for(let i = 0; i< 100*10000; i++){
arrTest2.push(i)
}
console.time('rotate2')
rotate2(arrTest2,10000)
console.timeEnd('rotate2') // rotate2: 10.7021484375 ms
//根据电脑配置不同,执行时间会存在差异
3、总结
方案二的空间复杂度步差于方案一。
方案二的时间复杂度优于方案一。
性能测试中,方案二的执行时间明显小于方案一,二者根本没在一个数量级。
综合考虑,方案二明显优于方案一。