top100 (6-10)
top100
6 两数之和
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案,并且你不能使用两次相同的元素。
你可以按任意顺序返回答案。
示例 1:
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
示例 2:
输入:nums = [3,2,4], target = 6
输出:[1,2]
示例 3:
输入:nums = [3,3], target = 6
输出:[0,1]
解法一:双层循环
思路:详情看以下代码
时间复杂度:O(n^2),双层循环
空间复杂度:O(1)
注意:如果这里的 nums 是有序的话,优化嵌套循环,可以考虑 “双指针”
var twoSum = function (nums, target) {for (let i = 0; i < nums.length; i++) {for (let j = i + 1; j < nums.length; j++) {if (nums[i] + nums[j] === target) {return [i, j]}}}
}
解法二:哈希
思路:遍历的同时借助哈希表,记录值和下标
时间复杂度:O(n),最多遍历数组一遍,每次查询哈希表都是O(1)
空间复杂度:O(n),最坏情况下找到数组结尾才找到,其他都加入哈希表,哈希表最长 n - 1
var twoSum = function (nums, target) {let len = nums.lengthif (len === 0) returnlet map = new Map()for (let i = 0; i < len; i++) {const temp = target - nums[i]if (!map.has(temp)) {map.set(nums[i], i) // {2: 0, 7:1}} else {return [map.get(temp), i]}}
};
7 爬楼梯【easy】
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
示例 1:
输入:n = 2
输出:2
解释:有两种方法可以爬到楼顶。
- 1 阶 + 1 阶
- 2 阶
示例 2:
输入:n = 3
输出:3
解释:有三种方法可以爬到楼顶。
3. 1 阶 + 1 阶 + 1 阶
4. 1 阶 + 2 阶
5. 2 阶 + 1 阶
解法一:递归(超时)
思路:满足斐波那契数列公式,最简单的肯定是递归
时间复杂度:O(2^n)
空间复杂度:
function climbStairs(n: number): number {if (n <= 2) return nreturn climbStairs(n - 1) + climbStairs(n - 2)
};
2 循环
var climbStairs = function (n) {if (n <= 2) return nlet n1 = 1let n2 = 1let res = 0for (let i = 2; i <= n; i++) {res = n1 + n2n2 = n1n1 = res}return res
};
3 动态规划
var climbStairs = function (n) {const dp = new Array(n + 1)dp[1] = 1dp[2] = 2for (let i = 3; i <= n; i++) {dp[i] = dp[i - 1] + dp[i - 2]}return dp[n]
};
8 全排列【medium】
给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。
示例 1:
输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
示例 2:
输入:nums = [0,1]
输出:[[0,1],[1,0]]
示例 3:
输入:nums = [1]
输出:[[1]]
解法一:
思路: 建立搜索树,然后遍历
使用递归法遍历
function permute(nums) {// 1. 如何遍历一棵树// var traverse = function(root) {// for(let i=0; i<root.children.length; i++) {// // 前序位置需要的操作// traverse(root.children[i])// // 后序位置需要的操作// }// }// 我们只要在递归之前做出选择,在递归之后撤销刚才的选择,就能正确得到每个节点的选择列表和路径。let res = [];let track = [];let used = new Array(nums.length).fill(false); // [false, false, false]// 比如当前树// 1// | \ \// 2 3 4// 路径:记录在 track 中// 选择列表:nums 中不存在于 track 的那些元素// 结束条件:nums 中的元素全都在 track 中出现const backtrack = (nums, track, used) => {// 触发结束条件if (track.length === nums.length) { // [1,2,3].length === 3res.push(track.slice()); // track.slice()复制数组return;}for (let i = 0; i < nums.length; i++) {// 排除不合法的选择if (used[i]) {// 剪枝,避免重复使用同一个数字continue;}// 做选择track.push(nums[i]);used[i] = true;// 进入下一层决策树backtrack(nums, track, used);// 取消选择track.pop();used[i] = false;}}backtrack(nums, track, used);return res;
}
GPT解法: DFS遍历
var permute = function(nums) {let result = [];let used = new Array(nums.length).fill(false);function dfs(current, path) {if (current.length === nums.length) {result.push([...path]);return;}for (let i = 0; i < nums.length; i++) {if (!used[i]) {used[i] = true;dfs(current.concat(nums[i]), path.concat(nums[i]));used[i] = false;}}}dfs([], []);return result;
};
9 最大子数组和(连续子数组最大和) 【easy】
给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
子数组 是数组中的一个连续部分。
示例 1:
输入:nums = [-2, 1, -3, 4, -1, 2, 1, -5, 4]
输出:6
解释:连续子数组[4, -1, 2, 1] 的和最大,为 6 。
示例 2:
输入:nums = [1]
输出:1
示例 3:
输入:nums = [5, 4, -1, 7, 8]
输出:23
解法一:动态规划
思路:
- 可以用dp数组表示以下标i为终点的最大连续子数组和。
- 每次遇到一个新的数组元素,连续的子数组要么加,上变得更大,要么它本身就更大,因此状态转移为
- dp[i] = max(dp[i - 1] + array[i], array[i])。
遍历数组,每次只要比较取最大值即可。
时间复杂度:O(n),遍历一遍。
空间复杂度:O(n),动态规划辅助数组长度为n
function maxSumFunc(nums) {const len = nums.lengthif (len === 0) return 0if (len === 1) return nums[0]let maxSum = nums[0]const dp = [] // dp数组表示以下标i为终点的最大连续子数组和。dp[0] = nums[0]for (let i = 1; i < len; i++) {dp[i] = Math.max(nums[i], dp[i - 1] + nums[i])maxSum = Math.max(maxSum, dp[i])}return maxSum
}
解法二:动态规划空间优化
思路:
我们注意到方法一的动态规划在状态转移的时候只用到了i一1的信息,没有使用整个数组的信息。
我们可以使用两个变量迭代来代替数组。
状态转移的时候更新变量y,该轮循环结束的再更新x为y即可做到每次迭代都是上一轮的dp。
function maxSumFunc(nums) {const len = nums.lengthif (len === 0) return 0if (len === 1) return nums[0]let x = nums[0]let y = 0let maxSum = xfor (let i = 1; i < len; i++) {y = Math.max(nums[i], x + nums[i])maxSum = Math.max(maxSum, y)x = y}return maxSum
}
10 路径总和 【Easy】
给你二叉树的根节点 root 和一个表示目标和的整数 targetSum 。判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum 。如果存在,返回 true ;否则,返回 false 。
叶子节点 是指没有子节点的节点。
示例 1:
输入:root = [5,4,8,11,null,13,4,7,2,null,null,null,1], targetSum = 22
输出:true
解释:等于目标和的根节点到叶节点路径如上图所示。
示例 2:
输入:root = [1,2,3], targetSum = 5
输出:false
解释:树中存在两条根节点到叶子节点的路径:
(1 --> 2): 和为 3
(1 --> 3): 和为 4
不存在 sum = 5 的根节点到叶子节点的路径。
示例 3:
输入:root = [], targetSum = 0
输出:false
解释:由于树是空的,所以不存在根节点到叶子节点的路径。
解法一:递归
var hasPathSum = function (root, targetSum) {if (root == null) return falseif (root.left == null && root.right == null && root.val === targetSum) return truereturn hasPathSum(root.left, targetSum - root.val) || hasPathSum(root.right, targetSum - root.val)
};
解法二: 迭代
思路:
在二叉树中能够用递归解决的问题,很多时候我们也可以用非递归来解决。这里遍历过程
也可以使用栈辅助,进行dfs遍历,检查往下的路径中是否有等于sum的路径和。
注意,这里仅是dfs,而不是先序遍历,左右节点的顺序没有关系,因为每次往下都是单
独添加某个节点的值相加然后继续往下,因此左右节点谁先遍历不管用。
function hasPathSum(root, targetSum) {if (root == null) return falseconst treeNodeStack = []const valueStack = []treeNodeStack.push(root)valueStack.push(root.val)while (treeNodeStack.length) {const node = treeNodeStack.pop()const valSum = valueStack.pop()// 是叶子节点且当前路径和等于valSumif (node.left == null && node.right == null && valSum === targetSum) return trueif (node.left != null) {treeNodeStack.push(node.left)valueStack.push(node.left.val + valSum)}// 右节点&对应路径和入栈if (node.right != null) {treeNodeStack.push(node.right)valueStack.push(node.right.val + valSum)}}return false
}