给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。
示例:
给定 nums = [2, 7, 11, 15], target = 9
因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]
const twoSum = (nums, target) => {
const prevNums = {}; // 存储出现过的数字,和对应的索引
for (let i = 0; i < nums.length; i++) { // 遍历元素
const curNum = nums[i]; // 当前元素
const targetNum = target - curNum; // 满足要求的目标元素
const targetNumIndex = prevNums[targetNum]; // 在prevNums中获取目标元素的索引
if (targetNumIndex !== undefined) { // 如果存在,直接返回 [目标元素的索引,当前索引]
return [targetNumIndex, i];
} else { // 如果不存在,说明之前没出现过目标元素
prevNums[curNum] = i; // 存入当前的元素和对应的索引
}
}
}
给出一个 32 位的有符号整数,你需要将这个整数中每位上的数字进行反转。
示例 1:
输入: 123
输出: 321
示例 2:
输入: -123
输出: -321
示例 3:
输入: 120
输出: 21
注意:
假设我们的环境只能存储得下 32 位的有符号整数,则其数值范围为 [−231, 231 − 1]。请根据这个假设,如果反转后整数溢出那么就返回 0。
var reverse = function(x) {
// x ->num
var str = x.toString() //str->String
let arr = str.split(''); //arr->Array
// 判断是否有负号
if(arr[0]!=="-"){
let num =Number(arr.reverse().join(''));
if(num <= 2147483647 && num >= -2147483648){
return num;
}else{
return 0
}
}else {
delete arr[0];
let num =Number(arr.reverse().join(''));
if(num <= 2147483647 && num >= -2147483648){
return ~num+1;
}else{
return 0
}
}
};
判断一个整数是否是回文数。回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。
示例 1:
输入: 121
输出: true
示例 2:
输入: -121
输出: false
解释: 从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。
示例 3:
输入: 10
输出: false
解释: 从右向左读, 为 01 。因此它不是一个回文数。
/**
* @param {number} x
* @return {boolean}
*/
var isPalindrome = function(x) {
var str = x.toString()
var arr = str.split('')
if(x>0 && arr[arr.length-1]!=0){
let num =Number(arr.reverse().join(''));
if (x == num){
return true
}else {
return false
}
} else if(x == 0){
return true
}else {
return false
}
};
简化版:
var isPalindrome = function(x) {
return x === Number(x.toString().split('').reverse().join(''))
};
双指针的方法:
var isPalindrome = function(x) {
if(x < 0) return false;
if(x < 10) return true;
let right = 1;
let left = 0;//初始为 x的总位数
let sum = x;
while(sum >= 1){//算出总位数
sum /= 10;
left++;
}
//获取第n位的数
let getNum = (_x, n) => {
return Math.floor(_x % Math.pow(10, n) / Math.pow(10, n - 1));
}
while(left > right){
if(getNum(x, left) != getNum(x, right)) return false;
left--;
right++;
}
return true;
};
罗马数字包含以下七种字符: 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 到 3999 的范围内。
示例 1:
输入: “III”
输出: 3
示例 2:
输入: “IV”
输出: 4
示例 3:
输入: “IX”
输出: 9
示例 4:
输入: “LVIII”
输出: 58
解释: L = 50, V= 5, III = 3.
示例 5:
输入: “MCMXCIV”
输出: 1994
解释: M = 1000, CM = 900, XC = 90, IV = 4.
/**
* @param {string} s
* @return {number}
*/
var romanToInt = function(s) {
var hashMap = {
'M': 1000,
'D': 500,
'C': 100,
'L': 50,
'X': 10,
'V': 5,
'I': 1
};
var num = 0;
for(i=0;i
编写一个函数来查找字符串数组中的最长公共前缀。
如果不存在公共前缀,返回空字符串 “”。
示例 1:
输入: [“flower”,“flow”,“flight”]
输出: “fl”
示例 2:
输入: [“dog”,“racecar”,“car”]
输出: “”
解释: 输入不存在公共前缀。
说明:
所有输入只包含小写字母 a-z 。
var longestCommonPrefix = function(strs) {
if(strs.length==0){
return ''
}
var ans = strs[0]
for(i=0;i
给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
注意空字符串可被认为是有效字符串。
示例 1:
输入: “()”
输出: true
示例 2:
输入: “()[]{}”
输出: true
示例 3:
输入: “(]”
输出: false
示例 4:
输入: “([)]”
输出: false
示例 5:
输入: “{[]}”
输出: true
/**
* @param {string} s
* @return {boolean}
*/
var isValid = function(s) {
const n = s.length;
if (n % 2 === 1) {
return false;
}
const pairs = new Map([
[')', '('],
[']', '['],
['}', '{']
]);
const stk = [];
s.split('').forEach(ch => {
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;
};
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例:
输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} l1
* @param {ListNode} l2
* @return {ListNode}
*/
// 非递归
/*
var mergeTwoLists = function(l1, l2) {
var cew = new ListNode()
var cewq = cew
while(l1!=null&l2!=null){
if(l1.val
给定一个正整数 n(1 ≤ n ≤ 30),输出外观数列的第 n 项。
注意:整数序列中的每一项将表示为一个字符串。
「外观数列」是一个整数序列,从数字 1 开始,序列中的每一项都是对前一项的描述。前五项如下:
1
11
21
1211
111221
第一项是数字 1
描述前一项,这个数是 1 即 “一个 1 ”,记作 11
描述前一项,这个数是 11 即 “两个 1 ” ,记作 21
描述前一项,这个数是 21 即 “一个 2 一个 1 ” ,记作 1211
描述前一项,这个数是 1211 即 “一个 1 一个 2 两个 1 ” ,记作 111221
示例 1:
输入: 1
输出: “1”
解释:这是一个基本样例。
示例 2:
输入: 4
输出: “1211”
解释:当 n = 3 时,序列是 “21”,其中我们有 “2” 和 “1” 两组,“2” 可以读作 “12”,也就是出现频次 = 1 而 值 = 2;类似 “1” 可以读作 “11”。所以答案是 “12” 和 “11” 组合在一起,也就是 “1211”。
正则表达式解析: \d表示匹配数字,g表示全局匹配, 如:“1aa11aa111”.replace(/\d/g,6)
返回的是"6aa66aa666",就是找出所有数字,并替换成6; \1表示匹配的是 所获取的第1个()匹配的引用,
例如,’(\d)\1’ 匹配两个连续数字字符。如33aa 中的33。如"1a11aa221".replace(/(\d)\1/g,6),
返回的是"1a6aa61",匹配了"11"和"22"; 表示匹配{0,无穷大}次, \1就是表示\1可以出现0次或者更多次,
如"1a11aa221".replace(/(\d)\1*/g,6), 返回的是"6a6aa66",匹配了1,11,22,1;replace用法: 看个例子: “1aa11aa111”.replace(/(\d)\1*/g,i => i . l e n g t h {i.length} i.length{i[0]}),
返回的是"11aa21aa31",就是1变成1,11变成21,111变成31;replace第二个参数是箭头函数,返回了一个模板字符串
/**
* @param {number} n
* @return {string}
*/
// 通过正则合并相同元素完成累加
var countAndSay = function(n) {
let prev = '1'
for(let i = 1; i < n; i++){
prev = prev.replace(/(\d)\1*/g, item =>`${item.length}${item[0]}`)
}
return prev
};
递归 + 双指针方法:
核心思想:
创建慢指针和快指针 left、right 来确定每个字符的重复数量。
具体思路:
当 left 和 right 所指字符相同时,right 向右移动一位,直到 right 所指与 left 不同。
确定相同字符的个数,既 right - left 的值,该值表示的即是 left 所指字符的个数。
将结果转为以字符的形式储存。
继续历遍,因为 left 和 right 之间的字符已经历遍过了,所以更新指针 left 至 right。
当 right 超出字符范围时,结束循环。
//递归 + 双指针
var countAndSay = function (n) {
if (n === 1) {
return "1";
};
let pre = countAndSay(n - 1);
let result = "", left = 0, right = 0;
while (right < pre.length) {
while (pre[left] === pre[right] && right < pre.length) {
right++;
};
result += (right - left).toString() + pre[left];
left = right;
}
return result;
};
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例:
输入: [-2,1,-3,4,-1,2,1,-5,4]
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
/**
* @param {number[]} nums
* @return {number}
*/
var maxSubArray = function(nums) {
let pre = 0, maxAns = nums[0];
nums.forEach((x) => {
pre = Math.max(pre + x, x);
maxAns = Math.max(maxAns, pre);
});
return maxAns;
};
给定一个仅包含大小写字母和空格 ’ ’ 的字符串 s,返回其最后一个单词的长度。如果字符串从左向右滚动显示,那么最后一个单词就是最后出现的单词。
如果不存在最后一个单词,请返回 0 。
说明:一个单词是指仅由字母组成、不包含任何空格字符的 最大子字符串。
示例:
输入: “Hello World”
输出: 5
/**
* @param {string} s
* @return {number}
*/
var lengthOfLastWord = function(s) {
s3 = s.trim();
// 找出目标字符串中所有空白字符替换为一个空白字符
s2 = s3.replace(/\s+/g, ' ')
var a = s2.lastIndexOf(' ')
var i = s2.length-1-a
if(s2.length == 0){
return 0
}else if(s2.length != 0 && i == -1){
return s2.length
}else{
return i
}
};
给定一个由整数组成的非空数组所表示的非负整数,在该数的基础上加一。
最高位数字存放在数组的首位, 数组中每个元素只存储单个数字。
你可以假设除了整数 0 之外,这个整数不会以零开头。
示例 1:
输入: [1,2,3]
输出: [1,2,4]
解释: 输入数组表示数字 123。
示例 2:
输入: [4,3,2,1]
输出: [4,3,2,2]
解释: 输入数组表示数字 4321。
/**
* @param {number[]} digits
* @return {number[]}
*/
var plusOne = function(digits) {
const len = digits.length;
for(let i = len - 1; i >= 0; i--) {
digits[i]++;
digits[i] %= 10;
if(digits[i]!=0)
return digits;
}
/*
digits = [...Array(len + 1)].map(_=>0);;
digits[0] = 1;
return digits;
*/
return [1,...digits]
};
给你两个二进制字符串,返回它们的和(用二进制表示)。
输入为 非空 字符串且只包含数字 1 和 0。
示例 1:
输入: a = “11”, b = “1”
输出: “100”
示例 2:
输入: a = “1010”, b = “1011”
输出: “10101”
提示:
每个字符串仅由字符 ‘0’ 或 ‘1’ 组成。
1 <= a.length, b.length <= 10^4
字符串如果不是 “0” ,就都不含前导零。
/**
* @param {string} a
* @param {string} b
* @return {string}
*/
// 二进制前缀0b,例如:var a = 0b11; // a=3
// 数字转各进制字符串用num.toString(n),n为2-36的数字,表示2-36进制
var addBinary = function(a, b) {
return (BigInt('0b' + a) + BigInt('0b' + b)).toString(2)
}
给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。
示例 1:
输入: 1->1->2
输出: 1->2
示例 2:
输入: 1->1->2->3->3
输出: 1->2->3
/**
* Definition for singly-linked list.
* function ListNode(val) {
* this.val = val;
* this.next = null;
* }
*/
/**
* @param {ListNode} head
* @return {ListNode}
*/
var deleteDuplicates = function(head) {
var pre = head;
while(pre&&pre.next){
if(pre.val==pre.next.val){
pre.next = pre.next.next
} else {
pre = pre.next
}
}
return head
};
给你两个有序整数数组 nums1 和 nums2,请你将 nums2 合并到 nums1 中,使 nums1 成为一个有序数组。
说明:
初始化 nums1 和 nums2 的元素数量分别为 m 和 n 。
你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素。
示例:
输入:
nums1 = [1,2,3,0,0,0], m = 3
nums2 = [2,5,6], n = 3
输出:[1,2,2,3,5,6]
/**
* @param {number[]} nums1
* @param {number} m
* @param {number[]} nums2
* @param {number} n
* @return {void} Do not return anything, modify nums1 in-place instead.
*/
var merge = function(nums1, m, nums2, n) {
let index1 = m - 1
let index2 = n - 1
let tail = m + n - 1
while (index2 >= 0) {
if (nums1[index1] > nums2[index2]) {
nums1[tail] = nums1[index1]
index1--
tail--
} else {
nums1[tail] = nums2[index2]
index2--
tail--
}
}
};
给定两个二叉树,编写一个函数来检验它们是否相同。
如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。
/**
* 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} p
* @param {TreeNode} q
* @return {boolean}
*/
var isSameTree = function(p, q) {
if(p == null && q == null)
return true;
if(p == null || q == null)
return false;
if(p.val != q.val)
return false;
return isSameTree(p.left, q.left) && isSameTree(p.right, q.right);
};
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
/**
* @param {TreeNode} root
* @return {boolean}
*/
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
/**
* @param {TreeNode} root
* @return {boolean}
*/
var isSymmetric = function (root) {
const check = (left, right) => { // 参数接收左右子树
if (!left && !right) return true // 左右子树都不存在 也是对称的
if (left && right) { // 左右子树都存在,要继续判断
return left.val === right.val && // 左右子树的顶点值要相等
check(left.left, right.right) && // 左子树的left和右子树的right相等
check(left.right, right.left) // 左子树的right和右子树的left相等
}
return false; // 左右子树中的一个不存在,一个存在,不是对称的
}
return !root || check(root.left, root.right) // root为null也是对称的
// 不满足则调用check判断左右子树
};
迭代:
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.root1 = this.root2 = null;
* }
*/
/**
* @param {TreeNode} root
* @return {boolean}
*/
const isSymmetric = (root) => {
// 1. 设置当前层
const nowRoot = [root, root];
// 2. 判断当前层是否可以延续
while (nowRoot.length) {
// 3. 获取左右部分
const root1 = nowRoot.pop();
const root2 = nowRoot.pop();
// 4. 如果两者是空节点,继续
if (!root1 && !root2) {
continue;
}
// 5. 如果左边右边只有一个空,或者左边的值不等于右边
if (!root1 || !root2 || root1.val !== root2.val) {
return false;
}
// 6. 重点:添加值
nowRoot.push(root1.left);
nowRoot.push(root2.right);
nowRoot.push(root1.right);
nowRoot.push(root2.left);
}
// 7. 如果上面没情况发生
return true;
};
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
/**
* @param {TreeNode} root
* @return {number}
*/
var maxDepth = function(root) {
if(!root) {
return 0;
} else {
const left = maxDepth(root.left);
const right = maxDepth(root.right);
return Math.max(left, right) + 1;
}
};
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
/**
* @param {TreeNode} root
* @return {number[][]}
*/
/* BFS
var levelOrderBottom = function(root) {
if(!root) return []
let res = [],
queue = [root]
while(queue.length) {
let curr = [],
temp = []
while(queue.length) {
let node = queue.shift()
curr.push(node.val)
if(node.left) temp.push(node.left)
if(node.right) temp.push(node.right)
}
res.push(curr)
queue = temp
}
return res.reverse()
};
*/
// DFS
var levelOrderBottom = function(root) {
const res = []
var dep = function (node, depth){
if(!node) return
res[depth] = res[depth]||[]
res[depth].push(node.val)
dep(node.left, depth + 1)
dep(node.right, depth + 1)
}
dep(root, 0)
return res.reverse()
};
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
/**
* @param {number[]} nums
* @return {TreeNode}
*/
var sortedArrayToBST = function (nums) {
if (nums.length == 0) return null;
let cen = Math.floor(nums.length / 2)
let node = new TreeNode(nums[cen])
node.left = sortedArrayToBST(nums.slice(0, cen))
node.right = sortedArrayToBST(nums.slice(cen + 1))
return node;
};
答一:自顶向下(暴力法)
解题思路: 自顶向下的比较每个节点的左右子树的最大高度差,如果二叉树中每个节点的左右子树最大高度差小于等于 1 ,即每个子树都平衡时,此时二叉树才是平衡二叉树
代码实现:
var isBalanced = function (root) {
if(!root) return true
return Math.abs(depth(root.left) - depth(root.right)) <= 1
&& isBalanced(root.left)
&& isBalanced(root.right)
}
var depth = function (node) {
if(!node) return -1
return 1 + Math.max(depth(node.left), depth(node.right))
}
复杂度分析:
时间复杂度:O(nlogn),计算 depth 存在大量冗余操作
空间复杂度:O(n)
解答二:自底向上(优化)
解题思路: 利用后续遍历二叉树(左右根),从底至顶返回子树最大高度,判定每个子树是不是平衡树 ,如果平衡,则使用它们的高度判断父节点是否平衡,并计算父节点的高度,如果不平衡,返回 -1 。
遍历比较二叉树每个节点 的左右子树深度:
比较左右子树的深度,若差值大于 1 则返回一个标记 -1 ,表示当前子树不平衡
左右子树有一个不是平衡的,或左右子树差值大于 1 ,则二叉树不平衡
若左右子树平衡,返回当前树的深度(左右子树的深度最大值 +1 )
代码实现:
var isBalanced = function (root) {
return balanced(root) !== -1
};
var balanced = function (node) {
if (!node) return 0
const left = balanced(node.left)
const right = balanced(node.right)
if (left === -1 || right === -1 || Math.abs(left - right) > 1) {
return -1
}
return Math.max(left, right) + 1
}
复杂度分析:
时间复杂度:O(n)
空间复杂度:O(n)