一些简单的前端算法题

129 阅读4分钟

前序遍历

preOrder(root) {
    // 中(节点输出) 左 右
    /**
     * 1、二叉树只有左右节点的概念(从root 根结点开始便利 后面所有节点非左即右)
     * 2、右边先入栈、再左边节点入栈,迭代输出,可以确保所有节点都是左侧节点优先出栈
     */
    if (!root) return []
    const result = []
    const stack = [root]
    while (stack.length > 0) {
        const getNode = stack.pop()
        if (getNode.right) {
            stack.push(getNode.right)
        }
        if (getNode.left) {
            stack.push(getNode.left)
        }
        result.push(getNode.element)
    }
    return result
}

后序遍历

postOrder(root) {
    // 左 右 中
    /**
     * 区别于前序遍历
     * 1、从根节点开始,遍历左右子节点,如果遇到左节点,继续往下查找
     * 2、如果左侧节点没有值了,则开始遍历右子节点
     * 3、此时右子节点相当于根结点重复1的流程
     * 
     * 由于节点是从顶层开始往子节点遍历的,因此操作节点应该采用 unshift的方式
     */
    if (!root) return []
    const result = [], stack = [root]
    while (stack.length > 0) {
        const getNode = stack.pop()
        result.unshift(getNode.element)
        if (getNode.left) {
            stack.push(getNode.left)
        }
        if (getNode.right) {
            stack.push(getNode.right)
        }
    }
    return result
}

中序遍历

inOrder(root) {
    if (!root) {
        return []
    }
    const stack = [], result = []
    while (stack.length > 0 || root) {
        while(root) {
            stack.push(root)
            root = root.left
        }
        const data = stack.pop()
        result.push(data.element)
        root = data.right
    }
    return result
}

两数之和

/**
 * 1. 两数之和
 * 
 * 
    给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target  的那 两个 整数,并返回它们的数组下标。

    你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。

    你可以按任意顺序返回答案。

示例:
    输入:nums = [2,7,11,15], target = 9
    输出:[0,1]
    解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。




    ## 思路
    - 从时间复杂度角度出发,采用暴力解法,两层嵌套循环,,时间复杂度为 On2,不可取
    - 通过map存储对象的方式可以降低时间复杂度
    - 通过将key的差值进行存储,有利于下一次进行比较,比如 第一次  map.has(2) 不存在 那么就set map.set(9 - 2) = 2的序号,第二位 7 map.has(7)存在了,那么与他匹配的序号就是 map.get(7) = 2 了
 */
 
 
 var twoSum = function(nums, target) {
   const map = new Map()
   for (var i = 0; i < nums.length; i++) {
       if (!map.has(nums[i])) {
          map.set(target - nums[i], i)
       } else {
          return [map.get(nums[i]), i]
       }
   }
};

整数反转

/**
 * 整数反转
 * 
 * 
    给你一个 32 位的有符号整数 x ,返回将 x 中的数字部分反转后的结果。
    如果反转后整数超过 32 位的有符号整数的范围 [−231,  231 − 1] ,就返回 0。
    假设环境不允许存储 64 位整数(有符号或无符号)。
 */
 
 
/**
 * @param {number} x
 * @return {number}
 */
 var reverse = function(x) {
    var flag = x > 0, newValue = 0
    if (!flag) x = -x
    while (x > 9) {
        newValue = newValue * 10 + x % 10 * 10
        x = Math.floor(x / 10)
    }
    newValue += x
    if (!flag) {
        newValue = -newValue
    }
    if (newValue > Math.pow(2, 31) - 1 || newValue < Math.pow(2, 31) * -1) {
        return 0
    } else {
        return newValue
    }
};

回文数

/**
 * 回文数
 * 
 * 给你一个整数 x ,如果 x 是一个回文整数,返回 true ;否则,返回 false 。

    回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。例如,121 是回文,而 123 不是。
 */
 
 
/**
 * @param {number} x
 * @return {boolean}
 * 
 * 输入:x = 121
 * 输出:true
 * 
 * 输入:x = -121
 * 输出:false
 * 
 * 输入:x = 10
 * 输出:false
 * 
 * 
 * 思路:
 * 
 */
 var isPalindrome = function(x) {
    if (x < 0 || (x % 10 === 0 && x !== 0)) {
        return false
    }

    let revertedNum = 0

    while (x > revertedNum) {
        // 从右往左指针移动进行截取,revertedNum为左侧的数再进行反转
        revertedNum = revertedNum * 10 + x % 10
        // 从右往左指针移动进行截取,x为右侧的数
        x = Math.floor(x / 10)
        // 因此如果是奇数位那么指针必须在正中间偏左的位置才能保证 x < revertedNum 此时跳出循环
        // 如果是偶数位,就有可能对半切(1122)或者revertedNum占三个 x 只有一个 (2211)
    }
    console.log(x, revertedNum)
    // revertedNum === x 偶数位  revertedNum / 10 === x 奇数位(因为奇数位中间那一项不用管)
    return (revertedNum === x || Math.floor(revertedNum / 10) === x)

};
// console.log(isPalindrome(121))

最长公共前缀


/**
 * 14. 最长公共前缀
 * 
 * 编写一个函数来查找字符串数组中的最长公共前缀。
 * 如果不存在公共前缀,返回空字符串 ""。
 * 
 * 输入:strs = ["flower","flow","flight"]
 * 输出:"fl"
 * 
 * 
 * 思路:
 * 1、边界判断,如果strs 长度为 0 获取 1 取第一项作为公共前缀
 * 2、先将数组内第一个数据作为公共前缀,依次遍历 strs
 * 3、开辟一个新的循环, 依次比较strs[0], 如果 不相同记录当前序号
 * 4、通过比较获取最小的序号即为公共前缀序号
 */

/**
 * @param {string[]} strs
 * @return {string}
 */
 var longestCommonPrefix = function(strs) {
    if (strs.length === 0 || strs.length === 1) return strs[0]
    let result = strs[0], matchIndex = null
    for (let j = 1; j < strs.length; j++) {
        // 遍历每一项
        let i = 0
        for (; i < result.length && strs[j].length; i++) {
            if (result[i] !== strs[j][i]) break
        }
        if (matchIndex === null) {
            matchIndex = i
        } else {
            matchIndex = Math.min(matchIndex, i)
        }
    }
    return result.slice(0, matchIndex)
};

有效的括号


/**
 * 20. 有效的括号
 * 
 * 给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。

有效字符串需满足:

左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。

输入:s = "([)]"
输出:false

输入:s = "()[]{}"
输出:true
 */

const isValid = (str) => {
    const map = new Map(), container = []
    const sample = new Map()
    sample.set('(', ')')
    sample.set('{', '}')
    sample.set('[', ']')
    for (let i = 0; i < str.length; i++) {
        if (sample.get(str[i])) {
            // 存在的话 是左半边
            container.push(sample.get(str[i]))
        } else {
            // 不存在(右半边),将数组最后一位取出来并与之对比
            if (container.pop() === str[i]) {
                continue
            } else {
                return false
            }
        }
    }
    return container.length === 0
}
// console.log(isValid('([)]'))

删除有序数组中的重复项


/**
 * 26. 删除有序数组中的重复项
 * 
 * 给你一个有序数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。
 * 
 * 不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。
 * 
 * 说明:

    为什么返回数值是整数,但输出的答案是数组呢?

    请注意,输入数组是以「引用」方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。

    你可以想象内部操作如下:
    // nums 是以“引用”方式传递的。也就是说,不对实参做任何拷贝
    int len = removeDuplicates(nums);

    // 在函数里修改输入数组对于调用者是可见的。
    // 根据你的函数返回的长度, 它会打印出数组中 该长度范围内 的所有元素。
    for (int i = 0; i < len; i++) {
        print(nums[i]);
    }

示例一
输入:nums = [1,1,2]
输出:2, nums = [1,2]
解释:函数应该返回新的长度 2 ,并且原数组 nums 的前两个元素被修改为 1, 2 。不需要考虑数组中超出新长度后面的元素。


示例二
输入:nums = [0,0,1,1,1,2,2,3,3,4]
输出:5, nums = [0,1,2,3,4]
解释:函数应该返回新的长度 5 , 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4 。不需要考虑数组中超出新长度后面的元素。
 */

var removeDuplicates = function(nums) {
    const n = nums.length;
    if (n === 0) {
        return 0;
    }
    let fast = 1, slow = 1;
    while (fast < n) {
        // 因为是有序数组,因此只要相邻比较,这样就能确保中间不会发生交叉
        if (nums[fast] !== nums[fast - 1]) {
            // slow记录当前没有重复的位置,因为是个数,不是序号,因此是从1开始的
            nums[slow] = nums[fast];
            ++slow;
        }
        ++fast;
    }
    return slow;
};

搜索插入位置


/**
 * 35. 搜索插入位置
 * 
 * 给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
 * 
 * 请必须使用时间复杂度为 O(log n) 的算法。
 * 
 * 示例一
 * 输入: nums = [1,3,5,6], target = 5
 * 输出: 2
 * 
 * 示例二
 * 输入: nums = [1,3,5,6], target = 2
 * 输出: 1
 * 
 * 示例三
 * 输入: nums = [1,3,5,6], target = 7
 * 输出: 4
 * 
 * 示例四
 * 输入: nums = [1,3,5,6], target = 0
 * 输出: 0
 * 
 * 示例五
 * 输入: nums = [1], target = 0
 * 输出: 0
 */
/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number}
 * 
 * 二分法解
 */
 var searchInsert = function(nums, target) {
    let len = nums.length, l = 0, r = len - 1
    while (l <= r) {
        const mid = Math.floor(l + (r - l) / 2)
        if (nums[mid] < target) {
            l = mid + 1
        } else {
            r = mid - 1
        }
    }
    return l
};
// console.log(searchInsert([1,3,5,6], 5))
// console.log(searchInsert([1,3,5,6], 2))
// console.log(searchInsert([1,3,5,6], 7))
// console.log(searchInsert([1,3,5,6], 0))
// console.log(searchInsert([1], 0))


最大子数组和


/**
 * 53. 最大子数组和
 * 
 * 给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
 * 
 * 子数组 是数组中的一个连续部分。
 * 
 * 输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
 * 输出:6
 * 解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。
 * 
 * 输入:nums = [1]
 * 输出:1
 * 
 * 输入:nums = [5,4,-1,7,8]
 * 输出:23
 */

/**
 * @param {number[]} nums
 * @return {number}
 * 
 * 
 * 思路:如果之前的数之和加上自身后 反而变小了,显然这个数组没有继续下去的必要了,那就新开一个数组,那样就不至于会接上那些累赘了
 */
 var maxSubArray = function(nums) {
    const memo = []
    memo[0] = nums[0]
    let max = nums[0]
    for (let i = 1; i < nums.length; i++) {
        memo[i] = Math.max(nums[i] + memo[i - 1], nums[i])
        max = Math.max(max, memo[i])
    }
    return max
};

console.log(maxSubArray([5,4,-1,7,8]))



合并两个有序数组


/**
 *  合并两个有序数组
 * 
 * 
 * 输入:nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
 * 输出:[1,2,2,3,5,6]
 * 解释:需要合并 [1,2,3] 和 [2,5,6] 。
 * 合并结果是 [1,2,2,3,5,6] ,其中斜体加粗标注的为 nums1 中的元素。
 */

/**
 * @param {number[]} nums1
 * @param {number} m
 * @param {number[]} nums2
 * @param {number} n
 * @return {void} Do not return anything, modify nums1 in-place instead.
 */
var merge = function(nums1, m, nums2, n) {
    const result = []
    nums1 = nums1.slice(0, m)
    nums2 = nums2.slice(0, n)
    while (nums1.length && nums2.length) {
        if (nums1[0] < nums2[0]) {
            result.push(nums1.shift())
        } else {
            result.push(nums2.shift())
        }
    }
    while (nums1.length > 0) {
        result.push(nums1.shift())
    }
    while (nums2.length > 0) {
        result.push(nums2.shift())
    }
    return result
};

// console.log(merge([1,2,3,0,0,0],3, [2,5,6], 3))

斐波那契数列



/**
 * 斐波那契数列
 * 
 */

const fib = function(n) {
    if (n <= 1) return n
    const cache = []
    cache[0] = 0
    cache[1] = 1

    function memoize(number) {
        if (cache[number] !== undefined) {
            return cache[number]
        }
        cache[number] = memoize(number - 1) + memoize(number - 2)
        return cache[number]
    }

    const result = memoize(n)
    return result
}

// 优化空间复杂度

const fib2 = function(n) {
    if (n <= 1) return n
    let prev2 = 0
    let prev1 = 1
    let result = 0

    for (let i = 2; i <=n; i++) {
        result = prev1 + prev2
        prev2 = prev1
        prev1 = result
    }
    return result
}

求二维数组的全排列组合

var arrays = [["a0","a1"],["b0","b1"],["c0","c1"],["d0","d1"]];
var array = getArrayByArrays(arrays);

function getArrayByArrays(arrays) {
    var arr = [""];
    for(var i = 0;i<arrays.length;i++) {
      arr = getValuesByArray(arr,arrays[i]);
    }
    return arr;
}

function getValuesByArray(arr1,arr2) {
    var arr = [];
    console.log(arr1)
    for(var i=0;i<arr1.length;i++) {
      var v1 = arr1[i];
      for(var j=0;j<arr2.length;j++) {
        var v2 = arr2[j];
        var value = v1 + v2;
        arr.push(value);
      };
    };
    return arr;
}
console.log(array)

子集


/**
 * 78. 子集
 * 
 * 给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。

 * 解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。

    输入:nums = [1,2,3]
    输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
 */

/**
 * @param {number[]} nums
 * @return {number[][]}
 * 
 * 
    function backtrack(start, curr) {
         for (let i = start; i < nums.length; i++) {
             1、把nums[i] 加入curr数组
             2、backtrack(i + 1, curr)
             3、把curr数组的最后一个元素移除
         }
    }
 */
var subsets = function(nums) {
    const result = []

    function backtrack(start, curr) {
        result.push([...curr])
        for(let i = start; i < nums.length; i++) {
            curr.push(nums[i])
            backtrack(i + 1, curr)
            curr.pop()
        }
    }

    backtrack(0, [])
    return result
};

全排列


/**
 * @param {number[]} nums
 * @return {number[][]}
 */
 var permute = function(nums) {
    const res = [];
    const used = {};

    function dfs(path) {
        if (path.length == nums.length) { // 个数选够了
            res.push(path.slice()); // 拷贝一份path,加入解集res
            return;                 // 结束当前递归分支
        }
        for (const num of nums) { // for枚举出每个可选的选项
            // if (path.includes(num)) continue; // 别这么写!查找是O(n),增加时间复杂度
            if (used[num]) continue; // 使用过的,跳过
            path.push(num);         // 选择当前的数,加入path
            used[num] = true;       // 记录一下 使用了
            dfs(path);              // 基于选了当前的数,递归
            path.pop();             // 上一句的递归结束,回溯,将最后选的数pop出来
            used[num] = false;      // 撤销这个记录
        }
    }

    dfs([]); // 递归的入口,空path传进去
    return res;
};

二维数组全排列


/**
 * 二维数组全排列
 * 
 */



 function getArrayByArrays(arrays) {
   var arr = [""];
   for(var i = 0;i<arrays.length;i++) {
     // 通过来源两个合并的数组,继续向下一个数组进行合并,递归处理,有点像柯里化
     arr = getValuesByArray(arr,arrays[i]);
   }
   return arr;
 }

 function getValuesByArray(arr1,arr2) {
   // 合并两个数组
   var arr = [];
   for(var i=0;i<arr1.length;i++) {
     for(var j=0;j<arr2.length;j++) {
       arr.push(arr2[j] + arr1[i]);
     };
   };
   return arr;
 }
 
 var arrays = [["a0","a1"],["b0","b1","b2"]];
 var array = getArrayByArrays(arrays);