前言
今天仍然是一道算法题
输入一个数组(数组内不含非number类型数据),将这个数组中所有为零的元素,都移动到数组的末尾,其他元素顺序不变
请看示例1:
示例 1 :
输入:
arr = [1, 0, 3, 4, 0, 0, 0, 11, 0]
输出:
arr = [1, 3, 4, 11, 0, 0, 0, 0, 0]
示例 2 :
输入:
arr = [1, 3, 4, 11]
输出:
arr = [1, 3, 4, 11]
解题思路(方法一:嵌套循环)
遍历数组当中的值为零的元素,使用splice()方法截取当前为零的元素,并且新建一个数组,原数组中删除了多少个元素,新数组就添加多少个零,之后把两个数组拼接起来
代码实现
export function moveZero1(arr: number[]): void {
const length = arr.length
if (length === 0) return
let zeroLength = 0
// O(n^2)
// 为什么要length - zeroLength?
// 因为每次splice方法都会带走一个元素,使得数组长度减一
for (let i = 0; i < length - zeroLength; i++) {
if (arr[i] === 0) {
arr.push(0)
arr.splice(i, 1) // 本身就有 O(n)
i-- // 数组截取了一个元素,i 要递减,否则连续 0 就会有错误
zeroLength++ // 累加 0 的长度
}
}
}
解题思路(方法二:双指针)
-
定义指针i和j
-
指针j指向第一个0,i指向后面的第一个非0
-
交换i和j的值,继续向后移动
代码实现
export function moveZero2(arr: number[]): void {
const length = arr.length
if (length === 0) return
let i
let j = -1 // 指向第一个 0
for (i = 0; i < length; i++) {
if (arr[i] === 0) {
// 第一个 0
if (j < 0) {
j = i
}
}
if (arr[i] !== 0 && j >= 0) {
// 交换
const n = arr[i]
arr[i] = arr[j]
arr[j] = n
j++
}
}
}
算法时间度分析
结论:方法一的时间复杂度为O(n^2),方法二的时间复杂度为O(n)
方法一中,函数中本身含有一个for循环,复杂度O(n),并且for循环中又带有一个splice()方法,在移动数组时,每个元素都被移动,因此整体的时间复杂度为O(n^2)
方法二中,一共就只有一次遍历,指针j不参与每个元素的遍历,因此时间复杂度为O(n)
性能测试
// const arr1 = []
// for (let i = 0; i < 20 * 10000; i++) {
// if (i % 10 === 0) {
// arr1.push(0)
// } else {
// arr1.push(i)
// }
// }
// console.time('moveZero1')
// moveZero1(arr1)
// console.timeEnd('moveZero1') // 262ms
// const arr2 = []
// for (let i = 0; i < 20 * 10000; i++) {
// if (i % 10 === 0) {
// arr2.push(0)
// } else {
// arr2.push(i)
// }
// }
// console.time('moveZero2')
// moveZero2(arr2)
// console.timeEnd('moveZero2') // 3ms
可见,当数据量足够大时,方法二的性能远远高于方法一,也就是说,我们在使用splice()这些会对数组元素移动的方法要万分小心,尤其是他们被包含在for循环当中时
单元测试
describe('移动 0 到数组末尾', () => {
it('正常情况', () => {
const arr = [1, 0, 3, 4, 0, 0, 0, 11, 0]
moveZero2(arr)
expect(arr).toEqual([1, 3, 4, 11, 0, 0, 0, 0, 0])
})
it('没有 0', () => {
const arr = [1, 3, 4, 11]
moveZero2(arr)
expect(arr).toEqual([1, 3, 4, 11])
})
it('全是 0', () => {
const arr = [0, 0, 0, 0, 0]
moveZero2(arr)
expect(arr).toEqual([0, 0, 0, 0, 0])
})
})
测试所需要的环境变量:
{
"name": "interview-js-code",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "jest --detectOpenHandles",
"dev": "cross-env NODE_ENV=development webpack serve --config build/webpack.dev.js",
"build": "cross-env NODE_ENV=production webpack --config build/webpack.prod.js",
"build:analyzer": "cross-env NODE_ENV=production_analyzer webpack --config build/webpack.prod.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.13.14",
"@babel/preset-env": "^7.13.12",
"@types/jest": "^27.0.2",
"autoprefixer": "^10.2.5",
"babel-jest": "^27.3.0",
"babel-loader": "^8.2.2",
"clean-webpack-plugin": "^3.0.0",
"cross-env": "^7.0.3",
"css-loader": "^5.2.0",
"html-webpack-plugin": "^5.3.1",
"jest": "^27.3.0",
"less": "^4.1.1",
"less-loader": "^8.0.0",
"postcss-loader": "^5.2.0",
"style-loader": "^2.0.0",
"ts-loader": "^8.1.0",
"typescript": "^4.2.3",
"url-loader": "^4.1.1",
"webpack": "^5.30.0",
"webpack-bundle-analyzer": "^4.4.0",
"webpack-cli": "^4.6.0",
"webpack-dev-server": "^3.11.2",
"webpack-merge": "^5.7.3",
"ts-jest": "^27.0.7"
}
}