top100
给定一个整数数组 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.length
if (len === 0) return
let 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]
}
}
};
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
示例 1:
输入:n = 2
输出: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 n
return climbStairs(n - 1) + climbStairs(n - 2)
};
2 循环
var climbStairs = function (n) {
if (n <= 2) return n
let n1 = 1
let n2 = 1
let res = 0
for (let i = 2; i <= n; i++) {
res = n1 + n2
n2 = n1
n1 = res
}
return res
};
3 动态规划
var climbStairs = function (n) {
const dp = new Array(n + 1)
dp[1] = 1
dp[2] = 2
for (let i = 3; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2]
}
return dp[n]
};
给定一个不含重复数字的数组 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
// // 前序位置需要的操作
// 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 === 3
res.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;
};
给你一个整数数组 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
解法一:动态规划
思路:
function maxSumFunc(nums) {
const len = nums.length
if (len === 0) return 0
if (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.length
if (len === 0) return 0
if (len === 1) return nums[0]
let x = nums[0]
let y = 0
let maxSum = x
for (let i = 1; i < len; i++) {
y = Math.max(nums[i], x + nums[i])
maxSum = Math.max(maxSum, y)
x = y
}
return maxSum
}
给你二叉树的根节点 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 false
if (root.left == null && root.right == null && root.val === targetSum) return true
return hasPathSum(root.left, targetSum - root.val) || hasPathSum(root.right, targetSum - root.val)
};
解法二: 迭代
思路:
在二叉树中能够用递归解决的问题,很多时候我们也可以用非递归来解决。这里遍历过程
也可以使用栈辅助,进行dfs遍历,检查往下的路径中是否有等于sum的路径和。
注意,这里仅是dfs,而不是先序遍历,左右节点的顺序没有关系,因为每次往下都是单
独添加某个节点的值相加然后继续往下,因此左右节点谁先遍历不管用。
function hasPathSum(root, targetSum) {
if (root == null) return false
const treeNodeStack = []
const valueStack = []
treeNodeStack.push(root)
valueStack.push(root.val)
while (treeNodeStack.length) {
const node = treeNodeStack.pop()
const valSum = valueStack.pop()
// 是叶子节点且当前路径和等于valSum
if (node.left == null && node.right == null && valSum === targetSum) return true
if (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
}