# 字节前端算法题

# 合并两个有序数组(简单) (opens new window)

给你两个有序整数数组 nums1nums2,请你将 nums2 合并到 nums1 中,使 nums1 成为一个有序数组。

初始化 nums1nums2 的元素数量分别为 mn 。你可以假设 nums1 的空间大小等于 m + n,这样它就有足够的空间保存来自 nums2 的元素。

示例 1:

输入:nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
输出:[1,2,2,3,5,6]

示例 2:

输入:nums1 = [1], m = 1, nums2 = [], n = 0
输出:[1]

提示:

- nums1.length == m + n

- nums2.length == n

- 0 <= m, n <= 200

- 1 <= m + n <= 200

- -109 <= nums1[i], nums2[i] <= 109

思路:

  • 创建两个指针,一个从 num1 开始,一个从 num2 开始,

  • 判断两个指针指向的值的大小,进行交换

  • 但是要注意不从前往后开始遍历,因为如果从前往后遍历的话,在交换位置,还需要考虑被交换的元素与其它元素的大小于情况

  • 所以需要从后往前遍历,将满足的元素直接从后往前插入,将可以一步到位

/**
 * @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) {
    let start1 = m-1, start2 = n - 1
    let total = m+n-1
    while(start1 >= 0 && start2 >= 0){
        if(nums1[start1] <= nums2[start2]){
            nums1[total] = nums2[start2]
            start2--
        } else {
            nums1[total] = nums1[start1]
            start1--
        }
        total--
    }
    while(start2 >= 0){
        nums1[start2] = nums2[start2]
        start2--
    }
    return nums1
};

# 长度最小的子数组(中等) (opens new window)

原题 (opens new window)

给定一个含有 n个正整数的数组和一个正整数 target

找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, ..., numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0

示例 1:

输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。

示例 2:

输入:target = 4, nums = [1,4,4]
输出:1

示例 3:

输入:target = 11, nums = [1,1,1,1,1,1,1,1]
输出:0

思路:

  • 创建start, end 两个指针,分别指向最开始的位置

  • 计算 startend 之间的和

  • 不满足条件时,则保持 start 不动,end 不断右移,直到和满足条件为止

  • 当上一步满足条件时,尝试移除最左侧的元素,看剩下的元素加起来是否也条件

    1,2,3,4 的和大于 7
    但此时 2,3,4 和 3,4也是可以大于7的
    
/**
 * @param {number} target
 * @param {number[]} nums
 * @return {number}
 */
var minSubArrayLen = function(target, nums) {
    let result = 0 // 结果
    let len = nums.length
    let start = 0, end = 0
    let count = nums[0]
    while(end <= nums.length){
        if(count >= target){
            result = result ? Math.min(result, end-start+1): end-start+1
            if(result === 1) break // 如果没1,最小值长度了,直接返回
            count -= nums[start] // 长度大于1时, 减掉最左边的值,看下是否还能满足条件
            start++ // 右移 start 指针
            continue // 直接进入判断当前count是否满足值
        }
        end ++
        count += nums[end]
    }
    return result

};

# 路径总和(简单) (opens new window)

给你二叉树的根节点 root 和一个表示目标和的整数 targetSum ,判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和targetSum

叶子节点 是指没有子节点的节点。

示例:

输入:root = [5,4,8,11,null,13,4,7,2,null,null,null,1], targetSum = 22
输出:true

解一:使用深度优先遍历的方式计算每个分支的合,可以使用递归

/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @param {number} targetSum
 * @return {boolean}
 */
var hasPathSum = function(root, targetSum) {
    if(root === null) return false
    if(!root.left && !root.right && root.val === targetSum) return true // 如果没有节点,且满足条件了则直接返回
    return hasPathSum(root.left, targetSum - root.val) || hasPathSum(root.right, targetSum - root.val) // 递归调用
};

解一:使用广度优先遍历的方式

/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @param {number} targetSum
 * @return {boolean}
 */
var hasPathSum = function(root, targetSum) {
    let stack = [root]
    while(stack.length){
        let cur = stack.shift()
        if(!cur) continue
        if(!cur.left&&!cur.right&&cur.val === targetSum) {
            return true
        }
        if(cur.left){
            cur.left.val += cur.val // 添加上一节点的值
        }
        stack.push(cur.left)
        if(cur.right){
          cur.right.val += cur.val // 添加上一节点的值
        }
         stack.push(cur.right)
    }
    return false
};

# 数组中的第K个最大元素(中等) (opens new window)

在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

示例 1:

输入: [3,2,1,5,6,4] 和 k = 2
输出: 5

示例 2:

输入: [3,2,3,1,2,4,5,5,6] 和 k = 4
输出: 4

说明:

你可以假设 k 总是有效的,且 1 ≤ k ≤ 数组的长度。

解法一:

排序后,直接取 k 位置

/**
 * @param {number[]} nums
 * @param {number} k
 * @return {number}
 */
var findKthLargest = function(nums, k) {
    let arr = nums.sort((a,b) => a>=b ? -1:1)
    return arr[k-1]
};

解法二:

其实不是全部排序后才能得到 K 位置,只在 K 位置之前是排好序就可以了

/**
 * @param {number[]} nums
 * @param {number} k
 * @return {number}
 */

var findKthLargest = function(nums, k) {
    if(nums.length < k) {
        return
    }
    if(nums.length === k) {
        return findMin(nums)
    }
    let mid = nums[0]
    let left = []
    let right = []
    for(let i=1; i<nums.length; i++){
        if(nums[i] <= mid){
            left.push(nums[i])
        } else if(nums[i] > mid) {
            right.push(nums[i])
        }
    }
    if(right.length === k-1){
        return mid
    }
    else if(right.length >= k){
        return findKthLargest(right, k)
    } else {
        return findKthLargest(left, k-right.length-1)
    }
};
function findMin(nums){
    let min = nums[0]
    for(let i of nums){
        min = Math.min(min, i)
    }
    return min
}

# 字符串相加 (opens new window)

给定两个字符串形式的非负整数 num1 和num2 ,计算它们的和

提示:

  • num1 和num2 的长度都小于 5100

  • num1 和num2 都只包含数字0-9

  • num1 和num2 都不包含任何前导零

  • 你不能使用任何內建 BigInteger 库,也不能直接将输入的字符串转换为整数形式

题解:模块加法运算

/**
 * @param {string} num1
 * @param {string} num2
 * @return {string}
 */
var addStrings = function(num1, num2) {
    let start = 1
    let result = ''
    let add = 0
    while((num1.length - start) > -1 || (num2.length -start) > -1){
        let n1 = num1[num1.length - start] || 0
        let n2 = num2[num2.length - start] || 0
        let count = +n1 + +n2 + add
        result = count%10 + result
        add = count/10 | 0
        start++
    }
    if(add > 0){
      result = add + result
    }
    return result
};

# 翻转二叉树 (opens new window)

翻转一棵二叉树

示例:

输入:

     4
   /   \
  2     7
 / \   / \
1   3 6   9

输出:

     4
   /   \
  7     2
 / \   / \
9   6 3   1

解法一:使用递归

/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @return {TreeNode}
 */
var invertTree = function(root) {
    if(!root) return null
    let tem = invertTree(root.right)
    root.right = invertTree(root.left)
    root.left = tem
    return root
};

解法二:迭代法,利用每个节点都引用类型的特性

/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @return {TreeNode}
 */
var invertTree = function(root) {
    if(!root) return root
    let stack = [swap(root)]
    while(stack.length){
        let node = stack.shift()
        node.left&&stack.push(swap(node.left))
        node.right&&stack.push(swap(node.right))
    }
    function swap(node){
        let tem = node.left
        node.left = node.right
        node.right = tem
        return node
    }
    return root
};

# 求根到叶子节点数字之和 (opens new window)

给定一个二叉树,它的每个结点都存放一个 0-9 的数字,每条从根到叶子节点的路径都代表一个数字。

例如,从根到叶子节点路径 1->2->3 代表数字 123。

计算从根到叶子节点生成的所有数字之和。

说明: 叶子节点是指没有子节点的节点。

示例 1:

输入: [1,2,3]
    1
   / \
  2   3
输出: 25
解释:
从根到叶子节点路径 1->2 代表数字 12.
从根到叶子节点路径 1->3 代表数字 13.
因此,数字总和 = 12 + 13 = 25.

示例 2:

输入: [4,9,0,5,1]
    4
   / \
  9   0
 / \
5   1
输出: 1026
解释:
从根到叶子节点路径 4->9->5 代表数字 495.
从根到叶子节点路径 4->9->1 代表数字 491.
从根到叶子节点路径 4->0 代表数字 40.
因此,数字总和 = 495 + 491 + 40 = 1026.

解法一:深度优先遍历

/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @return {number}
 */
var sumNumbers = function(root) {
    let result = 0
    var sum = function(node, count){
        let s = count * 10 + node.val
        if(!node.left && !node.right){
            result = result + s
        } else {
            node.left&&sum(node.left, s)
            node.right&&sum(node.right, s)
        }
    }
    sum(root, 0)
    return result
};

解法二:深度优先遍历

/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @return {number}
 */
var sumNumbers = function(root) {
    let result = 0
    let stack = [root]
    while(stack.length){
        let node = stack.shift()
        if(!node.left && !node.right){
            result += node.val
            continue
        }
        if(node.left){
            node.left.val += node.val*10
            stack.push(node.left)
        }
        if(node.right){
            node.right.val += node.val*10
            stack.push(node.right)
        }
    }
    return result
};

# 路径总和 II (opens new window)

给定一个二叉树和一个目标和,找到所有从根节点到叶子节点路径总和等于给定目标和的路径。

说明: 叶子节点是指没有子节点的节点。

示例: 给定如下二叉树,以及目标和 sum = 22,

              5
             / \
            4   8
           /   / \
          11  13  4
         /  \    / \
        7    2  5   1

返回:

[
   [5,4,11,2],
   [5,8,4,5]
]

题解:

/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @param {number} targetSum
 * @return {number[][]}
 */
var pathSum = function(root, targetSum) {
    if(!root) return []
    let result = []
    var wakl = function(node, count, stack){
        stack.push(node.val)
        count += node.val
        if(!node.left&&!node.right&&count === targetSum){
            result.push(stack]
        }
        if(node.left){
            wakl(node.left, count, [...stack])
        } 
        if(node.right){
            wakl(node.right, count, [...stack])
        }
    }
    wakl(root, 0, [])
    return result
};

上面代码最主要的问题在于递归 wakl 方法时,总是要复制一次数组,这一步造成了额外的空间消耗。所以使用回逆方法做下优化

var pathSum = function(root, targetSum) {
    if(!root) return []
    let result = []
    var wakl = function(node, count, stack){
        stack.push(node.val)
        count += node.val
        if(!node.left&&!node.right&&count === targetSum){
            result.push(stack.slice()) // 复制结果,这样不受下面 `pop()` 的影响
        }
        if(node.left){
            wakl(node.left, count, stack)
        } 
        if(node.right){
            wakl(node.right, count, stack)
        }
        stack.pop() // 回逆
    }
    wakl(root, 0, [])
    return result
};

# 两数之和 (opens new window)

解法一

遍历多次,略

解法二:两遍哈希表

使用 map 结构可以更有效的方法来检查数组中是否存在目标元素

 var twoSum = function(nums, target) {
    const map = {}
    for(let i in nums) {
        map[nums[i]] = i
    }
    for(let i = nums.length-1; i >=0 ; i--){
        let tar = target - nums[i]
        if(tar in map && map[tar] !==i ){
                return [i, map[tar]]
        }
    }
};

方法三:一遍哈希表

方法二的优化,将两次遍历完合成一次遍历

 /**
 * @param {number[]} nums
 * @param {number} target
 * @return {number[]}
 */
var twoSum = function(nums, target) {
    const map = {}
    for(let i = nums.length-1; i >=0 ; i--){
        let tar = target - nums[i]
        if(tar in map && map[tar] !==i ){
                return [i, map[tar]]
        }
        map[nums[i]] = i
    }
};

注意点:

  • for(let i = nums.length-1; i >=0 ; i--)for(let i = 0; i < nums.length ; i++) 效率高,节省 nums.length 的计算次数

  • for 循环比 forEacth 效率高

  • 下面代码额外多了 result 变量,会多点用点内存

      var twoSum = function(nums, target) {
          const map = {}
          let result = null
          for(let i = nums.length-1; i >=0 ; i--){
              let tar = target - nums[i]
              if(tar in map && map[tar] !==i ){
                   result = [i, map[tar]]
                   break
              }
              map[nums[i]] = i
          }
          return result
      };
    

https://github.com/afatcoder/LeetcodeTop/blob/master/bytedance/frontend.md

https://leetcode-cn.com/explore/interview/card/bytedance/