aaa-leetcode-record's People
aaa-leetcode-record's Issues
leetcode 周周乐
第 248 场周赛 2021-07-04
最近都在医院状态不好,前一天凌晨5点才入睡,第二天就没有参加早上 10:30 的比赛,后面进行了模拟赛。
完成情况:
总结
5800 有手就行
5801 排序后想清楚贪心策略
5802 快速幂类问题,卡死在数据取模的处理上了,缺失知识点:
- 1e7 + 7 的两个数字相乘的情况下会溢出。所以需要更高精度的运算来做,需要使用 BigInt 来完成相乘后取模的操作
- 题目中 n 的取值最大为 1e15 ,范围较大需要分成多个阶段分别取模后计算,这方面敏感度还是不够。
- 取模运算在乘法下的性质,比赛时候临时推导浪费了不少时间。需记住,(a * b) % p = (a % p) * (b % p) % p,和加法时候的性质是类似的。
5803 直接没有思路
Power of X
各种判断一个数字是不是某个数的次幂
231. Power of Two
- loop!
var isPowerOfTwo = function(n) {
if(0 === n) return false;
while(0 === (1 & n)) n >>= 1;
return 1 === n ? true : false;
};
- 利用 n & n - 1 还能更加简洁
var isPowerOfTwo = function(n) {
return n > 0 && 0 === (n & n - 1);
};
342. Power of Four
- 比较直观的一个解 loop!:
var isPowerOfFour = function(num) {
let n = num;
if(0 === n) return false;
while(0 === (n & 3)) n >>= 2;
return 1 === n ? true : false;
};
- 更为简洁的解法:
var isPowerOfFour = function(num) {
return (num&(num-1)) == 0 && // 在二进制表示下只有一位为1,其余都是0
(num & 0x55555555) != 0; // 在二进制表示下唯一为1的位,在奇数位,0x5 -> 二进制计数: 101,
};
- 利用数论结论的解法
var isPowerOfFour = function(num) {
return num > 0 && (num & (num - 1)) == 0 && num % 3 == 1;
};
326. Power of Three
这个实在想不出怎么解, 抄答案了, 这里用到一个数论原理,
TODO 待我去学习下
const Max3PowerInt = 1162261467; // 3^19, 3^20 = 3486784401 > MaxInt32
var isPowerOfThree = function(n) {
if (n <= 0 || n > Max3PowerInt) return false;
return Max3PowerInt % n == 0;
};
二叉搜索树类题目
https://leetcode.com/problems/search-in-a-binary-search-tree
https://leetcode.com/problems/insert-into-a-binary-search-tree
938. Range Sum of BST
暴力遍历所有的节点,然后过滤掉不在范围中的值
var rangeSumBST = function(root, L, R) {
if(!root) return 0;
let { left, right, val } = root;
if(val < L || val > R) val = 0;
return val + rangeSumBST(left, L, R) + rangeSumBST(right, L, R);
};
进化一下,添加剪纸策略
var rangeSumBST = function(root, L, R) {
if(!root) return 0;
let { left, right, val } = root;
const leftSum = (val >= L) ? rangeSumBST(left, L, R) : 0;
const rightSum = (val <= R) ? rangeSumBST(right, L, R): 0;
if(val < L || val > R) val = 0;
return val + leftSum + rightSum;
};
5800. 基于排列构建数组
各种二叉树问题
1315. Sum of Nodes with Even-Valued Grandparent
对于一个节点是否满足条件,依赖于其祖父节点。
如果 node 结构有 parent 域,是最方便的,
不过我们可以通过传入 partent 和 grandParent 来解决问题,
var sumEvenGrandparent = function(root, parent = null, grandParent = null) {
if(!root) return 0;
const isAccept = grandParent && (grandParent.val % 2 === 0);
const val = isAccept ? root.val : 0;
return val + sumEvenGrandparent(root.left, root, parent) +
sumEvenGrandparent(root.right, root, parent);
};
543. Diameter of Binary Tree
考察数的后续遍历,必然存在一个节点,是整个最长路劲的根节点,也就是整个路径可以表述为:
path = subTree(node.left) + subTree(node.right) + node;
那么作为最长路径即:左子树的最大深度 + 右子树的最大深度,最大的节点为目标节点。
var diameterOfBinaryTree = function(root) {
let maxDiameter = 0;
const treeHeight = root => {
if(!root) return 0;
const leftHeight = treeHeight(root.left);
const rightHeight = treeHeight(root.right);
maxDiameter = Math.max(maxDiameter, leftHeight + rightHeight);
return Math.max(leftHeight, rightHeight) + 1;
}
treeHeight(root);
return maxDiameter;
};
1325. Delete Leaves With a Given Value
考察树的后续遍历,这个题目当子树满足:
- isLeaf,子树为叶子节点
- 值为 target 的时候
将该子节点自身从父节点上移除,并且在左右子树都被移除,父节点成为新的叶子节点的时候,
递归的作用到父节点,以及父父节点上。
采用 postOrder 后续遍历,然后对每一个子树做同样的判断操作即可。
解法一:尝试利用了 js 的闭包特性,把删除操作作为回调传递给子树。
var removeLeafNodes = function(root, target) {
postOrder(root, target, () => root = null);
return root;
};
const postOrder = (root, target, delSelfFn) => {
if(!root) return;
postOrder(root.left, target, () => root.left = null);
postOrder(root.right, target, () => root.right = null);
if(
root.val === target &&
!root.left &&
!root.right
) {
delSelfFn();
}
}
解法二:按照递归定义,
var removeLeafNodes = function(root, target) {
return postOrder(root, target);
};
const isLeaf = root => root && !root.left && !root.right;
const postOrder = (root, target) => {
if(!root) return null;
root.left = postOrder(root.left, target);
root.right = postOrder(root.right, target);
return (isLeaf(root) && root.val === target) ? null : root;
}
513. Find Bottom Left Tree Value
- bfs解法
var findBottomLeftValue = function(root) {
if(!root) return null;
let row = [root], value;
while(row.length)
{
value = row[0].val;
row = bfs(row);
}
return value
};
function bfs(row){
let nextRow = [];
for(let node of row)
{
if(node.left) nextRow.push(node.left);
if(node.right) nextRow.push(node.right);
}
return nextRow;
}
- dfs解法
var findBottomLeftValue = function(root) {
if(!root) return null;
const nice = { deep: 0, val: root.val };
dfs(root, 0, nice);
return nice.val;
};
function dfs(root, deep, nice){
if(!root) return;
if(!root.left && !root.right)
{
if(deep > nice.deep)
{
nice.deep = deep;
nice.val = root.val;
}
return;
}
dfs(root.left, deep + 1, nice);
dfs(root.right, deep + 1, nice);
}
位运算相关题目
相关知识点:
- 移动:a << n,数 a 向左移动 n 位,等效于 a * (2**n)
- 按位操作:或 a | b,
1290. Convert Binary Number in a Linked List to Integer
一个关键的优化点是,curNode.val && (total += curNode.val),做了一个短路
var getDecimalValue = function(head) {
let total = 0, curNode = head;
while(curNode) {
total <<= 1;
curNode.val && (total += curNode.val);
curNode = curNode.next;
}
return total;
};
1323. Maximum 69 Number
思路一:数字转 digits 数组,处理后再转换回来
思路二:从最高位开始依次获取十进制位的值。(num / 10**k) % 10 获取对应 k 位的十进制 digit
/**
* @param {number} num
* @return {number}
*/
var maximum69Number = function(num) {
let tmp = 10**12;
while(tmp > 0) {
const digit = (num / tmp | 0) % 10;
if(digit === 6) {
num += (tmp * 3);
break;
}
tmp /= 10;
}
return num;
};
1342. Number of Steps to Reduce a Number to Zero
- 统计一个数的二进制表示中,1 和 0的数量
- 对于一个二进制数,除了最高位的 1 需要消耗 2 次操作消去,0 需要消耗一次操作
/**
* @param {number} num
* @return {number}
*/
var numberOfSteps = function(num) {
const { zeroCount, oneCount } = countDigit(num);
const step = zeroCount + oneCount * 2 - 1;
return step;
};
const countDigit = num => {
let zeroCount = 0, oneCount = 0;
while(num) {
(num & 1) ? (oneCount += 1) : (zeroCount += 1);
num >>= 1;
}
return { oneCount, zeroCount };
}
数组的累加和问题
算法模板
https://leetcode.com/problems/range-sum-query-immutable/
const cumSum = nums => {
const sumNums = [0], len = nums.length;
for(let i = 1; i <= len; i++) {
sumNums[i] = sumNums[i - 1] + nums[i - 1];
}
return sumNums;
}
1314. Matrix Block Sum
这个问题可以看作数组的累加和在矩阵上的推广
const cumNums = nums => {
const cumedNums = [0], len = nums.length;
for(let i = 1; i <= len; i++) {
cumedNums[i] = cumedNums[i - 1] + nums[i - 1];
}
return cumedNums;
}
/**
* @param {number[][]} mat
* @param {number} K
* @return {number[][]}
*/
var matrixBlockSum = function(mat, K) {
const cumedRowMat = mat.map(cumNums);
const rowN = mat.length, colN = mat[0].length;
const sumMatrixBlock = (i, j) => {
const startRow = i - K < 0 ? 0 : i - K;
const endRow = i + K >= rowN ? rowN - 1 : i + K;
const startCol = j - K < 0 ? 0 : j - K;
const endCol = j + K >= colN ? colN - 1 : j + K;
let sum = 0;
for(let row = startRow; row <= endRow; row++) {
const cumedRowMatRow = cumedRowMat[row];
const rangeSum = cumedRowMatRow[endCol + 1] - cumedRowMatRow[startCol];
sum += rangeSum;
}
return sum;
}
const blockSumMat = new Array(rowN).fill(null).map(() => new Array(colN).fill(0));
for(let row = 0; row < rowN; row++) {
for(let col = 0; col < colN; col++) {
blockSumMat[row][col] = sumMatrixBlock(row, col);
}
}
return blockSumMat;
}
525. Contiguous Array
- 果断暴♂力
var findMaxLength = function(nums) {
const sum = [0], len = nums.length;
for(let i = 0, num; i < len; i++) sum[i + 1] = nums[i] + sum[i];
let slow, fast, max = 0;
for(slow = 0; slow < len; slow++)
{
for(fast = slow + 1; fast < len; fast++)
{
if((sum[fast + 1] - sum[slow]) * 2 === (fast - slow + 1))
{
max = Math.max(max, fast - slow + 1);
}
}
}
return max;
};
果断超时, 跪+1
- 痛定思痛, 思考问题.
首先我们来看基础的子数组和问题的公式: sum[i ,j] = sum[0, j] - sum[0, i - 1] (令: sum[-1] = 0).
对于这道题, 我们定义数组索引为idx, 则区间[0, idx], x为区间中1的总数, y为区间中0的总数,
于是我们获得一个三元组(idx, x, y).
那么对于一前一后2个点有(idx1, x1, y1), (idx2, x2, y2).
即当: y2 - y1 == x2 - x1 时, 区间[idx1 + 1, idx2]截取的子数组为满足题设条件的子数组.
y2 - y1 == x2 - x1经过一个简单的变换得: x1 - y1 == x2 - y2. 我们令k = x - y.
又由于x + y == idx + 1, 则 k = x - y = 2 * x - idx - 1.
那么对于一个给定的idx我们只需要查找是否有某个对应的位置存在k相同的点就可以了.
利用hash可以很快的查找.
var findMaxLength = function(nums) {
const sums = [0], offsetToIdxs = { 0: [-1] }, len = nums.length;
let max = 0, offset;
for(let i = 0; i < len; i++) sums[i + 1] = nums[i] + sums[i];
for(let i = 0; i < len; i++)
{
offset = 2 * sums[i + 1] - i - 1;
if(!offsetToIdxs[offset]) offsetToIdxs[offset] = [];
offsetToIdxs[offset].push(i);
max = Math.max(max, i - offsetToIdxs[offset][0]);
}
return max;
};
238. Product of Array Except Self
- 这道题目在于不准用除法, 上来还是先把公式写出来:
令: product[i, j]表示数组nums在i~j区间上的连乘, len为nums的长度,
p[i]为procutst[0, len - 1] / nums[i].
则: p[i] = product[0 , i - 1] * product[i + 1, len - 1]
那么我们需要对应的从左到右, 以及从右到左, 乘一次生成查询数组就可以了.
var productExceptSelf = function(nums) {
const len = nums.length, leftProducts = [], rightProducts = [], ans = [];
leftProducts[0] = nums[0];
for(let i = 1; i < len; i++) leftProducts[i] = leftProducts[i - 1] * nums[i];
rightProducts[len - 1] = nums[len - 1];
for(let i = len - 2; i >= 0; i--) rightProducts[i] = rightProducts[i + 1] * nums[i];
ans[0] = rightProducts[1];
ans[len - 1] = leftProducts[len - 2];
for(let i = 1; i < len - 1; i++)
{
ans[i] = leftProducts[i - 1] * rightProducts[i + 1];
}
return ans;
};
- 要求充分利用空间. todo 容我三思
做题记录 stash
922. Sort Array By Parity II
双指针,利用一个 evenIdx 和 oddIdx 进行两趟遍历
var sortArrayByParityII = function(nums) {
let evenIdx = 0, oddIdx = 1, len = nums.length;
while(evenIdx < len) {
[nums[evenIdx], nums[oddIdx]] = [nums[oddIdx], nums[evenIdx]];
while(nums[evenIdx] % 2 === 0) evenIdx += 2;
while(nums[oddIdx] % 2 === 1) oddIdx += 2;
}
return nums;
};
1346. Check If N and Its Double Exist
主要是 coner case 当数组值为 0 的时候,0 * 2 === 0,0 / 2 === 0,会漏掉 i !== j 的判断。
所以 num 的值为 0的时候需要添加一个额外判断。
/**
* @param {number[]} arr
* @return {boolean}
*/
var checkIfExist = function(nums) {
const record = {};
for(const num of nums) {
record[num] ? record[num] += 1 : record[num] = 1;
const isZeroDouble = num === 0 && record[num] === 2;
const isNonZeroDouble = num !== 0 && (
record[num * 2] || (num % 2 === 0 && record[num / 2])
);
if(isZeroDouble || isNonZeroDouble) {
return true;
}
}
return false;
};
1347. Minimum Number of Steps to Make Two Strings Anagram
TODO: 更为细化一点
分解为3个基础操作:
1. �如何生成所有英语字母的字母表
2. 遍历一个字符串,通过 hash 表统计每个字符出现的次数
3. 计算2个字符串,之间字母构成的差异
const ALPHAS = [];
for(let i = 0; i < 26; i++) {
ALPHAS.push(String.fromCharCode((97 + i)));
}
const countAlpha = s => {
const record = {};
for(const alpha of ALPHAS) {
record[alpha] = 0;
}
for(const char of s) {
record[char] += 1;
}
return record;
}
/**
* @param {string} s
* @param {string} t
* @return {number}
*/
var minSteps = function(s, t) {
const [recordS, recordT] = [countAlpha(s), countAlpha(t)];
let sum = 0;
for(const alpha of ALPHAS) {
sum += (Math.abs(recordS[alpha] - recordT[alpha]));
}
return sum / 2;
};
1333. Filter Restaurants by Vegan-Friendly, Price and Distance
利用 filter,sort,map 操作按照题设计算就好,
注意 Vegan-Friendly 这个过滤项是在 true 的时候要求 Restaurant 必为 true,为 false 的时候为任意值
1329. Sort the Matrix Diagonally
这道题主要考察的是如何按照对角线来遍历一个矩阵
矩阵对角线的起始点为矩阵的第一行和第一列即:
1. row = 0 时 col 的范围为 [0, rowN - 1]
2. col = 0 时 row 的范围为 [0, colN - 1]
已知一个点 matrix[row][col] 则,对角线上的下一个点为 matrx[row + 1, col + 1]
对对角线进行编码标记
令 k = row - col,我们可以标记对应的对角线 k,并且对应起始点偏移量为 offset 的点
则由对角线的起始点必然 startRow === 0 || startCol === 0,易得 offset = min(row, col);
我们可以得到 row-col 坐标系 和对角线 k-offset 坐标系之间得转换关系:
// 已知 row,col
k = row - col;
offset = Math.min(row, col);
方法一:分类讨论对角线的起始点分别在在第一行和第一列时的情况,这种方法较为繁琐。
/**
* @param {number[][]} mat
* @return {number[][]}
*/
var diagonalSort = function(matrix) {
const rowN = matrix.length, colN = matrix[0].length;
const sortMatrixDiagonal = (row, col) => {
const nums = [], n = matrix.length;
for(let k = 0; row + k < rowN && col + k < colN; k++) {
nums.push(matrix[row + k][col + k]);
}
nums.sort((x, y) => x > y ? 1 : -1);
for(let k = 0; row + k < rowN && col + k < colN; k++) {
matrix[row + k][col + k] = nums[k];
}
}
for(let row = 0, col = 0; col < colN; col++) {
sortMatrixDiagonal(row, col);
}
for(let row = 0, col = 0; row < rowN; row++) {
sortMatrixDiagonal(row, col);
}
return matrix;
};
方法二:这里巧妙得利用 stack 来保存和还原了对角线上的值
/**
* @param {number[][]} mat
* @return {number[][]}
*/
var diagonalSort = function(matrix) {
const rowN = matrix.length, colN = matrix[0].length, record = {};
for(let row = 0; row < rowN; row++) {
for(let col = 0; col < colN; col++) {
const k = row - col;
if(!record[k]) record[k] = [];
record[k].push(matrix[row][col]);
}
}
Object.values(record).map(nums => nums.sort((x, y) => x > y ? -1 : 1));
for(let row = 0; row < rowN; row++) {
for(let col = 0; col < colN; col++) {
const k = row - col;
matrix[row][col] = record[k].pop();
}
}
return matrix;
};
1109. Corporate Flight Bookings
解法一:暴力破解
/**
* @param {number[][]} bookings
* @param {number} n
* @return {number[]}
*/
var corpFlightBookings = function(bookings, n) {
const record = [];
for(let i = 1; i <= n; i++) {
record[i] = 0;
}
for(const [start, end, k] of bookings) {
for(let i = start; i <= end; i++) {
record[i] += k;
}
}
record.shift();
return record;
};
1014. Best Sightseeing Pair
关键在理解公式:A[i] + A[j] + i - j
第一种做法,思路:考虑选择了 nums[i],那么结合公式距离 i 超过 A[i] 的地点实际上就没有必须再去了,这样负责的就被限制在了,N * max(nums)。
/**
* @param {number[]} A
* @return {number}
*/
var maxScoreSightseeingPair = function(nums) {
let max = -1, len = nums.length;
nums.forEach((num, i) => {
let offset = 1, j;
while(offset <= num && (j = i + offset) < len) {
const score = nums[i] + nums[j] + i - j;
(score > max) && (max = score);
offset += 1;
}
});
return max;
};
第二种思路,我们整理一下公式可得:(A[i] + i) + (A[j] - j),取最大:
那么当 i 一定的时候,A[i] + i + max(A.map((val, j) => val - j)),使得最大。
那么当 j 一定的时候,A[j] - j + max(A.map((val, i) => val + i)),使得最大。
第一个版本显得非常繁琐:
/**
* @param {number[]} A
* @return {number}
*/
var maxScoreSightseeingPair = function(nums) {
const len = nums.length;
const aNums = nums.map((n, i) => n + i);
const bNums = nums.map((n, j) => n - j);
const cNums = [];
cNums[len - 1] = bNums[len - 1];
for(let i = len - 2; i >= 0; i--) {
cNums[i] = Math.max(bNums[i], cNums[i + 1]);
}
aNums.pop();
const dNums = aNums.map((val, i) => val + cNums[i + 1]);
const max = Math.max(...dNums);
return max;
};
实际上经过合适的调整,可以在一趟遍历中通过缓存 max = Math.max(A[i] + i) 解决问题:
var maxScoreSightseeingPair = function(nums) {
const len = nums.length;
let tmp = nums[0], max = -1;
for(let j = 1; j < len; j++) {
max = Math.max(nums[j] - j + tmp, max);
tmp = Math.max(tmp, nums[j] + j);
}
return max;
};
1232. Check If It Is a Straight Line
几何类的题目,最近都快忘光了。TODO: 后面好好补习下
/**
* @param {number[][]} coordinates
* @return {boolean}
*/
var checkStraightLine = function(coordinates) {
const [[x1, y1], [x2, y2]] = coordinates;
const [dx, dy] = [x1 - x2, y1 - y2];
const crossProduct = x1 * dy - y1 * dx;
for(const [x, y] of coordinates) {
if(x * dy - y * dx !== crossProduct) return false;
}
return true;
};
661. Image Smoother
很直白的矩阵遍历类的题目,可以分成几个基本过程:
- 判断 pos = { row, col } 是否在矩阵的范围内
- 由点 pos,按照题设获取他所有的相邻点
- 由点 pos,获取其周围点的平均值
- 遍历矩阵按照规则映射出新的矩阵
const TOWARDS = [
[0, 0],
[-1, 0],
[-1, 1],
[0, 1],
[1, 1],
[1, 0],
[1, -1],
[0, -1],
[-1, -1]
];
/**
* @param {number[][]} M
* @return {number[][]}
*/
var imageSmoother = function(matrix) {
const rowN = matrix.length, colN = matrix[0].length;
const isInRange = (row, col) => row >= 0 && row < rowN && col >= 0 && col < colN;
const getNbrsAverVal = (curRow, curCol) => {
const vals = [];
TOWARDS.forEach( ([dRow, dCol]) => {
const [row, col] = [curRow + dRow, curCol + dCol];
isInRange(row, col) && vals.push(matrix[row][col]);
});
const sum = vals.reduce((total, val) => total += val, 0);
const averVal = sum / vals.length | 0;
return averVal;
};
const smoothMatrix = matrix.map((nums, row) => {
return nums.map((num, col) => getNbrsAverVal(row, col));
});
return smoothMatrix;
};
1299. Replace Elements with Greatest Element on Right Side
从右往左,执行一次冒泡。
1291. Sequential Digits
这道题比较有技巧的地方是如何巧妙的生成范围内所有有效的数字。
const DIGITS = "123456789";
const sequentialNums = [];
for(let offset = 0; offset < DIGITS.length; offset++) {
for(let len = 1; offset + len <= DIGITS.length; len++) {
sequentialNums.push(Number(DIGITS.substr(offset, len)));
}
}
sequentialNums.sort((x, y) => x > y ? 1 : -1);
var sequentialDigits = function(low, high) {
return sequentialNums.filter(num => num >= low && num <= high);
};
605. Can Place Flowers
/**
* @param {number[]} flowerbed
* @param {number} n
* @return {boolean}
*/
var canPlaceFlowers = function(flowerbed, n) {
flowerbed = [1, 0, ...flowerbed, 0, 1];
let [slow, fast, len, count, delta] = [0, 2, flowerbed.length, 0, 0];
while(fast < len) {
while(flowerbed[fast] === 0) fast += 1;
delta = parseInt((fast - slow) / 2) - 1;
delta < 0 && (delta = 0);
count += delta;
[slow, fast] = [fast, fast + 1];
}
return count >= n;
};
- 这道题先考虑最简形式,考虑一个所有值为0的数组,放入若干个 1,并要求所有 1 不相邻,显然我们使用贪心策略,可以使得放入的1的数量最大化(如何严格证明是个很有趣的事情)。
- 考虑数组中已经有若干个1的情况,这个时候实际上若干个1,把整个数组分割成了多个独立的类似形式 1 的区域。求出分割了多少个这样的区域,然后每个区域单独求解后取累计,即为所求。
这里用了在数组前后添加 10 和 01 的前后缀来简化中间的判断,还有要注意循环的 break 条件。
989. Add to Array-Form of Integer
标准的大整数相加,最开始想苟的,不过没苟住,看来数据没那么弱。
苟不住的版本:
var addToArrayForm = function(A, K) {
const val = parseInt(A.join('')) + K;
return val.toString().split('');
};
558. Quad Tree Intersection
/**
* // Definition for a QuadTree node.
* function Node(val,isLeaf,topLeft,topRight,bottomLeft,bottomRight) {
* this.val = val;
* this.isLeaf = isLeaf;
* this.topLeft = topLeft;
* this.topRight = topRight;
* this.bottomLeft = bottomLeft;
* this.bottomRight = bottomRight;
* };
*/
/**
* @param {Node} quadTree1
* @param {Node} quadTree2
* @return {Node}
*/
var intersect = function(t1, t2) {
if(isTrueLeaf(t1) || isTrueLeaf(t2)) {
return createQuadTree(true);
}
if(isFalseLeaf(t1) && isFalseLeaf(t2)) {
return createQuadTree(false);
}
if(!t1.isLeaf && !t2.isLeaf) {
const [t1Children, t2Children] = [
getQuadTreeChildren(t1),
getQuadTreeChildren(t2)
];
const intersectChildren = [];
for(let i = 0, len = t1Children.length; i < len; i++) {
const intersectChild = intersect(t1Children[i], t2Children[i]);
intersectChildren.push(intersectChild);
}
const intersectedTree = createQuadTree(true, intersectChildren);
const mergedTree = mergeQuadTreeChildren(intersectedTree);
return mergedTree;
}
return !t1.isLeaf ? t1 : t2;
};
const createQuadTree = (val, children) => {
const isLeaf = !children;
if(!children) {
children = [null, null, null, null];
}
return {
val,
isLeaf,
topLeft: children[0],
topRight: children[1],
bottomLeft: children[2],
bottomRight: children[3]
};
};
const getQuadTreeChildren = (t) => {
return [t.topLeft, t.topRight, t.bottomLeft, t.bottomRight];
};
const isTrueLeaf = (quadTree) => {
return quadTree.isLeaf && quadTree.val;
};
const isFalseLeaf = (quadTree) => {
return quadTree.isLeaf && quadTree.val;
};
const mergeQuadTreeChildren = (t) => {
const children = getQuadTreeChildren(t);
const leafCount = children.filter(child => child && child.isLeaf).length;
if(leafCount !== children.length) {
return t;
}
const vals = children.map(child => child.val);
const valCount = vals.filter(val => vals[0] === val).length
if(valCount !== children.length) {
return t;
}
const mergedQuadTree = createQuadTree(vals[0]);
return mergedQuadTree;
};
总的来说这一版,执行效率不是太好,不过函数接口上还算清晰吧,
TODO: 复盘
二分查找
1283. Find the Smallest Divisor Given a Threshold
利用好单调性,if(a < b) 则 f(a) <= f(b)
/**
* @param {number[]} nums
* @param {number} threshold
* @return {number}
*/
var smallestDivisor = function(nums, threshold) {
let start = 1, end = Math.max(...nums);
if (nums.length === threshold) return end;
while(start < end) {
let mid = (start + end) / 2 | 0;
let sum = 0;
for(const num of nums) {
sum += Math.ceil(num / mid);
}
sum > threshold ? (start = mid + 1) : (end = mid);
}
return start;
};
671. Second Minimum Node In a Binary Tree
如果left.val === root.val, 那么我们需要对left, 进行一次寻找第二小值得运算, 对于right亦然.
这里对左右分别作了处理以后, 进行一次交换, 保证leftVal < rightVal, 由于整个树的值都是正数,
所以-1必然在左边. 下面是代码:
var findSecondMinimumValue = function(root) {
if(!root.left) return -1;
let [leftVal, rightVal] = [root.left.val, root.right.val];
if(leftVal === root.val) leftVal = findSecondMinimumValue(root.left);
if(rightVal === root.val) rightVal = findSecondMinimumValue(root.right);
// 保证leftVal <= rightVal, 我们这里做一次交换!
if(leftVal > rightVal) [leftVal, rightVal] = [rightVal, leftVal];
if(-1 === leftVal) return rightVal;
return leftVal;
};
回溯类问题汇总
39. Combination Sum
已知一组正整数的集合 nums,nums 的长度为 len,
求满足:c0N0 + c1N1 + ... ckNk == target,k 为 0 ~ len - 1 的解的数量。
考虑当 N0 已知时,问题就转化为:求 nums[1..len - 1] 下,target - c0N0 的解的数量。
那么剩下就很简单了,利用 dfs 来解,不过运行速度确实比较糟糕。
/**
* @param {number[]} candidates
* @param {number} target
* @return {number[][]}
*/
var combinationSum = function(
nums, target, deep = 0,
solution = [], solutionSet = []
) {
if(target === 0) {
solutionSet.push(solution.slice());
return solutionSet;
};
if(deep >= nums.length) return solutionSet;
let solutionCount = 0, count = 0, rest;
while((rest = target - count * nums[deep]) >= 0) {
solutionCount += combinationSum(nums, rest, deep + 1, solution, solutionSet);
solution.push(nums[deep]);
count += 1;
}
for(let i = 0; i < count; i++) {
solution.pop();
}
return solutionSet;
};
各种 Island 问题, 求面积, 求周长...
1254. Number of Closed Islands
const VERTEX_STATUS = {
unvisit: 0,
water: 1,
visited: 2
}
/**
* @param {number[][]} grid
* @return {number}
*/
var closedIsland = function(matrix) {
const rowN = matrix.length, colN = matrix[0].length;
let count = 0;
for(let row = 0; row < rowN; row++) {
for(let col = 0; col < colN; col++) {
if(matrix[row][col] === VERTEX_STATUS.unvisit) {
bfs(matrix, [{ row, col }]) && (count += 1);
}
}
}
return count;
};
const TOWARDS = [
[-1, 0],
[0, 1],
[1, 0],
[0, -1]
];
const getNbrs = (sRow, sCol, rowN, colN) => {
const nbrs = [];
for(const [dRow, dCol] of TOWARDS) {
const [row, col] = [sRow + dRow, sCol + dCol];
if(0 <= row && row < rowN && 0 <= col && col < colN) {
nbrs.push({ row, col });
}
}
return nbrs;
}
const bfs = (matrix, startVs) => {
const rowN = matrix.length, colN = matrix[0].length;
let [vs, nextVs] = [startVs, []], isClosed = true;
while(vs.length > 0) {
for(const v of vs) {
if(
v.row === 0 || (v.row === rowN - 1) ||
v.col === 0 || (v.col === colN - 1)
) {
isClosed = false;
}
const nbrs = getNbrs(v.row, v.col, rowN, colN);
for(const nbr of nbrs) {
if(matrix[nbr.row][nbr.col] === VERTEX_STATUS.unvisit) {
nextVs.push(nbr);
matrix[nbr.row][nbr.col] = VERTEX_STATUS.visited;
}
}
}
[vs, nextVs] = [nextVs, []];
}
return isClosed;
}
200. Number of Islands
这道题为了保持代码结构和下面那道一致, 做了一些无用的计算.
var numIslands = function(grid) {
if(!grid || !grid[0]) return 0;
const rowN = grid.length, colN = grid[0].length;
let count = 0;
for(let row = 0; row < rowN; row++)
{
for(let col = 0; col < colN; col++)
{
count += dfs(grid, row, col) ? 1 : 0;
}
}
return count;
};
function dfs(grid, row, col){
if(!grid[row] || !grid[row][col] || '0' === grid[row][col]) return 0;
grid[row][col] = '0';
return 1 + dfs(grid, row - 1, col) + dfs(grid, row + 1, col) +
dfs(grid, row, col - 1) +dfs(grid, row, col + 1);
}
695. Max Area of Island
var maxAreaOfIsland = function(grid) {
if(!grid || !grid[0]) return 0;
const rowN = grid.length, colN = grid[0].length;
let maxArea = 0, area;
for(let row = 0; row < rowN; row++)
{
for(let col = 0; col < colN; col++)
{
area = dfs(grid, row, col);
maxArea = maxArea > area ? maxArea : area;
}
}
return maxArea;
};
function dfs(grid, row, col){
if(!grid[row] || !grid[row][col]) return 0;
grid[row][col] = 0;
return 1 + dfs(grid, row - 1, col) + dfs(grid, row + 1, col) +
dfs(grid, row, col - 1) + dfs(grid, row, col + 1);
}
733. Flood Fill
唯一注意当newColor和oldColor一样的时候出现corner case
var floodFill = function(image, sr, sc, newColor) {
const rowN = image.length, colN = image[0].length;
if(image[sr][sc] === newColor) return image;
return dfs(image, sr, sc, image[sr][sc], newColor);
};
function dfs(grid, row, col, oldColor, newColor){
if(!grid[row] || oldColor !== grid[row][col]) return grid;
grid[row][col] = newColor;
return dfs(grid, row - 1, col, oldColor, newColor) &&
dfs(grid, row + 1, col, oldColor, newColor) &&
dfs(grid, row, col - 1, oldColor, newColor) &&
dfs(grid, row, col + 1, oldColor, newColor);
}
547. Friend Circles
找基友的故事, 这次dfs时候走的方向不再是上, 下, 左, 右, 而是直接是一行(或者一列),
当M[i][j]为1的时候, 那么我们需要继续遍历所有i的朋友, j的朋友, 极其朋友的朋友,
直到访问完整个朋友圈. 即M[i][0 ~ n], M[j][0 ~ n].
var findCircleNum = function(M) {
if(!M || !M[0]) return 0;
const len = M.length;
let count = 0;
for(let n = 0; n < len; n++)
{
count += dfs(M, n, n);
}
return count;
};
function dfs(M, row, col){
if(!M[row][col]) return 0;
M[row][col] = M[col][row] = 0;
const n = M.length;
for(let i = 0; i < n; i++)
{
dfs(M, row, i);
dfs(M, col, i);
}
return 1;
}
463. Island Perimeter
求岛屿周长的题目, 岛屿的周边就是岛屿与外界的边界, 可以表述为岛屿与水的相交处,
而显然这个相交处对于一个岛屿各自和水各自来说是唯一的。一条边在这个系统里面被
2个各自共享。
最开始的dfs解法
var islandPerimeter = function(grid) {
const rowN = grid.length, colN = grid[0].length;
for(let row = 0; row < rowN; row++)
{
for(let col = 0; col < colN; col++)
{
if(grid[row][col]) return dfs(grid, row, col);
}
}
};
function dfs(grid, row, col){
if(!grid[row] || !grid[row][col]) return 1;
if(2 === grid[row][col]) return 0;
grid[row][col] += 1; // this island is checked !!!
return dfs(grid, row - 1, col) + dfs(grid, row + 1, col) +
dfs(grid, row, col - 1) + dfs(grid, row, col + 1);
}
不过后来看到题解有利用期数学原理的更强解法!!!
公式: 边长 = 岛屿数量 * 4 - 两个岛屿的临边数量 * 2;
这个解法的好处是不需要修改输入的gird, 而且能在O(n^2)时间复杂度内解决问题。
var islandPerimeter = function(grid) {
const rowN = grid.length, colN = grid[0].length;
let islandCount = 0, innerEdgeCount = 0;
for(let row = 0; row < rowN; row++)
{
for(let col = 0; col < colN; col++)
{
if(!grid[row][col]) continue;
islandCount += 1;
if(grid[row][col + 1] ) innerEdgeCount += 1;
if(grid[row + 1] && grid[row + 1][col]) innerEdgeCount += 1;
}
}
return 4 * islandCount - 2 * innerEdgeCount;
};
419. Battleships in a Board
题设要求不修改gird, 并且时间复杂度为O(N^2). 题目关键在于:
- 判断一个格子是否是一个矩形的开头:
任意横放或者竖放的矩形条的首格必然在上, 左方向上与水域相交。
即: 0 === board[row - 1][col] && 0 === board[row][col - 1] - 判断是否存在十字交叉的部分:
如果出现十字交叉的点, 假设坐标为p(row, col), 那么这个格子右, 下边界必然与岛屿相邻。
即: 1 === board[row - 1][col] && 1 === board[row][col - 1]
var countBattleships = function(board) {
const rowN = board.length, colN = board[0].length;
let count = 0, tmp;
for(let row = 0; row < rowN; row++)
{
for(let col = 0; col < colN; col++)
{
if(!matrixVal(board, row, col)) continue;
if(0 === matrixVal(board, row - 1, col) + matrixVal(board, row, col - 1)) count += 1;
if(2 === matrixVal(board, row + 1, col) + matrixVal(board, row, col + 1)) return 0; // 这是一个非法的排列
}
}
return count;
};
function matrixVal(matrix, row, col){
if(!matrix[row] || !matrix[row][col]) return 0;
return matrix[row][col] === '.' ? 0 : 1;
}
---------我是猥琐的分割线
657. Judge Route Circle
leetcode说这个题目是 547 朋友圈的类似题目, 但是怎么看都不像啊,
既然leetcode都说是了, 那就是吧, 勉强放这里.
�两种解法:
- 第一种直接模拟直, 解释器模式走起:
var judgeCircle = function(moves) {
let posX = posY = 0;
for(let move of moves)
{
switch(move)
{
case 'U':
posY -= 1;
break;
case 'D':
posY += 1;
break;
case 'L':
posX -= 1;
break;
case 'R':
posX += 1;
break;
default:
throw '位置的指令: ' + move;
}
}
return 0 === posX && 0 === posY;
};
- 实际上只要对应方向的指令数量一致,那么对应方向最后一定回到原点:
var judgeCircle = function(moves) {
const record = { R: 0, L: 0, U: 0, D: 0 };
for(let move of moves) record[move] += 1;
return (record.R === record.L) && (record.U === record.D);
};
two-sum problems
1. Two Sum
要求在一个无序数组中找到2个和为target的数, 然后返回这两个数的位置,
利用HashMap来做, 直接通过记录和查表,时间复杂度 O(NlogN)。唯一注意比较特殊一点,
同一个数字不能用2次, 但是相等的2个不同数字是合法的。
解法:
var twoSum = function(nums, target) {
const numTable = {};
for(let i = 0, len = nums.length, j, num; i < len; i++)
{
num = nums[i];
j = numTable[target - num];
if(undefined !== j && i !== j) return [j, i];
numTable[num] = i;
}
}
167. Two Sum II - Input array is sorted
这个可以利用DP来做, 算法时间复杂度为O(N), 如果数组没有排序那么加上排序那么就是O(NlogN)
var twoSum = function(numbers, target) {
let begin = 0, end = numbers.length - 1, tmp;
while(begin < end)
{
tmp = numbers[begin] + numbers[end];
if(tmp === target) return [begin + 1, end + 1];
tmp > target ? end -= 1 : begin += 1;
}
};
653. Two Sum IV - Input is a BST
var findTarget = function(root, k) {
const nums = inOrder(root, []);
return twoSum(nums, k);
};
function inOrder(root, vals){
if(!root) return vals;
inOrder(root.left, vals);
vals.push(root.val);
inOrder(root.right, vals);
return vals;
}
function twoSum(nums, target){
let begin = 0, end = nums.length - 1, sum;
while(begin < end)
{
sum = nums[begin] + nums[end];
if(target === sum) return true;
sum > target ? end -= 1 : begin += 1;
}
return false;
}
594. Longest Harmonious Subsequence
令count(n)为数字n在数组nums中出现的次数,
根据题目描述我们需要找到两个相邻的数字: n, n + 1,使得: count(n) + count(n + 1)最大, 就可以了,
所以我们的解题步骤分为2步:
- 遍历nums, 统计各个数字的出现次数, 生成一张查询表countTable, 这里直接hash, 复杂度nlogn
- 遍历countTable, 找到count(n) + count(n+1)的最大值
ps: 注意js的类型转换, Object的key会被转成string来处理
var findLHS = function(nums) {
const countTable = countNums(nums);
let max = 0;
for(let num in countTable)
{
num = Number(num); // 注意这里转一下!!!
if(countTable.hasOwnProperty(num + 1)) max = Math.max(max, countTable[num] + countTable[num + 1]);
if(countTable.hasOwnProperty(num - 1)) max = Math.max(max, countTable[num] + countTable[num - 1]);
}
return max;
};
function countNums(nums){
const countTable = {};
for(let num of nums)
{
if(!countTable[num]) countTable[num] = 0;
countTable[num] += 1;
}
return countTable;
}
780. Reaching Points
var reachingPoints = function(sx, sy, tx, ty) {
const history = {};
let preRound = [[sx, sy]], round = [],
x, y, newPosStr;
while(preRound.length)
{
for(let pos of preRound)
{
x = pos[0];
y = pos[1];
if(nextPos(x + y, y, tx, ty, round, history)) return true;
if(nextPos(x, x + y, tx, ty, round, history)) return true;
}
preRound = round;
round = [];
}
return false;
};
function nextPos(x, y, sx, sy, round, history){
if(x === sx && y === sy) return true;
if(x > sx || y > sy) return false;
let newPosStr = x + '_' + y; // Let's encode position.
if(history[newPosStr]) return;
round.push([x, y]);
history[newPosStr] = true;
return false;
}
广度优先搜索, 直接超时, OTL+1
思考题目, 明显有规律的!!!
var reachingPoints = function(sx, sy, tx, ty) {
let [x, y] = [tx, ty];
while(x >= sx && y >= sy)
{
if(x === sx && y === sy) return true;
if(x === sx) return (y - sy) % x === 0;
if(y === sy) return (x - sx) % y === 0;
[x, y] = (x > y) ? [x - y, y] : [x, y - x];
}
return false;
};
各类DFS, BFS问题
872. Leaf-Similar Trees
分解为2个基本的子问题:
- 求取一颗二叉树的所有叶子节点,preOrder 遍历整棵树,并记录叶子节点。
- 比较2颗数的叶子节点是否等价。
注意:�bfs 遍历只能获得最后一层的叶子节点。
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
/**
* @param {TreeNode} root1
* @param {TreeNode} root2
* @return {boolean}
*/
var leafSimilar = function(root1, root2) {
const [leaves1, leaves2] = [dfs(root1), dfs(root2)];
if(leaves1.length !== leaves2.length) return false;
for(let i = 0, len = leaves1.length; i < len; i++) {
if(leaves1[i].val !== leaves2[i].val) return false;
}
return true;
};
const dfs = (root, vs = []) => {
if(!root) return vs;
if(!root.left && !root.right) {
vs.push(root);
}
dfs(root.left, vs);
dfs(root.right, vs);
return vs;
}
688. Knight Probability in Chessboard
棋盘大小为N, 假设马儿每次往8个方向上的移动都是随机的,
问在走了K步以后, 马儿留在棋盘上的概率是多少?
- 首先考虑暴力破解, 利用dfs进行求解, 算法时间复杂度O(8^K):
var knightProbability = function(N, K, r, c) {
let count = dfs(N, K, r, c, 0);
return count / (8 ** K);
};
const Moves = [[-2, -1], [-2, 1], [2, -1], [2, 1], [-1, -2], [1, -2], [-1, 2], [1, 2]];
function dfs(N, maxDeep, posX, posY, deep){
const isOnTheBoard = (posX >= 0 && posX < N && posY >= 0 && posY < N);
if(!isOnTheBoard) return 0;
if(deep >= maxDeep) return 1;
let count = 0;
for(let move of Moves) count += dfs(N, maxDeep, posX + move[0], posY + move[1], deep + 1) ;
return count;
}
果断超时, Time Limit Exceeded, OTL, 跪+1.
- 考虑记录每一个位置, 还有K步要走的时候的生存概率
var knightProbability = function(N, K, r, c) {
const record = { }; // 三维矩阵...算了还是hash表好点
const count = dfs(N, K, r, c, 0, record);
return count / (8 ** K);
};
const Moves = [[-2, -1], [-2, 1], [2, -1], [2, 1], [-1, -2], [1, -2], [-1, 2], [1, 2]];
function dfs(N, maxDeep, posX, posY, deep, record){
const isOnTheBoard = (posX >= 0 && posX < N && posY >= 0 && posY < N);
if(!isOnTheBoard) return 0;
if(maxDeep === deep) return 1;
// 查表, 如果有记录那么直接返回记录!!!
let count = record[encodeStatus(posX, posY, maxDeep - deep)];
if(count) return count;
count = 0;
for(let move of Moves) count += dfs(N, maxDeep, posX + move[0], posY + move[1], deep + 1, record);
record[encodeStatus(posX, posY, maxDeep - deep)] = count;
return count;
}
function encodeStatus(posX, posY, step){
return [posX, posY, step].join('_');
}
通过对历史的记录这样减少了很多重复的搜索,AC掉, 算法时间复杂度O(N^2 * K).
- TODO 用DP的方式重新写一次
状态转移函数!!!!
397. Integer Replacement
var integerReplacement = function(n) {
const record = { 1: Number.MAX_SAFE_INTEGER };
dfs(record, n, -1);
return record[1];
};
function dfs(record, n, distance){
if(record[n] > ++distance) record[n] = distance;
if(1 === n || distance >= record[1]) return;
n % 2 ? (dfs(record, n + 1, distance) || dfs(record, n - 1, distance)) :
dfs(record, n / 2, distance);
}
752. Open the Lock
这个是个BFS的问题, 如果用DFS那么可能的最大搜索深度会是10^4, 只能用BFS来解,
这道题注意一点, 密码锁是可以反着拨的, 1200, 可以变成1100, 也就是说每个状态的邻接状态是8个!
var openLock = function(deadends, target) {
if(-1 !== deadends.indexOf('0000')) return -1;
const record = { "0000": 0 };
for(let deadend of deadends) record[deadend] = -1;
return bfs(record, [[0, 0, 0, 0]], target, 0);
};
function bfs(record, statusList, target, deep){
if(!statusList.length) return -1;
let len = statusList.length, status, statusNum, nextStatus;
while(len--)
{
status = statusList.shift();
statusNum = status.join('');
if(statusNum === target) return deep;
for(let i = 0; i < 4; i++)
{
for(let j of [1, 9])
{
nextStatus = status.slice();
nextStatus[i] = (status[i] + j) % 10;
if(undefined !== record[nextStatus.join('')]) continue;
record[nextStatus.join('')] = deep + 1;
statusList.push(nextStatus);
}
}
}
return bfs(record, statusList, target, deep + 1);
}
1004. Max Consecutive Ones
1004. Max Consecutive Ones III
这道题感觉类似贪心的经典题目,问一个数组最大连续和问题的变种。
781. Rabbits in Forest
具有相同报数的兔子,可以认为是一个组的, 直到相同报数的兔子溢出
var numRabbits = function(answers) {
let sum = 0, record = [];
for(let answer of answers)
{
if(!record[answer])
{
record[answer] = 0;
sum += (answer + 1);
}
record[answer] += 1;
if(record[answer] > answer) record[answer] = 0;
}
return sum;
};
各种股票买卖类问题(Dp)
121. 买卖股票的最佳时机
思路一:先假定在确定的某天 i 天卖出,求买入时机,以使得收益最大为
题设限制我们只能在买卖一次,那么考虑对于任何一天 i 卖出,我们必定需要在 [0..i] 内进行过一次买入,
我们能获得的最大收益是在 [0..i] 天内,股价最低的时候进行买入。
那么为了方便查询任意 [0..i] 内的最小股价,我们可以使用前缀最小值来方便查询。
那么任何一天卖出所能获得的最大收益,可以表述为公式:
profit[i] = prices[i] - preMin[i]
思路二:动态规划,考虑到状态转移
让我们考虑我们当前的股票交易状态如下:
- waiting 观望,未持有
- holding 以持有
- liquidation 清仓,消耗掉所有的买卖机会
对于任意时刻 i,用户可以通过执行一些操作(买入或者卖出股票)转移到另外一种状态中:
type Status = "waiting" | "holding" | "liquidating";
function maxProfit(prices: number[]): number {
const N = prices.length;
// dp[0].liquidating 为 0,代表不炒股的策略,如果必须至少进行一次交易,这个值就为负无穷
const dp: Array<Record<Status, number>> = Array(N).fill(null).map(v => ({
waiting: 0,
holding: -prices[0],
liquidating: 0,
}));
for(let i = 1; i < N; i++) {
dp[i].waiting = dp[i - 1].waiting;
dp[i].holding = Math.max(
dp[i - 1].waiting - prices[i],
dp[i - 1].holding
);
dp[i].liquidating = Math.max(
dp[i - 1].holding + prices[i],
dp[i - 1].liquidating
);
}
return dp[N - 1].liquidating;
};
思路三:从差分的角度考虑问题
611. Valid Triangle Number
题目: 从一个数组里面找到所有能构成三角形三边的三个数, 保证数组长度不超过1000, 数字范围0 ~ 1000
d
牛顿法解方程
TODO !!!
现在考虑 y = x^2 - n 的情况,
数学相关的问题
258. Add Digits
解法一:就是简单的递归
/**
* @param {number} num
* @return {number}
*/
var addDigits = function(num) {
while(num > 9) {
num = addDigitsOnce(num);
}
return num;
};
const addDigitsOnce = num => {
let sum = 0;
while(num > 0) {
sum += (num % 10);
num = (num / 10 | 0);
}
return sum;
}
解法二:
652. Find Duplicate Subtrees
这道题的关键是二叉树的序列化
var findDuplicateSubtrees = function(root) {
const duplicateSubtrees = [ ], table = { };
treeToString(root, table, duplicateSubtrees);
return duplicateSubtrees;
};
// 序列化树为字符串
function treeToString(root, table, duplicateSubtrees){
let s;
if(!root) return '';
s = root.val.toString();
if(root.left || root.right)
{
s += '(' + treeToString(root.left, table, duplicateSubtrees) + ')';
if(root.right) s += '(' + treeToString(root.right, table, duplicateSubtrees) + ')';
}
if(!table[s]) table[s] = 0;
table[s] += 1;
if(table[s] === 2) duplicateSubtrees.push(root);
return s;
}
各种edit distance问题
72. Edit Distance
Levenshtein Distance 令函数edit(i, j)表示字符串s1的子串(0, i), s2的子串(0, j)的最小编辑距离,
则有: edit(i, j) = min(edit(i - 1, j), edit(i, j - 1), edit(i - 1, j - 1) + equal(s1[i - 1], s2[j - 1]));
var minDistance = function(word1, word2) {
let curStatus = [], preStatus = [];
for(let i = word2.length; i >= 0; i--) preStatus[i] = i;
for(let row = 0, rowLen = word1.length; row < rowLen; row++)
{
curStatus[0] = row + 1;
for(let col = 1, colLen = word2.length; col <= colLen; col++)
{
curStatus[col] = Math.min(
curStatus[col - 1] + 1,
preStatus[col] + 1,
preStatus[col - 1] + (word1[row] === word2[col - 1] ? 0 : 1)
);
}
[preStatus, curStatus] = [curStatus, preStatus];
}
return preStatus.pop();
};
583. Delete Operation for Two Strings
这里我们需要理解, 每次状态转移的时候, 对应于i, j值得变化, 对应的编辑操作是什么.
- 对s1进行添加字符操作, 对应的状态转移是s(i, j) + 1=> s(i, j + 1)
- 对s1进行删除字符操作, 对应的状态转移是s(i, j) + 1 => s(i + 1, j)
- 对s1的添加, 等效于对s2的删除, 反之亦然, 这里蕴含着对称性.
- (已被ban)对s1进行修改操作, 对应的状态转移是s(i, j) => s(i + 1, j + 1), 不过如果对应的word1[i] === word2[j], 实际上就不需要修改.
- 由于修改操作被ban, s(i, j) => s(i + 1, j + 1), 可以等效为同时对word1, word2做了删除字符操作.
综上所述, 这道题目实际上我们只需要, 在上一道题目的基础上改动一个字符, 就可以AC了.
var minDistance = function(word1, word2) {
let preStatus = [], curStatus = [];
for(let i = 0, len = word2.length; i <= len; i++) preStatus[i] = i;
for(let row = 0, rowLen = word1.length; row < rowLen; row++)
{
curStatus[0] = row + 1;
for(let col = 1, colLen = word2.length; col <= colLen; col++)
{
curStatus[col] = Math.min(
curStatus[col - 1] + 1,
preStatus[col] + 1,
preStatus[col - 1] + (word1[row] === word2[col - 1] ? 0 : 2)
);
}
[preStatus, curStatus] = [curStatus, preStatus];
}
return preStatus.pop();
};
@todo LCS !!!!
712. Minimum ASCII Delete Sum for Two Strings
这道题相当不错, 很NICE, 现在我们在编辑操作中引入了对于不同字符的权重的考虑, 根据字符的不同现在字符串操作带来的开销会变得不一样. 这里我们先整理下, 各种操作对应的状态转移:
- 对s1进行删除字符操作, 对应的状态转移是s(i, j) + weight(word1[i]) => s(i + 1, j)
- 对s2进行删除字符操作, 对应的状态转移是s(i, j) + weight(word2[j])=> s(i, j + 1)
- 对s1的添加, 现在不等效于对s2的删除, 因为字符不同, 权重不同.
- 修改操作已被ban
- 由于修改操作被ban, 但是可以等效为同时对s1, s2做了删除字符操作,
s(i, j) + weight(word1[i]) + weight(word2[j]) => s(i + 1, j + 1)
综上所述, 这道题目实际上可以在72的基础上, 引入权重, 这里一定要注意,初始化和状态转移的过程中,
对应的状态的值要做好对应的处理!
var minimumDeleteSum = function(word1, word2) {
let preStatus = [0], curStatus = [];
for(let i = 1; i <= word2.length; i++)
{
preStatus[i] = preStatus[i - 1] + word2.charCodeAt(i - 1);
}
for(let row = 0, rowLen = word1.length; row < rowLen; row++)
{
curStatus[0] = preStatus[0] + word1.charCodeAt(row);
for(let col = 1, colLen = word2.length; col <= colLen; col++)
{
curStatus[col] = Math.min(
curStatus[col - 1] + word2.charCodeAt(col - 1),
preStatus[col] + word1.charCodeAt(row),
preStatus[col - 1] + (word1[row] === word2[col - 1] ? 0 :
word1.charCodeAt(row) + word2.charCodeAt(col - 1))
);
}
[preStatus, curStatus] = [curStatus, preStatus];
}
return preStatus.pop();
};
718. Maximum Length of Repeated Subarray
最长公共子数组问题
var findLength = function(A, B) {
const rowN = A.length, colN = B.length;
let preRound = [], round = [0], max = 0, tmp;
for(let i = 0; i <= colN; i++) preRound[i] = 0;
for(let row = 1; row <= rowN; row++)
{
for(let col = 1; col <= colN; col++)
{
tmp = A[row - 1] === B[col - 1] ? 1 : 0;
round[col] = tmp ? preRound[col - 1] + 1 : 0;
max = Math.max(round[col], max);
}
preRound = round;
round = [0];
}
return max;
};
字符数量统计类题目(名字 todo)总结
相同结构的问题:
349. Intersection of Two Arrays
1002. Find Common Characters
350. Intersection of Two Arrays II
这一类的问题可以总结出3基本的过程:
- itemCount:统计一个字符串或数组内各个元素的出现次数,生成一张统计表
- mergeRecord:将多个统计表依据一定的规则合并成一张新的统计表
- expandRecord: 将统计表中每一项按照对应的重复次数映射成一个数组,然后将获得的结果拍平
用到的一些基础操作:
- 在数组的 reduce 方法中,可以利用逗号运算符来达道精简代码的目的。
- 已知一个字符串数组将其映射成一个 object:strs.reduce((total, s) => (total[s] = true, total), {});
- 利用 Array.fill 生成一个所有值为特定值的数组:Array(count).fill(item)
- 利用 Array.push 和数组的解构运算符,向数组添加项目:arrA.push(...arrB);
/**
* @param {number[]} nums1
* @param {number[]} nums2
* @return {number[]}
*/
var intersect = function(nums1, nums2) {
const records = [
itemCount(nums1),
itemCount(nums2)
];
const totalRecord = mergeRecord(records);
const alphas = expandRecord(totalRecord);
return alphas.map(Number);
};
const itemCount = (arr) => arr.reduce((total, item) => (
total[item] = (total[item] || 0) + 1,
total
), {});
const mergeRecord = records => Object.keys(records[0]).reduce((total, key) => (
total[key] = Math.min(...records.map(r => r[key] || 0)),
total
), {});
const expandRecord = record => Object.entries(record).reduce((total, [item, count]) => (
total.push(...Array(count).fill(item)),
total
), []);
桶排序
- Divide Array in Sets of K Consecutive Numbers
最开始没完全读懂题,这里有个很强的条件,不要被分割的各个子序列的顺序和原数组中数据一致。
想起了整理一副扑克牌的日子。有了以下解法,不过效率很低,速度只排到 17%。
/**
* @param {number[]} nums
* @param {number} k
* @return {boolean}
*/
var isPossibleDivide = function(nums, k) {
if(nums.length % k !== 0) return false;
if(k === 1) return true;
const sortedNums = nums.sort((x, y) => x > y ? 1 : -1);
let record = {}, sequenceCount = 0;
for(let num of sortedNums) {
const slot = record[num - 1];
(!record[num]) && (record[num] = []);
if(slot && slot.length > 0) {
let sequence = slot.pop();
sequence += 1;
if(sequence === k) {
sequenceCount -= 1;
continue;
}
record[num].push(sequence);
} else {
record[num].push(1);
sequenceCount += 1;
}
}
return sequenceCount === 0;
};
进一步考虑到不要求顺序,可以利用一个 hashMap 构成桶来计数
parse 相关问题
1309. Decrypt String from Alphabet to Integer Mapping
TODO: 可以优化的点:
- 字母表的生成
- 字符串截取用其他的函数
const ALPHA_MAP = {
'1': 'a',
'2': 'b',
'3': 'c',
'4': 'd',
'5': 'e',
'6': 'f',
'7': 'g',
'8': 'h',
'9': 'i',
'10#': 'j',
'11#': 'k',
'12#': 'l',
'13#': 'm',
'14#': 'n',
'15#': 'o',
'16#': 'p',
'17#': 'q',
'18#': 'r',
'19#': 's',
'20#': 't',
'21#': 'u',
'22#': 'v',
'23#': 'w',
'24#': 'x',
'25#': 'y',
'26#': 'z',
}
/**
* @param {string} s
* @return {string}
*/
var freqAlphabets = function(text) {
let offset = 0, alphas = [], len = text.length;
while(offset < len) {
const token = (text[offset + 2] === '#') ?
text.slice(offset, offset + 3) :
text.slice(offset, offset + 1);
alphas.push(ALPHA_MAP[token]);
offset += token.length;
}
return alphas.join('');
};
Binary Search Problems
最为传统的二分查找是在一个单调的序列中找到对应的那个数字, 数轴上的3个位置: begin, middle, end.
当array[middle] > target时, 我们所需求的值在区间: [begin, middle - 1],
当array[middle] < target时, 我们所需求的值在区间: [middle + 1, end]
不断循环这个过程并更新begin, end直到begin > end.
69. Sqrt(x) 二分查找解法:
- 和经典的模型比起来, 唯一的区别在于当middle < sqrt(x) 的时候, 我们的搜索区间变成[middle, end],
注意这里的左边界是关闭的, 这时边界的处理需要小心一点:
var mySqrt = function(x) {
let begin = 0, end = x, middle, tmp;
while(1 < end - begin)
{
middle = (begin + end) / 2 | 0;
middle * middle > x ? end = middle - 1 : begin = middle;
}
return end * end <= x ? end : begin;
};
- 牛顿法开平方
/*
* 牛顿法开平方
* 令方程: y= x^2 - n,
* 当x > 0时过任意一点x做切线与x轴交点, x = (a^2 + n)/2a, y = 0
*/
var mySqrt = function(x) {
let n = x;
while(x * x > n) x = ((x * x + n) / (2 * x)) | 0;
return x;
}
todo 看到讨论中的bit操作方法
367. Valid Perfect Square
同69题:
var isPerfectSquare = function(num) {
let begin = 1, end = num, middle;
while(begin <= end)
{
middle = (begin + end) / 2 | 0;
middle * middle > num ? end = middle - 1 : begin = middle + 1;
}
return Math.pow(begin - 1, 2) === num;
};
127. Word Ladder
这道题目卡了很久, 于是这里记录下来做题思路.
- 起手直接BFS
const Alphas = [];
for(let i = 'a'.charCodeAt(0), len = 'z'.charCodeAt(0), alpha; i <= len; i++)
{
Alphas.push(String.fromCharCode(i));
}
var ladderLength = function(beginWord, endWord, wordList) {
const record = { }, words = {};
record[beginWord] = 1;
for(let word of wordList) words[word] = true;
return bfs(words, record, [beginWord.split('')], endWord, 1);
};
function bfs(words, record, statusList, target, deep){
let len;
while(true)
{
len = statusList.length;
if(0 === len) return 0;
while(len--)
{
getNewStatusList(words, record, statusList, statusList.shift(), target, deep);
if(record[target]) return record[target];
}
deep += 1;
}
}
function getNewStatusList(words, record, statusList, status, target, deep){
let newStatus, word;
for(let i = 0, len = status.length; i < len; i++)
{
// 这个判断可以使速度提升5倍左右, 但是会导致一些解无法搜索出来, 剪枝失败, OTL 跪+2
// if(status[i] === target[i]) continue;
for(let alpha of Alphas)
{
newStatus = status.slice();
newStatus[i] = alpha;
word = newStatus.join('');
if(!words[word] || undefined !== record[word]) continue;
record[word] = deep + 1;
statusList.push(newStatus);
}
}
}
直接超时, OTL 跪+1,
好好分析题目: 令单词长度为n, 每一位可选字母数为k, 搜索深度为deep. 那么单词总的状态空间大小为: k^n.
当前策略是, 每次对一个单词按照每一位进行变换, 那么每一次状态变换搜索状态数为k * n,
那么算法时间复杂度可以估计为: k * n * deep
-
思考现实中有效的英语单词数量, 远小于可生成的单词数量。那么每一次状态转移,我们不是去生成,
而是搜索现有单词(wordList)中有哪些单词与当前单词的距离为1, 是否能更快的得到答案?
每一次状态转移需要搜索的状态数为: wordListLen * n,
那么算法时间复杂度可以估计为: wordListLen * n * deep.
而这里wordListLen > k, 更慢了 OTL 跪+3 -
马勒戈壁啊混蛋!!!!!
PS: 时间18/1/18, 这个笔记当时写了20多分钟忘了存, 点了其他页面数据全部清空, 啊~~~.
各种二叉树问题
530. Minimum Absolute Difference in BST
求二叉树上两值得最小绝对值, 一个很朴素的解法, 先用中序遍历生成数组, 然后就很简单了.
var getMinimumDifference = function(root) {
const vals = inOrder(root, []);
let min = Number.MAX_SAFE_INTEGER;
for(let i = 1, len = vals.length; i < len; i++)
{
min = Math.min(min, vals[i] - vals[i - 1]);
}
return min;
};
function inOrder(root, vals){
if(!root) return vals;
inOrder(root.left, vals);
vals.push(root.val);
inOrder(root.right, vals);
return vals;
}
图论
模板
class VertexNode<T> {
indegree = 0;
private nbrSet = new Set<T>();
addNbr(nbr: T) {
this.nbrSet.add(nbr);
}
get nbrs() {
return this.nbrSet.values();
}
get outgree() {
return this.nbrSet.size;
}
}
const buildGraph = <T,>(edges: ReadonlyArray<[T, T, number]>) => {
type VNode = VertexNode<T>;
const graph = new Map<T, VNode>();
const weightMap = new Map<T, Map<T, number>>();
const getNode = (v: T): VNode => {
if(!graph.has(v)) {
graph.set(v, new VertexNode());
weightMap.set(v, new Map<T, number>());
}
return graph.get(v);
}
const getNbrs = (v: T) => getNode(v).nbrs;
const getWeight = (src: T, dist: T) => weightMap.get(src).get(dist);
edges.forEach(([src, dist, cost]) => {
getNode(src).addNbr(dist);
getNode(dist).indegree += 1;
weightMap.get(src).set(dist, cost);
});
return {
getNode,
getNbrs,
getWeight
};
}
const bfsOnce = <T,>(
vs: T[],
getNbrs: (v: T) => Iterable<T>,
callbacks: {
on?: (v: T, nbrs: Iterable<T>) => void,
pre?: (nbr: T, v: T) => boolean;
} = {}
) => {
const { on, pre = () => true } = callbacks;
const N = vs.length;
for(let i = 0; i < N; i++) {
const v = vs[i];
const nbrs = getNbrs(v);
on && on(v, nbrs);
for(const nbr of nbrs) {
if(!pre(nbr, v)) {
continue;
}
vs.push(nbr);
}
}
vs.splice(0, N);
}
判断有向图是否有环
https://leetcode.com/problems/course-schedule
判断有向图上某点是否可到达
- Jump Game III
这个算是一个标准的模板题目了
bfs 算法
- 构建 nextMap 和 indegreeMap(用于记录每个 vertex 的 indegree)
- 找到所有 indegree 为零的 sourceVertex
- 移除所有的 sourceVertex 节点,更新 sourceVertex 指向的 vertex 对应的 indegree 减1
s。 - 经过步骤3,已经产生了新的 sourceVertex,这时候回到步骤2,直到找不到 sourceVertex 为止
- 如果 indegreeMap 为空,说明图上没有环。
dfs 算法
1. 构建 nextMap 和 statusMap �(0:未访问,1已访问,2在路径中)
2. 遍历每一个 vertex ,如果已访问则跳过,变更其状态为 IN_PATH
3. 深度优先遍历 vertex 的每一个 nextVertex,
TODO:
代码如下:
/**
* @param {number} numCourses
* @param {number[][]} prerequisites
* @return {boolean}
*/
var canFinish = function(numVertexs, edges) {
const { nextMap, indegreeMap, statusMap } = buildGraph(numVertexs, edges);
return !dfs(nextMap, statusMap);
// return !bfs(nextMap, indegreeMap);
};
const UNVISIT = 0;
const VISIT = 1;
const IN_PATH = 2;
function dfs(nextMap, statusMap) {
for(let vertex in statusMap) {
if(statusMap[vertex] !== UNVISIT) continue;
if(dfsVertex(vertex, nextMap, statusMap)) return true;
}
return false;
}
function dfsVertex(vertex, nextMap, statusMap) {
if(statusMap[vertex] === IN_PATH) return true;
if(statusMap[vertex] === VISIT) return false;
statusMap[vertex] = IN_PATH;
for(let next of nextMap[vertex]) {
if(dfsVertex(next, nextMap, statusMap)) return true;
}
statusMap[vertex] = VISIT;
return false;
}
function bfs(nextMap, indegreeMap) {
let [sourceVertexs, nextSourceVertexs] = [[], []];
for(let vertex in indegreeMap) {
if(indegreeMap[vertex] === 0) sourceVertexs.push(vertex);
}
while(sourceVertexs.length !== 0) {
while(sourceVertexs.length > 0) {
const sourceVertex = sourceVertexs.pop();
for(let next of nextMap[sourceVertex]) {
indegreeMap[next] -= 1;
if(indegreeMap[next] === 0) nextSourceVertexs.push(next);
}
delete indegreeMap[sourceVertex];
}
[sourceVertexs, nextSourceVertexs] = [nextSourceVertexs, sourceVertexs];
}
return Object.keys(indegreeMap).length !== 0;
}
const buildGraph = (numVertexs, edges) => {
const nextMap = {}, indegreeMap = {}, statusMap = {};
for(let vertex = 0; vertex < numVertexs; vertex ++) {
nextMap[vertex] = [];
indegreeMap[vertex] = 0;
statusMap[vertex] = UNVISIT;
}
for(let [start, end] of edges) {
nextMap[start].push(end);
indegreeMap[end] += 1;
}
return {
nextMap,
indegreeMap,
statusMap
}
}
1030. Matrix Cells in Distance Order
/**
* @param {number} R
* @param {number} C
* @param {number} r0
* @param {number} c0
* @return {number[][]}
*/
const VERTEX_STATUS = {
unvisit: 0,
visited: 1
};
var allCellsDistOrder = function(maxX, maxY, startX, startY) {
const matrix = createMatrix(maxX, maxY, VERTEX_STATUS.unvisit);
const startV = { x: startX, y: startY };
matrix[startV.x][startV.y] = VERTEX_STATUS.visited;
const vs = [startV];
bfs([startV], matrix, v => vs.push(v));
const cells = vs.map(v => [v.x, v.y]);
return cells;
};
/**
* 初始化一个二维矩阵
*/
const createMatrix = (maxX, maxY, initVal) => {
const matrix = [];
for(let x = 0; x < maxX; x++) {
const row = [...new Array(maxY)].map(val => initVal);
matrix.push(row);
}
return matrix;
};
const bfs = (startVs, matrix, visitor) => {
let [vs, nextVs] = [startVs, []], topOrder = 0, v;
while(vs.length > 0) {
topOrder += 1;
while((v = vs.pop()) !== undefined) {
const nbrs = getBrs(v, matrix);
for(let nbr of nbrs) {
const nbrVal = matrix[nbr.x][nbr.y];
if(nbrVal !== VERTEX_STATUS.unvisit) continue;
visitor(nbr, nbrVal, topOrder);
nextVs.push(nbr);
matrix[nbr.x][nbr.y] = VERTEX_STATUS.visited;
}
}
[vs, nextVs] = [nextVs, vs];
}
};
const isValidVertex = ({ x, y }, maxX, maxY) => {
return 0 <= x && x < maxX && 0 <= y && y < maxY;
}
const getBrs = ({ x, y }, matrix) => {
const [maxX, maxY] = [matrix.length, matrix[0].length];
const vs = [
{ x: x - 1, y },
{ x, y: y + 1 },
{ x: x + 1, y },
{ x, y: y - 1 }
];
return vs.filter(v => isValidVertex(v, maxX, maxY));
}
1162. As Far from Land as Possible
/**
* @param {number[][]} grid
* @return {number}
*/
var maxDistance = function(matrix) {
if(!matrix) return -1;
const startVs = [];
const [maxX, maxY] = [matrix.length, matrix[0].length];
for(let x = 0; x < maxX; x++) {
for(let y = 0; y < maxY; y++) {
const val = matrix[x][y];
if(val !== VERTEX_STATUS.visited) continue;
startVs.push({ x, y });
}
}
let maxTopOrder = -1;
bfs(startVs, matrix, (v, vVal, topOrder) => maxTopOrder = topOrder);
return maxTopOrder;
};
const VERTEX_STATUS = {
unvisit: 0,
visited: 1,
};
const bfs = (startVs, matrix, visitor) => {
let [vs, nextVs] = [startVs, []], topOrder = 0, v;
while(vs.length > 0) {
topOrder += 1;
while((v = vs.pop()) !== undefined) {
const nbrs = getNbrs(matrix, v);
for(let nbr of nbrs) {
const nbrVal = matrix[nbr.x][nbr.y];
if(nbrVal !== VERTEX_STATUS.unvisit) continue;
visitor(nbr, nbrVal, topOrder);
nextVs.push(nbr);
matrix[nbr.x][nbr.y] = VERTEX_STATUS.visited;
}
}
[vs, nextVs] = [nextVs, vs];
}
};
const getNbrs = (matrix, { x, y }) => {
const [maxX, maxY] = [matrix.length, matrix[0].length];
const vs = [
{ x, y: y - 1 },
{ x: x + 1, y },
{ x, y: y + 1 },
{ x: x - 1, y },
];
return vs.filter(v => isValidVertex(v, maxX, maxY));
};
const isValidVertex = ({ x, y }, maxX, maxY) => {
return 0 <= x && x < maxX && 0 <= y && y < maxY;
}
572. Subtree of Another Tree
判断一个树t是不是另外一个树s的子树: 令Size(root)表示树root中节点的数量,
- 我们先暴力判断, 算法复杂度O(size(t) * size(s))
var isSubtree = function(s, t) {
if(!s) return false;
if(isEqualTree(s, t)) return true;
return isSubtree(s.left, t) || isSubtree(s.right, t);
};
function isEqualTree(t1, t2){
if(!t1 && !t2) return true;
if(!t1 || !t2) return false;
if(t1.val !== t2.val) return false;
return isEqualTree(t1.left, t2.left) && isEqualTree(t1.right, t2.right);
}
- todo 改进效率亚克西
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.