【哈希】
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”]]
- 【把每个字母重排列】(时间复杂度高)
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);
};
- 【素数乘积唯一】(优化版)
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. 接雨水
- 【动态规划】解题思路:
- 每个位置能接到的雨水为,两边高度的最小值减去当前的值
- 定义一个数组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
- 【双指针】解题思路
- 数组首尾各定义一个指针
- 找到左边的最大值,且同时计算当前元素与最大值的差,就是可以接到雨水的面积
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” 的异位词。
- 【滑动窗口】
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]
- 【利用素数乘积唯一】
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 步到达最后一个下标。
- 【贪心算法】解题思路
- 定义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 步到达数组的最后一个位置。
- 【贪心算法】解题思路
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
- 【动态规划】解题思路
- 定义一个数组,存放的是当前数额下,使用的硬币最少
- 遍历硬币数组,找到最少使用硬币数
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” 拼接成
- 【动态规划】解题思路
- 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。
- 【动态规划】解题思路
- 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。
- 【动态规划】解题思路
- 数组维护当前位置的乘积最大值和乘积最小值(负数考虑)
// 【好理解版本】空间复杂度高
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
- 【动态规划】解题思路
- 从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 的总和最小。
- 【动态规划】解题思路
- 每次更新当前位置的最小路径和
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” 同样是符合题意的答案。
- 【中心扩散】解题思路
- 以每个元素为中心,去向两边扩散,如果两边是相同的元素,则一直向外扩散
- 找到长度最大子串
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. 最长公共子序列
- 【动态规划】解题思路
- 构建一个二维数组,[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]])
- 【回溯法】解题思路
- 固定第一个位置,排列后面的元素
- 递归调用函数,重新排列
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
输出:[“((()))”,“(()())”,“(())()”,“()(())”,“()()()”]
- 【回溯法】解题思路
- 通过二叉树的深度优先遍历算法,将所有可能性全列举出来
- 再进行剪枝(增加条件约束,左括号个数不能大于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
- 【回溯法】解题思路(时间复杂度较高)
- 额外维护了一个数组,表示当前元素被使用过,防止重复使用
- 定义了一个方向数组,表示当前坐标应该向左、向下、向右、向上移动
/**
* @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;
};
- 【回溯法】解题思路(优化版)
- 维护一个变量,表示找到了第几个元素
- 将当前元素存起来,暂设为*,防止下次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]]
- 【回溯法】解题思路
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
- 【矩阵】解题思路
- 维护两个数组,分别表示当前行的第几个元素为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. 螺旋数组
将二维数组螺旋输出
- 【矩阵】解题思路
- 定义上下左右四个边界,不算改变这四个边界的值进行想右、下、左、上遍历
- 当有上下边界或者左右边界相遇的时候就说明已经遍历结束,跳出循环
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. 旋转图像
- 【矩阵】解题思路
- 先顺时针移动,先分为左上角、右上角、左下角、右下角
- 分别调换这四个元素的位置,然后在移动下一个
- 边界条件为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. 搜索二维矩阵
- 【矩阵】解题思路
- 从矩阵的右上角开始寻找
- 如果找到元素,直接结束循环
- 当前元素 > 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
- 解题思路
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
- 【字符串】解题思路
- 定义两个字符串,一个表示正向字符串’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 。
- 【动态规划】解题思路
- 第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].
- 解题思路:
- 先对数组进行左端点排序
- 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]
- 解题思路
- 将元素左边所有元素都乘一遍
- 将元素右边的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=0
,0^1=1
0异或任何数=任何数
(2) 1^0=1
,1^1=0
1异或任何数-任何数取反
(3) 任何数 ^ 自己=把自己置0
- 【按位异或】解题思路
- 将每个元素进行异或,如果相同就变为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 天,下一个更高温度出现在几天后
- 【单调栈】解题思路
- 定一个栈,保存的是每个温度的下标
- 当前元素 > 栈顶元素,需要进行出栈,同时更新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”
- 【栈】解题思路
- 两个栈,一个存储重复次数,另一个存储字母
- 字母或者数字的情况都是用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
}
【二叉树】
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. 二叉树的直径
- 【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. 验证二叉搜索树
- 【中序遍历】解题思路
- 中序遍历,因为搜索二叉树中序遍历一定是一个递增的序列
- 定义一个临时变量存储上一次的值,进行对比,只要不满足那就不是二叉搜索树
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小的元素
二叉搜索树具有如下性质:
- 结点的左子树只包含小于当前结点的数。
- 结点的右子树只包含大于当前结点的数。
- 所有左子树和右子树自身必须也是二叉搜索树。
- 【中序遍历】解题思路
- 根据二叉搜索树的特点,且中序遍历,那么值一定是递增的,所以就可以得到第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;
};
二叉树的右视图
- 【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;
};
二叉树展开为链表
- 【前序遍历】空间复杂度较高,定义数组存储
- 先将元素节点都按照前序遍历放入数组
- 再遍历数组,重新构造树,将左节点置为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;
};
- 【前序遍历】空间复杂度较低,在树的基础上直接移动
- 先用两个变量把原先的左右子树保存起来
- 将左子树作为右子树,根节点的左子树置为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;
};
从前序与中序遍历序列构造二叉树
- 【前序+中序】解题思路
- 前序遍历,根、左、右,所以出现的第一个元素一定是根元素
- 中序遍历,左、根、右,第一个元素是左孩子
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
- 【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;
};