将数组旋转K步,寻求最优解!

简介: 前言将数组旋转K步这算是一道非常经典的面试题了,题目不算太难,我相信绝大多数小伙伴都有实现思路。针对算法题,我们不仅仅实现它就好了,我们更重要的是学会思路,更要有时间复杂度和空间复杂度的概念,今天这篇文章再讲解题目的同时,还希望大家能够有自己的见解。

1.实现目标



这道题目比较简单,我们先来看看题目需求。


题目描述:

假如有一个数组[1,2,3,4,5,6,7],我们需要将它旋转K步,K是一个数字。


输入输出分析:


案例一

输入:[1,2,3,4,5,6,7] K=3

输出:[5,6,7,1,2,3,4]


案例二

输入:[1,2,3,4,5,6,7] K=4

输出:[4,5,6,7,1,2,3]


总体来说,题目不难,我们需要实现一个方法,这个方法返回一个新的数组,方法接收一个原始数组和K两个参数。

接下来我们就来实现一下。


2.利用popunshift


利用popunshift是大多数小伙伴都能想到的方法,pop是取出数组最后一个元素,unshift是在数组最前面插入一个元素,我们画一张图,就能够更好理解这种实现思路了,如下图44.png


上了上面的图大家应该一下就能理解了,原理非常的简单,接下来编写代码即可。


代码如下:

let array1 = [1, 2, 3, 4, 5, 6, 7];
function revolve1(arr, k) {
  const length = arr.length;
  if (!length || !k) {
    return arr;
  }
  // 取k的绝对值
  const step = Math.abs(k);
  for (let index = 0; index < step; index++) {
    const item = arr.pop(); // 取出最后一个
    if (item !== null) {
      arr.unshift(item); //插到最前面
    }
  }
  return arr;
}
// 测试
console.log(revolve1(array1, 3)); // [5, 6, 7, 1, 2, 3, 4]


上段代码其实非常的简单,就一个for循环,然后执行popunshift操作。


3.利用concat


使用concat也比较简单,我们也画张图一起来看看,如下图:


45.png


上面的步骤就比较简单了,基本上散步就可以完成了,首先就是将原数组拆分为了两端:part1part2,然后再拼接两端数组即可,但是大家需要注意,上图中很明显原数组没有改变。


代码如下:

let array2 = [1, 2, 3, 4, 5, 6, 7];
function revolve2(arr, k) {
  const length = arr.length;
  if (!length || !k) {
    return arr;
  }
  // 取k的绝对值
  const step = Math.abs(k);
  const part1 = arr.slice(-step); // 从末尾取出几个元素,并组成新数组
  const part2 = arr.slice(0, length-step); // 取出剩余元素组成新数组
  const part3 = part1.concat(part2);
  return part3;
}
// 测试
console.log(revolve2(array2, 4)); // [4, 5, 6, 7, 1, 2, 3]


4.复杂度分析


做算法我们最重要的不是实现功能,而是要注意效率,所以我们很有必要分析代码的时间复杂度和空间复杂度,所以我们分析一下上面两端代码的时间复杂度和空间复杂度,给大家提供思路,以后在做算法题也不会抓瞎了。


我们先看一张关于算法复杂度的坐标图,方便后续理解,如下图:



46.png


从上图可以简单看出,O(1)是算法复杂度最低的,O(n^2)算法复杂度是较高的。


4.1 pop和unshift


时间复杂度:O(n^2)


空间复杂度:O(1)


解释:


为什么时间复杂度是O(n^2)呢?有些小伙伴可以以为代码中只有一个for循环,所以时间复杂度应该是O(n),但是大家忽略了一个API,那就是unshift。这个API的时间复杂度也是O(n),给大家看一张图大家就理解了。



47.png

47.png

上图就展示了使用unshift往数组最前面插入一个元素的整个过程,很明显,unshift改变了元素组,而且插入一个元素,所有的数组元素都得往后挪一个,因为数组是一个有序结构,所以unshift的时间复杂度是O(n),结合for循环时间复杂度自然而然变为了O(n^2)


那为什么空间复杂度为O(1)呢?因为我们整段代码中没有新增加数组,所有操作都是在原数组上完成的,没有造成额外的空间存储,所以空间复杂度为O(1)



4.2 slice和concat


时间复杂度:O(1)


空间复杂度:O(n)


解释:


采用sliceconcat的时间复杂度要低的多,为O(1),我们先来看一张图。48.png


从上图可以看到我们原数组arr自始至终都是没有变化的,而是新生成了两个新数组part1part2,代码中也没有循环,所以时间复杂度为O(1)


然而也因为新生成了两个新数组,使用的新的存储空间,所以空间复杂度是O(n)


但是我们针对于前端而言,我们是重时间轻空间的,所以这种方法优于上一种。


5.性能对比


耳听为虚,眼见为实,我们真正做一个实验,来看看我们所谓的算法复杂度有没有体现出来。


代码如下:

const arr1 = []
for (let index = 0; index < 10 * 10000; index++) {
  arr1.push(index)
}
console.time('revolve1');
revolve1(arr1, 9  * 10000)
console.timeEnd('revolve1')
const arr2 = []
for (let index = 0; index < 10 * 10000; index++) {
  arr2.push(index)
}
console.time('revolve2');
revolve2(arr2, 9 * 10000)
console.timeEnd('revolve2')


上段代码中我们定义了两个非常大的数组,然后分别利用两个方法来实现K步旋转,最后看看它们的计算时间是多少。


输出结果:49.png

从上图可以看出,两个时间对比相差不是一点半点,相差了成百上千倍,当然这也和电脑性能相关。

不过在重时间轻空间的前提下,无疑是使用concat的方法是最优秀的!


总结


虽然这道面试题比较简单,但是可以带给我们很多启发,比如关于重时间轻空间的概念,原生JS API的一些特点等等。当然,还有小伙伴有其它办法,甚至有些小伙伴直接采用更改坐标的形式在做,不是说不可以,但是没必要!


如果觉得文章太繁琐或者没看懂,可以观看视频: 小猪课堂



相关文章
|
4天前
【代数学作业5】理想的分解:高斯整数环中理想的结构,并根据其范数和素数的性质进行分解
【代数学作业5】理想的分解:高斯整数环中理想的结构,并根据其范数和素数的性质进行分解
37 0
|
4天前
【每日一题Day146】给定行和列的和求可行矩阵 | 贪心
【每日一题Day146】给定行和列的和求可行矩阵 | 贪心
22 0
|
4天前
|
算法 测试技术 C#
【树上倍增】【内向基环树】【 图论 】2836. 在传球游戏中最大化函数值
【树上倍增】【内向基环树】【 图论 】2836. 在传球游戏中最大化函数值
|
5月前
|
算法 C# C++
C++二分算法:找到最接近目标值的函数值(二)
C++二分算法:找到最接近目标值的函数值
|
5月前
|
算法 测试技术 C++
C++二分算法:找到最接近目标值的函数值(一)
C++二分算法:找到最接近目标值的函数值
|
10月前
|
算法
算法思想之三步翻转法
算法思想之三步翻转法
|
12月前
|
机器学习/深度学习 自然语言处理 算法
【每日算法Day 93】不用额外空间,你会旋转一个矩阵吗?
【每日算法Day 93】不用额外空间,你会旋转一个矩阵吗?
|
前端开发 算法 JavaScript
【前端算法】将一个数组旋转K步
使用typescript完成将一个数组旋转K步的过程
每日三题-旋转图像、合并区间、除自身以外数组的乘积
每日三题-旋转图像、合并区间、除自身以外数组的乘积
49 0
每日三题-旋转图像、合并区间、除自身以外数组的乘积
|
算法
判断三角形的性质(直角或等腰)简便算法
判断三角形的性质(直角或等腰)简便算法
96 0