字符串有效性
给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。 有效字符串需满足: 左括号必须用相同类型的右括号闭合。 左括号必须以正确的顺序闭合。
思路: 第一种情况:已经遍历完了字符串,但是栈不为空,说明有相应的左括号没有右括号来匹配,所以return false 第二种情况:遍历字符串匹配的过程中,发现栈里没有要匹配的字符。所以return false 第三种情况:遍历字符串匹配的过程中,栈已经为空了,没有匹配的字符了,说明右括号没有找到对应的左括号return false
var isValid = function(s) {
const n = s.length;
if (n % 2 === 1) {
return false;
}
const pairs = new Map([
[')', '('],
[']', '['],
['}', '{']
]);
const stk = [];
for (let ch of s){
if (pairs.has(ch)) {
if (!stk.length || stk[stk.length - 1] !== pairs.get(ch)) {
return false;
}
stk.pop();
}
else {
stk.push(ch);
}
};
return !stk.length;
};
翻转二叉树/二叉树镜像
思路:我们从根节点开始,递归地对树进行遍历,并从叶子节点先开始翻转。如果当前遍历到的节点 \textit{root}root 的左右两棵子树都已经翻转,那么我们只需要交换两棵子树的位置,即可完成以 \textit{root}root 为根节点的整棵子树的翻转。
var invertTree = function(root) { if (root === null) { return null; } const left = invertTree(root.left); const right = invertTree(root.right); root.left = right; root.right = left; return root; };
比较版本号
方法一:字符串分割
我们可以将版本号按照点号分割成修订号,然后从左到右比较两个版本号的相同下标的修订号。在比较修订号时,需要将字符串转换成整数进行比较。注意根据题目要求,如果版本号不存在某个下标处的修订号,则该修订号视为 00。
var compareVersion = function(version1, version2) {
const v1 = version1.split('.');
const v2 = version2.split('.');
for (let i = 0; i < v1.length || i < v2.length; ++i) {
let x = 0,
y = 0;
if (i < v1.length) {
x = parseInt(v1[i]);
}
if (i < v2.length) {
y = parseInt(v2[i]);
}
if (x > y) {
return 1;
}
if (x < y) {
return -1;
}
}
return 0;
};
时间复杂度:O(n+m)(或 O(n,m),这是等价的),其中 nn 是字符串version1 的长度,mm 是字符串version2 的长度。
空间复杂度:O(n+m),我们需要 O(n+m) 的空间存储分割后的修订号列表。
方法二:双指针
方法一需要存储分割后的修订号,为了优化空间复杂度,我们可以在分割版本号的同时解析出修订号进行比较。
var compareVersion = function(version1, version2) {
const n = version1.length, m = version2.length;
let i = 0, j = 0;
while (i < n || j < m) {
let x = 0;
for (; i < n && version1[i] !== '.'; ++i) {
x = x * 10 + version1[i].charCodeAt() - '0'.charCodeAt();
}
++i; // 跳过点号
let y = 0;
for (; j < m && version2.charAt(j) !== '.'; ++j) {
y = y * 10 + version2[j].charCodeAt() - '0'.charCodeAt();
}
++j; // 跳过点号
if (x !== y) {
return x > y ? 1 : -1;
}
}
return 0;
};
复杂度分析
时间复杂度:O(n+m),其中 nn 是字符串version1的长度,mm 是字符串 version2 的长度。
空间复杂度:O(1),我们只需要常数的空间保存若干变量。
二进制矩阵中的所有元素不是 0 就是 1 。
四叉树交集
给你两个四叉树,quadTree1 和 quadTree2。其中 quadTree1 表示一个 n * n 二进制矩阵,而 quadTree2 表示另一个 n * n 二进制矩阵。
请你返回一个表示 n * n 二进制矩阵的四叉树,它是 quadTree1 和 quadTree2 所表示的两个二进制矩阵进行 按位逻辑或运算 的结果。
注意,当 isLeaf 为 False 时,你可以把 True 或者 False 赋值给节点,两种值都会被判题机制 接受 。
四叉树数据结构中,每个内部节点只有四个子节点。此外,每个节点都有两个属性:
val:储存叶子结点所代表的区域的值。1 对应 True,0 对应 False; isLeaf: 当这个节点是一个叶子结点时为 True,如果它有 4 个子节点则为 False 。
class Node {
public boolean val;
public boolean isLeaf;
public Node topLeft;
public Node topRight;
public Node bottomLeft;
public Node bottomRight;
}
我们可以按以下步骤为二维区域构建四叉树:
如果当前网格的值相同(即,全为 0 或者全为 1),将 isLeaf 设为 True ,将 val 设为网格相应的值,并将四个子节点都设为 Null 然后停止。 如果当前网格的值不同,将 isLeaf 设为 False, 将 val 设为任意值,然后如下图所示,将当前网格划分为四个子网格。 使用适当的子网格递归每个子节点。
如果你想了解更多关于四叉树的内容,可以参考 wiki 。
四叉树格式:
输出为使用层序遍历后四叉树的序列化形式,其中 null 表示路径终止符,其下面不存在节点。
它与二叉树的序列化非常相似。唯一的区别是节点以列表形式表示 [isLeaf, val] 。
如果 isLeaf 或者 val 的值为 True ,则表示它在列表 [isLeaf, val] 中的值为 1 ;如果 isLeaf 或者 val 的值为 False ,则表示值为 0 。
示例 1:
输入:
quadTree1 = [[0,1],[1,1],[1,1],[1,0],[1,0]]
quadTree2 = [[0,1],[1,1],[0,1],[1,1],[1,0],null,null,null,null,[1,0],[1,0],[1,1],[1,1]]
输出:[[0,0],[1,1],[1,1],[1,1],[1,0]]
解释:quadTree1 和 quadTree2 如上所示。由四叉树所表示的二进制矩阵也已经给出。
如果我们对这两个矩阵进行按位逻辑或运算,则可以得到下面的二进制矩阵,由一个作为结果的四叉树表示。
注意,我们展示的二进制矩阵仅仅是为了更好地说明题意,你无需构造二进制矩阵来获得结果四叉树。
示例 2:
输入:
quadTree1 = [[1,0]],
quadTree2 = [[1,0]]
输出:[[1,0]]
解释:两个数所表示的矩阵大小都为 1*1,值全为 0
结果矩阵大小为 1*1,值全为 0 。
示例 3:
输入:
quadTree1 = [[0,0],[1,0],[1,0],[1,1],[1,1]],
quadTree2 = [[0,0],[1,1],[1,1],[1,0],[1,1]]
输出:[[1,1]]
示例 4:
输入:quadTree1 = [[0,0],[1,1],[1,0],[1,1],[1,1]]
, quadTree2 = [[0,0],[1,1],[0,1],[1,1],[1,1],null,null,null,null,[1,1],[1,0],[1,0],[1,1]]
输出:[[0,0],[1,1],[0,1],[1,1],[1,1],null,null,null,null,[1,1],[1,0],[1,0],[1,1]]
示例 5:
输入:quadTree1 = [[0,1],[1,0],[0,1],[1,1],[1,0],null,null,null,null,[1,0],[1,0],[1,1],[1,1]]
, quadTree2 = [[0,1],[0,1],[1,0],[1,1],[1,0],[1,0],[1,0],[1,1],[1,1]]
输出:[[0,0],[0,1],[0,1],[1,1],[1,0],[1,0],[1,0],[1,1],[1,1],[1,0],[1,0],[1,1],[1,1]]
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(quadTree1, quadTree2) {
if (quadTree1.isLeaf) {
if (quadTree1.val) {
return new Node(true, true)
}
return new Node(quadTree2.val, quadTree2.isLeaf, quadTree2.topLeft, quadTree2.topRight, quadTree2.bottomLeft, quadTree2.bottomRight)
}
if (quadTree2.isLeaf) {
return intersect(quadTree2, quadTree1)
}
const o1 = intersect(quadTree1.topLeft, quadTree2.topLeft)
const o2 = intersect(quadTree1.topRight, quadTree2.topRight)
const o3 = intersect(quadTree1.bottomLeft, quadTree2.bottomLeft)
const o4 = intersect(quadTree1.bottomRight, quadTree2.bottomRight)
if (o1.isLeaf && o2.isLeaf && o3.isLeaf && o4.isLeaf && o1.val === o2.val && o1.val === o3.val && o1.val === o4.val) {
return new Node(o1.val, true)
}
return new Node(false, false, o1, o2, o3, o4)
};
隔离病毒
病毒扩散得很快,现在你的任务是尽可能地通过安装防火墙来隔离病毒。
假设世界由 m x n 的二维矩阵 isInfected 组成, isInfected[i][j] == 0 表示该区域未感染病毒,而 isInfected[i][j] == 1 表示该区域已感染病毒。可以在任意 2 个相邻单元之间的共享边界上安装一个防火墙(并且只有一个防火墙)。
每天晚上,病毒会从被感染区域向相邻未感染区域扩散,除非被防火墙隔离。现由于资源有限,每天你只能安装一系列防火墙来隔离其中一个被病毒感染的区域(一个区域或连续的一片区域),且该感染区域对未感染区域的威胁最大且 保证唯一 。
你需要努力使得最后有部分区域不被病毒感染,如果可以成功,那么返回需要使用的防火墙个数; 如果无法实现,则返回在世界被病毒全部感染时已安装的防火墙个数。
const dirs = [[-1, 0], [1, 0], [0, -1], [0, 1]];
var containVirus = function(isInfected) {
const m = isInfected.length, n = isInfected[0].length;
let ans = 0;
while (true) {
const neighbors = [];
const firewalls = [];
for (let i = 0; i < m; ++i) {
for (let j = 0; j < n; ++j) {
if (isInfected[i][j] === 1) {
const queue = [];
queue.push([i, j]);
const neighbor = new Set();
let firewall = 0, idx = neighbors.length + 1;
isInfected[i][j] = -idx;
while (queue.length > 0) {
const arr = queue.shift();
let x = arr[0], y = arr[1];
for (let d = 0; d < 4; ++d) {
let nx = x + dirs[d][0], ny = y + dirs[d][1];
if (nx >= 0 && nx < m && ny >= 0 && ny < n) {
if (isInfected[nx][ny] === 1) {
queue.push([nx, ny]);
isInfected[nx][ny] = -idx;
} else if (isInfected[nx][ny] === 0) {
++firewall;
neighbor.add(getHash(nx, ny));
}
}
}
}
neighbors.push(neighbor);
firewalls.push(firewall);
}
}
}
if (neighbors.length === 0) {
break;
}
let idx = 0;
for (let i = 1; i < neighbors.length; ++i) {
if (neighbors[i].size > neighbors[idx].size) {
idx = i;
}
}
ans += firewalls[idx];
for (let i = 0; i < m; ++i) {
for (let j = 0; j < n; ++j) {
if (isInfected[i][j] < 0) {
if (isInfected[i][j] !== -idx - 1) {
isInfected[i][j] = 1;
} else {
isInfected[i][j] = 2;
}
}
}
}
for (let i = 0; i < neighbors.length; ++i) {
if (i !== idx) {
for (const val of neighbors[i]) {
let x = val >> 16, y = val & ((1 << 16) - 1);
isInfected[x][y] = 1;
}
}
}
if (neighbors.length === 1) {
break;
}
}
return ans;
}
const getHash = (x, y) => {
return (x << 16) ^ y;
};
千分位算法
数字有小数
let format = n => {
let num = n.toString() // 转成字符串
let decimals = ''
// 判断是否有小数
num.indexOf('.') > -1 ? decimals = num.split('.')[1] : decimals
let len = num.length
if (len <= 3) {
return num
} else {
let temp = ''
let remainder = len % 3
decimals ? temp = '.' + decimals : temp
if (remainder > 0) { // 不是3的整数倍
return num.slice(0, remainder) + ',' + num.slice(remainder, len).match(/\d{3}/g).join(',') + temp
} else { // 是3的整数倍
return num.slice(0, len).match(/\d{3}/g).join(',') + temp
}
}
}
format(12323.33) // '12,323.33'
数字无小数
let format = n => {
let num = n.toString()
let len = num.length
if (len <= 3) {
return num
} else {
let remainder = len % 3
if (remainder > 0) { // 不是3的整数倍
return num.slice(0, remainder) + ',' + num.slice(remainder, len).match(/\d{3}/g).join(',')
} else { // 是3的整数倍
return num.slice(0, len).match(/\d{3}/g).join(',')
}
}
}
format(1232323) // '1,232,323
一次循环取出数组重复的值
function duplicates(arr) {
return arr.filter((e,i) => arr.indexOf(e)!==arr.lastIndexOf(e) && arr.indexOf(e)===i);
}
数组扁平化
数组拍平也称数组扁平化,就是将数组里面的数组打开,最后合并为一个数组
实现
var arr = [1, 2, [3, 4, 5, [6, 7, 8], 9], 10, [11, 12]];
a:递归实现
function fn(arr) {
let arr1 = [];
arr.forEach(val => {
if (val instanceof Array) {
arr1 = arr1.concat(fn(val));
} else {
arr1.push(val);
}
});
return arr1;
}
b:reduce 实现
function fn(arr) {
return arr.reduce((prev, cur) => {
return prev.concat(Array.isArray(cur) ? fn(cur) : cur);
}, []);
}
c:flat
参数为层数(默认一层)
arr.flat(Infinity);
d:扩展运算符
function fn(arr) {
let arr1 = [];
let bStop = true;
arr.forEach(val => {
if (Array.isArray(val)) {
arr1.push(...val);
bStop = false;
} else {
arr1.push(val);
}
});
if (bStop) {
return arr1;
}
return fn(arr1);
}
e:toString
let arr1 = arr
.toString()
.split(",")
.map(val => {
return parseInt(val);
});
console.log(arr1);
f:apply
function flatten(arr) {
while (arr.some(item => Array.isArray(item))) {
arr = [].concat.apply([], arr);
}
return arr;
}
回文数
给你一个整数 x ,如果 x 是一个回文整数,返回 true ;否则,返回 false 。 回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。 例如,121 是回文,而 123 不是。
示例 1:
输入:x = 121
输出:true
示例 2:
输入:x = -121
输出:false
解释:从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。
示例 3:
输入:x = 10
输出:false
解释:从右向左读, 为 01 。因此它不是一个回文数。
/**
* @param {number} x
* @return {boolean}
*/
var isPalindrome = function (x) {
let arr = x.toString()
arr = arr.split("")
let arrRe = arr.reverse()
let str = arrRe.join("")
if (str == x) return true
return false
};
来源:力扣(LeetCode) 链接:leetcode.cn/problems/pa…
两数之和
给定一个整数数组 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]
var twoSum = function(nums, target) {
let map = new Map();
for(let i = 0, len = nums.length; i < len; i++){
if(map.has(target - nums[i])){
return [map.get(target - nums[i]), i];
}else{
map.set(nums[i], i);
}
}
return [];
};
来源:力扣(LeetCode) 链接:leetcode.cn/problems/tw…
二维网格迁移
给你一个 m 行 n 列的二维网格 grid 和一个整数 k。你需要将 grid 迁移 k 次。
每次「迁移」操作将会引发下述活动:
位于 grid[i][j] 的元素将会移动到 grid[i][j + 1]。 位于 grid[i][n - 1] 的元素将会移动到 grid[i + 1][0]。 位于 grid[m - 1][n - 1] 的元素将会移动到 grid[0][0]。 请你返回 k 次迁移操作后最终得到的 二维网格。
示例 1:
输入:grid = [[1,2,3],[4,5,6],[7,8,9]], k = 1
输出:[[9,1,2],[3,4,5],[6,7,8]]
示例 2:
输入:grid = [[3,8,1,9],[19,7,2,5],[4,6,11,10],[12,0,21,13]], k = 4
输出:[[12,0,21,13],[3,8,1,9],[19,7,2,5],[4,6,11,10]]
示例 3:
输入:grid = [[1,2,3],[4,5,6],[7,8,9]], k = 9
输出:[[1,2,3],[4,5,6],[7,8,9]]
/**
* @param {number[][]} grid
* @param {number} k
* @return {number[][]}
*/
var shiftGrid = function (grid, k) {
let arr = grid.flat(Infinity)
k = k % arr.length
if (k) {
let arr1 = arr.slice(-k).concat(arr.slice(0, arr.length - k))
let m = grid[0].length
let newArr = new Array()
for (let i = 0; i < grid.length; i++) {
newArr.push(arr1.slice(i * m, (i + 1) * m))
}
return newArr
} else {
return grid
}
};
来源:力扣(LeetCode) 链接:leetcode.cn/problems/sh…
罗马数字转整数
罗马数字包含以下七种字符: I, V, X, L,C,D 和 M。
字符 数值
I 1
V 5
X 10
L 50
C 100
D 500
M 1000
例如, 罗马数字 2 写做 II ,即为两个并列的 1 。12 写做 XII ,即为 X + II 。 27 写做 XXVII, 即为 XX + V + II 。
通常情况下,罗马数字中小的数字在大的数字的右边。但也存在特例,例如 4 不写做 IIII,而是 IV。数字 1 在数字 5 的左边,所表示的数等于大数 5 减小数 1 得到的数值 4 。同样地,数字 9 表示为 IX。这个特殊的规则只适用于以下六种情况:
I 可以放在 V (5) 和 X (10) 的左边,来表示 4 和 9。 X 可以放在 L (50) 和 C (100) 的左边,来表示 40 和 90。 C 可以放在 D (500) 和 M (1000) 的左边,来表示 400 和 900。 给定一个罗马数字,将其转换成整数。
示例 1:
输入: s = "III"
输出: 3
示例 2:
输入: s = "IV"
输出: 4
示例 3:
输入: s = "IX"
输出: 9
示例 4:
输入: s = "LVIII"
输出: 58
解释: L = 50, V= 5, III = 3.
示例 5:
输入: s = "MCMXCIV"
输出: 1994
解释: M = 1000, CM = 900, XC = 90, IV = 4.
/**
* @param {string} s
* @return {number}
*/
var romanToInt = function (s) {
let map = new Map()
let num = 0
map.set("I", 1)
map.set("V", 5)
map.set("X", 10)
map.set("L", 50)
map.set("C", 100)
map.set("D", 500,)
map.set("M", 1000)
for (let i = s.length - 1; i >= 0; i--) {
let curr = s[i]
let pre = i > 0 ? s[i - 1] : 0
if (map.get(pre) < map.get(curr)) {
num += map.get(curr) - map.get(pre)
i--
} else {
num += map.get(curr)
}
}
return num
};
来源:力扣(LeetCode) 链接:leetcode.cn/problems/ro…
最长公共前缀
编写一个函数来查找字符串数组中的最长公共前缀。
如果不存在公共前缀,返回空字符串 ""。
示例 1:
输入:strs = ["flower","flow","flight"]
输出:"fl"
示例 2:
输入:strs = ["dog","racecar","car"]
输出:""
解释:输入不存在公共前缀。
方法一:字典
/**
* @param {string[]} strs
* @return {string}
*/
var longestCommonPrefix = function (strs) {
let map = new Map()
let str = ""
let commonPre = { key: '', value: strs.length }
if (strs.length > 1) {
for (let i = 0; i < strs.length; i++) {
let str = strs[i]
for (let j = 0; j < str.length; j++) {
let char = str.slice(0, j + 1)
if (map.has(char)) {
let val = map.get(char) + 1
if (val >= commonPre.value) {
commonPre = {
key: char,
value: val
}
}
map.set(char, val)
} else {
map.set(char, 1)
}
}
}
} else {
return strs[0]
}
return commonPre.key
};
解法二
思路:一一对比,不满足跳出
/**
* @param {string[]} strs
* @return {string}
*/
var longestCommonPrefix = function (strs) {
if (strs.length === 1) return strs[0]
let ans = strs[0]
for (let i = 1; i < strs.length; i++) {
let curr = strs[i]
if (!curr) {
return ""
}
let j = 0
for (; j < ans.length && j < curr.length; j++) {
if (ans[j] != curr[j]) break
}
ans = ans.slice(0, j)
}
return ans
};
来源:力扣(LeetCode) 链接:leetcode.cn/problems/lo…
二叉树剪枝
给你二叉树的根结点 root ,此外树的每个结点的值要么是 0 ,要么是 1 。
返回移除了所有不包含 1 的子树的原二叉树。
节点 node 的子树为 node 本身加上所有 node 的后代。
示例 1:
输入:root = [1,null,0,0,1]
输出:[1,null,0,null,1]
解释:
只有红色节点满足条件“所有不包含 1 的子树”。 右图为返回的答案。
示例 2:
输入:root = [1,0,1,0,0,0,1]
输出:[1,null,1,null,1]
示例 3:
输入:root = [1,1,0,1,1,0,1,0]
输出:[1,1,0,1,1,null,1]
题解
/**
* @param {TreeNode} root
* @return {TreeNode}
*/
var pruneTree = function (root) {
if (!root) {
return null
}
root.left = pruneTree(root.left)
root.right = pruneTree(root.right)
if (!root.left && !root.right && root.val === 0) {
return null
}
return root
};
来源:力扣(LeetCode) 链接:leetcode.cn/problems/bi…
有效的括号
给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。 左括号必须以正确的顺序闭合。
示例 1:
输入:s = "()"
输出:true
示例 2:
输入:s = "()[]{}"
输出:true
示例 3:
输入:s = "(]"
输出:false
示例 4:
输入:s = "([)]"
输出:false
示例 5:
输入:s = "{[]}"
输出:true
自解
/**
* @param {string} s
* @return {boolean}
*/
var isValid = function (s) {
if(s.length<=1) return false
let map = new Map()
map.set(")","(")
map.set( "]","[")
map.set( "}","{")
let stack = []
for (let i = 0; i < s.length; i++) {
if (stack.length && map.get(s[i]) === stack[stack.length-1]) {
stack.pop(s[i - 1])
} else {
stack.push(s[i])
}
}
if (!stack.length) {
return true
}
return false
};
官方解法
/**
* @param {string} s
* @return {boolean}
*/
var isValid = function (s) {
if (s % 2) return false
let map = new Map([
[')', '('],
[']', '['],
['}', '{']
]
)
let stack = []
for (let i of s) {
if (map.has(i)) {
if (!stack.length || map.get(i) !== stack[stack.length - 1]) {
stack.push(i)
} else {
stack.pop()
}
} else {
stack.push(i)
}
}
if (!stack.length) {
return true
}
return false
};
来源:力扣(LeetCode) 链接:leetcode.cn/problems/va…
合并两个有序链表
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例 1:
输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]
示例 2:
输入:l1 = [], l2 = []
输出:[]
示例 3:
输入:l1 = [], l2 = [0]
输出:[0]
function ListNode(val, next) {
this.val = (val === undefined ? 0 : val)
this.next = (next === undefined ? null : next)
}
/**
* @param {ListNode} list1
* @param {ListNode} list2
* @return {ListNode}
*/
var mergeTwoLists = function (list1, list2) {
if (list1 === null) {
return list2
} else if (list2 === null) {
return list1
} else if (list1.val < list2.val) {
list1.next = mergeTwoLists(list1.next,list2)
return list1
} else {
list2.next = mergeTwoLists(list1,list2.next)
return list2
}
};
来源:力扣(LeetCode) 链接:leetcode.cn/problems/me…