有趣的js编程题
- 一、旋转数组
- 二、请找出1-10000中的对称数字
- 三、给定一个数组将该数组的0项放到最后
- 四、实现一个方法可以将传入的数字逆序字符串形式输出
- 五、myInterval(fn,a , b, n)定时函数
- 六、rgb转成16进制的方法
- 七、promise.retry方法
- 八、数组扁平化、去重、排序
- 八、数组拼接
- 九、地址路径搜索匹配
- 十、两个大数相加
- 十一、斐波那契
- 十二、写一个函数验证一个数是否是素数
- 十三、求最大公约数
- 十四、判断是否是回文
- 十五、将数字12345678转化成RMB形式:12,345,678
- 十六、删除相邻相同的字符串
- 十七、连字符转成驼峰
- 十八、贪心算法
- 十九、击鼓传花
- 二十、最长公共前缀
- 二十一、爬楼梯问题
- 二十二、二叉树的最大深度
- 二十三、 组成最长的回文串
- 二十四、十进制整数M转化为N进制数
- 二十五、获取对象的最深层级
- 二十六、打印九九乘法表
- 二十七、版本号比较
- 二十八、商品分类
- 二十九、语句反转
- 三十、fecth请求并考虑请求超时
- 三十一、扁平数组转树形
- 三十二、并发限制的promise异步调度器
- 三十三、字符全排列
前端必备工具推荐网站(免费图床、API和ChatAI等实用工具):
http://luckycola.com.cn/
一、旋转数组
1、题目描述:
传入[1, 2, 3, 4, 5, 6]数组和一个旋转的次数k返回旋转后的数据,
例子:传入[1, 2, 3, 4, 5, 6]和k=3,获得数组[4, 5, 6, 1, 2, 3]
2、实现思路
- pop与unshift方法
function roateArr1 (arr, k) {
let step = k % arr.length;
while(k) {
let last = arr.pop();
arr.unshift(last);
k--;
};
return arr;
}
console.log(roateArr1([1, 2, 3, 4, 5, 6], 1))
- slice方法(不改变原数组,返回新数组)
function roateArr2(arr, k) {
let step = k % arr.length;
let tem1 = arr.slice(-step);
let temp2 = arr.slice(0, arr.length - step);
return tem1.concat(temp2);
}
console.log(roateArr2([1, 2, 3, 4, 5, 6], 2))
- splice方法(改变原数组)
function roateArr3(arr, k) {
let step = k % arr.length;
return arr.splice(-step).concat(arr);
}
console.log(roateArr3([1, 2, 3, 4, 5], 4))
二、请找出1-10000中的对称数字
1、题目描述:
// 请找出1-10000中的对称数字
// 例如:121 2442
2、实现思路
构造数据列表的几种方式
let numsArr = [...Array(10000).keys()].map(i => i + 1);
let numsArr2 = Array.from({length: 10000}, (v, i) => i + 1);
let numsArr3 = new Array(10000).fill('').map((v, i) => i + 1)
// console.log(numsArr3);
- 筛选思路 revere倒转对比
function getLvLNums1(arr) {
return arr.filter((item, i) => item.toString().split('').reverse().join('') == item)
};
console.log(getLvLNums1(numsArr))
2.数学规律构造思路
// 2、数学规律构造
// 1、2、3、...9
// 11、22...99
// 101、111、121...191、202...292、909...999
// 1001、1111、1221....2002、2112.....9009、9119...9999
function getLvLNums2() {
let res = [];
let i = 1;
for(;i <= 9; i++) {
res.push(i);
res.push(11 * i);
for(let j = 0; j <=9; j++) {
res.push(101 * i + 10 * j);
res.push(1001 * i + 110 * j);
}
}
return res.sort((a, b) => a - b)
}
console.log(getLvLNums2())
三、给定一个数组将该数组的0项放到最后
1、题目描述:
// 给定一个数组将该数组的0项放到最后
// 例如:[1, 3, 4, 0, 22, 45, 0, 9] => [1, 3, 4, 22, 45, 9, 0, 0]
2、实现思路
1.filter筛选思路
let testArr = [1, 0, 3, 0, 0, 99, 0];
function afterZeroFn1(arr) {
return arr.filter(item => +item).concat(Array(arr.length - arr.filter(item => +item).length).fill('0').map(v => +v))
};
// console.log(afterZeroFn1(testArr));
2.push和splice思路
注意点: 需要一个变量来动态减少循环的次数
function afterZeroFn2(arr) {
let i = 0;
let num = 0;
for(;i < arr.length - num; i++) {
if (arr[i] === 0) {
arr.splice(i, 1);
arr.push(0);
num ++;
i--;
}
}
return arr;
}
// console.log(afterZeroFn2(testArr));
3.排序算法是思路–双指针思想
function afterZeroFn3(arr) {
let point1 = 0;
let i = 0;
for(;i < arr.length; i++) {
if (arr[i] === 0 && arr[point1] !== 0) {
point1 = i;
} else {
if (point1 < i && arr[i] != 0 && arr[point1] === 0) {
let temp = arr[i];
arr[point1] = temp;
arr[i] = 0;
point1 = i;
}
}
}
return arr;
}
console.log(afterZeroFn3(testArr));
四、实现一个方法可以将传入的数字逆序字符串形式输出
1、题目描述:
// 实现一个方法可以将传入的数字逆序字符串形式输出
// 例子:123 =》 ‘321’
2、实现思路
1.数组reserve思路
let str = '12345';
function strReverse1(str) {
return str.toString().split('').reverse().join('');
};
// console.log('strReverse1=>', strReverse1(str));
2.字符串递归截取思路
递归需要有一个合适的结束时机
function strReverse2(str) {
let temp = str + '';
if (temp.length === 1) return temp;
return temp.substring(temp.length - 1) + strReverse2(temp.substring(0, temp.length - 1));
}
// console.log('strReverse2=>', strReverse2(str));
- 数学取值思想(递归思想)
递归需要有一个合适的结束时机
// 12345 -----> 12345 % 10 = 5 Math.floor(12345 / 10) = 1234
// 1234 ----> 1234 % 10 = 4 Math.floor(1234 / 10) = 123
// 123 ----> 123 % 10 = 3 Math.floor(123 / 10) = 12
// 12 ----> 12 % 10 = 2 Math.floor(12 / 10) = 1
// 1 ----> 1 % 10 = 1 Math.floor(1 / 10) = 0
function strReverse3(str) {
temp = Math.floor(str / 10);
if (temp == 0) return str % 10;
return str % 10 + '' + strReverse3(temp);
}
console.log('strReverse3=>', strReverse3(str));
五、myInterval(fn,a , b, n)定时函数
1、题目描述:
// 第一题:写一个myInterval(fn,a , b, n),
// 每次间隔a, a+b, a+2b...a+nb的时间执行fn函数,执行n次欧关闭定时器
2、实现思路
1.闭包思路
function myInterval(fn, a, b, n) {
let _a = a;
let _b = b;
let _n = n;
let count = 0;
let _fn = fn;
function inFn() {
let time = _a + count * _b;
console.log('time=>', time)
if (count >= _n) {
return;
}
setTimeout(_ => {
_fn();
count++;
inFn();
}, time)
}
inFn();
}
myInterval(function(){
console.log('myInterval....');
}, 1000, 2000, 3);// 1s 3s 5s 7s
2.面向对象思想
unction myInterval(fn, a, b, n) {
this.a = a;
this.b = b;
this.fn = fn;
this.count = 1;
this.n = n;
}
// 正确实现
myInterval.prototype.start = function() {
let delay = this.a + this.count * this.b;
let timer = setTimeout(() => {
console.log('执行函数:', this.count);
this.fn();
this.count++;
this.start();
}, delay);
if (this.count > this.n) {
clearTimeout(timer);
console.log('清理定时器');
return;
};
};
let instance = new myInterval(function(){
console.log('执行了。。');
}, 1000, 2000, 3);
instance.start();
六、rgb转成16进制的方法
1、题目描述:
// 实现一个将rgb转成16进制的方法
// 例子:rgb(255, 255, 255) ==> #ffffff
2、实现思路
1.思路分析
思路分析
1、通过正则将rgb(255, 255, 255)中的颜色值提取出来
2、将数字进行16进制转化
2.1 通过toString()方法进行进制转化
2.2 必须注意大于16的数字转化成16精致前面是不需要补0 的
let testRGB = 'rgb(255, 255, 2)';
function TranfromColor() {
this.originColor = '';
this.colorArr = [];
this.result = '#';
// rgb(255, 255, 255)
// this.colorReg = /\d+/g;
this.colorReg = /^(rgb|RGB)\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)\s*$/;
this.get16Color = function(rgbColor) {
this.originColor = rgbColor;
if (!this.originColor) return;
this.getColorArr(this.originColor).forEach((item, idx) => {
// this.result += ('0' + Number(item).toString(16)).substr(-2);
this.result += (Number(item) > 16 ? '' : '0') + Number(item).toString(16);
// this.result += ('' + Number(item).toString(16)).padStart(2, 0);
});
return this.result;
};
this.getColorArr = (originColor) => {
let matchRes = originColor.match(this.colorReg);
if (!matchRes) return;
// console.log(matchRes, matchRes[0], matchRes[1], matchRes[2]);
matchRes[2] && this.colorArr.push(matchRes[2]);
matchRes[3] && this.colorArr.push(matchRes[3]);
matchRes[4] && this.colorArr.push(matchRes[4]);
[...Array(3).keys()].forEach((item, indx) => {
+this.colorArr[item] > 255 && (this.colorArr[item] = '255')
});
return this.colorArr;
}
}
let tranfromColor = new TranfromColor();
let color16 = tranfromColor.get16Color(testRGB);
console.log(color16)
七、promise.retry方法
1、题目描述:
// 实现一个promise.retry方法 重试异步函数
// 如果异步函数执行成功 resolve结果
// 如果异步函数执行失败 就尝试超过一定次数才进行reject
2、实现思路
1.思路分析
思路分析
1、通过await 和trycatch进行错误捕捉
2、while循环的是整个trycatch处理过程,而不是单纯的函数执行部分
Promise.retry = function(fn, times) {
return new Promise(async (resolve, reject) => {
while(true) {
try {
let res = await fn();
resolve(res);
console.log(res);
break;
} catch (error) {
if (times < 1) {
console.log('最终失败');
reject('fainaly fail');
break;
}
times--;
console.log('失败一次,剩余' + times + '次')
}
}
})
}
Promise.retry(testFn, 3).then((res) => {
console.log('okkk==>', res)
}, err => {
console.log('err===>', err);
})
八、数组扁平化、去重、排序
1、题目描述:
// 实现数组扁平化、去重、排序输出
let arrTest = [
9,
[1, 2],
[3, 4, [5, 6, 7]],
[8,0, [22, 33, 44, [55, 66]]]
];
2、实现思路
1.思路分析
思路分析
1、 核心思想 递归思想和数组concat方法
2、[].concat([1], [2], [3, 4]) => [1, 2, 3, 4]
3、[].concat(1, 2, 3, [4, 5]) => [1, 2, 3, 4, 5]
Array.prototype.flat = function() {
let arr = this;
let newArr = arr.map(item => {
if (Array.isArray(item)) {
return item.flat();
};
return [item];
});
console.log('中间状态:', newArr);
return [].concat(...newArr);
}
Array.prototype.unique = function() {
return [...new Set(this)];
}
// console.log(arrTest.flat());
Array.prototype.flat2 = function() {
let arr = this;
while(arr.some(item => Array.isArray(item))) {
arr = [].concat(...arr);
};
console.log('中间状态2=》', arr)
return arr;
};
console.log(arrTest.flat2().unique().sort((a, b) => a -b));
八、数组拼接
1、题目描述:
// 有下面两个数组
// [A1, A2, A3, A4, B1, B2, B3, B4, C1, C2, C3, D1]
// [A, B, C, D]
// 将他们合并为新的数组:[A1, A2, A3, A4, A, B1, B2, B3, B4, B, C1, C2, C3, C, D1, D]
2、实现思路
1.思路分析
双层循环和正则判断
var arr1 = ['A1', 'A2', 'A3', 'A4', 'B1', 'B2', 'B3', 'B4', 'C1', 'C2', 'C3', 'D1'];
var arr2 = ['A', 'B', 'C', 'D'];
function getNewArr1(arr1, arr2) {
// 创建一个新数组,以免在原始数组上进行操作导致意外的副作用
let newArr = [...arr1];
let lastIndex = 0; // 跟踪arr1中当前分组的最后一个元素的索引
for (let i = 0; i < arr2.length; i++) {
// 找到当前分组(A, B, C, D)在arr1中的最后一个元素的索引
while (lastIndex < newArr.length && newArr[lastIndex].charAt(0) === arr2[i]) {
lastIndex++;
}
// 在分组的末尾插入arr2的当前元素
newArr.splice(lastIndex, 0, arr2[i]);
// 更新lastIndex,以便下次循环时能够找到下一个分组的末尾
lastIndex++;
}
return newArr;
}
// 关注过程的思路
function getNewArr2(arr1, arr2) {
for(let i = 0; i < arr2.length; i++) {
for (let index = 0; index < arr1.length; index++) {
if (index - 1 >= 0 && arr1[index - 1].charAt(0) == arr2[i] && arr1[index].charAt(0) != arr2[i]) {
arr1.splice(index, 0, arr2[i]);
index++;
}
}
}
arr1.push(arr2[arr2.length - 1])
return arr1;
}
// console.log(getNewArr2(arr1, arr2));
九、地址路径搜索匹配
1、题目描述:
// 树状结构的关键词搜索
let testObj = {
babel: '北京市',
child: [
{
babel: '朝阳区',
child: [
{
babel: '西半街道',
},
{
babel: '向上向善',
}
]
},
{
babel: '昌平区',
child: [
{
babel: '香水百合',
},
{
babel: '昌平街道',
}
]
}
]
}
// 实现一个函数search可以进行关键词搜索,返回出关键词出现的链路
// 比如 search('西半') 返回 ['北京市', '朝阳区', '西半街道']
// 比如 search('朝阳区') 返回 ['北京市', '朝阳区']
// 比如 search('街道') 返回 ['北京市', '昌平区', '昌平街道']、 ['北京市', '朝阳区', '西半街道']
2、实现思路
1.思路分析
递归函数书写的核心思想是考虑下面几个问题:
1、 递归函数的处理节点是什么(什么时候进行下一个递归函数的调用)? 一个对象?一个字符串?
2、 递归函数的结束条件是什么(什么时候结束)?
3、如果这个这整个递归的链路里需要一个值去记录这递归过程,那么这个值应该是通过参数去层层传递的
2.代码实现
function search(string, obj) {
let result = [];
function inSearch(obj, parentPath) {
let curPath = parentPath.concat(obj.babel);
if (obj.babel.search(string) !== -1) {
result.push(curPath);
}
if(obj.child && obj.child.length > 0) {
obj.child.forEach(child => {
inSearch(child, curPath);
});
}
}
inSearch(obj, []);
console.log(result);
return result;
}
search('街道', testObj)
十、两个大数相加
1、题目描述:
实现 9007199254740991 与 1234567899999999999两个大数相加
2、实现思路
1.思路分析
思路分析
1、用0去补齐长度
let a = "9007199254740991";
let b = "1234567899999999999";
function add(a ,b){
//取两个数字的最大长度
let maxLength = Math.max(a.length, b.length);
//用0去补齐长度
a = a.padStart(maxLength , 0);//"0009007199254740991"
b = b.padStart(maxLength , 0);//"1234567899999999999"
//定义加法过程中需要用到的变量
let t = 0;
let f = 0; //"进位"
let sum = "";
for(let i=maxLength-1 ; i>=0 ; i--){
t = parseInt(a[i]) + parseInt(b[i]) + f;
f = Math.floor(t/10);
sum = t%10 + sum;
}
if(f == 1){
sum = "1" + sum;
}
return sum;
}
十一、斐波那契
思路分析
1、概念理解和递归结束的判断
0 ,1,1,2,3,5,8,13...这样的数列称为斐波那契数列
function fibonacci(n) {
if (n <= 1) {
return n;
}
return fibonacci(n - 1) + fibonacci(n - 2);
};
console.log(fibonacci(10));
// ---------------------类似的题目
// 求n! => n! = n * (n-1) * (n-2)...*2 * 1
function getResFn(n) {
if (n == 1 || n ==0) {
return 1;
}
return getResFn(n-1) * n;
}
// 写出求1+2+…+n、求1+1/2+1/3+..+1/n
function getResFn2(n) {
if (n <= 1) {
return 1;
}
return getResFn(n-1) + n;
}
function getResFn3(n) {
if (n <= 1) {
return 1;
}
return getResFn(n-1) + 1/n;
}
十二、写一个函数验证一个数是否是素数
思路分析
1、概念理解
// -如果这个数是 2 或 3,一定是素数;
// -如果是偶数,一定不是素数;
// -如果这个数不能被3~它的平方根中的任一数整除,m必定是素数。而且除数可以每次递增(排除偶数)
function isPrime(num){
if(num === 2 || num === 3) return true;
if (num <=1) return false;
if (num % 2 === 0) return false;
// 平方根
let sqrtN = Math.sqrt(num);
let dernum = 3;
while(sqrtN >= dernum) {
if (num % dernum === 0) {
return false;
} else {
dernum += 2;
}
}
return true;
}
console.log(isPrime(11)); // false
十三、求最大公约数
思路分析
1、概念理解
除数 在a和b的范围内,如果同时a和b处以除数的余等于0,就将此时的除数赋值给res;除数自增,不断循环上面的计算,更新res。
function gcd(a, b) {
// 保证a比b大
if (a < b) {
[a, b] = [b, a];
};
// 欧几里得算法
// 当b等于0时,a就是最大公约数
while(b != 0) {
let temp = b;
b = a % b;
a = temp;
}
return a;
}
console.log(gcd(48, 18))
十四、判断是否是回文
function isPalindrome(str){
if(!str) return false;
return str === str.split('').reverse().join('');
}
console.log(isPalindrome("madam"))
十五、将数字12345678转化成RMB形式:12,345,678
思路分析
字符串转成数组—》数组反转-〉数组转字符串
function changeStr(numStr) {
let strArr = numStr.split('').reverse();
let resArr = [];
for (let index = 0; index < strArr.length; index++) {
resArr.push(strArr[index]);
if ((index + 1) % 3 === 0) {
resArr.push(',')
}
};
return resArr.reverse().join('');
}
console.log('changeStr:', changeStr('12345565'))
十六、删除相邻相同的字符串
思路分析
前置标记对比思路
function getNewStr(str) {
let newStrArr = [];
let preStr = '';
for(let i = 0; i <= str.length; i++) {
if (str[i] != preStr) {
newStrArr.push(str[i]);
}
preStr = str[i];
}
return newStrArr.join('');
}
console.log('getNewStr:', getNewStr('qwwdeeeixsxj'))
十七、连字符转成驼峰
思路分析
function nameToUp(nameStr) {
let strArr = nameStr.split('-');
for(let i = 0; i < strArr.length; i++) {
i > 0 && (strArr[i] = strArr[i].charAt(0).toUpperCase() + strArr[i].substring(1));
}
return strArr.join('');
}
console.log('nameToUp:', nameToUp('id-xs-zajj'))
十八、贪心算法
1、题目描述:
// 贪心算法
// 贪心算法是一种在解决优化问题时使用的算法思想,其核心思想是每一步选择当前状态下的最优解,
// 而不考虑未来可能造成的影响。虽然贪心算法不能保证得到全局最优解,
// 但在某些情况下,它可以快速找到一个相对较好的解决方案,并且具有高效性。
// 经典问题:找零钱问题(找零问题)。该问题是指给定一定数量的硬币,要求找零钱时使用尽可能少的硬币数目。]
function greedyCoinChange(coins, amount) {
// 排序 从大到小
coins.sort((a, b) => b - a);
// 需要的硬币数量
let coinNum = 0;
// 剩余金额数
let remainingAmount = amount;
// 不同额度对应的硬币数量
let amountCountObj = {};
for (let i = 0; i < coins.length; i++) {
let curCoinNum = Math.floor(remainingAmount / coins[i]);
coinNum += curCoinNum;
// 更新剩余金额
remainingAmount = remainingAmount % coins[i];
amountCountObj[coins[i]] = curCoinNum;
if (remainingAmount == 0) {
break;
};
};
if (remainingAmount != 0) {
throw Error('找零失败');
}
return {coinNum, amountCountObj};
}
console.log("greedyCoinChange:", greedyCoinChange([10, 5, 2, 7], 47))
十九、击鼓传花
1、题目描述:
// "击鼓传花" 是一个传统的游戏,通常用于一群人中。游戏中,一个物品(比如一朵花)被传递给每个人,
// 同时播放音乐。当音乐停止时,持有物品的人需要接受某种挑战或惩罚。
// 如果我们用 JavaScript 来模拟这个游戏,可以想象我们有一个数组,
// 代表游戏中的参与者,然后我们模拟"击鼓"和"传花"的过程,直到音乐停止,然后找出最后"持花"的人。
class Gun {
constructor(people) {
this.people = people;
this.curIndex = 0;
this.lastPeople = '';
this.isrunning = false;
};
startGame() {
if (this.isrunning) {
throw Error('游戏正在进行中');
}
this.isrunning = true;
let inRunningFn = () => {
let time = Math.random() * 3;
setTimeout(() => {
this.curIndex = (this.curIndex + 1) % this.people.length;
inRunningFn();
}, time);
};
inRunningFn();
};
stopGame() {
if (!this.isrunning) {
throw Error('游戏未开始,无需停止');
};
this.isrunning = false;
this.lastPeople = this.people[this.curIndex];
console.log('拿到花的人是:', this.lastPeople);
return this.lastPeople ;
}
}
let GunInstance = new Gun(['小红', '小蓝', '小花', '小小']);
GunInstance.startGame();
setTimeout(() => {
GunInstance.stopGame();
}, 5000)
二十、最长公共前缀
1、题目描述:
/ /编写一个函数来查找字符串数组中的最长公共前缀。
function longestCommonPrefix(strArr) {
// 最大前缀的长度
let k = strArr[0].length;
for(let i = 0; i < strArr.length; i++) {
// 比较和更新k
k = k > strArr[i].length ? strArr[i].length : k;
// 查找k的小范围
for(let j = 0; j < k; j++) {
if (strArr[0][j] != strArr[i][j]) {
k = j;
}
}
}
return strArr[0].substring(0, k);
}
console.log('longestCommonPrefix:', longestCommonPrefix(['qwe123', 'qwe188883', 'qwe1999']))
二十一、爬楼梯问题
1、题目描述:
一个人要爬一个有 n 个台阶的楼梯,每次可以爬 1 个台阶或 2 个台阶,问总共有多少种不同的方式可以爬到楼顶。
function climbStairs(n) {
// 保存爬楼梯的方式数量
let dp = new Array(n + 1);
// <2小于两级台阶的情况
dp[1] = 1;
dp[2] = 2;
if (n <= 2) return n;
// 3级别台阶以上的情况
for (let index = 3; index <= n; index++) {
dp[index] = dp[index-1] + dp[index-2];
};
return dp[n];
}
console.log('climbStairs:', climbStairs(1), climbStairs(2), climbStairs(4))
二十二、二叉树的最大深度
1、题目描述:
// 左子树递归找最大深度 又子树递归找最大深度
// 根节点深度为0
function maxDepth(root) {
// 如果根节点不存在(即二叉树为空),函数直接返回深度为 0。
if (!root) {
return 0;
} else {
// 如果根节点存在
let left = maxDepth(root.left);// 递归地调用 maxDepth 函数来计算右子树的最大深度
let right = maxDepth(root.right);
// Math.max() 函数找出左右子树的最大深度,并加上当前节点的深度 1,即为以当前节点为根的子树的最大深度。然后将这个深度返回给调用者。
return Math.max(left, right) + 1;
}
}
二十三、 组成最长的回文串
1、题目描述:
// 给定一个包含大写字母和小写字母的字符串 s ,返回 通过这些字母构造成的 最长的回文串 。
// 在构造过程中,请注意 区分大小写 。比如 "Aa" 不能当做一个回文字符串。
function longestPalindrome(str) {
// 最终长度
let length = 0;
let mySet = new Set();
for (let index = 0; index < str.length; index++) {
const curstr = str[index];
if (mySet.has(curstr)) {
length += 2;
mySet.delete(curstr);
} else {
mySet.add(curstr);
}
}
if (mySet.size > 0) {
length++;
}
return length;
}
console.log('longestPalindrome:', longestPalindrome('abccccdd'))
二十四、十进制整数M转化为N进制数
1、题目描述:
写出将十进制整数M转化为N进制数的算法。
function decimalToNBase(M, N) {
// 进制在2~36
if (N < 2 || N > 36) throw Error('N只能在2-36之间的整数');
if (M == 0) return '0';
// 结果
let numToResult = '';
// 除N取余 直到M为零
while(M > 0) {
let remNum = M % N;
// 判断余数是否大于10
if (remNum > 10) {
// 转化成A-F 通过unicode编码转字符
let willaddNum = String.fromCharCode((remNum - 10) + 'A'.charCodeAt());
numToResult += remNum;
} else {
// 直接拼接结果
numToResult += remNum;
}
// 更新除数
M = Math.floor(M / N);
};
return numToResult;
}
console.log('decimalToNBase:', decimalToNBase(0, 8))
二十五、获取对象的最深层级
1、题目描述:
// 写一个函数去获取对象的最深层级
// 输入:
// let obj = {
// a: 'a',
// b: {
// bb: '2b'
// },
// c: {
// cc: {
// ccc: '3c'
// }
// },
// d: {
// dd: {
// ddd: {
// dddd: '4d'
// }
// }
// }
// }
// 输出: 4
function getDeepNum2(obj, deep = 0) {
if (typeof obj != 'object' || obj == null) {
return deep;
};
let maxLength = deep;
for (const key in obj) {
if (Object.hasOwnProperty.call(obj, key)) {
maxLength = Math.max(maxLength, getDeepNum2(obj[key], deep + 1))
}
}
return maxLength;
}
二十六、打印九九乘法表
// 打印九九乘法表
// 正三角九九乘法表
// 1*1
// 2*1 2*2
// 3*1 3*2 3*3
// ...
// 9*1 9*2 9*3 ... 9*1
for(let i = 1; i <= 9; i++) {
let res = '';
for(let j = 1; j <= i; j++) {
res += `${i} * ${j} = ${i * j} \t`;
};
console.log(res);
};
二十七、版本号比较
1、题目描述
* 请实现函数 compareVersions ,对两个版本号进行比较,并满足以下条件:
* @param {String} verA (必传)需要比较的新版本号
* @param {String} verB (必传)用于比较的旧版本号
* @param {String} type (可选)比较方案
* gt:大于
* gte:大于等于
* lt:小于
* lte:小于等于
* eq:等于
* @return {String|Boolean} 不传入type时,返回比较值,大于返回1、等于返回0、小于返回-1
传入type时,返回判断值 bool
// 运行结果验证
[
compareVersions('2.0', '1.0.0'), // 1
compareVersions('1', '1.0.0'), // 0
compareVersions('1.2.3', '2.1.0'), // -1
compareVersions('1.2.0', '1.10.0'), // -1
compareVersions('1.2.3', '2.1.0', 'gt'), // false
compareVersions('1.2.3', '2.1.0', 'eq'), // false
compareVersions('1.2.3', '2.1.0', 'lt'), // true
compareVersions('1.0.0', '1', 'gte'), // true
compareVersions('1.0.0', '1', 'gt'), // false
compareVersions('1.0.0', '1', 'eq'), // true
];
// [1, 0, -1, -1, false, false, true, true, false, true]
2、代码实现
function compareVersions(verA, verB, type) {
// 分割版本号
verA = verA.split('.');
verB = verB.split('.');
// 比较版本号
function compare(a, b) {
const lenA = a.length;
const lenB = b.length;
const maxLength = Math.max(lenA, lenB);
for (let i = 0; i < maxLength; i++) {
const numA = i < lenA ? +a[i] : 0;
const numB = i < lenB ? +b[i] : 0;
if (numA > numB) {
return 1;
} else if (numA < numB) {
return -1;
}
}
return 0;
}
// 根据 type 参数返回结果
if (type) {
switch (type) {
case 'gt':
return compare(verA, verB) === 1;
case 'gte':
return compare(verA, verB) >= 0;
case 'lt':
return compare(verA, verB) === -1;
case 'lte':
return compare(verA, verB) <= 0;
case 'eq':
return compare(verA, verB) === 0;
default:
throw new Error('type无效');
}
} else {
return compare(verA, verB);
}
}
二十八、商品分类
1、题目描述
* 请写一个数据结构转换函数 convert
* 现有商品列表数据 itemList
* 将该商品列表按照商品分类(category)进行分组,且分组间按该分类下商品的种数进行倒序排序(休闲零食有四种商品,因此排第一);
* 在每一分组下按商品的销量(saleCount)进行倒序排序。
* 要求不能对原数据 itemList 的任意属性值进行修改
// 运行结果验证
convert(itemList);
/* [
* {
* "category": "休闲零食",
* "items": [
* { "id": 7, "name": "商品7", "category": "休闲零食", "saleCount": 48 },
* { "id": 4, "name": "商品4", "category": "休闲零食", "saleCount": 42 },
* { "id": 6, "name": "商品6", "category": "休闲零食", "saleCount": 37 },
* { "id": 9, "name": "商品9", "category": "休闲零食", "saleCount": 26 }
* ]
* },
* {
* "category": "家居百货",
* "items": [
* ...
* ]
* },
* ...
* ]
*/
2、代码实现
function convert(itemList) {
// 使用Map数据结构按category分组
const groupedItems = new Map();
for (const item of itemList) {
const category = item.category;
if (!groupedItems.has(category)) {
groupedItems.set(category, []);
}
groupedItems.get(category).push(item);
}
// 转换Map为数组,并根据分组中商品数量进行排序
const sortedGroups = [...groupedItems].sort((a, b) => b[1].length - a[1].length);
// 对每个分组内的商品按销量进行排序
sortedGroups.forEach(group => {
group[1].sort((a, b) => b.saleCount - a.saleCount);
});
// 转换回所需格式的对象数组
return sortedGroups.map(([category, items]) => ({
category,
items
}));
}
二十九、语句反转
1、题目描述
/**
* 请实现函数 reverseWord ,对字符串中的单词进行反转,并满足以下条件:
* 1. 单词排列反序
* 2. 单词间以空格进行分割,若有多个空格只保留一个
* 3. 去除字符串所有首尾的所有空格
*/
// 运行结果验证
reverseWord(' hello the world! ');
// "world! the hello"
2、代码实现
function reverseWord(string) {
// 去除首尾空格
string = string.trim();
let words = string.split(/\s+/);
words.reverse();
// 去除每个单词前后的空格
words = words.map(word => word.trim());
// 用单个空格连接单词数组为字符串
return words.join(' ');
}
三十、fecth请求并考虑请求超时
1、题目描述
用fecth请求一个接口并考虑请求超时的情况
2、代码实现
// Promise.race方式
function myFetch(url. options, time = 1000 * 10) {
let timeOutPromise = new Promise((resolve. reject) => {
setTimeout(() => {
reject('请求超时');
}, time);
});
let fetchPromise = fetch(url, options);
return Promise.race([timeOutPromise, fetchPromise]).catch(err => {
console.log('myFetch catch err:', err);
throw err;
})
};
// AbortController方式
function myFetch2(url, options, time = 10 * 1000) {
let constr = new AbortController();
const { signal } = constr;
setTimeout(() => {
constr.abort();
}, time);
return fetch(url, {...options, signal}).then(res => {
return res.json();
}).catch(err => {
console.log('myFetch2 err');
throw err;
});
}
三十一、扁平数组转树形
1、题目描述
如何将一个扁平数组的数据结构,转换为树形结构。
例如,有数组 arr 如下:
const arr = [
{ id: 1, pid: 0 },
{ id: 2, pid: 1 },
{ id: 3, pid: 1 },
{ id: 4, pid: 2 },
{ id: 5, pid: 2 },
{ id: 6, pid: 3 },
];
要求编写一个函数 arr2tree(arr),得到输出结果如下
{
"id": 0,
"children": [
{
"id": 1,
"children": [
{
"id": 2,
"children": [
{
"id": 4
},
{
"id": 5
}
]
},
{
"id": 3,
"children": [
{
"id": 6
}
]
}
]
}
]
}
2、代码实现
function arr2tree(arr) {
// 创建一个映射,用于存储每个节点的引用
const map = {};
// 初始化根节点
map[0] = { id: 0, children: [] };
// 遍历数组,为每个节点创建引用,并初始化 children 数组
arr.forEach(item => {
map[item.id] = { ...item, children: [] };
});
// 遍历数组,构建树形结构
arr.forEach(item => {
// 查找父节点
const parent = map[item.pid];
// 如果找到了父节点,则将当前节点添加到父节点的 children 数组中
if (parent) {
parent.children.push(map[item.id]);
}
});
// 返回根节点(pid 为 0 的节点)
return map[0];
}
const arr = [
{ id: 1, pid: 0 },
{ id: 2, pid: 1 },
{ id: 3, pid: 1 },
{ id: 4, pid: 2 },
{ id: 5, pid: 2 },
{ id: 6, pid: 3 },
];
const tree = arr2tree(arr);
console.log(JSON.stringify(tree, null, 2)); // 格式化输出树形结构
三十二、并发限制的promise异步调度器
1、题目描述
32 const scheduler = new Scheduler();
33 const timeout = (time, order) => {
34 return new Promise(resolve => {
35 setTimeout(() => {
36 resolve(order);
37 }, time);
38 });
39 };
40 const addTask = (time, order) => {
41 scheduler.add(() => timeout(time, order));
42 };
43 addTask(4000, 1);
44 addTask(1000, 2);
45 addTask(900, 3);
46 addTask(3000, 4);
// 当没有并发限制的时使其出处结果为: 2 3 4 1
请实现一个并发数是2的promise异步调度器scheduler,使其出处结果为: 2 3 1 4
2、代码实现
class Scheduler {
constructor() {
this.max = 2;// 最大限制数
this.work = [];// 正在执行的任务盒子
this.unwork = [];// 等待的任务盒子
}
add(promise) {
if (this.work.length < this.max) {
//还没达到最大数量限制,可以直接执行
this.runTask(promise);
} else {
// 此时任务盒子数量达到并发最大数量,那就放在等待区域
this.unwork.push(promise);
}
}
runTask(promise) {
this.work.push(promise);
promise()
.then(res => {
console.log(res);
})
.finally(() => { // 任务执行完毕,就立马从执行任务盒子删掉
let index = this.work.indexOf(promise);
this.work.splice(index, 1); // 如果任务等待盒子还有任务,那就继续执行
if (this.unwork.length) {
this.runTask(this.unwork.shift());
}
});
}
}
const scheduler = new Scheduler();
const timeout = (time, order) => {
return new Promise(resolve => {
setTimeout(() => {
resolve(order);
}, time);
});
};
const addTask = (time, order) => {
scheduler.add(() => timeout(time, order));
};
addTask(4000, 1);
addTask(1000, 2);
addTask(900, 3);
addTask(3000, 4);
三十三、字符全排列
1、题目描述
// 输入一个长度为 n 字符串,打印出该字符串中字符的所有排列,你可以以任意顺序返回这个字符串数组。
// 例如输入字符串ABC,则输出由字符A,B,C所能排列出来的所有字符串ABC,ACB,BAC,BCA,CBA和CAB。
2、代码实现
function permute(s, l = s.length, p = [], result = []) {
// 如果当前排列的长度等于原始字符串的长度,那么它是一个有效的排列,将其添加到结果数组中
if (p.length === l) {
result.push(p.join(''));
return;
}
for (let i = 0; i < l; i++) {
// 如果当前字符已经使用过,则跳过
if (p.includes(s[i])) continue;
// 将当前字符添加到排列中
p.push(s[i]);
// 递归调用permute来继续构建下一个字符的排列
permute(s, l, p, result);
// 回溯,即撤销上一步的选择,以便尝试其他字符
p.pop();
}
return result;
}
// 示例使用
const str = "ABC";
console.log(permute(str));
// 输出: ["ABC", "ACB", "BAC", "BCA", "CBA", "CAB"]