有趣的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、实现思路

  1. 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))
  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))
  1. 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);
  1. 筛选思路 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));
  1. 数学取值思想(递归思想)
    递归需要有一个合适的结束时机
// 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"]