100道前端面试题(八):3-8 移动 0 到数组的末尾(TS+单元测试)

110 阅读2分钟

前言

2-1.webp

今天仍然是一道算法题


输入一个数组(数组内不含非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"
          }
     }