目录
21、跳跃游戏
题目内容
给定一个非负整数数组 nums ,你最初位于数组的 第一个下标 。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
判断你是否能够到达最后一个下标。
示例 1:
输入:nums = [2,3,1,1,4]
输出:true
解释:可以先跳 1 步,从下标 0 到达下标 1, 然后再从下标 1 跳 3 步到达最后一个下标。
示例 2:
输入:nums = [3,2,1,0,4]
输出:false
解释:无论怎样,总会到达下标为 3 的位置。但该下标的最大跳跃长度是 0 , 所以永远不可能到达最后一个下标。
题解
也是最基本的动态规划类型,不再多说。
/**
* @param {number[]} nums
* @return {boolean}
*/
var canJump = function (nums) {
let n = nums.length;
// 记录最左边的能够到达最后一个地方的位置
let pos = n - 1;
for (let i = n - 2; i >= 0; i--) {
if (i + nums[i] >= pos) {
pos = i;
}
}
return pos === 0;
};
22、合并区间
题目内容
以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。
示例 1:
输入:intervals = [[1,3],[2,6],[8,10],[15,18]]
输出:[[1,6],[8,10],[15,18]]
解释:区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].
示例 2:
输入:intervals = [[1,4],[4,5]]
输出:[[1,5]]
解释:区间 [1,4] 和 [4,5] 可被视为重叠区间。
题解
这个曾在面试中手撕过,其实也是动态规划的思想。
/**
* @param {number[][]} intervals
* @return {number[][]}
*/
var merge = function (intervals) {
// 升序排序
intervals.sort((a, b) => a[0] - b[0] || a[1] - b[1]);
// 区间起止
let start = -1,
end = -1;
// 结果
let res = [];
for (let interval of intervals) {
if (start === -1) {
start = interval[0];
end = interval[1];
} else if (interval[0] <= end && interval[1]>end) {
end = interval[1];
} else if (interval[0] > end) {
res.push([start, end]);
start = interval[0];
end = interval[1];
}
}
if (start > -1) {
res.push([start, end]);
}
return res;
};
23、不同路径
题目内容
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。
问总共有多少条不同的路径?
示例 1:
输入:m = 3, n = 7
输出:28
示例 2:
输入:m = 3, n = 2
输出:3
解释:
从左上角开始,总共有 3 条路径可以到达右下角。
1. 向右 -> 向下 -> 向下
2. 向下 -> 向下 -> 向右
3. 向下 -> 向右 -> 向下
示例 3:
输入:m = 7, n = 3
输出:28
示例 4:
输入:m = 3, n = 3
输出:6
题解
还是动态规划,规划麻了......
/**
* @param {number} m
* @param {number} n
* @return {number}
*/
var uniquePaths = function (m, n) {
// 到该点的路径数目
let dp = new Array(m).fill(0).map((item) => 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] = 1;
} else {
dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
}
}
}
return dp[m - 1][n - 1];
};
24、最小路径和
题目内容
给定一个包含非负整数的 m x n 网格 grid ,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。
说明:每次只能向下或者向右移动一步。
示例 1:
输入:grid = [[1,3,1],[1,5,1],[4,2,1]]
输出:7
解释:因为路径 1→3→1→1→1 的总和最小。
示例 2:
输入:grid = [[1,2,3],[4,5,6]]
输出:12
题解
又又又是动态规划......
/**
* @param {number[][]} grid
* @return {number}
*/
var minPathSum = function (grid) {
let m = grid.length,
n = grid[0].length;
// 到该点的最短路径长度
let dp = new Array(m).fill(0).map((item) => 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] = grid[i][j];
} else if (i === 0) {
dp[i][j] = dp[i][j - 1] + grid[i][j];
} else if (j === 0) {
dp[i][j] = dp[i - 1][j] + grid[i][j];
} else {
dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j];
}
}
}
return dp[m - 1][n - 1];
};
25、爬楼梯
题目内容
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
示例 1:
输入:n = 2
输出:2
解释:有两种方法可以爬到楼顶。
1. 1 阶 + 1 阶
2. 2 阶
示例 2:
输入:n = 3
输出:3
解释:有三种方法可以爬到楼顶。
1. 1 阶 + 1 阶 + 1 阶
2. 1 阶 + 2 阶
3. 2 阶 + 1 阶
题解
类似于斐波那契数列。
/**
* @param {number} n
* @return {number}
*/
var climbStairs = function (n) {
// f(n)=f(n-1)+f(n-2)
if (n <= 2) {
return n;
}
let p = 1,
q = 2,
x = -1;
for (let i = 3; i <= n; i++) {
x = p + q;
p = q;
q = x;
}
return x;
};
26、颜色分类
题目内容
给定一个包含红色、白色和蓝色、共 n 个元素的数组 nums ,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。
我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。
必须在不使用库的sort函数的情况下解决这个问题。
示例 1:
输入:nums = [2,0,2,1,1,0]
输出:[0,0,1,1,2,2]
示例 2:
输入:nums = [2,0,1]
输出:[0,1,2]
题解
我比较暴力,把0和2全删了,然后在开头和结尾插入即可......
这个思路显然不够优雅。
/**
* @param {number[]} nums
* @return {void} Do not return anything, modify nums in-place instead.
*/
var sortColors = function (nums) {
// 把0和2都删除,并存入数组
let zeros = [],
twos = [];
for (let i = 0; i < nums.length; ) {
if (nums[i] === 0) {
nums.splice(i, 1);
zeros.push(0);
} else if (nums[i] === 2) {
nums.splice(i, 1);
twos.push(2);
} else {
i++;
}
}
nums.unshift(...zeros);
nums.push(...twos);
};
27、子集
题目内容
给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
示例 1:
输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
示例 2:
输入:nums = [0]
输出:[[],[0]]
题解
这道题我解出来了,但解法肯定不是最优的。
具体思路在代码中写的很清楚了,简单来说,就是对于一个长度为n的数组,求0-n组合,用到了递归。与之前求全排列思路类似,但有些不同。全排列的时候,是标记第几个数字,并且在没有访问过的元素中选择这个数字,要求顺序,因此,在后面还是有可能取到这个数字,所以把它作为开头数字的所有情况都取到之后,还需要把它置为没有访问过;但对于组合,就必须把它置为访问过,且它之后的数字都置为没访问过,这样才能保证全部情况都能取到。
/**
* @param {number[]} nums
* @return {number[][]}
*/
var subsets = function (nums) {
let size = nums.length;
let visited = new Array(size);
let res = [];
for (let i = 0; i <= size; i++) {
visited.fill(false);
res.push(...nSubSet(nums, visited, i));
}
return res;
};
// 取n个元素的子集
function nSubSet(nums, visited, n) {
// 返回数据格式:一个数组,数组的每个元素都是数组,代表所有元素个数为n的子集
if (n === 0) {
return [[]];
}
if (n === nums.length) {
return [nums];
}
// 思路:按顺序枚举
// 例如:1 2 3 4 5,将会得到1 2 3、1 2 4、1 2 5等等
let res = [];
// 先找出所有还没访问过的元素的位置
let hasNotVisited = [];
for (let i = 0; i < nums.length; i++) {
if (!visited[i]) {
hasNotVisited.push(i);
}
}
// 从前往后,找出一个来,作为开头元素
for (let i = 0; i < hasNotVisited.length; i++) {
// 标记开头元素已经访问过
visited[hasNotVisited[i]] = true;
// 在开头元素确定的情况下,再找n-1个子集
let sub = nSubSet(nums, visited, n - 1);
// 把以当前元素开头的n子集放入结果数组
for (let s of sub) {
let temp = JSON.parse(JSON.stringify(s));
temp.unshift(nums[hasNotVisited[i]]);
res.push(temp);
}
// 此时,要把所有i后面的元素均标记为没有访问过
// 否则,已经访问过的元素无法作为开头元素了
for (let j = i + 1; j < hasNotVisited.length; j++) {
visited[hasNotVisited[j]] = false;
}
}
return res;
}
28、单词搜索
题目内容
给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。
单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
示例 1:
输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "ABCCED"
输出:true
示例 2:
输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "SEE"
输出:true
示例 3:
输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "ABCB"
输出:false
题解
相比于前面几道递归查找的题目,这道题难度要小些。只是记住,在从某个位置开始查找,如果结果为false,那么记得把这个位置改回未访问,否则再搜索的时候就又无法访问了。
/**
* @param {character[][]} board
* @param {string} word
* @return {boolean}
*/
var exist = function (board, word) {
let m = board.length,
n = board[0].length;
// 是否访问过
let visited = null;
for (let i = 0; i < m; i++) {
for (let j = 0; j < n; j++) {
visited = new Array(m).fill(0).map((item) => new Array(n).fill(false));
if (find(board, visited, word, i, j)) {
return true;
}
}
}
return false;
};
// 从startX,startY出发是否能够找到word
function find(board, visited, word, startX, startY) {
if (!word.length || board[startX][startY] !== word[0]) {
return false;
}
if (word.length === 1) {
return true;
}
visited[startX][startY] = true;
let subWord = word.substring(1, word.length);
// 递归查找
if (
startX - 1 >= 0 &&
!visited[startX - 1][startY] &&
find(board, visited, subWord, startX - 1, startY)
) {
return true;
}
if (
startY - 1 >= 0 &&
!visited[startX][startY - 1] &&
find(board, visited, subWord, startX, startY - 1)
) {
return true;
}
if (
startX + 1 < board.length &&
!visited[startX + 1][startY] &&
find(board, visited, subWord, startX + 1, startY)
) {
return true;
}
if (
startY + 1 < board[0].length &&
!visited[startX][startY + 1] &&
find(board, visited, subWord, startX, startY + 1)
) {
return true;
}
visited[startX][startY] = false;
return false;
}
29、二叉树的中序遍历
题目内容
给定一个二叉树的根节点 root ,返回 它的 中序 遍历 。
示例 1:
输入:root = [1,null,2,3]
输出:[1,3,2]
示例 2:
输入:root = []
输出:[]
示例 3:
输入:root = [1]
输出:[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 {number[]}
*/
var inorderTraversal = function (root) {
if (!root) {
return [];
}
let node = root;
// 结果
let res = [];
// 栈存储
let stack = [];
while (stack.length || node) {
while (node) {
stack.push(node);
node = node.left;
}
node = stack.pop();
res.push(node.val);
node = node.right;
}
return res;
};
30、不同的二叉搜索树
题目内容
给你一个整数 n ,求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。
示例 1:
输入:n = 3
输出:5
示例 2:
输入:n = 1
输出:1
题解
也是老朋友了,就是从1到n依次做根节点,求值即可。
/**
* @param {number} n
* @return {number}
*/
var numTrees = function (n) {
return treeNumUse(1, n);
};
function treeNumUse(m, n) {
// 求从m到n的二叉搜索树种类数
if (m > n) {
return 0;
}
if (m === n) {
return 1;
}
let res = 0;
// 依次做根节点
for (let i = m; i <= n; i++) {
// i做根节点
let leftNum = treeNumUse(m, i - 1);
let rightNum = treeNumUse(i + 1, n);
leftNum = leftNum === 0 ? 1 : leftNum;
rightNum = rightNum === 0 ? 1 : rightNum;
res += leftNum * rightNum;
}
return res;
}