如何将数组旋转k步

906 阅读4分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 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都要慎用,其时间复杂度都很大

1.jpeg 数组是一个有序序列,向前插入一个元素,数组中之前所有元素的下标都会发生改变

2.jpeg 而向数组后方插入一个元素,数组中之前所有元素的下标都不会会发生改变

更重要的是我们必须要知道,数组是需要的空间必须是一个连续的内存空间,它不能是分散的,这也就和链表形成了鲜明的对比,也是为什么两张图都使用了框将数据圈起来的原因

性能分析
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、总结

方案二的空间复杂度步差于方案一。
方案二的时间复杂度优于方案一。 性能测试中,方案二的执行时间明显小于方案一,二者根本没在一个数量级。
综合考虑,方案二明显优于方案一