『前端积累』算法汇总【持续更新】
简单
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]
解题思路:
- 用 hashMap 存储遍历过的元素和对应的索引。
- 每遍历一个元素,看看 hashMap 中是否存在满足要求的目标数字。
- 所有事情在一次遍历中完成(用了空间换取时间)
代码:
- 方式一:
**
* @param {number[]} nums
* @param {number} target
* @return {number[]}
*/
var twoSum = function(nums, target) {
var preNumObj = {}
for(let i = 0, j = nums.length; i < j; i++) {
let targetNum = target - nums[i];
if(preNumObj[targetNum] !== undefined) {
return [preNumObj[targetNum],i]
} else {
preNumObj[nums[i]] = i;
}
}
};
- 方式二:使用Map()
/**
* @param {number[]} nums
* @param {number} target
* @return {number[]}
*/
var twoSum = function(nums, target) {
let len = nums.length;
// 创建 MAP
const MAP = new Map();
// 由于第一个元素在它之前一定没有元素与之匹配,所以先存入哈希表
MAP.set(nums[0], 0);
for (let i = 1; i < len; i++) {
// 提取共用
let other = target - nums[i];
// 判断是否符合条件,返回对应的下标
if (MAP.get(other) !== undefined) return [MAP.get(other), i];
// 不符合的存入hash表
MAP.set(nums[i], i)
}
};
2.找出数组重复次数最多的元素(数组)
描述:
给定一个字符串数组, 每一个元素代表一个 IP 地址,找到出现频率最高的 IP。
注:给定数据只有一个频率最高的 IP
样例:
const lines = ['192.168.1.1', '192.118.2.1', '192.168.1.1'];
return '192.168.1.1';
题目分析: 用对象来处理,将元素赋值到属性上,判断之前有没有这个属性。
代码:
const lines = ['192.168.1.1', '192.118.2.1', '192.168.1.1'];
const mostTimes = function(arr) {
let [obj, max, name] = [{},1,''];
arr.forEach((item) => {
if(obj[item]) {
obj[item] ++;
if(obj[item] > max) {
max = obj[item];
name = item;
}
} else {
obj[item] = 1; // 没有值,初始化
}
})
return name;
}
console.log(mostTimes(lines)); // 192.168.1.1
3.水仙花数
水仙花数的定义: 一个 N 位非负整数,其各位数字的 N 次方和等于该数本身。
举例:
153 = 1^3 + 5^3 + 3^3
370 = 3^3 + 7^3 + 0^3
371 = 3^3 + 7^3 + 1^3
1634 = 14^4 + 64^4 + 34^4 + 44^4等等。
描述: 给出n,找到所有的n位十进制水仙花数。
样例:
比如 n = 1, 所有水仙花数为:[0,1,2,3,4,5,6,7,8,9]。
而对于 n = 2, 则没有 2 位的水仙花数,返回 []。
判断一个数是否为水仙花数: 要找出水仙花数,首先我们需要能识别一个数是否为水仙花数
// 判断一个数是否为水仙花数
const isTrue = num => {
const len = num.toString().length; // 数的长度
const str = num.toString(); // 数转成字符串
let total = 0;
for(let i = 0; i < len; i++) {
total += Math.pow(str.charAt(i),len);
}
if(total == num) {
return true;
}
return false;
}
console.log(isTrue(153)) // true
console.log(isTrue(154)) // false
找出所有的n位十进制水仙花数
- 确定查找的范围(找出n位的最大值与最小值)
- 遍历每个数,判断为水仙花数,添加到数组中
const getNumbersArr = n => {
let resultArr = []; // 满足水仙花数的结果数组
let min = Math.pow(10, n - 1);
let max = Math.pow(10, n);
for(let i = min; i < max; i++) {
const str = i.toString();
let total = 0;
let j = 0;
while(j < n) {
total += Math.pow(str.charAt(j),n);
j++;
}
if(total == i) {
resultArr.push(i);
}
}
return resultArr;
}
console.log(getNumbersArr(1)) // [1, 2, 3, 4, 5, 6, 7, 8, 9]
console.log(getNumbersArr(2)) // []
console.log(getNumbersArr(3)) // [153, 370, 371, 407]
注意:查找位数过大会出现性能问题,以及最大值溢出问题。
4.姓名去重(数组)
描述: 给一串名字,将他们去重之后返回。两个名字重复是说在忽略大小写的情况下是一样的。
说明: 你可以假设名字只包含大小写字母和空格。
样例, 给出:
[
'James',
'james',
'Bill Gates',
'bill Gates',
'Hello World',
'HELLO WORLD',
'Helloworld'
];
返回:
['james', 'bill gates', 'hello world', 'helloworld'];
题目分析: 先全部转换为小写再去重。
代码:
const removeDuplicateName = nameArr => {
nameArr.forEach((item,index) => {
nameArr[index] = item.toLowerCase();
})
return [...new Set(nameArr)].sort();
}
const arr = [
'James',
'james',
'Bill Gates',
'bill Gates',
'Hello World',
'HELLO WORLD',
'Helloworld'
];
console.log(removeDuplicateName(arr)); // ["bill gates", "hello world", "helloworld", "james"]
5.反转一个3位整数
描述: 反转一个只有 3 位数的整数
样例: 123 反转之后是 321。 900 反转之后是 9
代码:
const reverseNumber = num => parseInt(`${num}`.split('').reverse().join(''));
console.log(reverseNumber(123)) // 321
console.log(reverseNumber(900)) // 9
6.反转数字(包含小数、正数、负数)
描述: 将一个数进行反转,得到反转后的值
样例: 123反转后为321;1.23反转后为32.1;-500反转后为-5;
代码:
const reverseNumber = num => parseFloat(`${num}`.split('').reverse().join('')) * Math.sign(num);
console.log(reverseNumber(123)) // 321
console.log(reverseNumber(-123)) // -321
console.log(reverseNumber(1.23)) // 32.1
console.log(reverseNumber(-500)) // -5
7.查找斐波纳契数列中第 N 个数
描述: 所谓的斐波纳契数列是指:
- 前 2 个数是 0 和 1;
- 第 i 个数是第 i-1 个数和第 i-2 个数的和。
斐波纳契数列的前 10 个数字是:
0, 1, 1, 2, 3, 5, 8, 13, 21, 34
题目分析: 前两个数字可以算成是起始元素,从第三个元素才开始有规则
代码方式一:递归
const fibonacci = num => {
if(!(typeof num === 'number' && num > 0 && num % 1 === 0)) {
throw '请传入大于0的整数数字'
}
let arr = [0,1];
let temp = n => {
if(n == 1 || n == 2) {
return arr[n - 1];
}
arr[n - 1] = temp(n - 1) + temp(n - 2);
return arr[n - 1];
}
return temp(num);
}
console.log(fibonacci(1)) // 0
console.log(fibonacci(2)) // 1
console.log(fibonacci(10)) // 34
代码方式二:循环
const fibonacci = num => {
if(!(typeof num === 'number' && num > 0 && num % 1 === 0)) {
throw '请传入大于0的整数数字'
}
let arr = [0,1]; // 把前两位数字当起始元素
if(num == 1 || num == 2) {
return arr[num - 1];
}
for(let i = 3; i < num + 1; i++ ) {
arr[i - 1] = arr[i - 2] + arr[i - 3];
}
return arr[arr.length - 1];
}
console.log(fibonacci(1)) // 0
console.log(fibonacci(2)) // 1
console.log(fibonacci(10)) // 34
代码方式三:循环优化
const fibonacci = num => {
if(!(typeof num === 'number' && num > 0 && num % 1 === 0)) {
throw '请传入大于0的整数数字'
}
let arr = new Array(num).fill(0); // 初始化默认填充值
arr[1] = 1; // 默认设置第二位的值
for(let i = 2; i < num; i++) {
arr[i] = arr[i - 1] + arr[i - 2];
}
return arr[num - 1];
}
console.log(fibonacci(1)) // 0
console.log(fibonacci(2)) // 1
console.log(fibonacci(10)) // 34
8.合并排序数组
描述: 合并两个排序的整数数组 A 和 B 变成一个新的排序数组。
样例: 给出A=[1,2,3,4]
,B=[2,4,5,6]
,返回 [1,2,2,3,4,4,5,6]
使用sort排序:
const mergeSortedArray = (arrA,arrB) => arrA.concat(arrB).sort((a,b) => a - b);
let a = [1,2,3,4];
let b = [2,4,5,6];
console.log(mergeSortedArray(a,b));
// 结果: [1, 2, 2, 3, 4, 4, 5, 6]
使用for循环:
const mergeSortedArray = (arrA,arrB) => {
let i;
let j;
let arr = [];
for(i = 0, j = 0; i < arrA.length && j < arrB.length;) {
if(arrA[i] < arrB[j]) {
arr.push(arrA[i++]);
} else {
arr.push(arrB[j++])
}
}
while(i < arrA.length) {
arr.push(arrA[i++]);
}
while(j < arrB.length) {
arr.push(arrB[j++])
}
return arr;
}
let a = [1,2,3,4];
let b = [2,4,5,6];
console.log(mergeSortedArray(a,b));
// 结果: [1, 2, 2, 3, 4, 4, 5, 6]
9.分解质因数
质因数的定义: 能整除给定正整数的质数。
描述:
- 将一个整数分解为若干质因数之乘积
- 你需要从小到大排列质因子
样例:
给出 10, 返回 [2, 5]
给出 660, 返回 [2, 2, 3, 5, 11]
题目分析:
从小到大排列质因子,需要将同一个质因子整除干净。
比如:20 可以被 2 整除两次。
代码:
const primeFactorization = (num) => {
let arr = [];
for(let i = 2; i * i <= num; i++) {
while(num % i === 0) { // 发现质数
num = num / i;
arr.push(i);
}
}
if(num !== 1) arr.push(num);
return arr;
}
console.log(primeFactorization(27)); // [3, 3, 3]
console.log(primeFactorization(29)); // [29]
console.log(primeFactorization(20)); // [2, 2, 5]
10.删除一个字母的回文
描述:
- 给定非空字符串 s,您最多可以删除一个字符。判断是否可以成为回文。
- 该字符串仅包含小写字符 a-z,字符串的最大长度为 50000。
说明:“回文”的意思是“正读和反读都一样”,既要读之自然,更要顺理成章。“上海自来水来自海上”堪称一回文佳句。
样例:
Given s = "aba" return true
Given s = "abca" return true // delete c
题目分析:
如果单单是回文的话,就很简单了:
s === [...s].reverse().join(''); // 翻转字符串与原字符相比
题目中还有一个要求:删除一个字符,也就是允许一个字符的不同。
下面我们的解法主体思路就是通过循环,从两侧向中间比较,然后解决当出现不同的情况,如何保证只允许出现一个不同。
代码:
1.出现一处不同 将值传入一个新函数,再进行判断字符串:
const valid = s => {
let left = 0;
let right = s.length - 1;
while(left < right) {
if(s[left] !== s[right]) {
return (innerVaild(s,left + 1,right) || innerVaild(s,left,right - 1))
}
left++;
right--;
}
return true;
}
const innerVaild = (s,left,right) => {
while(left < right) {
if(s[left] !== s[right]) {
return false; // 去掉一个字符后,还是不同直接返回false
}
left++;
right--;
}
return true;
}
console.log(
'回文验证:',
valid('abaacaaa'),
valid('ab'),
valid('abc'),
valid('aabsjdbaa')
);
// 结果:回文验证: true true false false
上面的代码可以看出,编写了两个函数,思考能不能只用一个方法?
2.递归方法:
const valid = (s, left = 0, right = s.length - 1, type = 'init') => {
if(type == 'init') {
while(left < right) {
if(s[left] !== s[right]) {
return (valid(s,left + 1,right,'again') || valid(s,left,right +1,'again'));
}
left++;
right--;
}
return true;
} else {
while(left < right) {
if(s[left] !== s[right]) {
return false;
}
left++;
right--;
}
return true;
}
}
console.log(
'回文验证:',
valid('abaacaaa'),
valid('ab'),
valid('abc'),
valid('aabsjdbaa')
);
// 结果:回文验证: true true false false
能不能通过设置变量来处理?出现两次不同即失败。
3.根据出现两次不同即不为回文
const valid = s => {
let state = false;
for(let [i,j] = [0,s.length - 1]; i < j; i++, j--) {
if(s[i] !== s[j]) {
if(state) { // 第二次字符不同,直接返回false
return false;
}
if(s[i] === s[j - 1]) {
j--;
state = true;
} else if(s[i + 1] === s[j]) {
i++;
state = true;
} else {
return false;
}
}
}
return true;
}
console.log(
'回文验证:',
valid('abaacaaa'),
valid('ab'),
valid('abc'),
valid('aabsjdbaa')
);
// 结果:回文验证: true true false false
11.最大子数组
描述: 给定一个整数数组,找到一个具有最大和的子数组,返回其最大和。
样例: 给出数组[-2,2,-3,4,-1,2,1,-5,3]
,符合要求的子数组为[4, -1,2,1]
,其最大和为 6
思路分析: 只要找出最大和即可,保存两个值,一个为元素之间相加的值(需比较元素相加的值与当前元素的大小),一个为最大值。
代码:
const maxSubArray = arr => {
let max = arr[0]; // 初始化最大值
let newMax = arr[0]; // 数组元素相加的缓存值
for(let i = 1 ; i < arr.length; i++) {
newMax = Math.max(newMax + arr[i], arr[i]); // 相加是否大于当前值
max = Math.max(newMax,max); // 与最大值相比较
}
return max;
};
console.log(maxSubArray([-2,2,-3,4,-1,2,1,-5,3])) // 6
方式二:
const maxSubArray = arr => {
let max = arr[0]; // 初始化最大值
let newMax = arr[0]; // 数组元素相加的缓存值
for(let i = 1; i < arr.length; i++) {
let curNum = arr[i];
if(curNum >= 0) {
newMax = newMax > 0 ? newMax + curNum : curNum;
} else {
newMax = newMax < curNum ? curNum : newMax + curNum;
}
max = Math.max(newMax,max);
}
return max;
}
console.log(maxSubArray([-2,2,-3,4,-1,2,1,-5,3])) // 6
保存最大和的子数组:
const maxSubArray = arr => {
const max = {
num: arr[0],
start: 0,
end: 1
}
const newMax = {
num: arr[0],
start: 0,
end: 1
}
for(let i = 1; i < arr.length; i++) {
let curNum = arr[i];
if(newMax.num + curNum > curNum) {
newMax.num = newMax.num + curNum;
newMax.end = i + 1;
} else {
newMax.num = curNum;
newMax.start = i;
newMax.end = i + 1;
}
// 最大值被超过,重新赋值最大值
if(max.num < newMax.num) {
max.num = newMax.num;
max.start = newMax.start;
max.end = newMax.end;
}
}
let resutArr = arr.slice(max.start,max.end);
return {
arr: resutArr,
max: max.num
}
}
let result = maxSubArray([-2,2,-3,4,-1,2,1,-5,3]);
console.log(result.arr); // [4, -1, 2, 1]
console.log(result.max); // 6