剑指offer JavaScript版本每晚更新一道

flag: 限制今天起开始也刷剑指offer啦,一步一步来。

二维数组的查找

题目描述

在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。

//思路:题目不难,而且给出的限制也很小,我一开始试试用了暴力逐步循环发现也能通过,但是这题目设计的初衷不是简单的让你用暴力循环查找出来,而是要注意是有顺序的。所以我们查找的起始点最好从二维数组中的中间的点开始,不过为了方便,我们一般都是选择最左下角的那个点作为起始点,也就是a[array.lenth][0],比它大就往右边走,比它小就往上面走。话不多说,上代码。
function Find(target, array) {
    const n = array.length,
        m = array[0].length;
    let row = n - 1,
        col = 0;
    if (m === 0 && n === 0) {
        return false;
    }
    while (row >= 0 && col <= m - 1) {
        if (array[row][col] > target) {
            row--;
        } else if (array[row][col] < target) {
            col++;
        } else return true;
    }
    return false;
}

替换空格

题目描述

请实现一个函数,将一个字符串中的空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。

//思路 :这题比较简单把字符串转换为数组除最后一项,每项后面拼接"%20";最后再转换为字符串
function replaceSpace(str) {
    var arr = str.split(' ');
    var len = arr.length;
    for (let i = 0; i < len - 1; i++) {
        arr[i] = arr[i] + '%20';
    }
    return arr.join('');
}
console.log(replaceSpace('We Are Happy'));
//看了别人的解直接用正则替换比较牛逼 一行代码搞定 学习了
function replaceSpace(str) {
  return str.replace(/\s/g, '%20');
}

从尾到头打印链表

题目描述

输入一个链表,按链表从尾到头的顺序返回一个ArrayList。

//使用unshift()或者push()+reverse()
/*function ListNode(x){
    this.val = x;
    this.next = null;
}*/
function printListFromTailToHead(head)
{   
    // write code here
     let  res=[];
     let pNode=head;
    while(pNode!==null){
        res.push(pNode.val);
        pNode=pNode.next
    }
    return res.reverse();
}
--------------------------------------
function printListFromTailToHead(head)
{   
    // write code here
     let  res=[];
     let pNode=head;
    while(pNode!==null){
        res.unshift(pNode.val);
        pNode=pNode.next
    }
    return res;
}

用两个栈实现队列

题目描述

用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。

const outStack = [],
    inStack = [];
function push(node) {
    // write code here
    inStack.push(node)
}

function pop() {
    // write code here
    // 出栈为空需要把进栈的数据拿到
    if (!outStack.length) {
        while (inStack.length) {
            outStack.push(inStack.pop())
        }
    }
    return outStack.pop();
}

旋转数组的最小数字

题目描述

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。
输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。
例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。
NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。

//暴力解法 直接找最小值
function minNumberInRotateArray(rotateArray) {
    const length = rotateArray.length;
    if (!length) {
        return 0;
    }
    return Math.min.call(Math,...rotateArray);
}
// 二分查找法 
//举几个例子来推导解题细节(请记住题干的数组有序、某个点旋转这两个条件):
// arr[left] < arr[right]: 直接返回arr[left]。例如:1 2 3 4 5
// arr[left] < arr[mid]: 说明从数组下标范围为[left, right]的元素是递增的,此时最小值只可能出现在[mid + 1, length)范围内。例如:4 5 1 2 3
// arr[mid] < arr[right]: 说明从数组下标范围为[mid, right]的元素是递增的,此时最小值只可能出现在[left, mid] 范围内。注意,这里不能跳过下标mid。例如:3 3 3 4 5
// 其他情况,此时arr[mid] = arr[right] = arr[left]: 移动 left,缩小范围即可。例如:1 1 1 0 
function minNumberInRotateArray(rotateArray) {
    const length = rotateArray.length;
    if (!length) {
        return 0
    }
    let left = 0;
    let right = length - 1;
    while (left < right) {
        let mid = Math.floor((left + right) / 2);
        if (rotateArray[left] < rotateArray[right]) {
            return rotateArray[left];
        }
        if (rotateArray[left] < rotateArray[mid]) {
            left = mid + 1;
        } else if (rotateArray[mid] < rotateArray[right]) {
            right = mid;
        } else {
            left++;
        }
    }

}

菲波那切数列

// 递归就不说了 事件太长了 无法通过
// 采用循环的方式
function Fibonacci(n) {
    let r = 0;
    let sum = 1;
    for (let i = 0; i < n; i++) {
        sum += r;
        r = sum - r;
    }
    return r;
}

二进制问题

题目描述

输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。

//  核心代码  n&n-1  一个数 
//  分析一下代码: 这段小小的代码,很是巧妙。
//如果一个整数不为0,那么这个整数至少有一位是1。如果我们把这个整数减1,那么原来处在整数最右边的1就会变为0,原来在1后面的所有的0都会变成1(如果最右边的1后面还有0的话)。其余所有位将不会受到影响。
//举个例子:一个二进制数1100,从右边数起第三位是处于最右边的一个1。减去1后,第三位变成0,它后面的两位0变成了1,而前面的1保持不变,因此得到的结果是1011.我们发现减1的结果是把最右边的一个1开始的所有位都取反了。这个时候如果我们再把原来的整数和减去1之后的结果做与运算,从原来整数最右边一个1那一位开始所有位都会变成0。如1100&1011=1000.也就是说,把一个整数减去1,再和原整数做与运算,会把该整数最右边一个1变成0.那么一个整数的二进制有多少个1,就可以进行多少次这样的操作。
function NumberOf1(n)
{    
    let count =0;
    while(n){
        n=n&(n-1);
        count++;
    }
    return count;
}

数组奇数前移问题

题目描述

输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。

function reOrderArray(array)
{
   let l=0,r=0;
   let newarr=[];
   for(let i=0;i<array.length;i++){
       if(array[i]%2!==0){
           r++
       }
   }
   for(let i=0;i<array.length;i++){
       if(array[i]%2===0){
        newarr[r++]=array[i]
       }
       else{
           newarr[l++]=array[i]
       }
   }
   return newarr;
}

输出链表倒数第K个节点

题目描述

输入一个链表,输出该链表中倒数第k个结点。

//思路简单 双指针法 用两个指针来跑,两个指针中间相距k-1个节点,第一个指针先跑,跑到了第k个节点时,第二个指针则是第一个节点。
//这时候两个一起跑。当第一个跑到了最后一个节点时,这时候第一个指针则是倒数第k个节点。
/*function ListNode(x){
    this.val = x;
    this.next = null;
}*/
function FindKthToTail(head, k) {
    if (head === null || k <= 0) {
        return null;
    }
    let pNode1 = head;
    let pNode2 = head;
    while (--k) {
        if (pNode2.next !== null) {
            pNode2 = pNode2.next;
        } else {
            return null;
        }
    }
    while (pNode2.next !== null) {
        pNode2 = pNode2.next;
        pNode1 = pNode1.next;
    }
    return pNode1;
}

反转链表

题目描述

输入一个链表,反转链表后,输出链表的所有元素。

//原地反转法
/*function ListNode(x){
    this.val = x;
    this.next = null;
}*/
function ReverseList(pHead) {
    // write code here
    let ppre = null;
    let pnext = null;
    while (pHead !== null) {
        pnext = pHead.next;
        pHead.next = ppre;
        ppre = pHead;
        pHead = pnext;
    }
    return ppre;
}

见图

合并两个递增链表

题目描述

输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则

//设置一个“哨兵节点”叫 preHead,这会让代码写起来非常“清爽”。整体流程如下:
//如果 pHead1 和 pHead2,均没遍历完:
//如果 pHead1.val <= pHead2.val,那么当前 node 的 next 指向 pHead1。并且移动 pHead1 指针。
//否则,当前 node 的 next 指向 pHead2,移动 pHead2 指针。
//移动 node 指针
//继续循环
//否则,结束循环:
//如果 pHead1 未遍历完,node 的 next 指向 pHead1
//如果 pHead2 未遍历玩,node 的 next 指向 pHead2
/*function ListNode(x){
    this.val = x;
    this.next = null;
}*/
function Merge(pHead1, pHead2) {
    if (pHead1 === null) return pHead2;
    if (pHead2 === null) return pHead1;
    let preHead = new ListNode(-1);
    let node = preHead;
    while (pHead2 && pHead1) {
        if (pHead1.val < pHead2.val) {
            node.next = pHead1;
            pHead1 = pHead1.next;
        } else {
            node.next = pHead2;
            pHead2 = pHead2.next;
        }
        node = node.next;
    }
    if (pHead1) {
        node.next = pHead1;
    } else if (pHead2) {
        node.next = pHead2;
    }

    return preHead.next;
} 
看了下题解   发现别人用递归实现的很厉害 代码量更少
// 重点抓住这两个链表都是单挑递增的,因此我们只需要不断地比较他们的头结点就行,明显这是个重复的过程。
/* function ListNode(x){
 this.val = x;
 this.next = null;
 }*/
function Merge(pHead1, pHead2) {
  let pMergeHead = null;
  // write code here
  if (pHead1 === null) return pHead2;
  if (pHead2 === null) return pHead1;
  if (pHead1.val < pHead2.val) {
    pMergeHead = pHead1;
    pMergeHead.next = Merge(pHead1.next, pHead2);
  } else {
    pMergeHead = pHead2;
    pMergeHead.next = Merge(pHead1, pHead2.next);
  }
  return pMergeHead;
}

包含main函数的栈

题目描述

定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的min函数(时间复杂度应为O(1))。

注意:保证测试中不会当栈为空的时候,对栈调用pop()或者min()或者top()方法。

借用辅助栈
// 剑指offer里面推荐的思路就是增加了一个辅助栈,每次压入数据栈时,把当前栈里面最小的值压入辅助栈当中。这样辅助栈的栈顶数据一直是数据栈中最小的值。
//比如,data中依次入栈,  5, 4, 3, 8, 10, 11, 12, 1
 //则min依次入栈,   5, 4, 3, 3,  3,  3,  3, 1

var stack = [];
var minStack = [];
var temp = null;

function push(node) {
    // write code here
    if (temp !== null) {
        if (node < temp) {
            temp = node;
        }
        stack.push(node);
        minStack.push(temp);
    } else {
        temp = node;
        minStack.push(temp);
        stack.push(node)
    }
}

function pop() {
    // write code here
    stack.pop();
    minStack.pop();

}

function top() {
    // write code here
    return stack[stack.length-1]
}

function min() {
    // write code here
    return minStack[minStack.length-1]
}

栈的压入顺序弹出顺序

题目描述

输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。(注意:这两个序列的长度是相等的)

【思路】借用一个辅助的栈,遍历压栈顺序,先讲第一个放入栈中,这里是1,然后判断栈顶元素是不是出栈顺序的第一个元素,这里是4,很显然14,所以我们继续压栈,直到相等以后开始出栈,出栈一个元素,则将出栈顺序向后移动一位,直到不相等,这样循环等压栈顺序遍历完成,如果辅助栈还不为空,说明弹出序列不是该栈的弹出顺序。

举例:

入栈1,2,3,4,5

出栈4,5,3,2,1

首先1入辅助栈,此时栈顶14,继续入栈2

此时栈顶24,继续入栈3

此时栈顶34,继续入栈4

此时栈顶44,出栈4,弹出序列向后一位,此时为5,辅助栈里面是1,2,3

此时栈顶35,继续入栈5
此时栈顶5=5,出栈5,弹出序列向后一位,此时为3,辅助栈里面是1,2,3
依次执行,最后辅助栈为空。如果不为空说明弹出序列不是该栈的弹出顺序
….
function IsPopOrder(pushV, popV)
{
    // write code here
    var stack = [];
    var index = 0;
    for(let i = 0;i<pushV.length;i++){
        stack.push(pushV[i]);
        while(stack.length&&popV[index]==stack[stack.length-1]){
            stack.pop();
            index++;
        }
    }
    return stack.length==0;
}

从上往下遍历二叉树

题目描述

从上往下打印出二叉树的每个节点,同层节点从左至右打印。

//从下打印就是按层次打印,其实也就是树的广度遍历。
//一般来说树的广度遍历用队列,利用先进先出的特点来保存之前节点,并操作之前的节点。
function PrintFromTopToBottom(root) {
    const queue = [],
        res = [];
    if (root === null) {
        return res;
    }
    queue.push(root);
    while (queue.length) {
        const pRoot = queue.shift();
        if (pRoot.left !== null) {
            queue.push(pRoot.left);
        }
        if (pRoot.right !== null) {
            queue.push(pRoot.right);
        }
        res.push(pRoot.val);
    }
    return res;
}

树的子结构

题目描述

输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)

// Step1.在树A中找到和B的根结点的值一样的结点R;
// Step2.判断树A中以R为根结点的子树是不是包含和树B一样的结点。
function HasSubtree(pRoot1, pRoot2) {
    let res = false;
    //当Tree1或者Tree2都为零的时候,才进行比较。否则直接返回false
    if (pRoot1 === null || pRoot2 === null) return false;
    //如果找到了对应Tree2的根节点的点
    if (pRoot1.val === pRoot2.val)
    //以这个根节点为为起点判断是否包含Tree2
        res = doesTree1HasTree2(pRoot1, pRoot2);
    if (!res)
    //如果找不到,那么就再去root的左儿子当作起点,去判断时候包含Tree2
        res = HasSubtree(pRoot1.left, pRoot2);
    if (!res)
    //如果找不到,那么就再去root的右儿子当作起点,去判断时候包含Tree2
        res = HasSubtree(pRoot1.right, pRoot2);
    //返回结果
    return res;
}

function doesTree1HasTree2(pRoot1, pRoot2) {
    //如果Tree2已经遍历完了都能对应的上,返回true
    if (pRoot2 === null) return true;
    //如果Tree2还没有遍历完,Tree1却遍历完了。返回false
    if (pRoot1 === null) return false;
    //如果其中有一个点没有对应上,返回false
    if (pRoot1.val !== pRoot2.val) return false;
    //如果根节点对应的上,那么就分别去子节点里面匹配
    return doesTree1HasTree2(pRoot1.left, pRoot2.left) && doesTree1HasTree2(pRoot1.right, pRoot2.right);
}

数值的整次方

题目描述

给定一个 double 类型的浮点数 base 和 int 类型的整数 exponent。求 base 的 exponent 次方。保证 base 和 exponent 不同时为 0

① 傻瓜解法

function Power(base, exponent) {
    return Math.pow(base, exponent);
}

② 暴力解法

function Power(base, exponent) {
    if (exponent === 0) {
        return 1;
    }
    if (exponent === 1) {
        return base;
    }
    const isNegative = exponent < 0; // 是否是负指数
    const absExponent = Math.abs(exponent);
    let result = base;
    for (let i = 1; i < absExponent; ++i) {
        result = result * base;
    }
 
    return isNegative ? 1 / result : result;
}

③ 推荐解法

看大佬的解法tql

为了理解,假设 base=3,exponent= 5。那么 5 的二进制是:101。所以,3 的 5 次方可以写成下图的格式:

可以看到,对 base 进行自乘,导致 base 的指数每次都扩大 2 倍。与 exponent 的二进制相对应。

以上图为例,整个算法的流程如下:

  • 结果值 result 初始为 1
  • base 初始为 3,此时 exponent 的二进制最右位为 1,更新结果为:base * result
  • exponent 右移一位。base 进行累乘,base 更新为 3 的 2 次方。由于 exponent 的二进制最右位为 0,不更新结果
  • exponent 右移一位。base 进行累乘,base 更新为 3 的 4 次方。此时 exponent 的二进制最右位为 1,更新结果为:base * result
  • over
function Power(base, exponent) {
    if (exponent === 0) {
        return 1;
    }
    if (exponent === 1) {
        return base;
    }

    const isNegative = exponent < 0; // 是否是负指数
    let absExponent = Math.abs(exponent);
    let result = 1;
    while (absExponent) {
        // 如果exponent最右位是1,将当前base累乘到result
        if (absExponent & 1) {
            result = result * base;
        }

        base = base * base; // base自乘法
        absExponent = Math.floor(absExponent / 2); // exponent右移1位
    }

    return isNegative ? 1 / result : result;
}

二叉树的镜像

题目描述

操作给定的二叉树,将其变换为源二叉树的镜像。 就是镜子里面的倒像一样嘿嘿

递归   交换
function Mirror(root) {
        // write code here
        if (root === null) return null;
        if (root.left == null && root.right == null) return null
        var trmp = root.right;
        root.right = root.left;
        root.left = temp;
        if (root.left)
            Mirror(root.left);
        if (root.right)
            Mirror(root.right);
    }

二叉树的后续遍历

题目描述

输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出Yes,否则输出No。假设输入的数组的任意两个数字都互不相同。 `

//1.后续遍历我们可以知道,最右边的是根节点r。
//2.通过根节点r我们可以判断左子树和右子树。
//3.判断左子树中的每个值是否小于r,右子树的每个值是否大于r.
//4.对左、右子树递归判断。
function VerifySquenceOfBST(sequence) {
    if (!sequence.length) return false;
    return judge(sequence, 0, sequence.length - 1)
}

function judge(a, l, r) {
    if (l >= r) return true;
    let i = r;
    while (a[i - 1] > a[r] && i > l) i--;
    for (let j = i - 1; j >= l; j--) {
        if (a[j] > a[r]) return false
    }
    return judge(a, l, i - 1) && judge(a, i, r - 1)
}

重建二叉树

题目描述

输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。

 假设有二叉树如下: 
     1     
   / \
  2   3
 / \
4   5
它的前序遍历的顺序是:1 2 4 5 3。中序遍历的顺序是:4 2 5 1 3
因为前序遍历的第一个元素就是当前二叉树的根节点。那么,这个值就可以将中序遍历分成 2 个部分。在以上面的例子,中序遍历就被分成了 4 2 53 两个部分。4 2 5就是左子树,3就是右子树。
最后,根据左右子树,继续递归即可。
code:
/**
 * @param {TreeNode} pre
 * @param {TreeNode} vin
 * @return {TreeNode}
 */
function reConstructBinaryTree(pre, vin) {
    if (!pre.length || !vin.length) {
        return null;
    }
    const rootVal = pre[0];
    const node = new TreeNode(rootVal);
 
    let i = 0; // i有两个含义,一个是根节点在中序遍历结果中的下标,另一个是当前左子树的节点个数
    for (; i < vin.length; ++i) {
        if (vin[i] === rootVal) {
            break;
        }
    }
 
    node.left = reConstructBinaryTree(pre.slice(1, i + 1), vin.slice(0, i));
    node.right = reConstructBinaryTree(pre.slice(i + 1), vin.slice(i + 1));
    return node;
}

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

题目描述

输入一颗二叉树和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径

function FindPath(root, expectNumber) {
    let list = [];
    let listAll = [];
    return findpath(root, expectNumber, list, listAll);
}

function findpath(root, expectNumber, list, listAll) {
    if (root === null) { return listAll }
    list.push(root.val);
    const x = expectNumber - root.val
    if (root.left == null && root.right == null && x === 0) {
        listAll.push(Array.of(...list));
    }
    findpath(root.left, x, list, listAll)
    findpath(root.right, x, list, listAll)
    list.pop()
    return listAll
}

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

题目描述

数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。

//把数组转换为字符串然后使用一个对象把各个数字出现的次数存起来 在遍历整个对象就可以找出某个数字的最大出现次数 
function MoreThanHalfNum_Solution(numbers) {
    var str = numbers.join('');
    var obj = {};
    for (var i = 0; i < str.length; i++) {
        var char = str.charAt(i);
        if (obj[char]) {
            obj[char]++
        } else {
            obj[char] = 1
        }
    }
    let max = 0;
    var k = '';
    for (var key in obj) {
        // console.log(key);
        if (obj[key] > max) {
            max = obj[key]
            k = key;
        }
    }
    return (max > str.length / 2) ? k - 0 : 0
}

最小的K个数

题目描述

输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4,。

//  我使用了最简单的方法 通过 哈哈  看了题解 红黑树 堆栈 太难了 但是我的复杂度有点高 其实还可以通过冒泡排序巧妙利用每次把最小的数放到前面直到K就不排了 返回  今天太晚了 下次贴上代码
function GetLeastNumbers_Solution(input, k) {
    // write code here
    if (input.length == 0 || k == 0 || input.length < k) {
        return [];
    }
    input.sort((a, b) => {
        return a - b
    })
    return input.slice(0, k)
}

第一个只出现一次的字符

题目描述

在一个字符串(0<=字符串长度<=10000,全部由字母组成)中找到第一个只出现一次的字符,并返回它的位置, 如果没有则返回 -1(需要区分大小写).

// 记住和字符串遍历有关的都可以转换为一个对象 让对象来记录各个字符出现的次数
 function FirstNotRepeatingChar(str) {
   // write code here
   if (str.length < 1 || str.length > 10000)
       return -1;
   const map = {};
   for (let i = 0; i < str.length; i++) {
       if (!map[str[i]]) {
           map[str[i]] = 1
       } else {
           map[str[i]]++
       }
   }
   for (let i = 0; i < str.length; i++) {
       if (map[str[i]] === 1) {
           return i
       }
   }
   return -1;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值