1.救生艇
题目:
第 i
个人的体重为 people[i]
,每艘船可以承载的最大重量为 limit
。
每艘船最多可同时载两人,但条件是这些人的重量之和最多为 limit
。
返回载到每一个人所需的最小船数。(保证每个人都能被船载)。
思路:先排序,然后取体重最大的人,如果此时体重最大的人+体重最小的人小于limit,说明这两个人可以在一艘船上,否则体重最大的人单独一搜.可以用数组也可以用双指针的方式遍历
时间复杂度O(nlogn),空间复杂度O(logn)
/**
* @param {number[]} people
* @param {number} limit
* @return {number}
*/
var numRescueBoats = function(people, limit) {
people.sort((a, b) => a - b);
let c = 0;
const l = people.length;
let right = l - 1,
left = 0;
while (right >= left) {
if (right === left) {
right--;
c++;
continue;
}
if (people[right] + people[left] <= limit) {
left++;
}
right--;
c++;
}
return c;
};
2.螺旋矩阵
题目:
在 R 行 C 列的矩阵上,我们从 (r0, c0) 面朝东面开始
这里,网格的西北角位于第一行第一列,网格的东南角位于最后一行最后一列。
现在,我们以顺时针按螺旋状行走,访问此网格中的每个位置。
每当我们移动到网格的边界之外时,我们会继续在网格之外行走(但稍后可能会返回到网格边界)。
最终,我们到过网格的所有 R * C 个空间。
按照访问顺序返回表示网格位置的坐标列表。
思路:观察可知,每次移动的长度是1,1,2,2,3,3...,所以用一个变量记录方向,另一个变量记录当前这次走的长度。终止条件,可以提前计算四个顶点的坐标,判断此时经过了多少个顶点。也可以根据矩阵的宽高和起始位置计算路径矩阵的宽高
时间复杂度O(mn),空间复杂度O(mn)
/**
* @param {number} R
* @param {number} C
* @param {number} r0
* @param {number} c0
* @return {number[][]}
*/
var spiralMatrixIII = function(R, C, r0, c0) {
let flag = 0;
let res = [[r0, c0]];
let popsition = new Set([
`0-${C - 1}`,
`0-0`,
`${R - 1}-0`,
`${R - 1}-${C - 1}`,
]);
let c = popsition.has(`${r0}-${c0}`) ? 1 : 0;
let i = r0,
j = c0;
let l = 0;
while (c < popsition.size) {
let cur = ~~(l / 2) + 1;
l++;
if (flag === 0) {
while (cur--) {
j++;
res.push([i, j]);
if (popsition.has(`${i}-${j}`)) {
c++;
}
}
flag = 1;
} else if (flag === 1) {
while (cur--) {
i++;
res.push([i, j]);
if (popsition.has(`${i}-${j}`)) {
c++;
}
}
flag = 2;
} else if (flag === 2) {
while (cur--) {
j--;
res.push([i, j]);
if (popsition.has(`${i}-${j}`)) {
c++;
}
}
flag = 3;
} else {
while (cur--) {
i--;
res.push([i, j]);
if (popsition.has(`${i}-${j}`)) {
c++;
}
}
flag = 0;
}
}
return res.filter(
(item) => item[0] >= 0 && item[0] < R && item[1] >= 0 && item[1] < C
);
};
3.可能的二分法
题目:
给定一组 N 人(编号为 1, 2, ..., N), 我们想把每个人分进任意大小的两组。
每个人都可能不喜欢其他人,那么他们不应该属于同一组。
形式上,如果 dislikes[i] = [a, b],表示不允许将编号为 a 和 b 的人归入同一组。
当可以用这种方法将所有人分进两组时,返回 true;否则返回 false。
思路:这题和之前一题很像,相邻染色即可,
初始化数组dp,dp[i]表示每个人的状态。0表示未分组,1表示分到了组1,2表示分到了组2,3表示不再组1或者组2,也就是出现3的话,返回false
先记录每个人和对应的不喜欢的人,然后遍历每个人,如果这个人已经分组,就递归遍历他不喜欢的人,根据之前的分组,将这些人分到另一个组。在深度优先遍历的时候,遇到当前值和已有值不为0且不同,则该人状态置设为3
时间复杂度O(m+n),空间复杂度O(m+n)
/**
* @param {number} N
* @param {number[][]} dislikes
* @return {boolean}
*/
var possibleBipartition = function(N, dislikes) {
const dp = new Array(N).fill(0);
const map = new Map();
for (const [a, b] of dislikes) {
if (!map.get(a - 1)) {
map.set(a - 1, []);
}
if (!map.get(b - 1)) {
map.set(b - 1, []);
}
map.get(a - 1).push(b - 1);
map.get(b - 1).push(a - 1);
}
const dfs = (i, v) => {
if (dp[i]) {
if (dp[i] !== v) {
dp[i] = 3;
}
return;
} else {
dp[i] = v;
const next = map.get(i);
if(!next)return
next.forEach((item) => {
dfs(item, v === 1 ? 2 : 1);
});
}
};
for (let i = 0; i < N; i++) {
if (dp[i]) continue;
dfs(i, 1);
}
return dp.every((item) => item !== 3);
};
4.根据前序遍历和后序遍历构造二叉树
题目:
返回与给定的前序和后序遍历匹配的任何二叉树。
pre
和 post
遍历中的值是不同的正整数。
思路:前序遍历,是先遍历根节点再遍历左右节点。
后序遍历,是先遍历左右子节点再遍历根节点。
这种根据遍历的结果构造二叉树的,基本上都能用递归做。
前序遍历的第一个节点是根节点,然后是左子树的节点,然后是右子树的节点。
所以将第一个值构造为根节点,然后找到左子树的根节点在后序遍历的下标,这样下标+1就是左子树的节点数量,
然后在前序遍历截取左、右子树的节点,递归处理。
时间复杂度O(n),空间复杂度O(logn)
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
/**
* @param {number[]} pre
* @param {number[]} post
* @return {TreeNode}
*/
var constructFromPrePost = function(pre, post) {
if (pre.length == 0) {
return null;
}
let tmp = pre[0];
let index = post.findIndex((item) => item === pre[1]);
let root = new TreeNode(tmp);
root.left = constructFromPrePost(
pre.slice(1, index + 2),
post.slice(0, index + 1)
);
root.right = constructFromPrePost(
pre.slice(index + 2),
post.slice(index + 1, post.length - 1)
);
return root;
};
5.查找和替换模式
题目:
你有一个单词列表 words 和一个模式 pattern,你想知道 words 中的哪些单词与模式匹配。
如果存在字母的排列 p ,使得将模式中的每个字母 x 替换为 p(x) 之后,我们就得到了所需的单词,那么单词与模式是匹配的。
(回想一下,字母的排列是从字母到字母的双射:每个字母映射到另一个字母,没有两个字母映射到同一个字母。)
返回 words 中与给定模式匹配的单词列表。
你可以按任何顺序返回答案。
思路:这里其实是个双向映射,原字母一一映射到模式字母,模式字母也要一一映射到原字母,
因为字符串有多个,用字母映射的话,需要重复记录多个映射关系,所以转换为数字即可。
第一个没有出现过的字符记为1,第二个没有出现过的字符记为2..,然后直接比较字符串是否相等即可,记得加分隔符
时间复杂度O(mn),空间复杂度O(,),m是words长度,n是字符串长度
/**
* @param {string[]} words
* @param {string} pattern
* @return {string[]}
*/
var findAndReplacePattern = function(words, pattern) {
const getS = (item) => {
const map = new Map();
let c = 1;
let res = "";
for (const s of item) {
if (!map.get(s)) {
map.set(s, c++);
}
res += `${map.get(s)}-`;
}
return res;
};
const copyWords = words.map(getS);
const cPattern = getS(pattern);
return words.filter((i, index) => {
return cPattern === copyWords[index];
});
};