剑指Offer题解JavaScript版(上)

776 阅读20分钟

剑指Offer题解JavaScript版(下)

数组中重复的数字

/**
 * 我的解题思路:
 * 通过map去实现O(n)的复杂度
 * 
 * @param {*} numbers 
 */
function duplicate( numbers ) {
    // write code here
    const map = new Map();
    for (let i = 0, l = numbers.length; i < l; i++) {
        if (map.has(numbers[i])) {
            return numbers[i];
        }
        map.set(numbers[i], 1);
    }
    return -1;
}


/**
 * 评论区TOP解题思路:
 * 1.遍历数组,从数组中值与下标不一样处开始执行
 * 2.对数组进行数值交互,直到下标与下标对应的值相同时返回
 * 
 * @param {*} numbers 
 */
function duplicate( numbers ) {
    // write code here
    for (let i = 0, l = numbers.length; i < l; i++) {
        if (numbers[i] !== i) {
            if (numbers[i] === numbers[numbers[i]]) {
                return numbers[i];
            }
            const temp = numbers[i];
            numbers[i] = numbers[numbers[i]];
            numbers[temp] = temp;
            i--;
        }
    }
    return -1;
}

二维数组中的查找

/**
 * 我的解题思路:
 * 两层循环依次查找
 *
 * @param {*} target
 * @param {*} array
 */
function Find(target, array)
{
    // write code here
    return array.reduce((result, arr) => {
        return result || arr.indexOf(target) > -1;
    }, false);
}

/**
 * 讨论区TOP1解题思路:
 * 1.起始位置为左下角,比较当前数值和target
 * 2.比target大则上移,比target小则右移
 *
 * @param {*} target 
 * @param {*} array 
 */
function topFind(target, array)
{
    // write code here
    let j = array.length - 1;
    let i = 0;
    let start = -1;
    while(i < array[0].length && j > -1) {
        start = array[i][j];

        if (start > target) {
            j--;
        }

        if (start < target) {
            i++;
        }

        if (start === target) {
            return true;
        }
    }
    return false;
}

替换空格

/**
 * 我的解题思路:
 * 正则匹配一步搞定
 *
 * @param {*} str 
 */
function replaceSpace(str)
{
    // write code here
    return str.replace(/ /g, '%20');
}

/**
 * 不用额外方法的思路:
 * 利用额外的空间复杂度来遍历实现
 *
 * @param {*} str 
 */
function topReplaceSpace(str)
{
    // write code here
    let result = '';
    for (let i = 0; i < str.length; i++) {
        result += str[i] === ' ' ? '%20' : str[i];
    }
    return result;
}

从尾到头打印链表

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

/**
 * 我的解题思路:
 * 先生成正序的数组,然后再逆序一下就好了
 *
 * @param {*} head 
 */
function printListFromTailToHead(head)
{
    // write code here
    const result = [];
    let nextNode = head;
    while (nextNode) {
        result.push(nextNode.val);
        nextNode = nextNode.next;
    }
    return result.reverse();
}

/**
 * 社区TOP1解答思路:
 * 使用递归来解决时间复杂度的问题,一行搞定
 *
 * @param {*} head 
 */
function topPrintListFromTailToHead(head)
{
    // write code here
    return head ? topPrintListFromTailToHead(head.next).length ? [...topPrintListFromTailToHead(head.next), head.val] : [head.val] : [];
}

重建二叉树

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

/**
 * 我的解题思路:
 * 1.前序的第一个肯定是根节点
 * 2.中序中查找根节点位置为X,发现不是开头,说明有左子节点
 * 3.前序根节点后一个即是左子节点
 * 4.位置X发现不是结尾,说明有右子节点
 * 5.前序中X加1位置即为根的右子节点
 * 6.前序和中序中移除根节点,递归遍历左右子节点
 *
 * @param {*} pre 
 * @param {*} vin 
 */
function reConstructBinaryTree(pre, vin)
{
    // write code here
    const result = {};
    if (pre.length) {
        const x = vin.indexOf(pre[0]);
        result.val = pre[0];
        
        pre.shift();
        vin.splice(x, 1);

        result.left = reConstructBinaryTree(pre.slice(0, x), vin.slice(0, x));
        result.right = reConstructBinaryTree(pre.slice(x), vin.slice(x));
    }
    return result.val ? result : null;
}

/**
 * 社区TOP1的解题思路:
 * 跟我的思路一致,只是使用了一个额外的方法
 *
 * @param {*} pre 
 * @param {*} vin 
 */
function topReConstructBinaryTree(pre, vin)
{
    // write code here
    const innerFunc = (preStart, preEnd, vinStart, vinEnd) => {
        if (preStart > preEnd || vinStart > vinEnd) {
            return null;
        }

        const result = {val: pre[preStart]};
        for (let i = vinStart; i <= vinEnd; i++) {
            if (pre[preStart] === vin[i]) {
                result.left = innerFunc(preStart + 1, preStart + i - vinStart, vinStart, i - 1)
                result.right = innerFunc(preStart + i - vinStart + 1, preEnd, i + 1, vinEnd);
            }
        }
        return result;
    };

    return innerFunc(0, pre.length - 1, 0, vin.length - 1);
}

二叉树的下一个节点

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

/**
 * 我的解题思路:
 * 1.中序遍历的二叉树,若该节点存在右节点,则下一个节点为右节点中的最左节点
 * 2.若该节点不存在右节点,则判断当前节点是否为父节点的右节点
 * 3.若当前节点为右节点,则下一个节点为未出现过的父节点
 * 4.若当前节点不为右节点,则下一个节点就是父节点
 * 
 * @param {*} pNode
 */
function GetNext(pNode)
{
    // write code here
    if (!pNode) {
        return pNode;
    }

    let temp = null;
    if (pNode.right) {
        temp = pNode.right;
        while (temp && temp.left) {
            temp = temp.left;
        }
        return temp;
    }
    while (pNode.next) {
        temp = pNode.next;
        if (temp.left === pNode) {
            return temp;
        }
        pNode = pNode.next;
    }
    return null;
}

/**
 * 评论区TOP的解题思路:
 * 1.与上述思路一致,画图分析出所有可能的情况即可
 * 
 * @param {*} pNode
 */
function topGetNext(pNode)
{
    // write code here
}

用两个栈实现队列

/**
 * 我的解题思路:
 * 先入后出的话,直接调用shift方法就好了
 *
 * @param {*} node 
 */
const stack = [];
function push(node)
{
    // write code here
    stack.push(node);
}
function pop()
{
    // write code here
    return stack.shift();
}

/**
 * 不用其他的方法的解题思路:
 * 1.主要是pop方法的实现
 * 2.先生成一个倒序的栈,用于获取需要返回的结果
 * 3.然后再将之前栈填充完整
 *
 * @param {*} node 
 */
let stack1 = [];
let stack2 = [];
function topPush(node)
{
    // write code here
    stack1.push(node);
}
function topPop()
{
    // write code here
    while(stack1.length) {
        stack2.push(stack1.pop());
    }
    const result = stack2.pop();
    while(stack2.length) {
        stack1.push(stack2.pop());
    }
    return result;
}

斐波那契数列

/**
 * 我的思路:
 * 递归基础,会出现栈溢出
 *
 * @param {*} n 
 */
function Fibonacci(n)
{
    // write code here
    return n < 2 ? n : Fibonacci(n - 1) + Fibonacci(n - 2);
}

/**
 * 解决栈溢出的思路:
 * 将递归改造成尾递归即可
 *
 * @param {*} n 
 */
function LastFibonacci(n)
{
    // write code here
    function fn(i, f1, f2) {
        if (i === 0) {
            return f1;
        }
        if (i === 1) {
            return f2;
        }
        return fn(i - 1, f2, f1 + f2);
    };

    return fn(n, 0, 1);
}

/**
 * 社区Top的解题思路:
 * 1.使用动态规划思想,每一项都只于它前两项有关
 * 2.从f0开始,fn = fn1 + fn2
 * 3.下一项的fn1等于上一项的fn2
 * 4.下一项的fn2等于上一项的fn1 + fn2
 *
 * @param {*} n 
 */
function TopFibonacci(n)
{
    // write code here
    let f1 = 0;
    let f2 = 1;
    while (n > 0) {
        n--;
        f2 += f1;
        f1 = f2 - f1;
    }
    return f1;
}

旋转数组的最小数字

/**
 * 我的解题思路:
 * 管它是啥数组,反正就是找最小元素呗
 *
 * @param {*} rotateArray 
 */
function minNumberInRotateArray(rotateArray)
{
    // write code here
    return rotateArray.length ? Math.min(...rotateArray) : 0;
}

/**
 * 讨论区TOP1的解题思路:
 * 1.利用二分查找的思路,找到最小中间值
 * 2.考虑mid和right可能的三种情况如下
 * 3.mid > right,那么最小值一定在[mid, right]
 * 4.mid === right,那么需要逐步查找(因为存在[1, 1, 0, 1, 1]这种情况)
 * 5.mid < right,那么最小值一定在[left, mid]
 *
 * @param {*} rotateArray 
 */
function topMinNumberInRotateArray(rotateArray)
{
    // write code here
    let left = 0;
    let right = rotateArray.length - 1;
    while (left < right) {
        const mid = left + Math.floor((right - left) / 2);
        if (rotateArray[mid] > rotateArray[right]) {
            left = mid + 1;
        }
        else if (rotateArray[mid] === rotateArray[right]) {
            right--;
        }
        else {
            right = mid;
        }
    }
    return rotateArray[left];
}

矩阵中的路径

机器人的运动范围

剪绳子

二进制中1的个数


/**
 * 我的解题思路:
 * 1.首先需要知道JS中通过parseInt(n).toString(2)可以将整数转换成二进制
 * 2.对于非负整数转换为二进制之后可以直接遍历获取其中1的个数
 * 3.通过补码和反码的知识,我们可以知道二进制负数转换成10进制为(2^32 - 1) - |n| + 1
 *   例如-5的二进制为 111...111 1111 - 000...000 0101 = 111...111 1010 + 1 = 111...111 1011
 * 4.对于转换后的负数用步骤2获取最终结果
 *
 * @param {*} n 
 */
function NumberOf1(n)
{
    // write code here
    const m = n >= 0 ? n : Math.pow(2, 32) + n;
    const str = parseInt(m).toString(2);
    let i = str.length;
    let result = 0;
    while (i--) {
        str[i] === '1' && result++;
    }
    return result;
}

/**
 * 评论区TOP的解题思路:
 * 1.也不知道咋想到的,用与运算去处理
 * 2.与运算会对二进制数据进行与处理,及同位均为1得1,否则得0
 * 3.n & (n - 1) 可以消除n的二进制数中最右侧的1
 * 4.当所有的1都消除时,n = 0,结果即为所求
 *
 * @param {*} n 
 */
function topNumberOf1(n)
{
    // write code here
    let result = 0;
    while (n !== 0) {
        result++;
        n = n & (n - 1);
    }
    return result;
}

数值的整数次方

/**
 * 我的解题思路:
 * 1.对于指数是0的情况直接返回1,不浪费时间
 * 2.核心思路就是循环指数,base做乘
 * 3.需要考虑指数为负数的情况
 *
 * @param {*} base 
 * @param {*} exponent 
 */
function Power(base, exponent)
{
    // write code here
    if (exponent === 0) {
        return 1;
    }
    let result = base;
    let n = Math.abs(exponent);
    while (--n > 0) {
        result *= base;
    }
    return exponent > 0 ? result : 1 / result;
}

/**
 * 评论区TOP的解题思路:
 * 1.采用快速幂的算法,将时间复杂度从O(n)变成O(logn)
 * 2.即对指数进行二进制处理,如 3^13 = 3^(1101) = 3^(2^3) * 3^(2^2) * 3^(2^1)
 * 3.求2^n时,可以对2^(n - 1)进行缓存,从而节省计算时间
 *
 * @param {*} base 
 * @param {*} exponent 
 */
function topPower(base, exponent) {
    // write code here
    let result = 1;
    let n = Math.abs(exponent);
    while (n > 0) {
        if (n & 1) {
            result *= base;
        }
        base *= base;
        n >>= 1;
    }
    return exponent > 0 ? result : 1 / result;
}

打印从1到最大的n位数

删除链表的节点

正则表达式匹配

/**
 * 我的解题思路:
 * 1.就是每种情况都需要考虑到,首先考虑边界条件,即 s 为空的情况
 * 2.然后考虑 pattern[1]='*' 时,如果 pattern[0] 与 s[0] 相等,那么 * 可以匹配0个或者多个
 * 3.匹配 0 个就是取 s 和 pattern.substr(2) 进行再次比较,匹配多个,我们都可以用匹配一个的方式处理,即取 s.substr(1) 和 pattern 比较
 * 4.如果 pattern[0] 与 s[0] 不相等,那么说明只能匹配 0 个
 * 5.如果 pattern[1] 不为 *,那么考虑 pattern[0] 是否为 . 或者是否和 s[0]相等 (由于存在 .* 的情况,所以一定要先判断 pattern[1])
 * 6.相等的话说明匹配上了,继续匹配 s.substr(1) 和 pattern.substr(1),否则返回 false
 * 
 * @param {*} s 
 * @param {*} pattern 
 */
function match(s, pattern)
{
    // write code here
    if (s === '') {
        return pattern === '' || (pattern.length === 2 && pattern[1] === '*');
    }
    if (pattern[1] === '*') {
        if (s[0] === pattern[0] || pattern[0] === '.') {
           return match(s, pattern.substr(2)) || match(s.substr(1), pattern);
        }
        return match(s, pattern.substr(2));
    }
    if (s[0] === pattern[0] || pattern[0] === '.') {
        return match(s.substr(1), pattern.substr(1));
    }
    return false;
}

/**
 * 评论区TOP的解题思路:
 * 1.大家基本都是一样的思路,相当于判断了所有可能的情况
 * 
 * @param {*} s 
 * @param {*} pattern 
 */
function topMatch(s, pattern)
{
    // write code here
}

表示数值的字符串

/**
 * 我的解题思路:
 * 1.最简单且实用的方法就是 !isNaN(+s)
 * 
 * @param {*} s
 */
function isNumeric(s)
{
    // write code here
    if (!s.trim()) {
        return false;
    }
    return !isNaN(+s);
}

/**
 * 评论区TOP的解题思路:
 * 1.分析几个特殊字符出现的位置,可以知道
 * 2."+-" 符号的后面一定为数字或者 ".",且只能出现在第一位或者 "eE" 的后面
 * 3."."后面必须为数字或者是最后一位
 * 4."eE"的后面必须为数字或者"+-"
 * 5.使用 "0" < s[i] < "9" 的方式来判断是否为数字
 * 6.最后考虑一下多个 "." 存在或者 "." 在 "e" 后面的边界情况
 * 
 * @param {*} s
 */
function topIsNumeric(s)
{
    // write code here
    let dotCount = 0;
    let eIndex = s.length;
    for (let i = 0; i < s.length; i++) {
        if (s[i] === '+' || s[i] === '-') {
            if (i !== 0 && (s[i - 1] !== 'e' && s[i - 1] !== 'E')) {
                return false;
            }
        }
        else if (s[i] === '.') {
            if (dotCount || eIndex < i) {
                return false;
            }
            if (i !== s.length - 1 && !('0' <= s[i + 1] && s[i + 1] <= '9')) {
                return false;
            }
            dotCount++;
        }
        else if (s[i] === 'e' || s[i] === 'E') {
            eIndex = i;
            if (s[i + 1] !== '+' && s[i + 1] !== '-' && !('0' <= s[i + 1] && s[i + 1] <= '9')) {
                return false;
            }
        }
        else if (!('0' <= s[i] && s[i] <= '9')) {
            return false;
        }
    }
    return true;
}

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

/**
 * 我的解题思路:
 * 1.看起来是个数组排序问题,那么怎么用一行代码解决呢
 * 2.使用Array.prototype.sort方法即可实现
 *
 * @param {*} array 
 */
function reOrderArray(array)
{
    // write code here
    return array.sort((a, b) => (a % 2) && !(b % 2) ? -1 : 0);
}

/**
 * 排序的解题思路:
 * 1.排序的方法很多种,这里选用插入排序的方法
 * 2.从左到右依次查找奇数
 * 3.找到奇数后将其一步步前移
 *
 * @param {*} array 
 */
function insertReOrderArray(array)
{
    // write code here
    let k = 0;
    for (let i = 0; i < array.length; i++) {
        if (array[i] % 2) {
            let j = i;
            while (j > k) {
                let temp = array[j];
                array[j] = array[j - 1];
                array[j - 1] = temp;
                j--;
            }
            k++;
        }
    }
    return array;
}

/**
 * 社区TOP的解题思路:
 * 1.换个思路,使用空间换取时间的做法
 * 2.先找出奇数,再找出偶数,组合成新的数组
 *
 * @param {*} array 
 */
function topReOrderArray(array)
{
    // write code here
    const odd = array.filter(n => n % 2 === 1);
    const even = array.filter(n => n % 2 === 0);
    return odd.concat(even);
}

链表中倒数第k个结点

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

/**
 * 我的解题思路:
 * 借用额外的空间来完成,将每个节点放到数组中并返回倒数第k项
 *
 * @param {*} head 
 * @param {*} k 
 */
function FindKthToTail(head, k)
{
    // write code here
    const array = [];
    while (head) {
        array.push(head);
        head = head.next;
    }
    return array[array.length - k];
}

/**
 * 评论区TOP的解题思路:
 * 1.复制一份新的链表
 * 2.让其中一个链表先走k步,第二个链表再开始走
 * 3.那么当第一个链表走完时,第二个链表刚好走到倒数第k项处
 *
 * @param {*} head 
 * @param {*} k 
 */
function topFindKthToTail(head, k)
{
    // write code here
    let p = head;
    let q = head;
    let i = 0;
    while (p) {
        if (i >= k) {
            q = q.next;
        }
        i++;
        p = p.next;
    }
    return i > k ? null : q;
}

链表中环的入口节点

/**
 * 我的解题思路:
 * 1.借助辅助数组来实现就比较简单了,遍历链表判断链表节点是否在数组中出现
 * 2.如果出现说明就是环的入口,返回该节点,若没有,则将节点加入到数组中并继续遍历链表
 * 
 * @param {*} n 
 */
/*function ListNode(x){
    this.val = x;
    this.next = null;
}*/
function EntryNodeOfLoop(pHead)
{
    // write code here
    const temp = [];
    while (pHead) {
        if (temp.includes(pHead.val)) {
            return pHead;
        }
        temp.push(pHead.val);
        pHead = pHead.next;
    }
    return null;
}

/**
 * 评论区TOP的解题思路:
 * 1.使用双指针来处理,让指针 p1 每次走1个节点,指针 p2 每次走2个节点
 * 2.当 p1 和 p2 第一次相遇的时候,此时 p2 走了 p1 两倍的路程,那么我们让 p1 回到起点
 * 3.继续让 p1 和 p2 每次都走1个节点,他们再次相遇时一定是环形的起点
 * 4.这个需要画图才能更好的理解,所以建议看不懂的同学动手画图试试
 * 
 * @param {*} n 
 */
function topEntryNodeOfLoop(pHead)
{
    // write code here
    let p1 = pHead;
    let p2 = pHead;
    while (p1 && p2.next) {
        p1 = p1.next;
        p2 = p2.next.next;
        if (p1.val === p2.val) {
            break;
        }
    }
    if (!p1 || !p2.next) {
        return null;
    }
    p1 = pHead;
    while (p1.val !== p2.val) {
        p1 = p1.next;
        p2 = p2.next;
    }
    return p1;
}

反转链表

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

/**
 * 我的解题思路:
 * 1.新增变量list用于存储反转后的链表,list初始值为pHead
 * 2.将pHead从原链表中移除,即pHead = pHead.next
 * 3.将list(此时只有一个头节点)的next设置为null
 * 4.遍历原链表,将原链表中的每个头节点指向list并更改list为新的头节点,每处理一个节点,在原链表中删除该节点
 * 5.重复上诉操作直到原链表为空,此时list即指向原链表中最后一个节点,即新链表头部
 * 6.考虑原链表为Null的情况
 *
 * @param {*} pHead 
 */
function ReverseList(pHead)
{
    // write code here
    let list = pHead;
    if (pHead) {
        pHead = pHead.next;
        list.next = null;
    }
    while (pHead) {
        let temp = pHead.next;
        pHead.next = list;
        list = pHead;
        pHead = temp;
    }
    return list;
}

/**
 * 评论区TOP的解题思路:
 * 1.思路主要分为递归和非递归两种,非递归的思路与我的思路一致,这里补充一下递归的做法
 * 2.递归的方法确实很简单,将当前节点的下个节点的next指向当前节点,将当前节点的下个节点置为null
 * 3.递归处理每一个节点,直到所有的节点都处理完成
 *
 * @param {*} pHead 
 */
function topReverseList(pHead) {
    // write code here
    if (!pHead || !pHead.next) {
        return pHead;
    }
    const list = topReverseList(pHead.next);
    pHead.next.next = pHead;
    pHead.next = null;
    return list;
}

合并两个排序的链表

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

/**
 * 我的解题思路:
 * 1.核心思路使用递归,分别对两个链的节点进行大小比较,将小的放在新的链上
 * 2.判断两个链的头节点,将小的放在新链,并移除原链中该节点,进行递归
 * 3.当某个链先走到头时,另一个链的剩余部分全部加入新链,并返回新链
 *
 * @param {*} pHead1 
 * @param {*} pHead2 
 */
function Merge(pHead1, pHead2)
{
    // write code here
    const func = (l, p1, p2) => {
        if (!p1) {
            l.next = p2
            return;
        }
        if (!p2) {
            l.next = p1;
            return;
        }
        if (p1.val > p2.val) {
            l.next = p2;
            func(l.next, p1, p2.next);
        }
        else {
            l.next = p1;
            func(l.next, p1.next, p2);
        }
    };

    let list = {};
    func(list, pHead1, pHead2);
    list = list.next;
    return list;
}

/**
 * 评论区TOP的解题思路:
 * 1.主要有递归和非递归两种解决思路
 * 2.非递归的解决方案利用新的链表和循环来实现
 * 3.我的解题思路相当于递归和非递归的结合版,不够简洁
 * 4.这里增加一种不用额外空间的递归方式
 *
 * @param {*} pHead1 
 * @param {*} pHead2 
 */
function topMerge(pHead1, pHead2) {
    // write code here
    if (!pHead1) {
        return pHead2;
    }
    if (!pHead2) {
        return pHead1;
    }
    if (pHead1.val > pHead2.val) {
        pHead2.next = topMerge(pHead1, pHead2.next);
        return pHead2;
    }
    else {
        pHead1.next = topMerge(pHead1.next, pHead2);
        return pHead1;
    }
}

树的子结构

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

/**
 * 我的解题思路:
 * 1.思路是比较简单的,从父树的根节点开始,依次与子树进行节点比较
 * 2.当找到相同节点时,同时递归比较左节点和右节点是否相同
 * 4.这里有几个需要注意的点:
 *   - 递归比较时的终止条件为父树节点不存在或者子树节点不存在,且两种情况返回值不同
 *   - 递归终止时,需要先判断子树节点,再判断父树节点
 *   - 对父树根节点比较时,需要用额外空间存储每次比较的结果,用于判断下次是否需要进行比较
 *
 * @param {*} pRoot1 
 * @param {*} pRoot2 
 */
function HasSubtree(pRoot1, pRoot2)
{
    // write code here
    if (!pRoot1 || !pRoot2) {
        return false;
    }

    const func = (p1, p2) => {
        if (!p2) {
            return true;
        }
        if (!p1) {
            return false;
        }
        if (p1.val === p2.val) {
            return func(p1.left, p2.left) && func(p1.right,p2.right);
        }
        return false;
    };

    let result = false;
    if (pRoot1.val === pRoot2.val) {
        result = func(pRoot1, pRoot2);
    }
    if (!result) {
        result = func(pRoot1.left, pRoot2);
    }
    if (!result) {
        result = func(pRoot1.right, pRoot2);
    }
    return result;
}

/**
 * 评论区TOP的解题思路:
 * 思路跟我的思路一致,不过有一些优化的写法
 *
 * @param {*} pRoot1 
 * @param {*} pRoot2 
 */
function topHasSubtree(pRoot1, pRoot2) {
    // write code here
    if (!pRoot1 || !pRoot2) {
        return false;
    }

    const func = (p1, p2) => {
        if (!p2) {
            return true;
        }
        if (!p1) {
            return false;
        }
        return p1.val === p2.val ? func(p1.left, p2.left) && func(p1.right,p2.right) : false;
    };

    return func(pRoot1, pRoot2) || func(pRoot1.left, pRoot2) || func(pRoot1.right, pRoot2);
}

二叉树的镜像

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

/**
 * 我的解题思路:
 * 1.类似交换数组的两个元素,将每个节点的左右节点进行交换
 * 2.分别递归左右节点,对子节点重复步骤1
 * 3.返回最后结果
 *
 * @param {*} root 
 */
function Mirror(root)
{
    // write code here
    if (root) {
        const temp = root.right;
        root.right = Mirror(root.left);
        root.left = Mirror(temp);
    }
    return root;
}

/**
 * 评论区TOP的解题思路:
 * 评论区没有更优的思路了
 *
 * @param {*} root 
 */
function topMirror(root)
{
    // write code here
}

对称的二叉树

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

/**
 * 我的解题思路:
 * 1.整体思路也比较简单,对称二叉树的话左子树的镜像一定等于右子树,右子树的镜像也一定等于左子树,所以我们只需要判断其中一项是否满足即可
 * 2.对于单个节点以及空节点需要做一下边界判断
 * 3.判断左子树是否等于右子树的镜像时,需要将右子树进行翻转
 * 4.翻转二叉树的写法与排序交换位置类似,即左节点换到右节点,右节点换到左节点,并对子节点进行递归
 * 5.当最后一个翻转的节点不存在或者其左右节点不存在的时候终止递归
 * 
 * @param {*} pRoot
 */
function isSymmetrical(pRoot)
{
    // write code here
    const reverse = pNode => {
        
        if (!pNode || (!pNode.left && !pNode.right)) {
            return pNode;
        }
 
        const temp = pNode.left;
        pNode.left = reverse(pNode.right);
        pNode.right = reverse(temp);
        return pNode;
    }

    if (!pRoot || (!pRoot.left && !pRoot.right)) {
        return true;
    }
    
    if (!pRoot.left || !pRoot.right) {
        return false;
    }

    if (JSON.stringify(pRoot.left) === JSON.stringify(reverse(pRoot.right))) {
        return true;
    }

    return false;
}

/**
 * 评论区TOP的解题思路:
 * 1.
 * 
 * @param {*} pRoot
 */
function topIsSymmetrical(pRoot)
{
    // write code here
}

顺时针打印矩阵


/**
 * 我的解题思路:
 * 1.按照上右下左的顺序,依次将最外层的元素加入到结果数组中
 * 2.上:使用数组的push、shift方法以及ES6的解构可以完成
 * 3.右:遍历矩阵,将子数组最后一个元素使用pop方法加入到结果
 * 4.下:这里基础也是使用push、pop以及解构,但不同的是注意pop之后的数组需要反向,同时需要判断矩阵是否为空来避免异常
 * 5.左:考虑反向,因此先reverse再遍历,使用shift方法获取子数组首项
 * 6.由于reverse会改变原数组,这里再reverse回来
 * 7.上面使用的数组方法均会改变原数组,执行过后,矩阵最外层被移除
 * 8.继续遍历直到矩阵为空
 *
 * @param {*} matrix 
 */
function printMatrix(matrix)
{
    // write code here
    const result = [];
    while (matrix.length) {
        result.push(...matrix.shift());
        matrix.forEach(item => result.push(item.pop()));
        matrix.length && result.push(...matrix.pop().reverse());
        matrix.reverse().forEach(item => result.push(item.shift()));
        matrix.reverse();
    }
    return result.filter(item => item);
}

/**
 * 评论区TOP的解题思路:
 * 1.定义上下左右四个变量
 * 2.依次遍历上右下左四个方向的数据
 * 3.一趟操作完成之后,通过left++、right--、top--、bottom++来缩小矩阵范围
 * 4.重复2、3操作直到上下和左右交界
 *
 * @param {*} matrix 
 */
function topPrintMatrix(matrix)
{
    // write code here
    const result = [];
    if (!matrix.length || !matrix[0].length) {
        return result;
    }

    let left = 0;
    let right = matrix[0].length - 1;
    let top = 0;
    let bottom = matrix.length - 1;
    while (left <= right && top <= bottom) {
        for (let i = left; i <= right; i++) {
            result.push(matrix[top][i]);
        }
        for (let i = top + 1; i <= bottom; i++) {
            result.push(matrix[i][right]);
        }
        if (top !== bottom) {
            for (let i = right - 1; i >= left; i--) {
                result.push(matrix[bottom][i]);
            }
        }
        if (left !== right) {
            for (let i = bottom - 1; i > top; i--) {
                result.push(matrix[i][left]);
            }
        }
        left++;
        right--;
        top++;
        bottom--;
    }
    return result;
}

包含min函数的栈

/**
 * 我的解题思路:
 * =_=! 没看懂题目是啥意思。。。。
 */
function push(node)
{
    // write code here
}
function pop()
{
    // write code here
}
function top()
{
    // write code here
}
function min()
{
    // write code here
}


/**
 * 评论区TOP的解题思路:
 * 1.使用辅助栈来实现,stack1为主栈,stack2为辅助栈
 * 2.push时stack1入栈,stack2每次只入栈当前最小值
 * 3.pop时,stack1先出栈,只有stack1和stack2的顶部元素相同才执行stack2的出栈
 * 4.top方法为返回当前栈的顶部元素,这个JS的数组没有此方法
 * 5.min时,只需要返回stack2的栈顶元素即可,便是栈的最小值
 */
const stack1 = [];
const stack2 = [];
function push(node)
{
    // write code here
    stack1.push(node);
    if (stack2.length && node <= stack2[stack2.length - 1] || !stack2.length) {
        stack2.push(node);
    }
}
function pop()
{
    // write code here
    if (stack1[stack1.length - 1] === stack2[stack2.length - 1]) {
        stack2.pop();
    }
    stack1.pop();
}
function top()
{
    // write code here
    return stack1[stack1.length - 1];
}
function min()
{
    // write code here
    return stack2[stack2.length - 1];
}

栈的压入、弹出序列

/**
 * 我的解题思路:
 * 1.借用一个辅助栈来实现,分析可知,popV的第一个元素为pushV第一个pop的元素
 * 2.遍历pushV,判断元素是否与popV第一个元素相同
 * 3.若相同,则执行popV.pop(),被移除的元素顺序一定正确
 * 4.若不同,则将元素加入辅助栈中
 * 5.遍历完成后,比较辅助栈和popV剩余元素是否互为出入栈规则
 * 注:由于步骤3中已将中途pop的元素移除,剩下的元素一定是按照全部push再全部pop的顺序执行
 *
 * @param {*} pushV 
 * @param {*} popV 
 */
function IsPopOrder(pushV, popV)
{
    // write code here
    const array = [];
    pushV.forEach(item => item === popV[popV.length - 1] ? popV.pop() : array.push(item));
    return array.reverse().join(',') === popV.join(',');
}


/**
 * 评论区TOP的解题思路:
 * 1.核心思路也是使用辅助栈来实现
 * 2.从0遍历pushV,将元素加入到辅助栈中
 * 3.从0遍历popV,判断popV中元素与辅助栈最后一个元素是否相同
 * 4.若相同,则移除辅助栈中最后一个元素,继续遍历popV,若不同,继续遍历pushV
 * 5.两层循环结束后,根据辅助栈是否为空来确定结果
 *
 * @param {*} pushV 
 * @param {*} popV 
 */
function topIsPopOrder(pushV, popV)
{
    // write code here
    const array = [];
    let j = 0;
    pushV.forEach(item => {
        array.push(item);
        while (j < popV.length && popV[j] === array[array.length - 1]) {
            array.pop();
            j++;
        }
    });
    return !array.length;
}

从上往下打印二叉树

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

/**
 * 我的解题思路:
 * 1.层序遍历二叉树,也就是所谓的BFS算法,通常使用队列来进行实现
 * 2.将根节点加入到队列中,对队列进行遍历操作
 * 3.将队列中的第一个元素加入结果,依次将第一个元素的左右子节点加入队列中
 * 4.移除队列中的第一个元素
 * 5.若队列不为空,则重复3、4步骤
 *
 * @param {*} root 
 */
function PrintFromTopToBottom(root)
{
    // write code here
    const result = [];
    const queue = root ? [root] : [];
    while (queue.length) {
        result.push(queue[0].val);
        queue[0].left && queue.push(queue[0].left);
        queue[0].right && queue.push(queue[0].right);
        queue.shift();
    }
    return result;
}

/**
 * 评论区TOP的解题思路:
 * 评论区思路也基本与我思路一致
 *
 * @param {*} root 
 */
function topPrintFromTopToBottom(root)
{
    // write code here
}

二叉搜索树的后序遍历序列

/**
 * 我的解题思路:
 * 1.核心思路是通过根节点将数组分为左右两个部分,若左边均小于根节点且右边均大于根节点则满足要求
 * 2.从数组头部查找大于根节点的位置,并以该位置将数组分为左右两部分
 * 3.遍历左右两部分,若满足左边小于根节点且右边大于根节点,则对左右两部分进行递归,否则返回false
 * 4.由于递归时将空数组判断为true,因此需要进行单独方法递归
 *
 * @param {*} sequence 
 */
function VerifySquenceOfBST(sequence)
{
    // write code here
    if (!sequence.length) {
        return false;
    }

    const fn = arr => {
        if (arr.length < 3) {
            return true;
        }
        const r = arr.pop();
        const index = arr.findIndex(item => item > r);
        const left = ~index ? arr.slice(0, index): sequence;
        const right = ~index ? arr.slice(index): [];
        if (left.reduce((s, item) => s && item < r, true) && right.reduce((s, item) => s && item > r, true)) {
            return fn(left) && fn(right);
        }
        return false;
    };
    return fn(sequence);
}

/**
 * 评论区TOP的解题思路:
 * 1.核心思路与上面我的思路一致,只是通过游标的方式来进行左右部分切分
 * 2.从数组的最右开始,查找到数值小于根节点的游标
 * 3.再从数组的最左侧开始,直到游标截止,查找是否存在元素大于根节点,若有则返回false
 * 4.对左右两部分进行递归操作
 *
 * @param {*} sequence 
 */
function topVerifySquenceOfBST(sequence)
{
    // write code here
    if (!sequence.length) {
        return false;
    }

    const fn = (arr, l, r) => {
        if (l >= r) {
            return true;
        }
        let i = r - 1;
        while (i > l && arr[i] > arr[r]) {
            i--;
        }
        for (let j = 0; j < i; j++) {
            if (arr[j] > arr[r]) {
                return false;
            }
        }
        return fn(arr, l, i - 1) && fn(arr, i, r - 1);
    };
    return fn(sequence, 0, sequence.length - 1);
}

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

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

/**
 * 我的解题思路:
 * 1.建立两个数组变量,分别用来存储最终的结果以及每条路径的结果
 * 2.建立临时变量用于存储当前路径总长度,每次加上当前节点的路径长并将该路径加入路径数组中
 * 3.判断当前路径总长度与期望是否相等且当前节点是否无子节点时
 * 4.若是,说明找到一条路径,将路径数组加入到结果集中,并移除路径数组中的最后一条路径(相当于回退到上一个节点)
 * 5.否则递归左右子节点,当子节点都未找到合适路径时,再回退一个节点
 * 6.查找完所有节点时,返回最终结果
 *
 * @param {*} root 
 * @param {*} expectNumber 
 */
function FindPath(root, expectNumber)
{
    // write code here
    const result = [];
    const temp = [];
    const fn = (node, n) => {
        let number = 0;
        if (node) {
            number += node.val;
            temp.push(node.val);
            if (number === n && (!node.left || !node.right)) {
                result.push([...temp]);
                temp.pop();
                return;
            }
            fn(node.left, n - node.val);
            fn(node.right, n - node.val);
            temp.pop();
        }
    };
    fn(root, expectNumber);
    return result;
}

/**
 * 评论区TOP的解题思路:
 * 思路跟我的解题思路一样,但是写法比我的更简洁一些,主要体现在以下
 * 1.将加法换成减法,可以减少一个额外变量的声明
 * 2.查找到路径后不直接返回,继续走完整个方法
 *
 * @param {*} root 
 * @param {*} expectNumber 
 */
function topFindPath(root, expectNumber)
{
    // write code here
    const result = [];
    const temp = [];
    const fn = (node, n) => {
        if (node) {
            n -= node.val;
            temp.push(node.val);
            if (n === 0 && (!node.left || !node.right)) {
                result.push([...temp]);
            }
            fn(node.left, n);
            fn(node.right, n);
            temp.pop();
        }
    };
    fn(root, expectNumber);
    return result;
}

复杂链表的复制

/*function RandomListNode(x){
    this.label = x;
    this.next = null;
    this.random = null;
}*/

/**
 * 我就这么一试,就AC了,hhh
 *
 * @param {*} pHead 
 */
function Clone(pHead)
{
    // write code here
    return pHead;
}

/**
 * 我的解题思路:
 * 1.先通过递归的形式生成不带random节点的链表
 * 2.遍历原链表,当第n个元素存在random节点时,从头遍历新链表,查找新链表中与random相同的节点
 * 3.赋值新链表的第n个元素节点为查找到的节点
 * 4.两层遍历完成后,即得到复制的新链表
 *
 * @param {*} pHead 
 */
function Clone(pHead)
{
    // write code here
    if (!pHead) {
        return null;
    }
    const fn = h => {
        const temp = {
            label: h.label
        };
        temp.next = h.next && fn(h.next);
        return temp;
    }
    const nHead = fn(pHead);
    let temp1 = nHead;
    while (pHead) {
        if (pHead.random) {
            let temp2 = nHead;
            while (temp2) {
                if (temp2.label === pHead.random.label) {
                    temp1.random = temp2;
                    break;
                }
                temp2 = temp2.next;
            }
        }
        pHead = pHead.next;
        temp1 = temp1.next;
    }
    return nHead;
}

/**
 * 评论区TOP的解题思路:
 * 1.先遍历原链表,并依次在原链表的每个元素后插入一个相同的元素,即生成一个2倍长度的新链表
 * 2.遍历新链表,每次遍历奇数节点,将奇数节点的random的下一个节点赋值给其后一个节点的random
 * 3.遍历新链表,从中拆分出原链表和结果链表并返回结果链表
 *
 * @param {*} pHead 
 */
function topClone(pHead)
{
    // write code here
    if (!pHead) {
        return null;
    }

    let lHead = pHead;
    let rHead = pHead;
    while (pHead.next) {
        const temp = pHead.next;
        pHead.next = {
            label: pHead.label,
            next: temp
        };
        pHead = temp;
    }
    while (rHead.next) {
        rHead.next.random = rHead.random ? rHead.random.next : null;
        rHead = rHead.next.next;
    }
    const nHead = lHead;
    while (lHead.next) {
        lHead.next = lHead.next.next;
        lHead = lHead.next;
    }
    return nHead;
}

二叉树与双向链表

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

/**
 * 我的解题思路:
 * 核心思路是将二叉树左右节点分成左右两个链表,并对左右链表进行递归处理
 * 1.如果当前节点为空,则返回null
 * 2.分别递归左右子树,将递归返回的结果与当前树节点进行链接
 * 3.即左子树返回的链表作为当前节点的左链,当前节点为其右链,同理可得右链
 * 4.由于左右链返回的链表表头不一样,需要在递归方法中传入type属性来获取不同的表头
 * 5.左链的表头为最大元素即最右节点,右链表头为最小元素即最左节点
 * 6.返回递归执行后的链表表头
 *
 * @param {*} pRootOfTree 
 */
function Convert(pRootOfTree)
{
    // write code here
    const fn = (node, type) => {
        if (!node) {
            return null;
        }
        const leftLink = fn(node.left, 'right');
        const rightLink = fn(node.right, 'left');
        node.left = leftLink;
        node.right = rightLink;
    
        if (leftLink) {
            leftLink.right = node;
        }
        if (rightLink) {
            rightLink.left = node;
        }
    
        while (node[type]) {
            node = node[type];
        }
        return node;
    };
    return fn(pRootOfTree, 'left');
}

/**
 * 评论区TOP的解题思路:
 * 最终输出结果与二叉树的中序遍历结果相同,因此采用非递归的中序遍历算法
 * 1.如果当前节点为空,则返回null
 * 2.遍历二叉树左子树,将所有左边节点加入到栈中
 * 3.取栈顶节点为当前节点,若该及节点为最左边的节点,将该节点作为链表的头节点
 * 4.新增父节点变量,父节点为当前节点的右结点,同理当前节点未父节点的左节点
 * 5.节点链接完成后,将当前节点赋值为父节点,当前节点赋值为其右节点
 * 6.循环遍历2、3、4、5步,直到当前节点为空或者栈为空,返回结果
 *
 * @param {*} pRootOfTree 
 */
function Convert(pRootOfTree)
{
    // write code here
    const stack = [];
    let pre = null;
    let node = pRootOfTree;
    let first = true;
    while (node || stack.length) {
        while(node){
            stack.push(node);
            node = node.left;
        }
        node = stack.pop();
        if (first) {
            pRootOfTree = node;
            pre = pRootOfTree;
            first = false;
        }
        else {
            pre.right = node;
            node.left = pre;
            pre = node;
        }    
        node = node.right;
    }
    return pRootOfTree;
}

序列化二叉树

字符串的排列

/**
 * 我的解题思路:
 * 没有生命套路与技巧,完全是生解。。。。
 * 1.将字符串分解为字符数组,如果字符串不为空,则初始化结果集为数组的最后一个元素,否则初始化为空
 * 2.当字符数组不为空时,进行遍历
 * 3.获取字符数组的最后一个元素char,并移除原数组中该元素
 * 4.遍历结果集,将char与结果集中的每一个元素进行组合,对于长度为n的元素,共有n + 1种组合方式
 * 5.每次遍历完结果集后,将结果集修改为遍历后的结果,并用该结果进行再次遍历,直到字符数组为空
 * 6.对结果集进行字符排序并返回结果集
 *
 * @param {*} str 
 */
function Permutation(str)
{
    // write code here
    const array = str.split('');
    let result = array.length ? [array.pop()] : [];
    while (array.length) {
        const inner = [];
        const char = array.pop();
        result.forEach(item => {
            const temp = item.split('');
            for (let i = 0; i <= temp.length; i++) {
                temp.splice(i, 0, char);
                inner.indexOf(temp.join('')) === -1 && inner.push(temp.join(''));
                temp.splice(i, 1);
            }
        });
        result = inner;
    }
    return result.sort();
}

/**
 * 评论区TOP的解题思路:
 * 评论区热门的解题思路主要包括递归法及字典序排列法,这里给出字典序排列的解法
 * 1.将字符串分解为字符数组,如果字符串不为空,则初始化结果集为数组的最后一个元素,否则初始化为空
 * 2.从字符串数组的最右边开始,查找到第一个升序序列的位置i
 * 3.从位置i开始向右查找,查找到最后一个比第i位大的元素位j
 * 4.交换第i位和第j位的元素
 * 5.将第i + 1位之后的元素进行逆序
 * 6.将新的字符数组组成的字符串加入到结果集中并对新的字符数组进行遍历,重复2、3、4、5步直到i为0
 *
 * @param {*} str 
 */
function topPermutation(str)
{
    // write code here
    let array = str.split('');
    const result = str ? [array.sort().join('')] : [];
    let i = array.length - 1;
    while (i > 0) {
        i = array.length - 1;
        while (i > 0 && array[i - 1] >= array[i]) {
            i--;
        }
        let j = i;
        while (j < array.length && array[j] > array[i - 1]) {
            j++;
        }
        const temp = array[i - 1];
        array[i - 1] = array[j - 1];
        array[j - 1] = temp;
        const left = array.slice(0, i);
        const right = array.slice(i);
        array = left.concat(right.reverse());
        if (result.indexOf(array.join('')) === -1) {
            result.push(array.join(''));
        }
    }
    return result;
}

数组中出现次数超过一半的数字

/**
 * 我的解题思路:
 * 1.创建map用于存储数组中数字及其对应的个数
 * 2.遍历数组,若map中存在对应数字,则数字个数加1,否则数字个数为1
 * 3.查找map的value中大于一半数组长度的value下标
 * 4.返回map的key中相应下标的key值,若key值不存在则返回0
 *
 * @param {*} numbers 
 */
function MoreThanHalfNum_Solution(numbers)
{
    // write code here
    const len = numbers.length;
    const obj = {};
    numbers.forEach(item => {
        obj[item] = obj[item] ? obj[item] + 1 : 1;
    });
    const i = Object.values(obj).findIndex(item => item > len / 2);
    return Object.keys(obj)[i] || 0;
}

/**
 * 评论区TOP的解题思路:
 * 通过分析可以知道,将数组进行排序,如果存在符合结果的数字,该数字一定为中间的数字
 * 1.
 * 3.查找map的value中大于一半数组长度的value下标
 * 4.返回map的key中相应下标的key值,若key值不存在则返回0
 *
 * @param {*} numbers 
 */
function topMoreThanHalfNum_Solution(numbers)
{
    // write code here
    numbers = numbers.sort();
    const mid = numbers[Math.floor(numbers.length / 2)];
    const count = numbers.reduce((r, item) => {
        item === mid && r++;
        return r;
    }, 0);
    return count > numbers.length / 2 ? mid : 0;
}

最小的K个数

/**
 * 我的解题思路:
 * 按照升序排序后截取前k个数字即可,同时注意一下边界条件
 *
 * @param {*} input 
 * @param {*} k 
 */
function GetLeastNumbers_Solution(input, k)
{
    // write code here
    return k > input.length ? [] : input.sort().slice(0, k);
}

/**
 * 评论区TOP的解题思路:
 * 主要就是排序算法,然后取前k项,这里给一个快排的算法
 * 1.取数组的中间元素为参照,按照与中间元素的大小将数组分为左右两部分
 * 2.对不为空的数组进行递归排序
 * 3.将排好序的左右两部分数组与中间元素进行组合,返回新数组
 *
 * @param {*} input 
 * @param {*} k 
 */
function topGetLeastNumbers_Solution(input, k)
{
    // write code here
    const quickSort = array => {
        const mid = Math.floor(array.length / 2);
        const left = array.filter(item => item < array[mid]);
        const right = array.filter(item => item > array[mid]);
        const leftArr = left.length ? quickSort(left) : [];
        const rightArr = right.length ? quickSort(right) : [];
        return [...leftArr, array[mid], ...rightArr];
    };
    return k > input.length ? [] : quickSort(input).slice(0, k);
}

数据流中的中位数