前端面试必刷力扣前100算法(更新中...)

文章目录

【哈希】

1. 两数之和

  • 输出所有和为sum的两个值
function twoNumberSum(array, sum) {
  let map = new Map();
  let result = [];

  array.forEach((item, index) => {
    if (typeof map.get(sum - item) === 'undefined') {
      map.set(item, index)
    }
    else {
      result.push([sum - item, item])
    }
  })
  return result
}
console.log(twoNumberSum([1,2,3,4,5,2,1, 0], 4))
  • 返回下标
var twoSum = function(nums, target) {
  let map = new Map();

  for (let i = 0; i < nums.length; i++) {
    if (typeof map.get(target - nums[i]) === 'undefined') {
      map.set(nums[i], i)
    }
    else {
      return [map.get(target - nums[i]), i];
    }
  }
  return [];
}

49. 字母异位词分组(异位词:字母重组合形成的字符串)

输入: strs = [“eat”, “tea”, “tan”, “ate”, “nat”, “bat”]
输出: [[“bat”],[“nat”,“tan”],[“ate”,“eat”,“tea”]]

  1. 【把每个字母重排列】(时间复杂度高)
var groupAnagrams = function(strs) {
    let obj = {};
    for(let i = 0; i < strs.length; i++) {
        let str = strs[i].split('').sort().join('');
        if(obj[str]!== undefined) {
            let arr = obj[str];
            arr.push(strs[i]);
            obj[str] = arr;
        }
        else {
            obj[str] = [strs[i]];
        }
    }
    return Object.values(obj);
};
  1. 【素数乘积唯一】(优化版)
var groupAnagrams = function (strs) {
	const primeNums = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101];
    const strMap = new Map();
    for (let i = 0; i < strs.length; i++) {
        let value = 1;
        for (let j = 0; j < strs[i].length; j++) {
            value = value ? value * primeNums[strs[i].charCodeAt(j) - 97] : primeNums[strs[i].charCodeAt(j) - 97];
        }
        strMap.has(value) ? strMap.get(value).push(strs[i]) : strMap.set(value, [strs[i]])
    }
    return Array.from(strMap.values());
}

28. 最长连续序列(数组中连续的序列长度)

输入:nums = [100,4,200,1,3,2]
输出:4
解释:最长数字连续序列是 [1, 2, 3, 4]。它的长度为 4。

var longestConsecutive = function(nums) {
    let set = new Set();
    let longLength = 0;
    for (let i = 0; i < nums.length; i++) {
        set.add(nums[i]);
    }
    for (let i = 0; i < nums.length; i++) {
        // 连续数字中的第一个
        if (!set.has(nums[i]-1)) {
            let currentItem = nums[i];
            let currentLength = 1;
            while(set.has(currentItem+1)) {
                currentItem += 1;
                currentLength += 1;
            }
            longLength = Math.max(longLength, currentLength)
        }
    }
    return longLength;
};

【双指针】

283. 移动零(将0移到数组最后面)

输入: nums = [0,1,0,3,12]
输出: [1,3,12,0,0]

var moveZeroes = function(arr) {
    let left = 0, right = 0;
    while(right < arr.length) {
        if (arr[right]){
            let temp = arr[left];
            arr[left] = arr[right];
            arr[right] = temp;
            left++;
        }
        right++;
    }
  return arr;
};

11. 盛最多水的容器(数组能组成的最大面积)

输入:[1,8,6,2,5,4,8,3,7]
输出:49
解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。
在这里插入图片描述

var maxArea = function(height) {
  let left = 0, right = height.length-1;
  let mul = 0;
  while(left < right) {
    if (height[left] < height[right]) {
      mul = Math.max(mul, height[left] * (right-left));
      left++;
    }
    else {
      mul = Math.max(mul, height[right] * (right-left));
      right--;
    }
  }
  return mul;
};

15. 三数之和

function threeNumberSum(array, sum) {
  let result = [];
  let left = 0, right = array.length - 1;
  array = array.sort((a, b) => a - b);
  for (let i = 0; i < array.length; i++) {
    left = i + 1;
    right = array.length - 1;
    if (i > 0 && array[i] === array[i - 1]) continue;
    while (left < right) {
      let current = array[i] + array[left] + array[right];
      if (sum - current === 0) {
        result.push([array[i], array[left], array[right]]);
        while (array[left] === array[left+1]) {
          left++;
        }
        while (array[right] === array[right-1]) {
          right--;
        }
        left++;
        right--;
      }
      else if (sum - current < 0) {
        right--;
      }
      else if (sum - current > 0) {
        left++;
      }
    }
  }
  return result;
}
console.log(threeNumberSum([-1,0,1,2,-1,-4], 0))

42. 接雨水

在这里插入图片描述

  1. 【动态规划】解题思路:
  • 每个位置能接到的雨水为,两边高度的最小值减去当前的值
  • 定义一个数组leftMax,表示和左边去对比,找到每个位置能达到的最大高度
  • 定义一个数组rightMax,表示和右边去对比,找到每个位置能达到的最大高度
  • 最后每个位置能接到的雨水就是当前位置的最小值减去当前的值
var trap = function(height) {
    const n = height.length;
    if (n == 0) {
        return 0;
    }

    const leftMax = new Array(n).fill(0);
    leftMax[0] = height[0];
    for (let i = 1; i < n; ++i) {
        leftMax[i] = Math.max(leftMax[i - 1], height[i]);
    }

    const rightMax = new Array(n).fill(0);
    rightMax[n - 1] = height[n - 1];
    for (let i = n - 2; i >= 0; --i) {
        rightMax[i] = Math.max(rightMax[i + 1], height[i]);
    }

    let ans = 0;
    for (let i = 0; i < n; ++i) {
        ans += Math.min(leftMax[i], rightMax[i]) - height[i];
    }
    return ans;
};
console.log(trap([0,1,0,2,1,0,1,3,2,1,2,1])) // 6
  1. 【双指针】解题思路
  • 数组首尾各定义一个指针
  • 找到左边的最大值,且同时计算当前元素与最大值的差,就是可以接到雨水的面积
var trap = function(height) {
    let ans = 0;
    let left = 0, right = height.length - 1;
    let leftMax = 0, rightMax = 0;
    while (left < right) {
        leftMax = Math.max(leftMax, height[left]);
        rightMax = Math.max(rightMax, height[right]);
        if (height[left] < height[right]) {
            ans += leftMax - height[left];
            ++left;
        } else {
            ans += rightMax - height[right];
            --right;
        }
    }
    return ans;
};
console.log(trap([0,1,0,2,1,0,1,3,2,1,2,1]))

【滑动窗口】

3. 无重复字符的最长子串

输入: s = “abcabcbb”
输出: 3
解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。

var lengthOfLongestSubstring = function(s) {
    let map = new Map(), max = 0, str = ''
    for(let i = 0, j = 0; j < s.length; j++) {
        if(map.has(s[j])) {
            i = Math.max(map.get(s[j]) + 1, i)
        }
        if (max < j - i + 1) {
			max = j - i + 1
			str = s.substring(i, j+1)
		}
        map.set(s[j], j)
    }
    console.log(str); // 最长不重复子串
    return max // 长度
};

49. 字母异位词分组(输出指定字符串的异分词的开始下标)

输入: s = “cbaebabacd”, p = “abc”
输出: [0,6]
解释:
起始索引等于 0 的子串是 “cba”, 它是 “abc” 的异位词。
起始索引等于 6 的子串是 “bac”, 它是 “abc” 的异位词。

  1. 【滑动窗口】
var findAnagrams = function (s, p) {
    const sLen = s.length, pLen = p.length;
    if (sLen < pLen) {
        return [];
    }

    const result = [];
    const sCount = new Array(26).fill(0);
    const pCount = new Array(26).fill(0);

    for (let i = 0; i < pLen; i++) {
        ++sCount[s[i].charCodeAt() - 'a'.charCodeAt()];
        ++pCount[p[i].charCodeAt() - 'a'.charCodeAt()];
    }

    if (sCount.toString() === pCount.toString()) {
        result.push(0);
    }

    for (let i = 0; i < sLen - pLen; i++) {
        --sCount[s[i].charCodeAt() - 'a'.charCodeAt()];
        ++sCount[s[i + pLen].charCodeAt() - 'a'.charCodeAt()];
        if (sCount.toString() === pCount.toString()) {
            result.push(i + 1);
        }
        console.log(sCount, 'sCount', pCount, 'pCount')
    }
    return result;
};
console.log(findAnagrams("cbaebabacd", "ab")); //  [1, 4, 5, 6]
  1. 【利用素数乘积唯一】
let result = [];
    let numP = 1;
    let array = [2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101];
    for (let i = 0; i < p.length; i++) {
        numP *= array[p[i].charCodeAt()-97];
    }
    for(let i = 0; i <= s.length-p.length; i++) {
        let numS = 1;
        for (j = i; j < p.length+i; j++) {
            numS *= array[s[j].charCodeAt()-97];
        }
        if (numS === numP) result.push(i)
    }
    return result;

【子串】

560. 和为k的子数组

输入:nums = [1,2,3], k = 3
输出:2 // 1+2,需要2个元素

var subarraySum = function(nums, k) {
  let count = 0;
  let mp = new Map();
  let pre = 0;
  mp.set(0, 1);
  for (let x of nums) {
    pre += x; // 前n个数字之和
    if (mp.get(pre-k)) {
        count += mp.get(pre-k);
    }
    if (mp.get(pre)) {
        mp.set(pre, mp.get(pre)+1);
    }
    else {
        mp.set(pre, 1);
    }
  }
  return count;
};

【贪心算法】

贪心算法:每一步选择中都采取在当前状态下最好或最优的选择,从而希望导致结果是最好或最优的算法,虽然贪心算法并不总能找到问题的最优解,但在某些问题中,它能提供有效、简单且高效的解决方案。

贪心算法和动态规划区别

  • 贪心算法
    局部最优选择:贪心算法在每一步都选择当前看起来最优的解,即局部最优解。
    一步到位:通过一系列的局部最优选择,期望能够找到全局最优解。
    无需回溯:一旦做出选择,就不会再修改,即无需回退和调整之前的决策。
    时间复杂度和空间复杂度较低
  • 动态规划
    分解子问题:动态规划将问题分解成若干个子问题,并通过解决子问题来构建原问题的解。
    子问题依赖:每个子问题的解可能依赖于其他子问题的解,通过递推关系逐步构建最终解。
    存储与复用:通过存储子问题的解(通常使用表格)避免重复计算,提高计算效率。
    时间复杂度和空间复杂度较高

121. 买卖股票的最佳时机(数组表示当天股票价格)

题目描述
给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0

1.【贪心算法】解题思路:

  • 找到最小值,也就是找到买入便宜的价格
  • 同时找到差值最大的结果,也就是赚的最多的时刻
var maxProfit = function(prices) {
  let result = 0;
  let Max = Number.MAX_VALUE;
  for (let i = 0; i < prices.length; i++) {
    if (prices[i] < Max) {
      Max = prices[i];
    }
    else if (prices[i] - Max > result) {
      result = prices[i] - Max;
    }
  }
  return result;
};
console.log(maxProfit([7,1,5,3,6,4])); // 5

45. 跳跃游戏

数组每项代表当前位置可以跳跃的最大值,判断是否可以跳到最后一个元素
输入:nums = [2,3,1,1,4]
输出:true
解释:可以先跳 1 步,从下标 0 到达下标 1, 然后再从下标 1 跳 3 步到达最后一个下标。

  1. 【贪心算法】解题思路
  • 定义rightMax是当前跳到的位置
var canJump = function(nums) {
    let rightMax = 0; // 当前最右边的下标
    let m = nums.length;
    for (let i = 0; i < m; i++) {
        if (i <= rightMax) {
            rightMax = Math.max(rightMax, i+nums[i]);
            if (rightMax >= m-1) {
                return true;
            }
        }
    }
    return false;
};

55. 跳跃游戏II

数组每项代表当前位置可以跳跃的最大值,跳到最后一个元素最少需要几次
输入: nums = [2,3,1,1,4]
输出: 2
解释: 跳到最后一个位置的最小跳跃数是 2。从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。

  1. 【贪心算法】解题思路
 var jump = function(nums) {
  let count = 0;
  let end = 0;
  let maxPosition = 0;
  if (nums.length === 1) return count;
  for (let i = 0; i < nums.length-1; i++) {
    maxPosition = Math.max(maxPosition, i + nums[i]);
    if (i === end) {
      end = maxPosition;
      count++;
    }
  }

  return count;
};

【动态规划】

动态规划:将问题划分为更小的子问题,逐个解决子问题并存储其结果(通常使用一个表),以避免重复计算,从而提高算法效率
总结:拆分子问题,记住过往,减少重复计算

斐波那契数列

function fibonacciDP(n) {
  if (n <= 1) return n;
  const dp = new Array(n + 1);
  dp[0] = 0;
  dp[1] = 1;
  for (let i = 2; i <= n; i++) {
    dp[i] = dp[i - 1] + dp[i - 2];
  }
  return dp[n];
}

背包问题

给定一组物品,每个物品有一定的重量和价值,在限定的总重量内选择某些物品,使得总价值最大

function knapsack(weights, values, capacity) {
  const n = weights.length;
  const dp = Array.from({ length: n + 1 }, () => Array(capacity + 1).fill(0));
  
  for (let i = 1; i <= n; i++) {
    for (let w = 1; w <= capacity; w++) {
      if (weights[i - 1] <= w) {
        dp[i][w] = Math.max(dp[i - 1][w], dp[i - 1][w - weights[i - 1]] + values[i - 1]);
      } else {
        dp[i][w] = dp[i - 1][w];
      }
    }
  }
  
  return dp[n][capacity];
}

70. 爬楼梯(一次爬1个或者两个,一共有多少种方案)

一次可以爬一个台阶,也可以爬两个

var climbStairs = function(n) {
  let dp = new Array(n+1).fill(-1);
  dp[0] = 1;
  dp[1] = 1;
  for (let i = 2; i <= n; i++) {
    dp[i] = dp[i-1]+dp[i-2];
  }
  return dp[n];
};

118. 杨辉三角

在这里插入图片描述

var generate = function(numRows) {
    let result = [[1], [1, 1]];
    let arr = [1, 1];
    if (numRows === 1) return [[1]];
    if (numRows == 2) return result;
    for (let i = 3; i < numRows+1; i++) {
        let temp = [];
        for (let j = 0; j <= arr.length; j++) {
            if (j === 0 || j === arr.length) temp.push(1);
            else temp.push(arr[j-1]+arr[j])
        }
        arr = temp;
        result.push(temp)
    }
    return result;
};

198. 打家劫舍

输入:[1,2,3,1]
输出:4
解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。

var rob = function(nums) {
    let maxMoney = 0;
    let dp = new Array(nums.length).fill(-1)
    for (let i = 0; i < nums.length; i++) {
        if (i === 0) {
            dp[i] = nums[i];
        }
        else if (i === 1) {
            dp[i] = Math.max(nums[i-1], nums[i])
        }
        else {
            dp[i] = Math.max(dp[i-2]+nums[i], dp[i-1]);
        }
        maxMoney = Math.max(dp[i], maxMoney);

    }
    return maxMoney;
};

322. 零钱兑换(零钱数组,金额,输出需要使用多少个钱)

给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。
计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。
输入:coins = [1, 2, 5], amount = 11
输出:3
解释:11 = 5 + 5 + 1

  1. 【动态规划】解题思路
  • 定义一个数组,存放的是当前数额下,使用的硬币最少
  • 遍历硬币数组,找到最少使用硬币数
var coinChange = function(coins, amount) {
    let dp = new Array(amount+1).fill(Infinity);
    dp[0] = 0;
    for (let i = 1; i <= amount; i++) {
        for (let j = 0; j < coins.length; j++) {
            if (i >= coins[j]) {
                dp[i] = Math.min(dp[i], dp[i-coins[j]]+1);
            }
        }
            
    }
    return dp[amount] === Infinity ? -1 : dp[amount];
};

139. 单词拆分(字典数组,和字符串,字典是否可以组成字符串)

输入: s = “leetcode”, wordDict = [“leet”, “code”]
输出: true
解释: 返回 true 因为 “leetcode” 可以由 “leet” 和 “code” 拼接成

  1. 【动态规划】解题思路
  • dp数组,存放结果
  • i表示当前元素位置
  • j表示(j, i)之前的内容是否能查找到,当[j i]能在字典库中找到,同时需要判断dp[j]的值是否为true,表示之前元素都在字典中
function wordBreak(s, wordDict) {
  let dp = new Array(s.length+1).fill(false);
  dp[0] = true;
  for (let i = 1; i <= s.length; i++) {
    for (let j = 0; j < i; j++) {
      let str = s.substring(j, i); // ca
      if (wordDict.includes(str) && dp[j]) {
        dp[i] = true;
        break;
      }
    }
  }
  return dp.pop();
}

300. 最长递增子序列

输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4。

  1. 【动态规划】解题思路
  • dp中存放着当前元素能形成的最长递增子序列长度
var lengthOfLIS = function(nums) {
  let dp = new Array(nums.length);
  dp[0] = 1;
  let maxLen = 1;
  for (let i = 0; i < nums.length; i++) {
    dp[i] = 1;
    for (let j = 0; j < i; j++) {
      if (nums[i] > nums[j]) {
        dp[i] = Math.max(dp[j]+1, dp[i]);
      }
    }
    maxLen = Math.max(maxLen, dp[i]);
  }
  return maxLen;
};

152. 乘积最大(给一个数组,找到最大乘积)

输入: nums = [2,3,-2,4]
输出: 6
解释: 子数组 [2,3] 有最大乘积 6。

  1. 【动态规划】解题思路
  • 数组维护当前位置的乘积最大值和乘积最小值(负数考虑)
// 【好理解版本】空间复杂度高
var maxProduct = function(nums) {
  let dp = new Array(nums.length).fill(0);
  let dpmin = new Array(nums.length).fill(0);
  let maxC = nums[0];
  dp[0] = nums[0];
  dpmin[0] = nums[0];
  for (let i = 1; i < nums.length; i++) {
    dp[i] = Math.max(dp[i-1]*nums[i], Math.max(dpmin[i-1]*nums[i], nums[i]));
    dpmin[i] = Math.min(dpmin[i-1]*nums[i], Math.min(dp[i-1]*nums[i], nums[i]));
    maxC = Math.max(maxC, Math.max(dp[i], dpmin[i]));
  }
  return maxC;
};
// 【优化版】空间复杂度低
var maxProduct = function(nums) {
  let max = nums[0];
  let min = nums[0];
  let maxC = nums[0];
  for (let i = 1; i < nums.length; i++) {
    let lastMax = max;
    let lastMin = min;
    max = Math.max(lastMax*nums[i], Math.max(lastMin*nums[i], nums[i]));
    min = Math.min(lastMin*nums[i], Math.min(lastMax*nums[i], nums[i]));
    maxC = Math.max(maxC, max);
  }
  return maxC;
};

279. 完全平方数(给一个数组,找到组成他的完全平方数个数)

输入:n = 12
输出:3
解释:12 = 4 + 4 + 4

  1. 【动态规划】解题思路
  • 从0…n找到每个数字的完全平方数和个数,通过dp存储
  • 找第i个元素的完全平方个数时,从j=1开始寻找,需要取若干数的平方,构成i-j*j
var numSquares = function(n) {
    const f = new Array(n + 1).fill(0);
    for (let i = 1; i <= n; i++) {
        let minn = Number.MAX_VALUE;
        for (let j = 1; j * j <= i; j++) {
            minn = Math.min(minn, f[i - j * j]);
        }
        f[i] = minn + 1;
    }
    return f[n];
};

64. 最小路径和

输入:grid = [[1,3,1],[1,5,1],[4,2,1]]
输出:7
解释:因为路径 1→3→1→1→1 的总和最小。
在这里插入图片描述

  1. 【动态规划】解题思路
  • 每次更新当前位置的最小路径和
var minPathSum = function(grid) {
    for(let i = 0; i < grid.length; i++) {
        for (let j = 0; j < grid[0].length; j++) {
            if (i === 0 && j === 0) continue;
            else if (i === 0) grid[i][j] = grid[i][j-1]+grid[i][j];
            else if (j === 0) grid[i][j] = grid[i-1][j]+grid[i][j];
            else grid[i][j] = Math.min(grid[i-1][j], grid[i][j-1])+grid[i][j];
        }
    }
    return grid[grid.length-1][grid[0].length-1];
};

62. 不同路径总数

在这里插入图片描述

var uniquePaths = function(m, n) {
  let dp = new Array(m);

  for (let i = 0; i < m; i++) {
    dp[i] = new Array(n).fill(1);
  }
  for (let i = 0; i < m; i++) {
    for (let j = 0; j < n; j++) {
      if (i > 0 && j > 0) dp[i][j] = dp[i-1][j] + dp[i][j-1];
    }
  }
  return dp[m-1][n-1];
};

5. 最长回文子串

输入:s = “babad”
输出:“bab”
解释:“aba” 同样是符合题意的答案。

  1. 【中心扩散】解题思路
  • 以每个元素为中心,去向两边扩散,如果两边是相同的元素,则一直向外扩散
  • 找到长度最大子串
var longestPalindrome = function(s) {
    if (s.length<2){
          return s
      }
      let l=0;
      let r=0
      for (let i = 0; i < s.length; i++) {
          // 回文子串长度是奇数
          helper(i, i)
          // 回文子串长度是偶数
          helper(i, i + 1) 
      }

      function helper(m, n) {
          while (m >= 0 && n < s.length && s[m] == s[n]) {
              m--
              n++
          }
          // 注意此处m,n的值循环完后  是恰好不满足循环条件的时刻 
          // 如果此轮询得到回文串长度大于之前记录, 记录此轮循边界
          if (n - m - 1 > r-l-1) {
            r = n
            l = m
          }
      }
      return s.slice(l+1, r)
};
console.log(longestPalindrome('cbbd'))

1143. 最长公共子序列

  1. 【动态规划】解题思路
  • 构建一个二维数组,[i][j]表示text11的0…i 与 text2的0…j的最长公共子序列
  • 当text1[i-1] == text2[j-1]时,说明当前字符可以加入到最长公共子序列中,因此dp[i][j] = dp[i-1][j-1] + 1。
  • 当text1[i-1] != text2[j-1]时,说明当前字符不可能存在于最长公共子序列中,因此需要考虑text1前i-1个字符和text2前j个字符的最长公共子序列以及text1前i个字符和text2前j-1个字符的最长公共子序列,取其中的最大值,即dp[i][j] = max(dp[i-1][j], dp[i][j-1])。
var longestCommonSubsequence = function(text1, text2) {
    const m = text1.length, n = text2.length;
    const dp = new Array(m + 1).fill(0).map(() => new Array(n + 1).fill(0));
    for (let i = 1; i <= m; i++) {
        const c1 = text1[i - 1];
        for (let j = 1; j <= n; j++) {
            const c2 = text2[j - 1];
            if (c1 === c2) {
                dp[i][j] = dp[i - 1][j - 1] + 1;
            } else {
                dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
            }
        }
    }
    return dp[m][n];
};

【回溯法】

回溯法:它通过在解空间中构建解的过程中探索所有可能的选择,并在发现某个选择不符合问题的约束条件时,撤回上一步的选择,即“回溯”,然后继续尝试其他可能的路径。
时间复杂度通常较高
深度优先搜索(DFS)的方式遍历解空间树:

46.数字的随机全排列([1,2,3]随机排列[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]])

  1. 【回溯法】解题思路
  • 固定第一个位置,排列后面的元素
  • 递归调用函数,重新排列
function backtrack(list, temp, nums) {
    if (temp.length === nums.length) {
        return list.push([...temp])
    }
    for (let i = 0; i < nums.length; i++) {
        if (temp.includes(nums[i])) continue;
        temp.push(nums[i]);
        backtrack(list, temp, nums);
        temp.pop();
    }
}

var permute = function(nums) {
    let list = [];
    backtrack(list, [], nums)
    return list;
};

console.log(permute([1,2,3])) 
// [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

17. 电话号码的字母组合

在这里插入图片描述

function backtrack(list, temp, digits, ii) {
  if (digits.length === 0) {
    return [];
  }
  else if (temp.length === digits.length) {
    return list.push(temp.join(''));
  }
  for (let i = ii; i < digits.length; i++) {
    if (obj[digits[i]].includes(temp[i])) continue;
    for (let j = 0; j < obj[digits[i]].length; j++) {
      
      temp.push(obj[digits[i]][j]);
      console.log(temp)
      backtrack(list, temp, digits, i+1);
      temp.pop();
    }
  }
}
let obj = {
    2: ['a', 'b', 'c'],
    3: ['d', 'e', 'f'],
    4: ['g', 'h', 'i'],
    5: ['j', 'k', 'l'],
    6: ['m', 'n', 'o'],
    7: ['p', 'q', 'r', 's'],
    8: ['t', 'u', 'v'],
    9: ['w', 'x', 'y', 'z'],
};
var letterCombinations = function(digits) {
    digits = digits.split('');
    let list = [];
    backtrack(list, [], digits, 0)
    return list;
};
console.log(letterCombinations('23')); 
// ["ad","ae","af","bd","be","bf","cd","ce","cf"]

22. 生成有效括号

输入:3
输出:[“((()))”,“(()())”,“(())()”,“()(())”,“()()()”]

  1. 【回溯法】解题思路
  • 通过二叉树的深度优先遍历算法,将所有可能性全列举出来
  • 再进行剪枝(增加条件约束,左括号个数不能大于n || 右括号个数不能大于左括号)
/**
 * @param {number} n
 * @return {string[]}
 */
function dfs(list, path, n, open, close) {
  if (open > n || close > open) return;
  if (path.length === 2*n) { // n个有效括号,括号总数为2*n个
    list.push(path);
    return;
  }
  dfs(list, path+'(', n, open+1, close);
  dfs(list, path+')', n, open, close+1);
}
var generateParenthesis = function(n) {
  let list = [];
  let open = 0; // 左括号个数
  let close = 0; // 右括号个数
  dfs(list, '', n, open, close);
  return list;
};

79. 单词搜索

在这里插入图片描述

输入:board = [[“A”,“B”,“C”,“E”],[“S”,“F”,“C”,“S”],[“A”,“D”,“E”,“E”]], word = “ABCCED”
输出:true

  1. 【回溯法】解题思路(时间复杂度较高)
  • 额外维护了一个数组,表示当前元素被使用过,防止重复使用
  • 定义了一个方向数组,表示当前坐标应该向左、向下、向右、向上移动
/**
 * @param {character[][]} board
 * @param {string} word
 * @return {boolean}
 */
function backtrack(temp, board, word, i, j, used) {
  if (used[i][j]) return;
  if (temp.length === word.length) {
    return temp === word;
  }
  used[i][j] = true;
  for (let m = 0; m < direction.length; m++) {
    let ii = i + direction[m][0];
    let jj = j + direction[m][1];
    if (ii >= 0 && ii < board.length && jj >= 0 && jj < board[0].length) {
      if(backtrack(temp+board[ii][jj], board, word, ii, jj, used)){
        return true;
      }
    }
  }
  used[i][j] = false;
  return false;
}
// 向左、向下、向右、向上
let direction = [[0,-1],[1,0],[0,1],[-1,0]]
var exist = function(board, word) {
  let used = new Array(board.length);    // 二维矩阵used,存放bool值
  for (let i = 0; i < board.length; i++) {
      used[i] = new Array(board[0].length).fill(false);
  }
  for (let i = 0; i < board.length; i++) {
    for (let j = 0; j < board[0].length; j++) {
      if (board[i][j] === word[0]) {
        if (backtrack(board[i][j], board, word, i, j, used)) return true;
      }
    }
  } 
  return false;
};
  1. 【回溯法】解题思路(优化版)
  • 维护一个变量,表示找到了第几个元素
  • 将当前元素存起来,暂设为*,防止下次dfs的时候使用
  • 再通过i,j指针向左、下、右、上dfs,需要处理边界 + 判断下一个元素是否为word对应的元素
/**
 * @param {character[][]} board
 * @param {string} word
 * @return {boolean}
 */
var exist = function(board, word) {
  let res = false;
  let m = board.length;
  let n = board[0].length;
  let depth = 0;

  let dfs = function (depth, board, word, i, j) {
    if (depth === word.length-1) {
      res = true;
      return;
    }
    console.log(depth, i, j, board[i][j])
    let tmp = board[i][j];
    board[i][j] = '*' // 标记当前元素为使用过
     // 向左
    if (j > 0 && board[i][j-1] === word[depth+1])
      dfs(depth+1, board, word, i, j-1);
     // 向下
    if (i+1 < m && board[i+1][j] === word[depth+1])
      dfs(depth+1, board, word, i+1, j);
     // 向右
    if (j+1 < n && board[i][j+1] === word[depth+1])
      dfs(depth+1, board, word, i, j+1);
     // 向上
    if (i > 0 && board[i-1][j] === word[depth+1])
      dfs(depth+1, board, word, i-1, j);
    board[i][j]= tmp;
  }

  for (let i = 0; i < m; i++) {
    for (let j = 0; j < n; j++) {
      if (board[i][j] === word[0]) {
        dfs(depth, board, word, i, j)
        if (res) return true;
      }
    }
  } 
  return false;
};

78. 子集

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

  1. 【回溯法】解题思路
var subsets = function(nums) {
  let result = [[]];
  // k表示当前元素的下标
  function dfs(temp, k) {
    if (k === nums.length) {
      return;
    }
    for (let i = k; i < nums.length; i++) {
      temp.push(nums[i]);
      result.push([...temp]);
      dfs(temp, i+1);
      temp.pop(); 
    }
  }
  dfs([], 0)
  return result;
};

39. 组合总和

输入:candidates = [2,3,6,7], target = 7
输出:[[2,2,3],[7]]
解释:
2 和 3 可以形成一组候选,2 + 2 + 3 = 7 。注意 2 可以使用多次。
7 也是一个候选, 7 = 7 。

// temp存放的是当前所有元素,sum存放的是元素之和
function dfs(list, candidates, temp, sum, target, k) {
  if (sum === target) {
    console.log('yes!')
    list.push([...temp]);
    return;
  }
  else if (sum > target) {
    return;
  }
  for (let i = 0; i < candidates.length; i++) {
    if (candidates[i]+sum > target) return; 
    if (temp[temp.length-1] < candidates[i]) return;
    temp.push(candidates[i]);
    sum += candidates[i];
    dfs(list, candidates, temp, sum, target, k);
    temp.pop();
    sum -= candidates[i];
  }
}
var combinationSum = function(candidates, target) {
  let result = [];
  let arr = candidates.sort(function(a, b){return a - b}); 
  dfs(result, arr, [], 0, target, 0);
  return result;
};

【矩阵】

73. 矩阵置零

有0元素当前行和当前列都置为0
在这里插入图片描述

  1. 【矩阵】解题思路
  • 维护两个数组,分别表示当前行的第几个元素为0,当前列第几个元素为0
  • 遍历数组时,只要当前行或者当前列为0,那么就将该元素置为0
var setZeroes = function(matrix) {
  let m = matrix.length;
  let n = matrix[0].length;
  let row = new Array(m).fill(false);
  let col = new Array(n).fill(false);
  for (let i = 0; i < m; i++) {
    for (let j = 0; j < n; j++) {
      if (matrix[i][j] === 0) {
        row[i] = true;
        col[j] = true;
      }
    }
  }
  for (let i = 0; i < m; i++) {
    for (let j = 0; j < n; j++) {
      if (row[i] || col[j]) {
        matrix[i][j] = 0
      }
    }
  }
  return matrix;
};

54. 螺旋数组

将二维数组螺旋输出
在这里插入图片描述

  1. 【矩阵】解题思路
  • 定义上下左右四个边界,不算改变这四个边界的值进行想右、下、左、上遍历
  • 当有上下边界或者左右边界相遇的时候就说明已经遍历结束,跳出循环
var spiralOrder = function(matrix) {
    let result = [];
    let l = 0, r = matrix[0].length-1, t = 0, b = matrix.length-1;
    let x = 0;
    while(true) { 
      // 向右
      for (let i = l; i <= r; i++) {
        result[x++] = matrix[t][i];
      }
      if (++t > b) break;
      // 向下
      for (let i = t; i <= b; i++) {
        result[x++] = matrix[i][r];
      }
      if (--r < l) break;
      // 向左
      for (let i = r; i >= l; i--) {
        result[x++] = matrix[b][i];
      }
      if (--b < t) break;
      // 向上
      for (let i = b; i >= t; i--) {
        result[x++] = matrix[i][l];
      }
      if (++l > r) break;
    }

    return result.filter(item => item !== undefined);
};

48. 旋转图像

在这里插入图片描述

  1. 【矩阵】解题思路
  • 先顺时针移动,先分为左上角、右上角、左下角、右下角
    在这里插入图片描述
  • 分别调换这四个元素的位置,然后在移动下一个
  • 边界条件为Math.floor(m/2)
// 空间复杂度O(1)
var rotate = function(matrix) {
  let m = matrix.length;
  for (let i = 0; i < Math.floor(m/2); i++) {
    for (let j = 0; j < Math.floor((m+1)/2); j++) {
      let temp = matrix[i][j];
      matrix[i][j] = matrix[m-1-j][i];
      matrix[m-1-j][i] = matrix[m-1-i][m-1-j];
      matrix[m-1-i][m-1-j] = matrix[j][m-1-i];
      matrix[j][m-1-i] = temp;
    }
  }
};
// 空间复杂度o(n*n)
var rotate = function(matrix) {
  let m = matrix.length;
  let n = matrix[0].length;
  let result = new Array(m);
  for (let i = 0; i < m; i++) {
    result[i] = new Array(n).fill(-1);
  }
  for (let i = 0; i < m; i++) {
    for (let j = 0; j < matrix[0].length; j++) {
      result[j][n-1] = matrix[i][j];
    }
    n--;
  }
  return result;
};

240. 搜索二维矩阵

  1. 【矩阵】解题思路
  • 从矩阵的右上角开始寻找
  • 如果找到元素,直接结束循环
  • 当前元素 > target,则表示应该向左移动
  • 当前元素 > target,则表示应该向下移动
var searchMatrix = function(matrix, target) {
  let i = 0;
  let j = matrix[0].length-1;
  while(i < matrix.length && j >= 0) {
    if (matrix[i][j] === target) {
      return true;
    }
    if (matrix[i][j] > target) {
      j--;
    }
    else if (matrix[i][j] < target) {
      i++;
    }
  }
  return false;
};

【链表】

160. 相交链表

输入:listA = [4,1,8,4,5], listB = [5,6,1,8,4,5]
输出:8

  1. 解题思路
var getIntersectionNode = function(headA, headB) {
    if (headA === null || headB === null) {
        return null;
    }
    let pA = headA, pB = headB;
    while (pA !== pB) {
        pA = pA === null ? headB : pA.next;
        pB = pB === null ? headA : pB.next;
    }
    return pA;
};

206. 反转链表

输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]

var reverseList = function(head) {
	let temp = null;
	let curr = head;
	while (curr) {
		let next = curr.next;
		curr.next = temp;
		temp = curr;
		curr = next;
	}
	return temp;
}

234. 回文链表

输入:head = [1,2,3,4,5]
输出:true

  1. 【字符串】解题思路
  • 定义两个字符串,一个表示正向字符串’12345’,另一个表示反转字符串’54321’,如果是回文串,ab则完全相同,比如123321,a和b都是’123321’
var isPalindrome = function(head) {
	let a = ''; // 表示正常字符串
	let b = ''; // 反转字符串
	while (head) {
		a = a + head.val;
		b = head.val + b;
		head = head.next;
	}
	return a === b;
}

141. 环形链表

输出是否有环

var hasCycle = function(head) {
    // 哈希表
    // let mp = new Map();
    // while (head) {
    //     if (mp.get(head)) {
    //         return true;
    //     }
    //     mp.set(head, true);
    //     head = head.next;
    // }
    // return false;
    // 快慢指针
    let fast = head;
    let low = head;
    while (fast) {
        if (fast.next === null) return false;
        low = low.next;
        fast = fast.next.next;
        if (low === fast) return true;
    }
    return false
};

142. 环形链表 II

输出环的位置

var detectCycle = function(head) {
    // 快慢指针
    let slow = head, fast = head;
    while (fast) {
        if (fast.next === null) return null;
        slow = slow.next;
        fast = fast.next.next;
        if (fast === slow) {
            let ptr = head;
            while (ptr !== slow) {
                ptr = ptr.next;
                slow = slow.next;
            }
            return ptr;
        }
    }
    return null;
};

2. 两数相加

var addTwoNumbers = function(l1, l2) {
    let node = new ListNode('0');
    let p1 = node;
    let count = 0; // 表示进位
    let sum = 0;
    while(count || l1 || l2) {
        const n1 = l1 ? l1.val : 0;
        const n2 = l2 ? l2.val : 0;
        sum = n1 + n2 + count;
        p1.next = new ListNode(sum % 10);
        count = sum >= 10 ? 1 : 0;
        p1 = p1.next;
        if (l1) l1 = l1.next 
        if (l2) l2 = l2.next 
    }
    return node.next;
};

【普通数组】

53. 最大连续子数组和

输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。

  1. 【动态规划】解题思路
  • 第i个位置的解来自于 【当前值 || 是当前值+之前所有的和】 的最大值
var maxSubArray = function(nums) {
 let pre = 0; // 存放之前的和
 let max = nums[0];
 for (let i = 0; i < nums.length; i++) {
   pre = Math.max(nums[i]+pre, nums[i]);
   max = Math.max(max, pre);
 }
 return max;
};

6. 合并重叠区间

输入:intervals = [[1,3],[2,6],[8,10],[15,18]]
输出:[[1,6],[8,10],[15,18]]
解释:区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].

  1. 解题思路:
  • 先对数组进行左端点排序
  • pre存放的是
var merge = function(intervals) {
  let result = [];
  let pre = intervals[0];
  // 左端点排序
  intervals.sort((a, b) => a[0]-b[0]);
  
  for (let i = 1; i < intervals.length; i++) {
    let cur = intervals[i];
    // 有重叠,更新右边界
    if (pre[1] >= cur[0]) {
      pre[1] = Math.max(pre[1], cur[1]);
    }
    // 不满足条件后,就将pre推出数组,同时更新pre
    else {
      result.push(pre);
      pre = cur;
    }
  }
  result.push(pre);
  return result;
};

189. 轮转数组

输入: nums = [1,2,3,4,5,6,7], k = 3
输出: [5,6,7,1,2,3,4]
解释:
向右轮转 1 步: [7,1,2,3,4,5,6]
向右轮转 2 步: [6,7,1,2,3,4,5]
向右轮转 3 步: [5,6,7,1,2,3,4]

var rotate = function(nums, k) {
  k = k % nums.length; // 数组的后k项会移到前面
  reverse(nums, 0, nums.length-1);
  reverse(nums, 0, k-1);
  reverse(nums, k, nums.length-1);
  return nums;
};
// 将元素从下标为k的地方分割,分别进行轮转
function reverse(nums, start, end) {
  while(start<end){
    let temp = nums[start];
    nums[start] = nums[end];
    nums[end] = temp;
    start++;
    end--;
  }
}
console.log(rotate([1,2,3,4,5,6,7], 3))

238. 除自身以外的数组乘积

输入: nums = [1,2,3,4]
输出: [24,12,8,6]

  1. 解题思路
  • 将元素左边所有元素都乘一遍
  • 将元素右边的right * 左边的乘积
var productExceptSelf = function(nums) {
  let result = new Array(nums.length).fill(1);
  // 将当前i元素左边的元素都乘一遍
  for (let i = 1; i < nums.length; i++) {
    result[i] = result[i-1]*nums[i-1];
  }
  // 将当前元素右边乘起来,放在right中存储
  // 当前元素i等于左边result[i]的✖️右边right
  let right = 1;
  for (let i = nums.length-1; i >= 0; i--) {
    result[i] = right*result[i];
    right = right*nums[i]
  }
  return result;
};

【技巧】

123. 只出现一次的数字【异或】

除了某个元素只出现一次以外,其余每个元素均出现两次
输入:nums = [2,2,1]
输出:1

异或:按位异或
(1) 0^0=00^1=1 0异或任何数=任何数
(2) 1^0=11^1=0 1异或任何数-任何数取反
(3) 任何数 ^ 自己=把自己置0

  1. 【按位异或】解题思路
  • 将每个元素进行异或,如果相同就变为0,0和剩下的那个元素异或为自己
  • 所以这个办法只能解决其余每个元素均出现两次的情况,变换题目,此方法不适用
function singleNumber(nums) {
  let single = 0;
  for (let num of nums) {
      single ^= num;
  }
  return single;
}

【栈】

739. 每日温度

输入: temperatures = [73,74,75,71,69,72,76,73]
输出: [1,1,4,2,1,1,0,0]
answer[i] 是指对于第 i 天,下一个更高温度出现在几天后

  1. 【单调栈】解题思路
  • 定一个栈,保存的是每个温度的下标
    • 当前元素 > 栈顶元素,需要进行出栈,同时更新result结果,结果为:当前元素的下标i - 栈顶元素(栈存放的是下标)
    • 当前元素 < 栈顶元素,一直入栈下标
var dailyTemperatures = function(temperatures) {
  let result = new Array(temperatures.length).fill(0)
  let stack = [];
  for (let i = 0; i < temperatures.length; i++) {
    while(stack.length && temperatures[stack[stack.length-1]] < temperatures[i]) {
      let top = stack.pop();
      result[top] = i-top;
    }
    stack.push(i);
    console.log(stack)
  }
  return result;
};

394. 字符串解码

输入:s = “3[a2[c]]”
输出:“accaccacc”

  1. 【栈】解题思路
  • 两个栈,一个存储重复次数,另一个存储字母
  • 字母或者数字的情况都是用result和count暂时记录
  • count和result都会记录上一次的结果,当出现[表示需要进行记录,就将result和count暂时存入栈中,并置空
  • 当出现],就需要开始出栈重复连接字母
var decodeString = function(s) {
  let str_stack = [];
  let num_stack = [];
  let result = '';
  let count = '';
  for (let i = 0; i < s.length; i++) {
    let c = s[i];
    if (!isNaN(c)) { // 数字,*10是为了兼容10[a]或100[a]这种情况
      count = count*10 + Number(c);
    }
    else if (c === '[') {
      str_stack.push(result);
      result = '';
      num_stack.push(count);
      count = 0;
    }
    else if (c === ']') {
      let times = num_stack.pop();
      result = str_stack.pop() + result.repeat(times);
    }
    else {
      result += c;
    }
  }
  return result;
};

155. 最小栈

var MinStack = function() {
  this.stack = [];
  this.min_stack = [Infinity]; // 在每个元素入栈时,都记录当前栈最小值
};
MinStack.prototype.push = function(val) {
  let min = this.min_stack[this.min_stack.length-1];
  if (val < min) {
    this.min_stack.push(val);
  } else {
    this.min_stack.push(min);
  }
  this.stack.push(val);
};
MinStack.prototype.pop = function() {
  this.stack.pop();
  this.min_stack.pop();
};
MinStack.prototype.top = function() {
  return this.stack[this.stack.length-1];
};
MinStack.prototype.getMin = function() {
  let min = this.min_stack[this.min_stack.length-1];
  return min;
};

20. 有效括号

输入:s = “()[]{}”
输出:true

var isValid = function(s) {
    const n = s.length;
    if (n % 2 === 1) {
        return false;
    }
    const pairs = new Map([
        [')', '('],
        [']', '['],
        ['}', '{']
    ]);
    const stk = [];
    for (let ch of s){
        if (pairs.has(ch)) {
            if (!stk.length || stk[stk.length - 1] !== pairs.get(ch)) {
                return false;
            }
            stk.pop();
        } 
        else {
            stk.push(ch);
        }
    };
    return !stk.length;
};

【堆】

215. 数组中第K大的元素

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

  • 建立大顶堆
  • 调整大顶堆,进行排序
var findKthLargest = function(nums, k) {
  // 建堆
  let len = nums.length;
  for(let i = Math.floor(len/2-1); i >=0; i--) {
    heapSort(nums, i, len);
  }
  console.log('====')
  // 调整堆
  for(let i = len-1; i > 0; i--) {
    swap(nums, 0, i);
    heapSort(nums, 0, i);
  }
  return nums[len-k];
};
function heapSort(A, i, len) {
  let temp = A[i];
  for (let j = 2*i+1; j < len; j = 2*j+1) {
    temp = A[i];
    if(j+1 < len && A[j] < A[j+1]) {
      j++;
    }
    if(temp < A[j]) {
      swap(A, i, j);
      i = j;
    } else {
      break;
    }
  }
}
function swap(A, i, j) {
  let temp = A[i];
  A[i] = A[j];
  A[j] = temp;
}

347. 前K个高频元素

给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。
输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]

let topKFrequent = function(nums, k) {
    let map = new Map(), arr = [...new Set(nums)]
    nums.map((num) => {
        if(map.has(num)) map.set(num, map.get(num)+1)
        else map.set(num, 1)
    })
    
    // 如果元素数量小于等于 k
    if(map.size <= k) {
        return [...map.keys()]
    }
    
    return bucketSort(map, k)
};

// 桶排序
let bucketSort = (map, k) => {
    let arr = [], res = []
    map.forEach((value, key) => {
        // 利用映射关系(出现频率作为下标)将数据分配到各个桶中
        if(!arr[value]) {
            arr[value] = [key]
        } else {
            arr[value].push(key)
        }
    })
    console.log(arr, 'arr')
    // 倒序遍历获取出现频率最大的前k个数
    for(let i = arr.length - 1;i >= 0 && res.length < k;i--){
        if(arr[i]) {
            res.push(...arr[i])
        }
	}
	return res
}

【二叉树】

二叉树前、中、后序遍历,递归和非递归,DFS与BFS

94. 二叉树的中序遍历

var inorderTraversal = function(root) {
    let result = [];
    let dfs = function (root) {
        if (!root) return;
        dfs(root.left);
        result.push(root.val)
        dfs(root.right);
    }
    dfs(root)
    return result;
};

104. 二叉树的最大深度

在这里插入图片描述

var maxDepth = function(root) {
    if (!root) return 0;
    let left = maxDepth(root.left);
    let right = maxDepth(root.right);
    return Math.max(left, right)+1;
};

226. 翻转二叉树

在这里插入图片描述

var invertTree = function(root) {
    if (!root) return root;
    let temp = root.left;
    root.left = root.right;
    root.right = temp;
    invertTree(root.left);
    invertTree(root.right);
    return root;
};

101. 对称二叉树

var isSymmetric = function(root) {
    if (!root) return true;
    let func = function(l, r) {
        if (!l && !r) { return true; }
        if (l && r && l.val === r.val && func(l.left, r.right) && func(l.right, r.left))
            return true;
        return false;
    }
    return func(root.left, root.right);
};

543. 二叉树的直径

在这里插入图片描述

  1. 【DFS】解题思路
  • 从根节点出发,当前路径为 左节点深度+右节点深度+1
  • 每个节点深度为左右子树最大深度
  • 最后结果需要-1,是因为需要减去根节点
var diameterOfBinaryTree = function(root) {
    // 默认为 1(根节点)
    let ans = 1;
    
    function depth(rootNode){
        if(!rootNode){
            return 0;
        }
        // 递归获取 左/右 子树的深度(节点数)
        let left = depth(rootNode.left);
        let right = depth(rootNode.right);
        // 最长路径(节点数)
        ans = Math.max(ans, left + right + 1);
        // 以根节点为数的最大深度
        return Math.max(left, right) + 1;
    }

    depth(root);
    // 节点数 - 1
    return ans - 1;
};

102. 二叉树的层序遍历

// BFS
var levelOrder = function(root) {
    if (root === null) return [];
    const result = [];
    const queue = [root];

    while (queue.length > 0) {
        const levelSize = queue.length; // 当前层的节点数
        const currentLevel = [];

        for (let i = 0; i < levelSize; i++) {
            const node = queue.shift(); // 取出队列中的第一个节点
            currentLevel.push(node.val);
            // 将当前节点的左右子节点加入队列中
            if (node.left !== null) queue.push(node.left);
            if (node.right !== null) queue.push(node.right);
        }
        result.push(currentLevel); // 将当前层的值加入结果数组
    }
    return result;
};

108. 将有序数组转换为平衡二叉搜索树

在这里插入图片描述

var sortedArrayToBST = function(nums) {
    // 因为涉及到递归,所以必然会有数组为空的情况
    if(!nums.length) {
        return null;
    }

    // 找到序列中点:
    const headIndex = Math.floor(nums.length / 2);

    // 实例化节点头部
    const head = new TreeNode(nums[headIndex]);
    let left = headIndex - 1;
    let right = headIndex + 1;
    // 因为是有序升序列表,则当前头部索引的左侧应该都在树的左子树,同理右子树
    if(left >=0) {
        // 左侧有节点,对左侧节点递归,形成左子树
        head.left = sortedArrayToBST(nums.slice(0, headIndex));
    }
    if(right < nums.length) {
        // 右侧有节点,对右侧节点递归,形成右子树
        head.right = sortedArrayToBST(nums.slice(right));
    }
    // 返回节点
    return head;
};

98. 验证二叉搜索树

  1. 【中序遍历】解题思路
  • 中序遍历,因为搜索二叉树中序遍历一定是一个递增的序列
  • 定义一个临时变量存储上一次的值,进行对比,只要不满足那就不是二叉搜索树
var isValidBST = function(root) {
    let result = false;
    let pre = Number.MIN_SAFE_INTEGER; // 记录上一次的值
    // 中序遍历,因为搜索二叉树中序遍历一定是一个递增的序列
    let dfs = function(root) {
        if(!root) return true;
        if (!dfs(root.left) || root.val <= pre) return false;
        pre = root.val;
        return dfs(root.right);
    }
    return dfs(root);
};

230. 二叉搜索树中第K小的元素

二叉搜索树具有如下性质:

  • 结点的左子树只包含小于当前结点的数。
  • 结点的右子树只包含大于当前结点的数。
  • 所有左子树和右子树自身必须也是二叉搜索树。
  1. 【中序遍历】解题思路
  • 根据二叉搜索树的特点,且中序遍历,那么值一定是递增的,所以就可以得到第k小的元素
var kthSmallest = function(root, k) {
    let result;
    let dfs = function(root) {
        if (!root) return;
        dfs(root.left);
        if (k === 1) result = root.val;
        k--;
        dfs(root.right);
    }
    dfs(root)
    return result;
};

二叉树的右视图

在这里插入图片描述

  1. 【BFS】解题思路
  • 利用广度优先遍历,按层遍历二叉树,从右向左遍历
  • 使用一个标记标记该层是否遍历,因为同一层只需要最右边的元素
var rightSideView = function(root) {
  if (!root) return [];
  if(!root.left && !root.right) return [root.val];
  let result  = [];
  const queue = [root];
  // BFS
  while (queue.length > 0) {
    // 记录每层是否已经遍历过
    let sign = true;
    const levelSize = queue.length; // 当前层的节点数
    console.log('levelSize=', levelSize)
    for (let i = 0; i < levelSize; i++) {
      const node = queue.shift(); // 取出队列中的第一个节点
      if (sign && node) {
        result.push(node.val);
        sign = false;
      }
      // 将当前节点的左右子节点加入队列中
      if (node.right) queue.push(node.right);
      if (node.left) queue.push(node.left);
    }
  }
  return result;
};

二叉树展开为链表

在这里插入图片描述

  1. 【前序遍历】空间复杂度较高,定义数组存储
  • 先将元素节点都按照前序遍历放入数组
  • 再遍历数组,重新构造树,将左节点置为null,右节点为数组下一个元素
var flatten = function(root) {
  let list = [];
  // 前序遍历
  let dfs = function(root) {
    if (root && root.val !== null) {
      list.push(root);
      dfs(root.left);
      dfs(root.right);
    }
  }
  dfs(root);
  for (let i = 1; i < list.length; i++) {
      const prev = list[i - 1], curr = list[i];
      prev.left = null;
      prev.right = curr;
  }
  return root;
};
  1. 【前序遍历】空间复杂度较低,在树的基础上直接移动
  • 先用两个变量把原先的左右子树保存起来
  • 将左子树作为右子树,根节点的左子树置为null
  • 将原先的右子树接到当前右子树的末端
var flatten = function (root) {
  if (root == null) return;
  flatten(root.left);
  flatten(root.right);
  // 1、左右子树已经被拉平成一条链表
  // 先用两个变量把原先的左右子树保存起来
  let left = root.left;
  let right = root.right;
  // 2、将左子树作为右子树
  root.left = null;
  root.right = left;
  // 3、将原先的右子树接到当前右子树的末端
  while (root.right != null) {
    root = root.right;
  }
  root.right = right;
  return root;
};

从前序与中序遍历序列构造二叉树

  1. 【前序+中序】解题思路
  • 前序遍历,根、左、右,所以出现的第一个元素一定是根元素
  • 中序遍历,左、根、右,第一个元素是左孩子
var buildTree = function(preorder, inorder) {
    if (preorder.length === 0) return null;
    // 通过根节点的位置拿到左子树的大小
    let leftSize = inorder.indexOf(preorder[0]);
    // 左子树的前序遍历结果
    let leftNodePre = preorder.slice(1, leftSize+1);
    // 左子树的中序遍历结果
    let leftNodeIn = inorder.slice(0, leftSize);
    // 右子树的前序遍历结果
    let rightNodePre = preorder.slice(leftSize+1);
    // 右子树的中序遍历结果
    let rightNodeIn = inorder.slice(leftSize+1);
    let leftTree = buildTree(leftNodePre, leftNodeIn);
    let rightTree = buildTree(rightNodePre, rightNodeIn);
    return new TreeNode(preorder[0], leftTree, rightTree);
};

437. 路径总和 III

  1. 【DFS】解题思路
  • 从根节点出发,分别遍历左、右节点
  • 对左右节点减去当前val,继续递归
let pathSum = function(root, targetSum) {
    if (!root) return 0;
    let page = func(root,targetSum);
    let sum1 = pathSum(root.left, targetSum);
    let sum2 = pathSum(root.right, targetSum);
    return page + sum1 +sum2;
};
let func = function(root, targetSum) {
  if (!root) return 0;
  let ret = 0;
  if (root.val === targetSum) ret = 1;
  ret += func(root.left, targetSum-root.val);
  ret += func(root.right, targetSum-root.val);
  return ret;
}

236. 二叉树的最近公共祖先

在这里插入图片描述

var lowestCommonAncestor = function (root, p, q) {
  if (!root) {
    return null;
  }
  if (root === p || root === q) {
    return root;
  }
  const left = lowestCommonAncestor(root.left, p, q);
  const right = lowestCommonAncestor(root.right, p, q);
  // p和q在左右子树
  if (left && right) {
    return root;
  }
  // p和q在左子树
  else if (left) {
    return left;
  }
  // p和q在右子树
  else {
    return right;
  }
};

124. 二叉树中的最大路径和

const maxPathSum = (root) => {
    let maxSum = Number.MIN_SAFE_INTEGER; // 最大路径和

    const dfs = (root) => {
        if (root == null) { // 遍历到null节点,收益0
           return 0;
        }
        const left = dfs(root.left);   // 左子树提供的最大路径和
        const right = dfs(root.right); // 右子树提供的最大路径和

        const innerMaxSum = left + root.val + right; // 当前子树内部的最大路径和
        maxSum = Math.max(maxSum, innerMaxSum);      // 挑战最大纪录

        const outputMaxSum = root.val + Math.max(0, left, right); // 当前子树对外提供的最大和

        // 如果对外提供的路径和为负,直接返回0。否则正常返回
        return outputMaxSum < 0 ? 0 : outputMaxSum;
    };

    dfs(root);  // 递归的入口

    return maxSum; 
};
  • 19
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值