剑指Offer题解JavaScript版(下)

326 阅读13分钟

剑指Offer题解JavaScript版(上)

连续子数组的最大和

/**
 * 我的解题思路:
 * 1.先求出长度为1到n的所有子数组中最大值
 * 2.再从这些最大值中找到最大值的最大值并返回结果
 *
 * @param {*} array 
 */
function FindGreatestSumOfSubArray(array)
{
    // write code here
    const result = [];
    let len = 1;
    while (len <= array.length) {
        const subArr = [];
        let i = 0;
        while (i + len < array.length + 1) {
            subArr.push(array.slice(i, i + len).reduce((r, item) => r + item, 0));
            i++;
        }
        result.push(Math.max(...subArr));
        len++;
    }
    return Math.max(...result);
}

/**
 * 评论区TOP的解题思路:
 * 1.使用动态规划思想,可以分析得到状态转移方程为 res(i) = max(res(i - 1), res(i - 1) + array[i])
 * 2.max表示结尾为array[i]的子数组和的最大值,result表示长度为i的数组中最大值
 * 3.遍历原数组,返回result
 *
 * @param {*} array 
 */
function topFindGreatestSumOfSubArray(array)
{
    // write code here
    let result = array[0];
    let max = array[0];
    for (let i = 1; i < array.length; i++) {
        max = Math.max(array[i], max + array[i]);
        result = Math.max(max, result);
    }
    return result;
}

整数中1出现的次数(从1到n整数中1出现的次数)

/**
 * 我的解题思路:
 * 1.遍历1到n,将每个数字抓换成字符串
 * 2.遍历字符串,若发现字符1,则结果数加1
 * 3.两层遍历完成,返回结果
 *
 * @param {*} n 
 */
function NumberOf1Between1AndN_Solution(n)
{
    // write code here
    let result = 0;
    for (let i = 1; i <= n; i++) {
        const temp = i.toString();
        for (let j = 0; j < temp.length; j++) {
            if (temp[j] === '1') {
                result++;
            }
        }
    }
    return result;
}

/**
 * 评论区TOP的解题思路:
 * 1.设i为计算1所在的位数,即i = 10,表示计算1在十位的个数,k表示n % (i * 10)
 * 2.那么通过归纳推理可以得到,count(i) = (n / (i * 10)) * i + (if(k > i * 2 - 1) i else if(k < i) 0 else k - i + 1)
 * 3.对2中结果进行简化处理可以得到 count(i) = (n / (i * 10)) * i + min(max(n % (i * 10) -i + 1, 0), i)
 * 4.遍历n的位数,得出结果并返回
 *
 * @param {*} n 
 */
function topNumberOf1Between1AndN_Solution(n)
{
    // write code here
    let result = 0;
    for (let i = 1; i < n; i *= 10) {
        const diviver = i * 10;
        result += (n / diviver) * i + Math.min(Math.max(n % diviver - i + 1, 0), i);
    }
    return result;
}

数字序列中的某一位数字

把数组排成最小的数

/**
 * 我的解题思路:
 * 1.比较好理解但是比较死的方式
 * 2.先找出所有的排列组合,然后对所有的排列进行排序,就能找到想要的值了
 * 3.核心的难点就在于找全排列,可以查看【字符串的排列】一题,已经说过这里不再重复
 * 
 * @param {*} numbers 
 */
function PrintMinNumber(numbers)
{
    let allArray = numbers.length ? [[numbers.pop()]] : [];
    while (numbers.length) {
        const inner = [];
        const char = numbers.pop();
        allArray.forEach(arr => {
            for (let i = 0; i <= arr.length; i++) {
                arr.splice(i, 0, char);
                inner.push([...arr]);
                arr.splice(i, 1);
            }
        });
        allArray = inner;
    }
    return allArray.map(item => item.join('')).sort()[0] || '';
}

/**
 * 评论区TOP的解题思路:
 * 1.提出了一个不太好想到的思路,将两个数a、b进行字符串拼接,然后对字符串进行比较大小
 * 2.若ab > ba,则说明 a > b,若 ab < ba,则说明 a < b,否则 a = b
 * 3.通过这个思路,我们可以进行一个类似于冒泡的排序算法,每次找到放在最高位的最小的数
 * 4.最终得到的排序数组直接按序进行字符串拼接后,得到的即是最小的组合
 * 
 * @param {*} numbers 
 */
function topPrintMinNumber(numbers)
{
    // write code here
    for (let i = 0; i < numbers.length; i ++){
        for (let j = i + 1; j < numbers.length; j++) {
            const ab = '' + numbers[i] + numbers[j];
            const ba = '' + numbers[j] + numbers[i];
            if (+ab > +ba) {
                const temp = numbers[i];
                numbers[i] = numbers[j];
                numbers[j] = temp;
            }
        }
    }
    return numbers.join('');
}

把数字翻译成字符串

礼物的最大价值

最长不含重复字符的子字符串

丑数

/**
 * 我的解题思路:
 * 1.既然每个丑数都能被2、3、5整除,那么就分别用这三个数去整除每一个数
 * 2.如果整除到最后的结果为1,那么说明这个数就是丑数,否则就继续找下一个数
 * 3.为了保证每次整除的结果不影响初始值,这里在整除前使用新的变量去操作
 * 4.这个方法很好理解,但是遗憾的是超时了
 * 
 * @param {*} index 
 */
function GetUglyNumber_Solution(index)
{
    // write code here
    let num = 1;
    let n = 1;
    const result = [];
    while (num !== index + 1) {
        let cur = n;
        while (cur % 2 === 0) {
            cur = cur / 2;
        }
        while (cur % 3 === 0) {
            cur = cur / 3;
        }
        while (cur % 5 === 0) {
            cur = cur / 5;
        }
        if (cur === 1) {
            num ++;
            result.push(n);
        }
        n ++;
    }
    return result[index - 1];
}

/**
 * 评论区TOP的解题思路:
 * 1.这个想法挺屌的,前7个数就不用看了,直接看后面的思路
 * 2.第一个丑数是1,也就是当前index对应的最小丑数,那么我们用这个最小的丑数分别乘以2、3、5,就可以得到3个新的丑数
 * 3.在这三个新的丑数中,我们可以找到最小的,那么它就是下一个最小丑数,它对应的因子为当前最小因子
 * 4.我们用下一个最小丑数乘以当前最小因子之后与其他的两个丑数比较,再次获得一个最小丑数
 * 5.重复3、4步骤,我们就可以得到一个index长度的丑数序列,且按照从小到大排序
 * 6.最后返回数组中第index个丑数即可
 * 
 * @param {*} index 
 */
function topGetUglyNumber_Solution(index)
{
    // write code here
    if (index < 7) return index;
    const res = [1];
    let t2 = 0;
    let t3 = 0;
    let t5 = 0;
    for (let i = 1; i < index; i ++) {
        res[i] = Math.min(res[t2] * 2, Math.min(res[t3] * 3, res[t5] * 5));
        if (res[i] === res[t2] * 2) {
            t2 ++;
        }
        if (res[i] === res[t3] * 3) {
            t3 ++;
        }
        if (res[i] === res[t5] * 5) {
            t5 ++;
        }
    }
    return res[index - 1];
}

第一个只出现一次的字符

/**
 * 我的解题思路:
 * 1.遍历字符串,以每个字符为分隔,若前面一段或后面一段能找到当前字符,说明该字符串没有只出现一次,继续遍历
 * 2.当找到一个字符,在它前后两段都没有找到相同字符时,那么返回这个字符
 * 3.如果遍历完成都没有找到单独的字符,就返回-1
 * 
 * @param {*} str 
 */
function FirstNotRepeatingChar(str)
{
    // write code here
    for (let i = 0; i < str.length; i ++) {
        if (~str.slice(i + 1).indexOf(str[i]) || ~str.slice(0, i).indexOf(str[i])) {
            continue;
        }
        return i;
    }
    return -1;
}

/**
 * 评论区TOP的解题思路:
 * 1.使用map来存储每个字符对应的个数,首次遍历用于查找个数
 * 2.第二次遍历用于查找第一个只出现一次的字符,如果找到则返回位置,否则返回-1
 * 
 * @param {*} str 
 */
function topFirstNotRepeatingChar(str)
{
    // write code here
    const map = {};
    for (let i = 0; i < str.length; i ++) {
        map[str[i]] = map[str[i]] ? map[str[i]] + 1 : 1;
    }
    for (let i = 0; i < str.length; i ++) {
        if (map[str[i]] === 1) {
            return i;
        }
    }
    return -1;
}

数组中的逆序对

/**
 * 我的解题思路:
 * 1.两层循环,从第一个数字开始,将它与后面每一个数都进行比较,如果发现后面有数比它小,那么计数器加1
 * 2.对于数组中每一个数都重复第1步的操作,知道遍历结束,返回计数器与1000000007的模
 * 3.这种思路很简单,但是很明显不会让我这么容易通过,果然最后超时了
 * 
 * @param {*} data 
 */
function InversePairs(data)
{
    // write code here
    let num = 0;
    for (let i = 0; i < data.length; i ++) {
        for (let j = i + 1; j < data.length; j ++) {
            if (data[i] > data[j]) {
                num ++;
            }
        }
    }
    return num % 1000000007;
}

/**
 * 评论区TOP的解题思路:
 * 1.我是想不明白怎么会有人能够想到这种解题思路的,我觉得自己也讲不明白
 * 2.还是直接看别人的解答吧:https://zhuanlan.zhihu.com/p/39811184
 * 
 * @param {*} data 
 */
function topInversePairs(data)
{
    // write code here
    const divide = (arr, copy, left, right) => {
        if (left === right) {
            return 0;
        }

        const mid = Math.floor((right + left) / 2);
        const leftNum = divide(arr, copy, left,  mid);
        const rightNum = divide(arr, copy, mid + 1, right);

        let i = mid;
        let j = right;
        let index = right;
        let count = 0;
        while (i >= left && j > mid) {
            if (arr[i] > arr[j]) {
                copy[index--] = arr[i--];
                count += j - mid;
            }
            else {
                copy[index--] = arr[j--];
            }
        }

        while (i >= left) {
            copy[index--] = arr[i];
            i--;
        }
        while (j > mid) {
            copy[index--] = arr[j];
            j--;
        }

        for(let s = left; s <= right; s++)
        {
            arr[s] = copy[s];
        }

        return leftNum + rightNum + count;
    };
    const num = divide(data, [...data], 0, data.length - 1);
    return num % 1000000007;
}

两个链表的第一个公共节点

/*function ListNode(x){
    this.val = x;
    this.next = null;
}*/

/**
 * 我的解题思路:
 * 1.emmmm,这个问题之前面头条的时候被问到过,没答出来的但是面试官给讲了思路哈哈哈,这次做出来了
 * 2.先让两个链表同时开始遍历,直到较短的那个链表所有节点都被遍历
 * 3.让较长的链表从头开始遍历,直到第二步中剩余部分遍历完成,此时长链表剩余部分与短链表长度相同
 * 4.直接顺序遍历两个链表,找到节点值相同的节点并返回即可,若找不到相同节点,返回null
 * 
 * @param {*} pHead1 
 * @param {*} pHead2 
 */
function FindFirstCommonNode(pHead1, pHead2)
{
    // write code here
    let temp1 = pHead1;
    let temp2 = pHead2;
    while (temp1 && temp2) {
        temp1 = temp1.next;
        temp2 = temp2.next;
    }
    while (temp1) {
        pHead1 = pHead1.next;
        temp1 = temp1.next;
    }
    while (temp2) {
        pHead2 = pHead2.next;
        temp2 = temp2.next;
    }
    while (pHead1 && pHead2 && pHead1.val !== pHead2.val) {
        pHead1 = pHead1.next;
        pHead2 = pHead2.next;
    }
    return pHead1;
}

/**
 * 评论区TOP的解题思路:
 * 1.思路还是那个思路,跟我的类似,只不过它采取了更简洁的写法
 * 2.遍历两个链表,直到找到相同的节点,结束遍历(在JS中无法直接比较节点,这里采用JSON.stringify转成字符串再比较的方式处理)
 * 3.若未找到相同节点,则继续遍历,如果发现短链表遍历结束,那么将短链表指向长链表的头部继续进行遍历,此时遍历的部分为全新的长链表和剩余部分的长链表
 * 4.在之前长链表剩余部分遍历完成之后,另一个长链表已经走出了多出的长度,此时让为空的长链表指向短链表的头部,这样得到的两个链表长度就一致了
 * 5.继续进行遍历,直到找到相同节点即可
 * 
 * @param {*} pHead1 
 * @param {*} pHead2 
 */
function topFindFirstCommonNode(pHead1, pHead2)
{
    // write code here
    let temp1 = pHead1;
    let temp2 = pHead2;
    while (JSON.stringify(temp1) !== JSON.stringify(temp2)) {
        temp1 = temp1 ? temp1.next : pHead2;
        temp2 = temp2 ? temp2.next : pHead1;
    }
    return temp1;
}

数字在排序数组中出现的次数

/**
 * 我的解题思路:
 * 1.直接遍历数组,找到与k相同的数字,那么结果加1
 * 2.遍历完成之后返回结果
 *
 * @param {*} data
 * @param {*} k
 */
function GetNumberOfK(data, k) {
    // write code here
    return data.reduce((r, j) => {
        j === k && r++;
        return r;
    }, 0);
}

/**
 * 评论区TOP的解题思路:
 * 1.利用上排序的特点,直接二分法查找
 *
 * @param {*} data
 * @param {*} k
 */
function topGetNumberOfK(data, k) {
    // write code here
    let n = 0;
    const fn = arr => {
        if (arr.length <= 1) {
            arr[0] === k && n++;
            return;
        }
        const mid = Math.floor(arr.length / 2);
        fn(arr.slice(0, mid));
        fn(arr.slice(mid));
    };
    fn(data);
    return n;
}

二叉搜索树的第k个节点

二叉树的深度

/* function TreeNode(x) {
    this.val = x;
    this.left = null;
    this.right = null;
} */

/**
 * 我的解题思路:
 * 1.使用递归去遍历树的左右子树,树的深度就为左子树深度 + 1或者右子树深度 + 1
 * 2.但是有可能左右子树都不存在,此时树的深度应该为1
 * 3.所以最终的结果就是树的左子树深度 + 1、右子树深度 + 1 和 1 中的最大值
 *
 * @param {*} pRoot
 */
function TreeDepth(pRoot)
{
    // write code here
    const fn = root => {
        let left = 0;
        let right = 0;
        if (!root) {
            return 0;
        }
        if (root.left) {
            left = fn(root.left) + 1;
        }
        if (root.right) {
            right = fn(root.right) + 1;
        }
        return Math.max(1, left, right);
    };
    return fn(pRoot);
}

/**
 * 评论区TOP的解题思路:
 * 1.跟我的思路一致,但是写法太简洁了,一行高度,甘拜下风
 *
 * @param {*} pRoot
 */
function topTreeDepth(pRoot)
{
    // write code here
    return pRoot ? Math.max(1 + topTreeDepth(pRoot.left), 1 + topTreeDepth(pRoot.right)) : 0;
}

数组中只出现一次的数字

/**
 * 我的解题思路:
 * 1.使用map来处理就好了,将每一个数字作为map的key值,value代表这个数字出现的次数
 * 2.遍历数组,当map[key]不存在,则说明key出现了一次,map[key] = 1,否则map[key] = map[key] + 1
 * 3.遍历完成之后,找到map中value值为1的key的值即可
 *
 * @param {*} pRoot
 */
function FindNumsAppearOnce(array)
{
    // write code here
    // return list, 比如[a,b],其中ab是出现一次的两个数字
    const map = {};
    array.forEach(item => map[item] = (map[item] ? map[item] + 1 : 1));
    return Object.keys(map).filter(item => map[item] === 1);
}

/**
 * 评论区TOP的解题思路:
 * 1.其实,不是很理解为啥要想出这么一个方法,感觉比map的思路复杂很多,但也没有比map快
 * 2.使用二进制数的异或逻辑来处理,相同数字异或结果为0,任何数字和0异或结果为它本身
 * 3.将所有数字进行一次异或处理,那么最终的结果肯定是待求的两个数字的异或值
 * 4.以最后的异或值二进制中首个出现的1的数位为标准,可以将所有的结果集分为两组数字,并且每一组数字中都包含最终两数其一
 * 5.将分好的两组数字再分别进行异或处理,那么最终两组异或的结果就是我们要求的两个数字
 *
 * @param {*} pRoot
 */
function topFindNumsAppearOnce(array)
{
    // write code here
    // return list, 比如[a,b],其中ab是出现一次的两个数字
    const xorResult = array.reduce((r, item) => item ^ r, 0);
    const bitResult = xorResult.toString(2);
    const first1Index = bitResult.split('').findIndex(item => item === '1') + 32 - bitResult.length;
    const result = [0, 0];
    array.forEach(item => {
        if (('0'.repeat(32 - item.toString(2).length) + item.toString(2))[first1Index] === '1') {
            result[0] ^= item;
        }
        else {
            result[1] ^= item;
        }
    });
    return result;
}

和为S的两个数字

/**
 * 我的解题思路:
 * 1.最好想的方法,遍历array中每一个数字,判断sum - array[i]是否在array中
 * 2.由于是递增的数组,所以从左往右遍历,找的第一组和为sum的数字即为乘积最小,直接返回即可
 * 3.找不到的话就返回空
 *
 * @param {*} array
 * @param {*} sum
 */
function FindNumbersWithSum(array, sum)
{
    // write code here
    for (let i = 0; i < array.length; i++) {
        if (array.includes(sum - array[i])) {
            return [array[i], sum - array[i]];
        }
    }
    return [];
}

/**
 * 评论区TOP的解题思路:
 * 1.跟上道题一样的滑动窗口的思路,但是比上一道题简单一些
 * 2.窗口左侧从数组最左边开始,右侧从数组最右边开始
 * 3.如果两数之和为sum,则直接返回,若比sum大,说明右侧太大了,则右侧缩小一位,若比sum小,则说明左侧太小了,左侧增大一位
 *
 * @param {*} array
 * @param {*} sum
 */
function topFindNumbersWithSum(array, sum)
{
    // write code here
    let i = 0;
    let j = array.length - 1;
    while (i < j) {
        const tempSum = array[i] + array[j];
        if (tempSum === sum) {
            return [array[i], array[j]];
        }
        else if (tempSum < sum) {
            i++;
        }
        else {
            j--;
        }
    }
    return [];
}

左旋转字符串

/**
 * 我的解题思路:
 * 1.每次将字符串按照首字符和剩余字符分成两段,首字符放在剩余字符后面形成新的字符串
 * 2.执行n次即可完成左移的操作
 * 3.但是对于null或者false什么的竟然要返回空字符串,我服了,排查半天还
 *
 * @param {*} str
 * @param {*} n
 */
function LeftRotateString(str, n)
{
    // write code here
    while (n-- && str) {
        str = str.substr(1) + str.substr(0, 1);
    }
    return str || '';
}

/**
 * 评论区TOP的解题思路:
 * 1.换个思路,要将左边的n个数放到右边,那么我们就将字符串长度加倍,这样只需要找到开始位置,并截取原字符串长度即可
 * 2.由于是循环移动,因此对于n小于str.length的,我们的起始位置就为n,否则起始位置为n % str.length
 * 3.空字符串需要单独处理
 *
 * @param {*} str
 * @param {*} n
 */
function topLeftRotateString(str, n)
{
    // write code here
    if (!str) {
        return '';
    }
    const start = n % str.length;
    return (str + str).substr(start, str.length);
}

滑动窗口的最大值

扑克牌顺子

/**
 * 我的解题思路:
 * 1.对于正常的5张牌来讲,按照从小到大排序后,如果牌中没有王,那么是顺子的牌相邻两张的差值应该为1,如果有王,那么相邻牌的差值减一的总数应该小于王的个数
 * 2.例如[1,2,3,4,5]相邻牌差值为1符合情况,
 *   [0,1,3,4,5]相邻牌差值减一的总数为1(3 - 1 - 1 = 1)与0的个数相同也符合情况,
 *   而[0,1,3,4,6]的相邻牌差值为2大于0的个数,所以不符合情况
 * 3.对于有重复数字和牌数不足5张的单独处理一下边界情况即可
 *
 * @param {*} numbers
 */
function IsContinuous(numbers)
{
    // write code here
    if (!numbers.length) {
        return false;
    }
    numbers = numbers.filter(item => item).sort();
    let n = Math.max(numbers.length - 1, 0);
    let maxDiffer = 0;
    while (n) {
        const temp = numbers[n] - numbers[n - 1];
        if (!temp) {
            return false;
        }
        maxDiffer += temp - 1;
        n--;
    }
    return maxDiffer <= (5 - numbers.length);
}

/**
 * 评论区TOP的解题思路:
 * 1.首先分析出满足顺子要求的牌具有的特征:必须是5张、除0之外的最大最小值差小于5、没有重复数字
 * 2.按照上述的3个特征分别进行判断即可
 *
 * @param {*} numbers
 */
function topIsContinuous(numbers)
{
    // write code here
    if (numbers.length !== 5) {
        return false;
    }
    const filterNumbers = numbers.filter(item => item);
    const max = Math.max(...filterNumbers);
    const min = Math.min(...filterNumbers);
    return (max - min) < 5 && filterNumbers.length === Array.from(new Set(filterNumbers)).length;
}

孩子们的游戏

/**
 * 我的解题思路:
 * 1.使用数组去模拟圆环,先找到当前顺序中满足要求的数字为,m - 1 相对于当前数组长度的余数;
 * 2.以找到的数字为分隔,将数组分为前后两部分,并且将后面的部分作为新数组的前半段,前面的部分作为新数组的后半段,这样就可以形成一个新的圆环;
 * 3.重复步骤1、2直到剩下的数组长度为1,此时剩余的数字即为最后一个数字返回即可
 * 
 * @param {*} n 
 * @param {*} m 
 */
function LastRemaining_Solution(n, m)
{
    // write code here
    let temp = [...Array(n).keys()];
    while (temp.length > 1) {
        const remainder = (m - 1) % temp.length;
        temp = [...temp.slice(remainder + 1), ...temp.slice(0, remainder)];
    }
    return temp[0] || -1;
}

/**
 * 评论区TOP的解题思路:
 * 1.著名的「约瑟夫问题」,由于只需要找到最后一个元素,因此可以从查找每一圈最后一个元素之间的关系入手;
 * 2.通过查找规律我们可以发现对于每一圈的结果f(n),下一圈的结果就为(f(n) + m) % n;
 * 3.至于为什么是上面那个规律,很难去描述清楚,可以找个示例自己动手画一下就能发现;
 * 4.根据递推公式 f(n) = (f(n - 1) + m) % n 可以使用递归去进行处理,这样就能省去每一圈的查找过程,从而提升效率
 * 5.中止条件为n为0以及n为1时
 * 
 * @param {*} n 
 * @param {*} m 
 */
function topLastRemaining_Solution(n, m)
{
    // write code here
    if (n === 0) {
        return -1;
    }
    if (n === 1) {
        return 0;
    }
    return (topLastRemaining_Solution(n - 1, m) + m) % n;
}

买卖股票的最好时机

求1+2+...+n

/**
 * 我的解题思路:
 * 1.实在是没想到怎么解
 * 
 * @param {*} n 
 */
function Sum_Solution(n)
{
    // write code here
}

/**
 * 评论区TOP的解题思路:
 * 1.竟然使用短路运算来作为判断的依据!!!
 * 2.这种就属于想到了就觉得很简单,想不到就觉得非常难的问题
 * 
 * @param {*} n 
 */
function topSum_Solution(n)
{
    // write code here
    return n && topSum_Solution(n - 1) + n;
}

不用加减乘除做加法

/**
 * 我的解题思路:
 * 1.数字的加法最终其实是二进制的加法,那么我们对二进制数的加法进行分析
 * 2.同位的两数都为1时,相加会发生进位,所以对两数进行与运算可以找到需要进位的部分
 * 3.将待进位部分左移一位即可完成进位
 * 4.接下来需要找不进位的部分,可以使用异或运算,便能找到不需要进位的部分
 * 5.最终结果为这两部分相加,但是因为无法使用加法,所以我们可以递归操作
 * 6.直到两数相加不能产生进位,即两数与运算为0,返回另一个数即为结果
 * 
 * @param {*} num1 
 * @param {*} num2 
 */
function Add(num1, num2)
{
    // write code here
    if (num1 === 0) {
        return num2;
    }
    return Add((num1 & num2) << 1, num1 ^ num2);
}

/**
 * 评论区TOP的解题思路:
 * 1.思路与我的思路一致,只不过将递归变成了循环操作
 * 
 * @param {*} num1 
 * @param {*} num2 
 */
function topAdd(num1, num2)
{
    // write code here
    while (num1 & num2) {
        const temp = num1;
        num1 = (num1 & num2) << 1;
        num2 = temp ^ num2;
    }
    return num1 | num2;
}

构建乘积数组

/**
 * 我的解题思路:
 * 1.遍历数组,并以每个下标为界将数组分为前后两部分
 * 2.将两部分组成一个新的数组,将该数组所有元素乘积作为结果集对应下标的值即可
 * 
 * @param {*} array 
 */
function multiply(array)
{
    // write code here
    const result = [];
    for (let i = 0; i < array.length; i++) {
       result.push([...array.slice(0, i), ...array.slice(i + 1)].reduce((r, item) => item * r, 1));
    }
    return result;
}

/**
 * 评论区TOP的解题思路:
 * 1.将源数组作为二维数组的每个元素,即得到二维数组为[[A0, A1, ... , An], ... , [A0, A1, ... , An]]
 * 2.从二维数组中不难发现待求数组B即为二维数组对角线全替换成1后每一行的乘积
 * 3.那么我们可以分别求出二维数组对角线上下两侧的 B[i上] 和 B[i下],并将两者相乘得到 B[i]的值
 * 
 * @param {*} array 
 */
function topMultiply(array)
{
    // write code here
    const result = [1];
    for (let i = 1; i < array.length; i++) {
        result[i] = result[i - 1] * array[i - 1];
    }
    let temp = 1;
    for (let i = array.length - 2; i >= 0; i--) {
        temp *= array[i + 1];
        result[i] *= temp;
    }
    return result;
}

把字符串转换成整数

/**
 * 我的解题思路:
 * 1.首先对传入字符串的符号进行处理,判断首位是否传入了符号,如果带有符号就先去掉符号,便于后续处理
 * 2.接着判断一下剩余的字符串是否数字,这里采用一种简单的判断方法,即使用正则test,因为test的参数就是字符串
 * 3.而如何将字符串数字转换成真正的数字,这里使用数字转字符串再判断的方式来获取每一位的数字
 * 4.然后根据字符串数字的位置来对数字进行乘10运算,并将每一位的结果相加即可
 * 5.最后就是根据前面获取的符合来返回整数或者负数即可
 * 
 * @param {*} str 
 */
function StrToInt(str)
{
    // write code here
    let symbol = true;
    if (str[0] === '-') {
        symbol = false;
        str = str.slice(1);
    }
    if (str[0] === '+') {
        str = str.slice(1);
    }
    if (!/^[1-9]\d*$/.test(str) || str === '0') {
        return 0;
    }

    let result = 0;
    const numbers = [...Array(10).keys()];
    for (let i = str.length - 1; i >= 0; i--) {
        let n = numbers.find(item => item.toString() === str[i]);
        let tenNum = str.length - 1 - i;
        while (tenNum--) {
            n *= 10;
        }
        result += n;
    }
    return symbol ? result : -result;
}

/**
 * 评论区TOP的解题思路:
 * 1.在评论区并没有看到很好的解题思路
 * 2.由于JS语言的特性,不能对字符串进行加减操作,不然会在内部之间进行转换
 * 
 * @param {*} str 
 */
function topStrToInt(str)
{
    // write code here
}

二叉搜索树的最近公共祖先

跳台阶

/**
 * 我的解题思路:
 * 这不是跟斐波那契数列一样吗?前面学到了动态规划,这里用一用
 *
 * @param {*} number 
 */
function jumpFloor(number)
{
    // write code here
    let r = 1;
    let l = 1;
    while (--number) {
        r = r + l;
        l = r - l;
    }
    return r;
}

/**
 * 社区TOP解题思路:
 * 跟斐波那契数列一样的,递归、尾递归、动态规划,不重复写了
 *
 * @param {*} number 
 */
function topJumpFloor(number) {
    // write code here
}

矩形覆盖

/**
 * 我的解题思路:
 * 1.枚举结果 0, 1, 2, 3, 5, 8, .... 可以知道 n > 2 时, f(n) = f(n - 1) + f(n - 2)
 * 2.跟斐波那契数列一样的,递归和尾递归的方法这里就不写了,直接给动态规划的方法
 *
 * @param {*} number 
 */
function rectCover(number)
{
    // write code here
    let result = number ? 1 : 0;
    let temp = 1;
    while (number-- > 1) {
        result += temp;
        temp = result - temp;
    }
    return result;
}


/**
 * 评论区TOP的解题思路:
 * 很遗憾,木得了
 *
 * @param {*} number 
 */
function topRectCover(number)
{
    // write code here
}

变态跳台阶

/**
 * 我的解题思路:
 * 1.首先可以分析出求解f(n)的公式为:f(n) = f(1) + f(2) + ... + f(n - 1) + 1
 * 2.根据数列消元法,f(n) - f(n - 1) = f(n - 1),即 f(n) = 2 * f(n - 1)
 * 3.那么就可以很容易用递归得出答案了
 *
 * @param {*} number 
 */
function jumpFloorII(number)
{
    // write code here
    return number === 1 ? 1 : 2 * jumpFloorII(number - 1);
}

/**
 * 动态规划的解题思路:
 * 得知 f(n) = 2 * f(n - 1) 之后,也可以使用动态规划解题
 *
 * @param {*} number 
 */
function dpJumpFloorII(number) {
    // write code here
    let result = 1;
    while (--number) {
        result += result;
    }
    return result;
}

/**
 * 评论区TOP解题方法:
 * 1.根据数据规律1、2、4、8、16、...可以知道最终的结果为 2^(n - 1)
 * 2.对于2^n的处理,采用位运算
 *
 * @param {*} number 
 */
function topJumpFloorII(number) {
    // write code here
    return 1 << (number - 1);
}

翻转单词顺序列

/**
 * 我的解题思路:
 * 1.按照空格将字符串分解为数组,然后使用数组的reverse方法进行逆序操作,最后将逆序的字符数组组合成新的字符串即可
 *
 * @param {*} str
 */
function ReverseSentence(str)
{
    // write code here
    return str.split(' ').reverse().join(' ');
}

/**
 * 评论区TOP的解题思路:
 * 1.评论区的思路主要分为两种,第一种跟我的思路一样,按照单词去进行逆序以及组合处理
 * 2.第二种思路则是按照字符去进行处理,先整个字符串进行逆序,然后再对每个单词进行逆序
 * 3.两种思路都很简单,这里就给出第二种思路的写法
 *
 * @param {*} str
 */
function topReverseSentence(str)
{
    // write code here
    const reverse = origin => {
        let reversedStr = '';
        for (let i = origin.length - 1; i >= 0; i--) {
            reversedStr += origin[i];
        }
        return reversedStr;
    };
    const allReversed = reverse(str);
    let temp = '';
    let result = '';
    for (let i = 0; i < allReversed.length; i ++) {
        if (allReversed[i] !== ' ') {
            temp += allReversed[i];
        }
        else {
            result += reverse(temp);
            result += ' ';
            temp = '';
        }
    }
    if (temp) {
        result += reverse(temp);
    }
    return result;
}

和为S的连续正数序列

/**
 * 我的解题思路:
 * 1.首先我们可以知道的是,连续最大的数字上限应该是sum的一半或者一半加1,那么我们便可以只遍历到这个位置
 * 2.从1开始遍历,每次遍历时总和tempSum从0开始,如果当前数字小于sum - tempSum,说明当前连续数字和未超出sum
 * 3.将tempSum加上当前数字,并且当前数字加1,再次判断是否小于sum - tempSum
 * 4.如果不小于,判断是否等于,若等于则说明找到了一组数字,继续从第2步开始遍历
 *
 * @param {*} pRoot
 */
function FindContinuousSequence(sum)
{
    // write code here
    const mid = Math.round(sum / 2);
    const result = [];
    for (let i = 1; i < mid; i ++) {
        const temp = [];
        let tempSum = 0;
        let current = i;
        while (current < (sum - tempSum)) {
            tempSum += current;
            temp.push(current);
            current++;
        }
        if (current === (sum - tempSum)) {
            temp.push(current);
            result.push(temp);
        }
    }
    return result;
}

/**
 * 评论区TOP的解题思路:
 * 1.使用滑动窗口进行计算,比较容易理解
 * 2.当前窗口左侧为1,右侧为2开始,如果发现窗口内的数字和等于sum,说明窗口内的数字即为一组所求
 * 3.如果窗口内的数字和小于sum,说明需要一个更大的数加入窗口,那么窗口右侧加1
 * 4.如果窗口内数字和大于sum,说明需要缩小窗口的值,那么窗口左侧加1
 * 5.由于整个窗口是从左往右滑动,所以不会存在左侧或者右侧减1的情况
 *
 * @param {*} pRoot
 */
function topFindContinuousSequence(sum)
{
    // write code here
    const result = [];
    let left = 1;
    let right = 2;
    while (left < right) {
        const tempSum = (left + right) * (right - left + 1) / 2;
        if (tempSum === sum) {
            const temp = [];
            for (let i = left; i <= right; i++) {
                temp.push(i);
            }
            result.push(temp);
            left++;
        }
        else if (tempSum < sum) {
            right++;
        }
        else {
            left++;
        }
    }
    return result;
}

字符流中第一个不重复的字符

/**
 * 我的解题思路:
 * 1.先吐槽一下,这是什么鬼题目,我🤮了
 * 2.思路很简单,使用栈去处理,如果有相同字符则出栈,没有就入栈
 * 3.每次返回栈低的字符,没有的话就返回 # 号
 */

let stack = [];
//Init module if you need
function Init()
{
    // write code here
    stack = [];
}
//Insert one char from stringstream
function Insert(ch)
{
    // write code here
    const index = stack.indexOf(ch);
    if (index > -1) {
        stack.splice(index, 1);
    }
    else {
        stack.push(ch);
    }
}
//return the first appearence once char in current stringstream
function FirstAppearingOnce()
{
    // write code here
    //return result.slice(-);
    return stack[0] || '#';
}

/**
 * 评论区TOP的解题思路:
 * 1.使用队列来存储所有首次出现的字符,使用哈希来记录每个字符出现的次数
 * 2.在查找时,从队首开始,如果字符出现次数为 1,说明是首次出现,返回该字符
 * 3.若次数不为 1,则说明不是首次出现,那么移除
 * 4.如果队列中没有满足要求的字符,返回 # 号
 */

let queue = [];
let map = {};
//Init module if you need
function Init()
{
    // write code here
    queue = [];
    map = {};
}
//Insert one char from stringstream
function Insert(ch)
{
    // write code here
    if (!map[ch]) {
        queue.push(ch);
    }
    map[ch] = map[ch] ? map[ch] + 1 : 1;
}
//return the first appearence once char in current stringstream
function FirstAppearingOnce()
{
    // write code here
    while (queue.length) {
        if (map[queue[0]] === 1) {
            return queue[0];
        }
        else {
            queue.shift();
        }
    }
    return '#';
}

删除链表中重复的节点

/*function ListNode(x){
    this.val = x;
    this.next = null;
}*/

/**
 * 我的解题思路:
 * 1.先遍历链表,使用 map 来记录链表中每个节点的个数
 * 2.再遍历一次链表,如果发现某个节点出现过不止1次,那么将该节点删掉(即当前节点上一个节点的 next 为当前节点下一个节点)
 * 3.如果节点只出现过1次,那么继续遍历下一个节点,知道所有节点都遍历完
 * 4.为了方便处理,这里会在第二次遍历之前给链表头部加一个节点
 * 
 * @param {*} pHead 
 */
function deleteDuplication(pHead)
{
    // write code here
    const map = {};
    let temp = pHead;
    while (temp) {
        map[temp.val] = map[temp.val] ? map[temp.val] + 1 : 1;
        temp = temp.next;
    }

    pHead = {
        next: pHead
    };
    const result = pHead;
    while (pHead && pHead.next) {
        if (map[pHead.next.val] > 1) {
            pHead.next = pHead.next.next;
        }
        else {
            pHead = pHead.next;
        }
    }
    return result.next;
}

/**
 * 评论区TOP的解题思路:
 * 1.首先还是在链表头部加一个节点,用于处理首节点就重复的情况
 * 2.设置 pre 节点指向当前重复节点的上一个节点,last 指向待遍历链表的头结点
 * 3.遍历链表,如果发现有节点重复,就找到重复节点后的首个不重复节点,并将 pre 节点的 next 指向该节点
 * 4.若果节点不重复,那么 pre 和 last 都指向下一个节点
 * 
 * @param {*} pHead 
 */
function topDeleteDuplication(pHead)
{
    // write code here
    const result = {
        next: pHead
    };
    let pre = result;
    let last = result.next;

    while (last) {
        if (last.next && last.val == last.next.val){
            // 找到最后的一个相同节点
            while (last.next && last.val == last.next.val){
                last = last.next;
            }
            pre.next = last.next;
            last = last.next;
        }
        else {
            pre = pre.next;
            last = last.next;
        }
    }

    return result.next;
}

按之字形打印二叉树

把二叉树打印成多行

平衡二叉树

/* function TreeNode(x) {
    this.val = x;
    this.left = null;
    this.right = null;
} */

/**
 * 我的解题思路:
 * 1.直接复用上道题的逻辑,即先获取左右子树的深度,然后看它们差的绝对值是否小于等于1
 *
 * @param {*} pRoot
 */
function IsBalanced_Solution(pRoot)
{
    // write code here
    const fn = root => root ? Math.max(1 + fn(root.left), 1 + fn(root.right)) : 0;
    return pRoot ? Math.abs(fn(pRoot.left) - fn(pRoot.right)) <= 1 : true;
}

/**
 * 评论区TOP的解题思路:
 * 1.看起来跟我的思路好像差不多,实际上在对左右子树递归判断时处理不一样
 * 2.该思路是在发现某个子树不是平衡二叉树时,就直接原路返回到根节点并给出结果
 * 3.而我的思路是每次都会遍历完所有的子树
 *
 * @param {*} pRoot
 */
function topIsBalanced_Solution(pRoot)
{
    // write code here
    const fn = root => {
        if (!root) {
            return 0;
        }
        const left = fn(root.left);
        if (left === -1) {
            return -1;
        }
        const right = fn(root.right);
        if (right === -1) {
            return -1;
        }
        return Math.abs(left - right) > 1 ? -1 : 1 + Max(left, right);
    };
    return fn(pRoot) !== -1;
}

调整数组顺序使奇数位于偶数前面(二)

二叉树中和为某一值的路径(一)

进阶剪绳子

二叉树中和为某一值的路径(二)

连续子数组最大和(二)

在二叉树中找到两个节点的最近公共祖先