使用前介绍
1 | // How to export and import |
不借助临时变量,交换整数
加减乘除法
注意:如果是浮点数,对于加减乘除法需要注意浮点数的精度丢失问题。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31// 思想:先求两个数的“和”,再用“和”去减
function swap(a, b) { // 如 [1, 3]
a = a + b; // a = 1 + 3 = 4
b = a - b; // b = 4 - 3 = 1
a = a - b; // a = 4 - 1 = 3
return [a, b]; // [3, 1]
}
// 思想:先求两个数的“差”,再用“差”去加
function swap(a, b) { // 如 [3, 1]
a = a - b; // a = 3 - 1 = 2
b = a + b; // b = 2 + 1 = 3
a = b - a; // a = 3 - 2 = 1
return [a, b]; // [1, 3]
}
// 思想:先求两个数的“乘积”,再用“乘积”去除
function swap(a, b) { // 如 [3, 8]
a = a * b; // a = 3 * 8 = 24
b = a / b; // b = 24 / 8 = 3
a = a / b; // a = 24 / 3 = 8
return [a, b]; // [8, 3]
}
// 思想:先求两个数的“商”,再用“商”去乘
function swap(a, b) { // 如 [8, 3]
a = a / b; // a = 8 / 3 = 8 / 3
b = a * b; // b = (8 / 3) * 3 = 8
a = b / a; // a = 8 / (8 / 3) = 3
return [a, b]; // [3, 8]
}
对象法
1 | function swap(a, b) { // 如 [1, 3] |
数组法
1 | function swap(a, b) { // 如 [1, 3] |
ES6数组解构法,推荐
1 | function swap(a, b) { // 如 [1, 3] |
数组去重
方法1(filter,推荐使用)
1 | // filter函数会过滤出满足条件的元素 |
方法2(新数组法)
1 | function uniqueArr(arr){ |
方法3(hash表法)
1 | function uniqueArr(arr) { |
方法4
1 | function uniqueArr(arr) { |
方法5(同时排序)
1 | function uniqueArr(arr){ |
方法6(set + Array.from)
1 | function uniqueArr(arr) { |
方法7(set + …扩展运算符)
1 | function uniqueArr(arr) { |
实现数组的扁平化
将多维数组变成一位数组:如[1, [2, 3, [4, 5]]] => [1, 2, 3, 4, 5]
toString
局限性:使用 toString()
方法后,所有的元素类型都变成了字符串,再使用Number还原,假设原数组中存在类型不一的元素,则不能还原类型1
2
3
4
5
6function flatten(arr) {
return arr.toString().split(',').map(item => {
return Number(item)
})
}
flatten([1, [2, 3, [4, 5]]])
flat
1 | [1, [2, 3]].flat() // 只适用于二维数组 |
reduce
遍历每一项,如果值为数组,则递归遍历1
2
3
4
5
6function flatten(arr) {
return arr.reduce((result, item, index, arr) => {
return result.concat(Array.isArray(item) ? flatten(item) : item)
}, [])
}
flatten([1, [2, 3, [4, 5]]])
递归
1 | function flatten(arr) { |
es6扩展运算符…
1 | function flatten(arr) { |
数组中含所有不定类型值的去重
普通法
1 | function uniqueArr(arr) { |
map法
1 | function uniqueArr(arr) { |
取数组中的最大最小值
排序法
1 | var arr = [0, 2, 5, 1, 4, 3]; |
apply方法
1 | Math.max.apply(null, [1, 2, 3, 4]); |
数组排序
冒泡排序
- 比较相邻的元素,如果第一个比第二个大,就交换他们两个。
- 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
- 针对所有的元素重复以上的步骤,除了最后一个。
- 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
- 实际排序时,由后往前完成排序动作。
1 | function bubbleSort(arr) { |
选择排序
- 首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置。
- 再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。
- 重复第二步,直到所有元素均排序完毕。
1 | function selectionSort(arr) { |
快速排序
- 从数列中挑出一个元素,称为 “基准”(pivot);
- 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
- 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序;
1 | function quickSort(arr) { |
插入排序
- 将第一待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。
- 从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面。)
1 | function insertionSort(arr) { |
二路归并排序
将两个按值有序序列合并成一个按值有序序列1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20function merge(left, right) {
var result = [],
il = 0,
ir = 0;
while (il < left.length && ir < right.length) {
if (left[il] < right[ir]) {
result.push(left[il++]);
} else {
result.push(right[ir++]);
}
}
while(left[il]){
result.push(left[il++]);
}
while(right[ir]){
result.push(right[ir++]);
}
return result;
}
希尔排序
1 | function shellSort(arr) { |
其他排序应用
数字、英文排序
1 | // 顺序: |
中文姓名排序
1 | // 顺序 |
数组乱序
遍历法
取随机位的值与当前的互换1
2
3
4
5
6
7function shuffle(arr) {
for (var i = arr.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
[arr[i], arr[j]] = [arr[j], arr[i]];
}
return array;
}
sort法
随机数大于0.5的概率为1/2,然后选择顺序或倒序即可1
2
3
4
5
6function shuffle(arr) {
arr.sort((a, b) => {
var sign = (Math.random() > 0.5) ? 1 : -1;
return (a - b) * sign;
});
}
Number数组中最大差值
1 | function getMaxProfit (arr) { |
打印九九乘法表
1 | for (var n = 1; n <= 9; n++) { |
求数组交集和差级
ES7方法:1
2
3let intersection = a.filter(v => b.includes(v))
let difference = a.concat(b).filter(v => !a.includes(v) || !b.includes(v))
字符串翻转
转换成array
1 | function reverseString(str) { |
反向遍历
1 | function reverseString(str){ |
生成随机字符串
1 | function randomString(n){ |
判断回文
1 | // 算法思想:每次判断第一个字符和最后一个字符是否相等,然后取第二个字符到倒数第二个字符之间的字符串递归 |
统计出现最多的元素
最常见的思路是先使用object统计出元素和个数,再循环取最大的,但这样无疑会增加复杂度。所以需要在第一次遍历的时候就缓存好最大的元素。
统计字符串中最多的字母和出现的次数
1 | // 算法思想:先遍历,将出现的字符和次数以object的形式输出;再obj遍历,输出次数最多的字符 |
统计数组中出现最多次数的元素和次数
1 | function findMaxDuplicateArr(arr) { |
阶乘
1x2x3x4x5…
递归
1 | function factorialize(num) { |
非递归
1 | function factorialize(num) { |
生成斐波那契数列
斐波那契数列(黄金分割数列): 0、1、1、2、3、5、8、13、21、34,考察递归
递归
1 | function getfib(n){ |
1 | function getfib(n){ |
非递归
1 | function getFibonacci(n) { |
二分查找
查找某个值是否在有序数组中,有则返回索引,数组必须是有序的
注意:
还有一种不传起始和终止下标参数,通过在函数体内使用slice取到新的arr,整体没有这种方法好。
递归
1 | /** |
非递归
1 | /** |
找出数组当中的质数
质数:也称素数,>1, 有无限个。除1和它自身之外不能被其他数整除,如2、3、5、7等,否则称为合数。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26// 思想:m % n === 0, 等于0表示能整除,即不是质数
// 循环生成一个100内的数组
var i, arr = [];
for (i = 1; i < 100; i++) {
arr.push(i);
}
var getPrimes = arr.filter((el) => {
var flag = true; // 定义一个boolean值,filter返回布尔值
if (el < 2) { // 小于2的直接排除
flag = false;
} else {
// 使用小于当前元素的数值去整除当前当前元素,有一个可以整除则跳出循环
for (var j = 2; j < el; j++) {
if (el % j === 0) {
flag = false;
break;
}
}
}
return flag;
});
console.log(getPrimes)
js求1! + 2! + 3! + 4! + 5!(阶乘)
思路:s
转换成 1! + (2 * 1!) + (3 * 2!) + (4 * 3!) + (5 * 4!)
1
2
3
4
5
6
7
8
9var sum = 0, // 总和
sum2 = 1;
for (var i = 1; i <= 5; i++) {
sum2 *= i; // 当前第一数
sum += sum2;
}
console.log(sum)
两数之和
给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
示例1
2
3
4给定 nums = [2, 7, 11, 15], target = 9
因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]
解答1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34// 常规错误想法
// 原因:未考虑相同位相加,如[3, 2, 4],会返回[0, 0],因为相同位的3 + 3已经满足了条件
/**
* @param {number[]} nums
* @param {number} target
* @return {number[]}
*/
var twoSum = function(nums, target) {
for (var i = 0; i < nums.length; i++) {
for (var j = 0; j < nums.length; j++) {
if (nums[i] + nums[j] === target) {
return [i, j];
}
}
}
};
// 正确解法: 双重循环式避开i和j相等的情况
/**
* @param {number[]} nums
* @param {number} target
* @return {number[]}
*/
var twoSum = function(nums, target) {
for (var i = 0; i < nums.length; i++) {
for (var j = i + 1; j < nums.length; j++) {
if (nums[i] + nums[j] === target) {
return [i, j];
}
}
}
};
无重复字符的最长子串
给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。
示例
示例 1:1
2
3输入: "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
示例 2:1
2
3输入: "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
示例 3:1
2
3
4输入: "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
解答1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32// 暴力法:耗时过长
/**
* @param {string} s
* @return {number}
*/
var lengthOfLongestSubstring = function(s) {
var maxLen = 0;
for (var i = 0; i < s.length; i++) {
for (var j = i + 1; j <= s.length; j++) {
// 通过确定i、j来获取所有的连续的子串,再通过方法查找该子串中是否存在重复的字符
if (allUnique(s, i, j)) maxLen = Math.max(maxLen, j - i);
}
}
return maxLen;
};
/**
* 检查一个子字符串是否含有重复的字符
* @param {string} str
* @param {number} start
* @param {number} end
* @return {boolean}
*/
var allUnique = function(str, start, end) {
var tempStr = '';
for (var i = start; i < end; i++) {
// 查看临时字符串中是否已经包含了当前字符
if (tempStr.includes(str.charAt(i))) return false;
tempStr += str.charAt(i); // 不包含则存储
}
return true;
}
整数反转
给出一个 32 位的有符号整数,你需要将这个整数中每位上的数字进行反转。
示例
示例 1:1
2输入: 123
输出: 321
示例 2:1
2输入: -123
输出: -321
示例 3:1
2输入: 120
输出: 21
假设我们的环境只能存储得下 32 位的有符号整数,则其数值范围为 [−2^31, 2^31 − 1]。请根据这个假设,如果反转后整数溢出那么就返回 0。
解答1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26/**
* @param {number} x
* @return {number}
*/
var reverse = function(x) {
let sum = 0, // 翻转后的
num = Math.abs(x); // 取绝对值
// 核心
while(num) {
sum = sum * 10 + num % 10;
num = parseInt(num / 10);
}
// 核心代码壳可改成:借助数组reverse方法直接翻转
// sum = parseInt(String(num).split('').reverse().join(''));
sum = x > 0 ? sum : -sum; // 补上原先的正负符号
// 判断溢出情况
if (sum > Math.pow(2, 31) || sum < -Math.pow(2, 31)) {
return 0;
}
return sum;
};
走楼梯
10个台阶,每次只能上1个、2个或3个,一共有多少种走法?
拆分:
如果上10个台阶,可以分解以下情况:
- 上9个台阶,最后上1个台阶,假设这种前面9个的走法是 m 种。
- 上8个台阶,最后上2个台阶,假设这种前面8个的走法是 n 种。
- 上7个台阶,最后上3个台阶,假设这种前面7个的走法是 l 种。
所以上10个台阶的方法其实就是 m + n + l 种
同理:
1 中的上9个台阶也可以分为:
- 上8个台阶,最后上1个台阶,假设这种前面8个的走法是 m 种。
- 上7个台阶,最后上2个台阶,假设这种前面7个的走法是 n 种。
- 上6个台阶,最后上3个台阶,假设这种前面6个的走法是 l 种。
所以上9个台阶的方法其实就是 x + y + z 种
可以递归为:
f(n) = f(n - 1) + f(n - 2) + f(n - 3)
1 | // 从后往前递归 |
今日头条算法推导题
假设在今日头条里面,有很多工作人员检查新闻是不是属于虚假新闻,所有新闻真实率到达了98%,工作人员在检验一个真实的新闻把它检验为一个虚假的新闻的概率为2%,而一个虚假的新闻被检验为真实的新闻的概率为5%. 那么,一个被检验为真实的新闻确实是真实的新闻的概率是多大?
A.0.9991
B.0.9989
C.0.9855
D.0.96
答案:B
分析条件得到:
真的新闻:98%
假的新闻:2%
真的->假的:2%
假的->真的:5%
分析要求:被检验为真实的新闻确实是真实的新闻
首先要明确被检验为真实的新闻包括了(本来是真的和本来是假的)所以分子为(真->真),分母为(真->真 + 假->真)
结果为:(真->真)/(真->真 + 假->真) = (98%(1-2%)) / (98%(1-2%) + 2%*5%) = 0.9604/0.9614 = 0.9989……
求排列组合
[a] 得到 [a]
[a, b] 得到 [a, b, ab]
[a, b, c] 得到 [a, b, ab, c, ac, bc, abc]
[a, b, c, d] 得到 [a, b, ab, c, ac, bc, abc, d, ad, bd, abd, cd, acd, bcd, abcd]
分析:
- [a]的结果为[a],数量为1
- 从[a]到[a, b],结果其实是[a]的结果加上b元素,然后再将[a]结果中的每一个元素和b进行组合,得到[a, b, ab],总数为 1+1+1=3
- 从[a, b]到[a, b, c],结果其实是[a, b]的结果加上c元素,然后将[a, b]结果中的每一个元素和c进行组合,得到[a, b, ab, c, ac, bc, abc],总数为3+1+3 = 7
所以,n个元素的排列组合为:fn = f(n-1) + 1 + f(n-1)
要实现多个元素的排列组合,可以从1个元素开始算,随着遍历往后移动,将前一个的结算结果与新元素拼接,然后进行组合,完成之后再覆盖临时变量
实现:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19function fn (arr) {
let res = [] // 存放最终的结果
let temp = [] // 临时存放
for (let i = 0; i < arr.length; i++) {
temp.push(arr[i]) // 暂存单个元素
for (let j = 0; j < res.length; j++) {
temp.push(res[j] + arr[i]) // 将上一轮得到的结果与新增元素进行一轮组合
}
res = [...temp] // 将这一轮的结果全部存入res中
// 或:
// res.push(temp)
// temp = []
}
return res
}
// ['a']: ['a']
// ['a', 'b']: ["a", "b", "ab"]
// ['a', 'b', 'c']: ["a", "b", "ab", "c", "ac", "bc", "abc"]
如果变换一下需求,如果想:
[a] 得到 [a]
[a, b] 得到 [a, ab]
[a, b, c] 得到 [a, ab, abc]
[a, b, c, d] 得到 [a, ab, abc, abcd]
最简单结合slice方法如下:1
2
3
4
5
6
7function fn (arr) {
let res = []
for (let i = 0; i < arr.length; i++) {
res.push(arr.slice(0, i + 1).join(''))
}
return res
}
如果不可以使用slice方法:1
2
3
4
5
6
7
8
9
10
11function fn (arr) {
let res = [] // 存放最终的结果
for (let i = 0; i < arr.length; i++) {
if (!res.length) {
res.push(arr[i])
} else {
res.push(res[res.length - 1] + arr[i])
}
}
return res
}
用两个栈实现一个队列
1 | // 用两个栈(后进先出 push、pop)实现队列(先进先出 push shift) |
实现不规范版本号的排序
1 | // ['1.1.1', '1.1.1.1.1', '65.1.1', '6.5.1', '7'] |
输入一组数,输出最大组合数
1 | // 例如:[31, 3, 34],输出 34331 |
二叉树排列(从上到下,从左到右)
1 |
二叉树的蛇形排列(从上到下,奇数行从左到右,偶数行从右到左)
1 | // 基数行按照从左到右,偶数行按照从右到左 |
二维有序数组的查找
1 | // [ |
for循环 + includes、indexOf
1 | function find(target, array) { |
some + includes、indexOf
1 | function find(target, array) { |
线性查找
1 | // 从右上角开始查找:比目标数大向左走,比目标数小向下走 |
防疲劳设置
弹窗每 10s 内只能出现 3次,假定弹窗本身只能看,不能操作1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52function showAlert (count) {
console.log('alert visible:', count)
}
function fn (time, n) {
var count = 0
return function () {
if (count >= n) return
showAlert(count)
count++
setTimeout(() => {
count--
}, time * 1000)
}
}
var f = fn(10, 3)
// 执行结果(sleep不需要实现)
console.log('sleep_0')
f() // 打印
setTimeout(() => {
console.log('sleep_' + 3*10e2)
f() // 打印
}, 3*10e2)
setTimeout(() => {
console.log('sleep_' + 6*10e2)
f() // 打印
}, 6*10e2)
setTimeout(() => {
console.log('sleep_' + 11*10e2)
f() // 打印
}, 11*10e2)
setTimeout(() => {
console.log('sleep_' + 13*10e2)
f() // 不打印
}, 13*10e2)
setTimeout(() => {
console.log('sleep_' + 14*10e2)
f() // 打印
}, 14*10e2)
setTimeout(() => {
console.log('sleep_' + 15*10e2)
f() // 不打印
}, 15*10e2)
setTimeout(() => {
console.log('sleep_' + 18*10e2)
f() // 打印
}, 18*10e2)
setTimeout(() => {
console.log('sleep_' + 20*10e2)
f() // 不打印
}, 20*10e2)
链式调用实现执行、等待执行、先执行操作
1 | /** |
链式调用的原理:一个对象里面的多个方法,每个方法内部return this,这样后面的方法在调用的时候就可以继续在this环境下执行。1
2
3
4
5
6
7
8
9
10
11
12
13const obj = {
eat () {
console.log('eat')
return this
},
drink () {
console.log('drink')
return this
}
}
obj.eat().drink() // eat、drink
obj.drink().eat() // drink、eat
本题实现1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57function Fn (msg) {
this.msg = msg
this.queue = [] // 使用任务队列(先进先出的思想模拟执行顺序)
const fn = () => {
console.log(`hello ${msg}`)
this.next() // 执行完之后取出队列下一个函数执行
}
this.queue.push(fn)
setTimeout(() => {
console.log(this)
this.next()
}, 0) // 异步,先等prototype执行完,将所以事件进行收集再执行
return this
}
Fn.prototype = {
wait (time) {
const fn = () => {
setTimeout(() => {
this.next()
}, time * 1000)
}
this.queue.push(fn) // 普通级别的放在队尾
return this
},
waitFirst (time) {
const fn = () => {
setTimeout(() => {
this.next()
}, time * 1000)
}
this.queue.unshift(fn) // 优先级高的放入队首
return this
},
do (name) {
const fn = () => {
console.log(`hello ${name}`)
this.next()
}
this.queue.push(fn) // 普通级别的放在队尾
return this
},
// 从任务队列中取出函数执行
next () {
const fn = this.queue.shift() // 从头部取出函数
fn && fn()
}
}
// new Fn('msg')
// new Fn('msg').wait(2).do('aaa')
// new Fn('msg').wait(2).do('aaa').do('bbb')
// new Fn('msg').waitFirst(2).do('aaa').do('bbb')
new Fn('msg').waitFirst(2).wait(2).do('bbb')