66. 加一
给定一个由 整数 组成的 非空 数组所表示的非负整数,在该数的基础上加一。
最高位数字存放在数组的首位, 数组中每个元素只存储单个数字。
你可以假设除了整数 0 之外,这个整数不会以零开头。
示例 1:
输入:digits = [1,2,3]
输出:[1,2,4]
解释:输入数组表示数字 123。
示例 2:
输入:digits = [4,3,2,1]
输出:[4,3,2,2]
解释:输入数组表示数字 4321。
示例 3:
输入:digits = [0]
输出:[1]
提示:
1 <= digits.length <= 100 0 <= digits[i] <= 9
解答:
/**
* @param {number[]} digits
* @return {number[]}
*/
var plusOne = function (digits) {
for (let i = digits.length - 1; i >= 0; i--) {
digits[i]++;
digits[i] = digits[i] % 10;
if (digits[i] != 0) return digits;
}
const newArr = new Array(digits.length + 1).fill(0);
newArr[0] = 1;
return newArr;
};
总结:
1、new Array().fill()。
2、parseInt()和parseFloat()方法如果数字很大会失真,使用bigInt()末尾会出现n。
var plusOne = function(digits) {
let num = parseInt(digits.join(""))+1
return num.toString().split("")
};
1.两数之和
给定一个整数数组 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]
提示:
2 <= nums.length <= 104
-109 <= nums[i] <= 109
-109 <= target <= 109
只会存在一个有效答案
解答:
var twoSum = function(nums, target) {
var store=new Map()
store.set(nums[0],0)
for(let i=1;i<nums.length;i++){
if(store.get(target-nums[i])===undefined){
store.set(nums[i],i)
}else{
return [store.get(target-nums[i]),i]
}
}
};
总结:
Map 对象存有键值对,其中的键可以是任何数据类型。
Map 对象记得键的原始插入顺序。
Map 对象具有表示映射大小的属性。
基本的 Map() 方法
| Method | Description |
|---|---|
| new Map() | 创建新的 Map 对象。 |
| set() | 为 Map 对象中的键设置值。 |
| get() | 获取 Map 对象中键的值。 |
| entries() | 返回 Map 对象中键/值对的数组。 |
| keys() | 返回 Map 对象中键的数组。 |
| values() | 返回 Map 对象中值的数组。 |
| clear() | 删除 Map 中的所有元素。 |
| delete() | 删除由键指定的元素。 |
| has() | 如果键存在,则返回 true。 |
| forEach() | 为每个键/值对调用回调。 |
拓展
1、Map转换JSON:使用 Object.fromEntries() 方法将Map转为对象;
const map = new Map([ ['name', '张三'],
['age', '18'],
['address', 'xian'],
]);
const json = Object.fromEntries(map);
console.log(json);
// {"name":"张三","age":"18","address":"xian"}
JSON.stringify() 是将对象转为json字符串;
const map = new Map([ ['name', '张三'],
['age', '18'],
['address', 'xian'],
]);
const json = JSON.stringify(Object.fromEntries(map));
console.log(json);
// '{"name":"张三","age":"18","address":"xian"}'
2、JSON转换Map
如果是JSON字符串必须使用JSON.parse()转为对象;
使用 Object.entries() 接受对象返回二维数组;
let arr = Object.entries({"name":"张三","age":"18","address":"xian"});
console.log(arr)
// [["name","张三"],["age","18"],["address","xian"]]
调用Map()构造函数
let arr = Object.entries({"name":"张三","age":"18","address":"xian"});
let map = new Map(arr);
console.log(map);
// {'name' => '张三', 'age' => '18', 'address' => 'xian'}
20. 有效的括号
给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。
有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
- 每个右括号都有一个对应的相同类型的左括号。
示例 1:
输入:s = "()"
输出:true
示例 2:
输入:s = "()[]{}"
输出:true
示例 3:
输入:s = "(]"
输出:false
解答:
function isValid(s) {
let temp;
do {
temp = s;
// 循环直到把相邻的()、[]、{}去掉最后s=""
s = s.split('()').join('');
s = s.split('[]').join('');
s = s.split('{}').join('');
// false时退出循环
}while (temp !== s);
return s.length === 0
};
总结:
1、do{}while()循环
2、split(separator,howmany)
split() 方法用于把一个字符串分割成字符串数组
| 参数 | 描述 |
|---|---|
| separator | 必需。字符串或正则表达式,从该参数指定的地方分割 stringObject。 |
| howmany | 可选。该参数可指定返回的数组的最大长度。如果设置了该参数,返回的子串不会多于这个参数指定的数组。如果没有设置该参数,整个字符串都会被分割,不考虑它的长度。 |
返回值
一个字符串数组。该数组是通过在 separator 指定的边界处将字符串 stringObject 分割成子串创建的。返回的数组中的字串不包括 separator 自身。
但是,如果 separator 是包含子表达式的正则表达式,那么返回的数组中包括与这些子表达式匹配的字串(但不包括与整个正则表达式匹配正则表达式匹配&spm=1001.2101.3001.7020)的文本)。
提示和注释
注释:如果把空字符串 ("") 用作 separator,那么 stringObject 中的每个字符之间都会被分割。
注释:String.split() 执行的操作与 Array.join执行的操作是相反的。
实例
例子 1
在本例中,我们将按照不同的方式来分割字符串:
<script type="text/javascript">
var str="How are you doing today?"
document.write(str.split(" ") + "<br />")
document.write(str.split("") + "<br />")
document.write(str.split(" ",3))
</script>
输出:
How,are,you,doing,today?
H,o,w, ,a,r,e, ,y,o,u, ,d,o,i,n,g, ,t,o,d,a,y,?
How,are,you
例子 2
在本例中,我们将分割结构更为复杂的字符串:
"2:3:4:5".split(":") //将返回["2", "3", "4", "5"]
"|a|b|c".split("|") //将返回["", "a", "b", "c"]
例子 3
使用下面的代码,可以把句子分割成单词:
var words = sentence.split(' ')
或者使用正则表达式作为 separator:
var words = sentence.split(/\s+/)
例子 4
如果您希望把单词分割为字母,或者把字符串分割为字符,可使用下面的代码:
"hello".split("") //可返回 ["h", "e", "l", "l", "o"]
若只需要返回一部分字符,请使用 howmany 参数:
"hello".split("", 3) //可返回 ["h", "e", "l"]
3、join()
join()方法就是将array数据中每个元素都转为字符串,用自定义的连接符分割
67. 二进制求和
难度简单960收藏分享切换为英文接收动态反馈
给你两个二进制字符串 a 和 b ,以二进制字符串的形式返回它们的和。
示例 1:
输入:a = "11", b = "1"
输出:"100"
示例 2:
输入:a = "1010", b = "1011"
输出:"10101"
提示:
1 <= a.length, b.length <= 104a和b仅由字符'0'或'1'组成- 字符串如果不是
"0",就不含前导零
解答:
//方法一
var addBinary = function(a, b) {
// 若遇到很大的数字这种方法会出现失真
// return (parseInt(a,2)+parseInt(b,2)).toString(2)
// 0b表示二进制,0o表示八进制,0x表示十六进制,先把二进制转为十进制相加,再把结果通过toString(2)转化为二进制字符串
return ((BigInt('0b' + a) + BigInt('0b' + b)).toString(2))
};
/*
方法二
思路:
1、把字符串转化为数组,较短的字符串使用unshift方法在前面补0使得两个字符串长度相等。
2、定义一个空数组newArr,从后往前遍历aArr数组,两数组每一相加为0或者1向newArr里unshift两数和
3、相加为2,则unshift(0),并判断是否为第一项(索引是否大于0),若大于0,aArr[i-1]加1,否则newArr.unshift(1)
4、相加为3,则unshift(1),并判断是否为第一项(索引是否大于0),若大于0,aArr[i-1]加1,否则newArr.unshift(1)
*/
var addBinary = function(a, b) {
const aArr=a.split('').map(Number)
const bArr=b.split('').map(Number)
const gap=Math.abs(aArr.length-bArr.length)
for(let i=0;i<gap;i++){
if(aArr.length>bArr.length){
bArr.unshift(0)
}
if(aArr.length<bArr.length){
aArr.unshift(0)
}
}
let newArr=[]
for(let i=aArr.length-1;i>=0;i--){
if(aArr[i]+bArr[i]===2){
newArr.unshift(0)
if(i>0){
aArr[i-1]+=1
}else{
newArr.unshift(1)
}
}else if(aArr[i]+bArr[i]===0 || aArr[i]+bArr[i]===1){
newArr.unshift(aArr[i]+bArr[i])
}else if(aArr[i]+bArr[i]===3){
newArr.unshift(1)
if(i>0){
aArr[i-1]+=1
}else{
newArr.unshift(1)
}
}
}
return newArr.join('')
};
总结:
1、toString()方法可把一个 Number 对象转换为一个字符串,并返回结果。NumberObject.toString(radix)。
radix为可选。规定表示数字的基数,使 2 ~ 36 之间的整数。若省略该参数,则使用基数 10。但是要注意,如果该参数是 10 以外的其他值,则 ECMAScript 标准允许实现返回任意值。
十进制转换为二进制:
var num = 100;
console.log(num.toString(2));
2、parseInt() 函数可解析一个字符串,并返回一个整数。parseInt(string, radix)。
其中,string为必需。要被解析的字符串。radix为可选。表示要解析的数字的基数。该值介于 2 ~ 36 之间。如果省略该参数或其值为 0,则数字将以 10 为基础来解析。如果它以 “0x” 或 “0X” 开头,将以 16 为基数。如果该参数小于 2 或者大于 36,则parseInt() 将返回 NaN。
3、
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
arr.map(String); //结果: ['1', '2', '3', '4', '5', '6', '7', '8', '9']
var a = ['1', '2', '3', '4', '5', '6', '7', '8', '9']
a.map(Number); //结果:[1, 2, 3, 4, 5, 6, 7, 8, 9]
二进制转十进制:
var num = 1100100;
console.log(parseInt(num,2));
4、BigInt,对于非十进制输入,BigInt构造函数能够识别以0x... (十六进制)、0o... (八进制)和0b... (二进制)开头的字符串,并转为十进制。
BigInt('0b','111')
5、arr.map(String)和str.map(Number);
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
arr.map(String); //结果: ['1', '2', '3', '4', '5', '6', '7', '8', '9']
var a = ['1', '2', '3', '4', '5', '6', '7', '8', '9']
a.map(Number); //结果:[1, 2, 3, 4, 5, 6, 7, 8, 9]
118. 杨辉三角
给定一个非负整数 numRows, 生成「杨辉三角」的前 numRows 行。
在「杨辉三角」中,每个数是它左上方和右上方的数的和。
示例 1:
输入: numRows = 5
输出: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]]
示例 2:
输入: numRows = 1
输出: [[1]]
提示:
1 <= numRows <= 30
解答:
//方法一
/*
每行数等于前一行数,前面补0,加上该数后面补0,如1331=0121+1210
*/
var generate = function (numRows) {
let arr = [[1]];
for (let i = 0; i < numRows-1; i++) {
let aArr = JSON.parse(JSON.stringify(arr[i]));
aArr.push(0);
let bArr = JSON.parse(JSON.stringify(arr[i]));
bArr.unshift(0);
let pushArr = [];
for (let j = 0; j < aArr.length; j++) {
pushArr.push(aArr[j] + bArr[j]);
}
arr.push(pushArr);
}
return arr;
};
//方法二
var generate = function(numRows) {
let arr=[];
for(let i=0;i<numRows;i++){
let newArr=new Array(i+1).fill(1);
for(let j=1;j<newArr.length-1;j++){
newArr[j]=arr[i-1][j-1]+arr[i-1][j];
}
arr.push(newArr);
}
return arr;
};
总结:
1、深拷贝和浅拷贝
浅拷贝:有两种方式,一种是把一个对象里面的所有的属性值和方法都复制给另一个对象,另一种是直接把一个对象赋给另一个对象,使
得两个都指向同一个对象。
深拷贝:把一个对象的属性和方法一个个找出来,在另一个对象中开辟对应的空间,一个个存储到另一个对象中。
两者就在于,浅拷贝只是简单的复制,对对象里面的对象属性和数组属性只是复制了地址,并没有创建新的相同对象或者数组。而深拷贝
是完完全全的复制一份,空间大小占用一样但是位置不同!!
前言:
目前JS数据类型总共有8种!
- Number
- String
- Boolean
- Null
- Undefined
- Object
- Symbol
- BigInt
按照类型来分有基本数据类型和引用数据类型:
基本数据类型: String、Number、Boolean、Null、Undefined、Symbol、BigInt
引用数据类型: Object【Object是个大类,function函数、array数组、date日期...等都归属于Object】
前者是存储在栈内存中,后者是将其地址存在栈内存中,而真实数据存储在堆内存中(引用数据类型--名存在栈内存中,值存在于堆内存中,但是栈内存会提供一个引用的地址指向堆内存中的值) 。
如下图所示,基本类型如number、string、boolean、Null和undefined等存储在栈内存中,而引用数据类型如Array、Object和函数等则是
分别存储数据1的地址、数据2的地址和数据3的地址。
- 拷贝对象为基本数据类型
js中的基本数据类型:String Number Boolean Null Undefined,在赋值的过程中都是深拷贝。例如,let a = 10 ; b = a , 修改其中一个变
量的值,不会影响到另一个变量的值
- 拷贝对象中有引用数据类型
浅拷贝:会在栈中开辟另一块空间,并将被拷贝对象的栈内存数据完全拷贝到该块空间中,即基本数据类型的值会被完全拷贝,而引用
类型的值则是拷贝了“指向堆内存的地址”。
深拷贝:不仅会在栈中开辟另一块空间,若被拷贝对象中有引用类型,则还会在堆内存中开辟另一块空间存储引用类型的真实数据。
深浅拷贝的示意图如下图:
引用数据类型--名存在栈内存中,值存在于堆内存中,但是栈内存会提供一个引用的地址指向堆内存中的值**,我们以上面浅拷贝的例子
画个图:
let a=[0,1,2,3,4]
let b=a
a[0]=1
console.log(a)//1,1,2,3,4
console.log(b)//1,1,2,3,4
当b=a进行拷贝时,其实复制的是a的引用地址,而并非堆里面的值。
而当我们a[0]=1时进行数组修改时,由于a与b指向的是同一个地址,所以自然b也受了影响,这就是所谓的浅拷贝了。
那,要是在堆内存中也开辟一个新的内存专门为b存放值,就像基本类型那样,岂不就达到深拷贝的效果了
a、浅拷贝
(1)、slice
let a=[1,2,3,4],
b=a.slice();
a[0]=2;
console.log(a,b);//[2,2,3,4] [1,2,3,4]
这样的话slice方法也是深拷贝了?,毕竟b也没受a的影响,上面说了,深拷贝是会拷贝所有层级的属性,还是这个例子,我们把a改改
let a=[0,1,[2,3],4],
b=a.slice();
a[0]=1;
a[2][0]=1;
console.log(a,b);//[1,1,[1,3],4] [0,1,[1,3],4]
拷贝的不彻底啊,b对象的一级属性确实不受影响了,但是二级属性还是没能拷贝成功,仍然脱离不了a的控制,说明slice根本不是真正的
深拷贝。
这里引用知乎问答里面的一张图:
第一层的属性确实深拷贝,拥有了独立的内存,但更深的属性却仍然公用了地址,所以才会造成上面的问题。
同理,concat()、Array.from()与slice也存在这样的情况,他们都不是真正的深拷贝,这里需要注意。
(2)、concat()
let arr2 = ['cat', 'dog', 'pig', {'name': '曹操', 'age': 18}]
let arr2Copy = [].concat(arr2)
arr2Copy[2] = 'big pig'
arr2Copy[3]['name'] = '杨玉环'
console.log(arr2)
console.log(arr2Copy)
(3)、Array.from()
(4)、Object.assign()
(5)、拓展运算符(...)
b、深拷贝
(1)、JSON对象的parse和stringify
2、new Array().fill()
缺点:
let a = new Array(3).fill({});
console.log(a); // [{}, {}, {}]
a[0].name = '张三';
console.log(a); // [{name: "张三"}, {name: "张三"}, {name: "张三"}]
3、array.fill()
fill() 方法用一个固定值填充一个数组中从起始索引到终止索引内的全部元素。不包括终止索引。返回修改后的原始数组,不创建新数组。
使用语法:array.fill( value [,start [,end]]),其中 :
value 用来填充数组元素的值,必填。
start 可选起始索引,默认值为0。
end 可选终止索引,默认值为 this.length。
let arr=[1,2,3,4,5,6,7]
arr.fill(9,1,5)// [1, 9, 9, 9, 9, 6, 7]
119. 杨辉三角 II
给定一个非负索引 rowIndex,返回「杨辉三角」的第 rowIndex 行。
在「杨辉三角」中,每个数是它左上方和右上方的数的和。
示例 1:
输入: rowIndex = 3
输出: [1,3,3,1]
示例 2:
输入: rowIndex = 0
输出: [1]
示例 3:
输入: rowIndex = 1
输出: [1,1]
提示:
0 <= rowIndex <= 33
解答:
var getRow = function (rowIndex) {
let arr = [[1]];
for (let i = 0; i < rowIndex; i++) {
// let temp=[0,...arr,0]
let aArr = JSON.parse(JSON.stringify(arr[i]));
aArr.push(0);
let bArr = JSON.parse(JSON.stringify(arr[i]));
bArr.unshift(0);
let pushArr = [];
for (let j = 0; j < aArr.length; j++) {
pushArr.push(aArr[j] + bArr[j]);
}
arr.push(pushArr);
}
return arr[rowIndex];
};
69. x 的平方根
给你一个非负整数 x ,计算并返回 x 的 算术平方根 。
由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。
注意:不允许使用任何内置指数函数和算符,例如 pow(x, 0.5) 或者 x ** 0.5 。
示例 1:
输入:x = 4
输出:2
示例 2:
输入:x = 8
输出:2
解释:8 的算术平方根是 2.82842..., 由于返回类型是整数,小数部分将被舍去。
提示:
0 <= x <= 2^31 - 1
解答:
/*
方法一:
思路:i*i<=x<=(i+1)*(i+1)
*/
var mySqrt = function(x) {
let i=0;
while(i*i<=x){
i++
}
return i-1
};
/*
方法二:
思路:二分法,效率更高
*/
//二分法查找
var mySqrt = function(x) {
if(x < 2) return x;
let left = 1;
let right = Math.floor(x / 2);
while(left <= right) {
let mid = Math.floor(left + (right - left) / 2);
if(mid * mid === x) return mid;
if(mid * mid < x) left = mid + 1;
else right = mid - 1;
}
return right;
};
136. 只出现一次的数字
给你一个 非空 整数数组 nums ,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
你必须设计并实现线性时间复杂度的算法来解决此问题,且该算法只使用常量额外空间。
示例 1 :
输入:nums = [2,2,1]
输出:1
示例 2 :
输入:nums = [4,1,2,1,2]
输出:4
示例 3 :
输入:nums = [1]
输出:1
提示:
- 1 <= nums.length <= 3 * 10^4
- -3 * 10^4 <= nums[i] <= 3 * 10^4
- 除了某个元素只出现一次以外,其余每个元素均出现两次。
解答:
/*
方法一:
思路:两两相同的数按位异或会为0,最后只出现一次的数异或会为它本身
*/
var singleNumber = function(nums) {
let ans = 0;
for(const num of nums) {
ans ^= num;//ans =ans ^ nums
}
return ans;
};
/*
方法二:
思路:只出现一次的数字用indexOf和lastIndexOf去索引返回的index相同
*/
var singleNumber = function(nums) {
for(let i=0; i<nums.length;i++){
if(nums.indexOf(nums[i])===nums.lastIndexOf(nums[i])){
return nums[i]
}
}
};
总结:
1、按位异或运算符^
将运算数以二进制表示, 对应位相同为0, 相异为1。 异或满足交换律和结合律, 数字与它本身进行异或操作, 得到0; 数字与0进行异或操作, 得到它本身。
190. 颠倒二进制位
颠倒给定的 32 位无符号整数的二进制位。
提示:
- 请注意,在某些语言(如 Java)中,没有无符号整数类型。在这种情况下,输入和输出都将被指定为有符号整数类型,并且不应影响您的实现,因为无论整数是有符号的还是无符号的,其内部的二进制表示形式都是相同的。
- 在 Java 中,编译器使用二进制补码记法来表示有符号整数。因此,在 示例 2 中,输入表示有符号整数
-3,输出表示有符号整数-1073741825。
示例 1:
输入:n = 00000010100101000001111010011100
输出:964176192 (00111001011110000010100101000000)
解释:输入的二进制串 00000010100101000001111010011100 表示无符号整数 43261596,
因此返回 964176192,其二进制表示形式为 00111001011110000010100101000000。
示例 2:
输入:n = 11111111111111111111111111111101
输出:3221225471 (10111111111111111111111111111111)
解释:输入的二进制串 11111111111111111111111111111101 表示无符号整数 4294967293,
因此返回 3221225471 其二进制表示形式为 10111111111111111111111111111111 。
提示:
- 输入是一个长度为
32的二进制字符串
解答:
var reverseBits = function(n) {
let res = 0;
for(let i=0;i<32;i++){
//res左移,然后取n的最低位,加到res的最低位
res = (res << 1) + (n & 1);
//n右移
n = n>>1;
}
//把符号位换成0
return res >>> 0;
};
总结:
位运算
121. 买卖股票的最佳时机
给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。
示例 1:
输入:[7,1,5,3,6,4]
输出:5
解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。
示例 2:
输入:prices = [7,6,4,3,1]
输出:0
解释:在这种情况下, 没有交易完成, 所以最大利润为 0。
提示:
- 1 <= prices.length <= 10^5
- 0 <= prices[i] <= 10^4
解答:
//方法一
var maxProfit = function(prices) {
const len = prices.length
if(len<2) return 0;
let res = 0, minPrice = prices[0]
for(let i=1; i<len; i++){
res = Math.max(res, prices[i] - minPrice)
minPrice = Math.min(prices[i], minPrice)
}
return res
};
//方法二
var maxProfit = function(prices) {
let dp = Array(prices.length).fill(0);
let min = prices[0]
for (let i = 1; i < prices.length; i++) {
min = Math.min(min, prices[i]);
dp[i] = prices[i] - min > dp[i-1] ? prices[i] - min : dp[i-1]
}
return Math.max(...dp);
};
总结:
1、Math.max()和Math.min()
Math.max() 函数返回作为输入参数的最大数字,如果没有参数,则返回 -Infinity。
Math.min() 函数返回作为输入参数的最小数字,如果没有参数,则返回 -Infinity。
Math.max(1,3,5,7) //7
Math.min(1,3,5,7) //1
2、传入参数为数组
//ES5写法
Math.max.apply(null, [1,3,5])
Math.max.apply(Math, [1, 3, 5])
//ES6的写法:扩展运算符可以展开数组
Math.max(...[1, 3, 5])// 等同于Math.max(1, 3, 5);
258. 各位相加
给定一个非负整数 num,反复将各个位上的数字相加,直到结果为一位数。返回这个结果。
示例 1:
输入: num = 38
输出: 2
解释: 各位相加的过程为:
38 --> 3 + 8 --> 11
11 --> 1 + 1 --> 2
由于 2 是一位数,所以返回 2。
示例 2:
输入: num = 0
输出: 0
提示:
- 0 <= num <= 2^31 - 1
解答:
var addDigits = function(num) {
if (num < 10){
return num;
} else {
let sum = 0;
while (num >= 1){
sum += num % 10;
num = Math.floor(num/10);
}
return addDigits(sum);
}
};
-
爬楼梯
假设你正在爬楼梯。需要 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 阶
提示:
1 <= n <= 45
解答:
//方法一:
// 找规律发现爬楼梯的方法呈斐波那契数列排布,即每一项的值为前两项之和
var climbStairs = function(n) {
//算数平方根
const sqrt_5 = Math.sqrt(5);
const fib_n = Math.pow((1 + sqrt_5) / 2, n + 1) - Math.pow((1 - sqrt_5) / 2,n + 1);
return Math.round(fib_n / sqrt_5);
};
//方法二:
// 要爬n阶有俩种方式,先爬到n-1阶,再爬1阶,或者先爬到n-2阶,再爬2阶
const climbStairs = (n) => {
const dp = new Array(n + 1).fill(0);
dp[0] = 1;
dp[1] = 1;
for (let i = 2; i < dp.length; i++) {
dp[i] = dp[i - 2] + dp[i - 1];
}
return dp[n];
//方法三:
var climbStairs = function(n) {
let p = 0, q = 0, r = 1;
for (let i = 1; i <= n; ++i) {
//表示爬n-2阶的方法
p = q;
//表示爬n-1阶方法
q = r;
//表示爬n阶方法
r = p + q;
}
return r;
};
总结:
如果观察数学规律,可知本题是斐波那契数列,那么用斐波那契数列的公式即可解决问题,公式如下:
88. 合并两个有序数组
给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。请你 合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。
注意: 最终,合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况,nums1 的初始长度为 m + n,其中前 m 个元素表示应合并的元素,后 n 个元素为 0 ,应忽略。nums2 的长度为 n 。
示例 1:
输入:nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
输出:[1,2,2,3,5,6]
解释:需要合并 [1,2,3] 和 [2,5,6] 。
合并结果是 [1,2,2,3,5,6] ,其中斜体加粗标注的为 nums1 中的元素。
示例 2:
输入:nums1 = [1], m = 1, nums2 = [], n = 0
输出:[1]
解释:需要合并 [1] 和 [] 。
合并结果是 [1] 。
示例 3:
输入:nums1 = [0], m = 0, nums2 = [1], n = 1
输出:[1]
解释:需要合并的数组是 [] 和 [1] 。
合并结果是 [1] 。
注意,因为 m = 0 ,所以 nums1 中没有元素。nums1 中仅存的 0 仅仅是为了确保合并结果可以顺利存放到 nums1 中。
解答:
var merge = function(nums1, m, nums2, n) {
//在索引m处,删除n个,再把num2加入num1中
nums1.splice(m,n,...nums2);
//升序排列
return nums1.sort((a,b)=>a-b);
}
总结:
1、splice(index,howmany,items)
index —— 必需。整数,规定添加/删除项目的位置,使用负数可从数组结尾处规定位置。
howmany —— 必需。要删除的项目数量。如果设置为 0,则不会删除项目。
items —— 可选。向数组添加的新项目。
2、sort()
sort() 方法用于对数组的元素进行排序。默认排序顺序是根据字符串Unicode码点。默认情况下,sort() 方法将按字母和升序将值作为字符串进行排序。这适用于字符串(“Apple” 出现在 “Banana” 之前)。但是,如果数字按字符串排序,则 “25” 大于 “100” ,因为 “2” 于“1”因为如此,sort() 方法在对数字进行排序时会产生不正确的结果。
array.sort(sortfunction)
sortfunction 可选。规定排序顺序。必须是函数。
注意: 这种方法会改变原始数组!。
如果想按照其他标准进行排序,就需要提供比较函数,该函数要比较两个值,然后返回一个用于说明这两个值的相对顺序的数字。比较函
数应该具有两个参数 a 和 b,其返回值如下:
若 a 小于 b,在排序后的数组中 a 应该出现在 b 之前(升序),则返回一个小于 0 的值。
若 a 等于 b,则返回 0。
若 a 大于 b,则返回一个大于 0 的值。
//数字升序
var points = [40,100,1,5,25,10];
points.sort(function(a,b){return a-b});
//数字升序
points.sort(function(a,b){return b-a});
//若数组的元素是数组,按元素的第一个数排升序
let reArr=[[0,1],[2,2],[3,3],[1,4]]
reArr.sort((a,b)=>a[0]-b[0]);//[[0,1],[1,4],[2,2],[3,3]]
125.验证回文串
难度简单617收藏分享切换为英文接收动态反馈
如果在将所有大写字符转换为小写字符、并移除所有非字母数字字符之后,短语正着读和反着读都一样。则可以认为该短语是一个 回文串 。
字母和数字都属于字母数字字符。
给你一个字符串 s,如果它是 回文串 ,返回 true ;否则,返回 false 。
示例 1:
输入: s = "A man, a plan, a canal: Panama"
输出:true
解释:"amanaplanacanalpanama" 是回文串。
示例 2:
输入:s = "race a car"
输出:false
解释:"raceacar" 不是回文串。
示例 3:
输入:s = " "
输出:true
解释:在移除非字母数字字符之后,s 是一个空字符串 "" 。
由于空字符串正着反着读都一样,所以是回文串。
解答:
var isPalindrome = function(s) {
//先替换掉所有非字母和数字
//再替换掉所有的空格
//然后后reverse()方法颠倒顺序
//最后两者进行对比
s=s.replace(/[^a-zA-Z0-9]/g,"").replace(/\s/g,"").toLowerCase();
return s===[...s].reverse().join("")
}
总结:
1、正则表达式
15. 三数之和
难度中等5712收藏分享切换为英文接收动态反馈
给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请你返回所有和为 0 且不重复的三元组。
注意: 答案中不可以包含重复的三元组。
示例 1:
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
解释:
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。
注意,输出的顺序和三元组的顺序并不重要。
示例 2:
输入:nums = [0,1,1]
输出:[]
解释:唯一可能的三元组和不为 0 。
示例 3:
输入:nums = [0,0,0]
输出:[[0,0,0]]
解释:唯一可能的三元组和为 0 。
解答:
var threeSum = function(nums) {
let ans = [];
const len = nums.length;
if(nums == null || len < 3) return ans;
// 排序
nums.sort((a, b) => a - b);
for (let i = 0; i < len ; i++) {
// 如果当前数字大于0,则三数之和一定大于0,所以结束循环
if(nums[i] > 0) break;
// 去重
if(i > 0 && nums[i] == nums[i-1]) continue;
let L = i+1;
let R = len-1;
while(L < R){
const sum = nums[i] + nums[L] + nums[R];
if(sum == 0){
ans.push([nums[i],nums[L],nums[R]]);
// 去重
while (L<R && nums[L] == nums[L+1]) L++;
// 去重
while (L<R && nums[R] == nums[R-1]) R--;
L++;
R--;
}
else if (sum < 0) L++;
else if (sum > 0) R--;
}
}
return ans;
};
给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
示例 1:
输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[7,0,8]
解释:342 + 465 = 807
示例 2:
输入:l1 = [0], l2 = [0]
输出:[0]
示例 3:
输入:l1 = [9,9,9,9,9,9,9], l2 = [9,9,9,9]
输出:[8,9,9,9,0,0,0,1]
解答:
var addTwoNumbers = function(l1, l2) {
let head = null, tail = null;
let carry = 0;
while (l1 || l2) {
const n1 = l1 ? l1.val : 0;
const n2 = l2 ? l2.val : 0;
const sum = n1 + n2 + carry;
if (!head) {
head = tail = new ListNode(sum % 10);
} else {
tail.next = new ListNode(sum % 10);
tail = tail.next;
}
//进位
carry = Math.floor(sum / 10);
if (l1) {
l1 = l1.next;
}
if (l2) {
l2 = l2.next;
}
}
if (carry > 0) {
tail.next = new ListNode(carry);
}
return head;
};
总结:
1、因为链表是逆序存储的,我们直接模拟加法,处理好进位就可以了。
- 使用哑结点(dummy),不用对头结点是否存在单独判断; 声明一个 carry 变量用于存储进位。
- x 的值为 l1 的 val,如果走到 l1 的尾部,设置为 0
- y 的值为 l2 的 val, 如果走到 l2 的尾部, 设置为 0
- 求和: sum = val1 + val2 + carry
- 求进位:Math.floor(sum / 10)
- 求链表对应的新值:sum % 10
- 创建新的结点,将新结点添加到链表中,并更新当前链表: cur = cur.next
- 更新 l1 和 l2
231. 2 的幂
给你一个整数 n,请你判断该整数是否是 2 的幂次方。如果是,返回 true ;否则,返回 false 。
如果存在一个整数 x 使得 n == 2x ,则认为 n 是 2 的幂次方。
示例 1:
输入:n = 1
输出:true
解释:20 = 1
示例 2:
输入:n = 16
输出:true
解释:24 = 16
示例 3:
输入:n = 3
输出:false
示例 4:
输入:n = 4
输出:true
示例 5:
输入:n = 5
输出:false
解答:
//方法一:
var isPowerOfTwo = function(n) {
for(let i=0;i<=31;i++){
if(Math.pow(2,i)===n) return true
}
return false
};
//方法二:
var isPowerOfTwo = function(n) {
// 如果n是正整数并且 n&(n-1)=0,那么n就是2的幂。
// 如果n是正整数并且 n&(-n)=n,那么n就是2的幂。
return n > 0 && (n & (n - 1)) === 0;
};
总结:
1、如果n是正整数并且 n&(n-1)=0,那么n就是2的幂。
2、如果n是正整数并且 n&(-n)=n,那么n就是2的幂。
326. 3 的幂
给定一个整数,写一个函数来判断它是否是 3 的幂次方。如果是,返回 true ;否则,返回 false 。
整数 n 是 3 的幂次方需满足:存在整数 x 使得 n == 3^x
示例 1:
输入:n = 27
输出:true
示例 2:
输入:n = 0
输出:false
示例 3:
输入:n = 9
输出:true
示例 4:
输入:n = 45
输出:false
解答:
415. 字符串相加
给定两个字符串形式的非负整数 num1 和num2 ,计算它们的和并同样以字符串形式返回。
你不能使用任何內建的用于处理大整数的库(比如 BigInteger), 也不能直接将输入的字符串转换为整数形式。
示例 1:
输入:num1 = "11", num2 = "123"
输出:"134"
示例 2:
输入:num1 = "456", num2 = "77"
输出:"533"
示例 3:
输入:num1 = "0", num2 = "0"
输出:"0"
解答:
var addStrings = function(num1, num2) {
// 双指针,add表示进位。
let i = num1.length - 1, j = num2.length - 1, add = 0;
const ans = [];
while (i >= 0 || j >= 0 || add != 0) {
const x = i >= 0 ? num1.charAt(i) - '0' : 0;
const y = j >= 0 ? num2.charAt(j) - '0' : 0;
const result = x + y + add;
ans.push(result % 10);
add = Math.floor(result / 10);
i -= 1;
j -= 1;
}
return ans.reverse().join('');
};
总结:
1、string.chartAt(index)
返回索引对应的字符
412. Fizz Buzz
难度简单279收藏分享切换为英文接收动态反馈
给你一个整数 n ,找出从 1 到 n 各个整数的 Fizz Buzz 表示,并用字符串数组 answer(下标从 1 开始)返回结果,其中:
answer[i] == "FizzBuzz"如果i同时是3和5的倍数。answer[i] == "Fizz"如果i是3的倍数。answer[i] == "Buzz"如果i是5的倍数。answer[i] == i(以字符串形式)如果上述条件全不满足。
示例 1:
输入:n = 3
输出:["1","2","Fizz"]
示例 2:
输入:n = 5
输出:["1","2","Fizz","4","Buzz"]
示例 3:
输入:n = 15
输出:["1","2","Fizz","4","Buzz","Fizz","7","8","Fizz","Buzz","11","Fizz","13","14","FizzBuzz"]
解答:
//方法一:for循环
var fizzBuzz = function(n) {
let answer=new Array(n).fill(0).map(String)
for(let i=0;i<answer.length;i++){
answer[i]=(i+1)%3===0&&(i+1)%5!==0?"Fizz"
: (i+1)%3!==0&&(i+1)%5===0?"Buzz"
: (i+1)%3===0&&(i+1)%5===0? "FizzBuzz"
: i+1+''
}
return answer
};
//方法二:while循环
var fizzBuzz = function(n) {
let answer=new Array(n).fill(0).map(String)
let i=0
while(i<n){
answer[i]=(i+1)%3===0&&(i+1)%5!==0?"Fizz"
: (i+1)%3!==0&&(i+1)%5===0?"Buzz"
: (i+1)%3===0&&(i+1)%5===0? "FizzBuzz"
: i+1+''
i=i+1
}
return answer
};
总结:
1、三元表达式
2、new Array().fill()
414. 第三大的数
给你一个非空数组,返回此数组中 第三大的数 。如果不存在,则返回数组中最大的数。
示例 1:
输入:[3, 2, 1]
输出:1
解释:第三大的数是 1 。
示例 2:
输入:[1, 2]
输出:2
解释:第三大的数不存在, 所以返回最大的数 2 。
示例 3:
输入:[2, 2, 3, 1]
输出:1
解释:注意,要求返回第三大的数,是指在所有不同数字中排第三大的数。
解答:
//方法一:
var thirdMax = function(nums) {
// 排序
numsSort=nums.sort(function(a,b){return a-b})
//去重
let newArr=new Set(numsSort)
newArr=[...newArr]
return newArr.length>=3?newArr[newArr.length-3]:newArr[newArr.length-1]
};
总结:
1、数组去重
(1)new Set()
const a = [1, 3, 3, 1];
let b = new Set(a); //数组a为可迭代对象,b此时为Set结构
b = Array.from(b); //Set为可迭代对象,b此时为数组结构
console.log(b);
const a = [1, 3, 3, 1];
let b = new Set(a); //数组a为可迭代对象,b此时为Set结构
b = [...b]; //将Set数组b中的每一个元素展开,赋值给数组结构b
console.log(b);
也可传入字符串去重,返回一个Set对象
(2)indexOf()
arr = ['blue', 'green', 'blue', 'yellow', 'black', 'yellow', 'blue', 'green', 'blue', 'blue', 'blue'];
function unique(arr) {
var newArr = [];
for (var i = 0; i < arr.length; i++) {
if (newArr.indexOf(arr[i]) === -1) {
newArr.push(arr[i]);
}
}
return newArr;
}
console.log(unique(arr)); // 输出结果: (4) ["blue", "green", "yellow", "black"]
(3)splice()
arr = ['blue', 'green', 'blue', 'yellow', 'black', 'yellow', 'blue', 'green', 'blue', 'blue', 'blue'];
function unique(arr) {
for (let i = 0; i < arr.length; i++) {
for (let j = i + 1; j < arr.length; j++) {
if (arr[i] == arr[j]) {
arr.splice(j, 1);
j--;
}
}
}
return arr;
}
console.log(unique(arr)); // 输出结果: (4) ["blue", "green", "yellow", "black"]
(4)利用sort()方法排序,然后对比前后元素
arr = ['blue', 'green', 'blue', 'yellow', 'black', 'yellow', 'blue', 'green', 'blue', 'blue', 'blue'];
function unique(arr) {
if (!Array.isArray(arr)) {
console.log('type error!')
return;
}
arr = arr.sort()
var arrry= [arr[0]];
for (var i = 1; i < arr.length; i++) {
if (arr[i] !== arr[i-1]) {
arrry.push(arr[i]);
}
}
return arrry;
}
console.log(unique(arr)); //输出: (4) ["blue", "green", "yellow", "black"]
(5)利用includes()方法检测数组是否有某个元素
arr = ['blue', 'green', 'blue', 'yellow', 'black', 'yellow', 'blue', 'green', 'blue', 'blue', 'blue'];
function unique(arr) {
if (!Array.isArray(arr)) {
console.log('type error!')
return;
}
var array =[];
for(var i = 0; i < arr.length; i++) {
if(!array.includes(arr[i])) { //includes检测数组是否有某个值
array.push(arr[i]);
}
}
return array;
}
console.log(unique(arr)); // 输出:(4) ["blue", "green", "yellow", "black"]
(6)利用filter去重
var arr = ['blue', 'green', 'blue', 'yellow', 'black', 'yellow', 'blue', 'green', 'blue', 'blue', 'blue'];
function unique(arr) {
return arr.filter(function(item, index, arr) {
//当前元素,在原始数组中的第一个索引==当前索引值,否则返回当前元素
return arr.indexOf(item, 0) === index;});
}
console.log(unique(arr)); // 输出:(4) ["blue", "green", "yellow", "black"]
(7)利用Map数据结构去重
let arr = ['blue', 'green', 'blue', 'yellow', 'black', 'yellow', 'blue', 'green', 'blue', 'blue', 'blue'];
let unique = (arr)=> {
let seen = new Map();
return arr.filter((item) => {
return !seen.has(item) && seen.set(item,1);
});
};
console.log(unique(arr)); //输出: (4) ["blue", "green", "yellow", "black"]
409. 最长回文串
给定一个包含大写字母和小写字母的字符串 s ,返回 通过这些字母构造成的 最长的回文串 。
在构造过程中,请注意 区分大小写 。比如 "Aa" 不能当做一个回文字符串。
示例 1:
输入:s = "abccccdd"
输出:7
解释:
我们可以构造的最长的回文串是"dccaccd", 它的长度是 7。
示例 2:
输入:s = "a"
输出:1
示例 3:
输入:s = "aaaaaccc"
输出:7
解答:
//方法一
var longestPalindrome = function(s) {
let countObj = new Map()
var res = 0;//最大
for(var i=0;i<s.length;i++){
if(!countObj.has(s[i])){
countObj.set(s[i],1)
}else{
res +=2;
countObj.delete(s[i])
}
}
if(s.length > res){
res +=1;
}
return res;
};
//方法二
var longestPalindrome = function(s) {
var countObj = {};
var res = 0;//最大
for(var i=0;i<s.length;i++){
if(!countObj[s[i]]){
countObj[s[i]] = 1;
}else{
res +=2;
delete countObj[s[i]];
}
}
if(s.length > res){ //剩余存在奇个数字符
res +=1;
}
return res;
};
//方法三
//思路:
/*
计算有多少对相同的字符,并去除
使用set集合来存放,set集合不能有重复的item,如果set集合有,那么就去除,结果+2。最后如果还剩项,结果+1
*/
var longestPalindrome = function(s) {
let temp=new Set()
let sum=0
s.split("").forEach(c=>{
if(temp.has(c)){
temp.delete(c)
sum+=2;
}else{
temp.add(c)
}
})
return sum+(temp.size>0?1:0);
};
总结:
1、(1)所有字符都是偶个数 + 中间一个单独奇数的 才能组成最大回文串; (2)累计所有偶个数的字符总个数; (3)判断是否还剩字符没累计,如果有 +1;
389.找不同
给定两个字符串 s 和 t ,它们只包含小写字母。
字符串 t 由字符串 s 随机重排,然后在随机位置添加一个字母。
请找出在 t 中被添加的字母。
示例 1:
输入:s = "abcd", t = "abcde"
输出:"e"
解释:'e' 是那个被添加的字母。
示例 2:
输入:s = "", t = "y"
输出:"y"
解答:
var findTheDifference = function(s, t) {
let sArr=s.split('')
let tArr=t.split('')
let len=tArr.length
for(let i=0;i<len;i++){
if(tArr.indexOf(sArr[i])!==-1&&tArr.length!==0){
tArr.splice(tArr.indexOf(sArr[i]),1)
}
}
return tArr[0]
};
/*
方法二:
*/
var findTheDifference = function(s, t) {
if (!s.length) return t;
let sum1 = 0, sum2 = 0, i = 0;
while (i < t.length) {
//计算出s和t中所有字母ASCII总和
if (s[i]) sum1 += s.charCodeAt(i);
if (t[i]) sum2 += t.charCodeAt(i);
i++;
}
//根据差值计算出多出那个字母的ASCII码
return String.fromCharCode(sum2 - sum1);
}
总结:
1、str.charCodeAt(index)
返回该索引处字母的ASCII码值。
2、String.fromCharCode()
该方法的返回值为String类型,其返回值为Unicode数值所表示的字符串。
String.fromCharCode( 65, 66, 67, 68, 69, 70, 71 ); // ABCDEFG
String.fromCharCode( 78 ); // N
String.fromCharCode( 20013, 22269 ); // 中国
String.fromCharCode(); // (空字符串)