JS版剑指offer
JS刷题总结
牛客网

递归算法的时间复杂度:递归的总次数*每次递归的数量。

递归算法的空间复杂度:递归的深度*每次递归创建变量的个数。

二叉树(12道):

剑指Offer(4):重建二叉树

题目描述

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

题目分析

/* function TreeNode(x) {this.val = x;this.left = null;this.right = null;} */
function reConstructBinaryTree(pre, vin) {// write code hereif (pre.length === 0 || vin.length === 0) {return null;}// 前序第一个是根节点,也是中序左右子树的分割点const index = vin.indexOf(pre[0]),left = vin.slice(0, index),right = vin.slice(index + 1);return {val: pre[0],// 递归左右子树的前序、中序left: reConstructBinaryTree(pre.slice(1, index + 1), left),right: reConstructBinaryTree(pre.slice(index + 1), right)};
}

剑指Offer(17):树的子结构

题目描述

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

题目分析

分析如何判断树B是不是树A的子结构,只需要两步。很容易看出来这是一个递归的过程。一般在树的求解方面都和递归有关。

Step1.在树A中找到和B的根结点的值一样的结点R;

Step2.判断树A中以R为根结点的子树是不是包含和树B一样的结点。

/* function TreeNode(x) {this.val = x;this.left = null;this.right = null;} */
function HasSubtree(pRoot1, pRoot2) {let res = false;if (pRoot1 === null || pRoot2 === null) return false;if (pRoot1.val === pRoot2.val) res = doesTree1HasTree2(pRoot1, pRoot2);if (!res) res = HasSubtree(pRoot1.left, pRoot2);if (!res) res = HasSubtree(pRoot1.right, pRoot2);return res;
}
function doesTree1HasTree2(pRoot1, pRoot2) {if (pRoot2 === null) return true;if (pRoot1 === null) return false;if (pRoot1.val !== pRoot2.val) return false;return doesTree1HasTree2(pRoot1.left, pRoot2.left) && doesTree1HasTree2(pRoot1.right, pRoot2.right);
}

剑指Offer(18):二叉树的镜像

题目描述

操作给定的二叉树,将其变换为源二叉树的镜像。

输入描述:

题目分析

很简单,交换左右节点,递归

/* function TreeNode(x) {this.val = x;this.left = null;this.right = null;} */
function Mirror(root) {if (root === null) return;Mirror(root.left);Mirror(root.right);[root.left, root.right] = [root.right, root.left];return root;
}

剑指Offer(22):从上往下打印二叉树

题目描述

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

题目分析

从下打印就是按层次打印,其实也就是树的广度遍历。

一般来说树的广度遍历用队列,利用先进先出的特点来保存之前节点,并操作之前的节点。

树的深度遍历一般来说用栈或者递归,利用先进后出的特点来保存之前节点,把之前的节点留到后面操作。

1:层序遍历,通过队列的思想,先往队列中放入一个根节点
2:对队列进行while循环,队列的长度是一直在变化的,变化的规律就是每次循环,取出队列头,放入res数组,然后将取出的队列头的左子树和右子树加入队列.

/* function TreeNode(x) {this.val = x;this.left = null;this.right = null;} */
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;
}

剑指Offer(24):二叉树中和为某一值的路径

题目描述

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

题目分析

这题基本上一看就知道应该深度遍历整个树,并且把已经走过的节点的和与期望值作比较就行,如果走到底还不符合要求的话,就要回退值。

function FindPath(root, expectNumber) {// write code hereconst list = [],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;
}

剑指Offer(38):二叉树的深度

题目描述

输入一棵二叉树,求该树的深度。从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。

题目分析

树的深度=左子树的深度和右子树深度中最大者+1

function TreeDepth(pRoot) {if (pRoot === null) return 0;const leftDep = TreeDepth(pRoot.left);const rightDep = TreeDepth(pRoot.right);return Math.max(leftDep, rightDep) + 1;
}

剑指Offer(39):平衡二叉树

题目描述

输入一棵二叉树,判断该二叉树是否是平衡二叉树。

题目分析

第一种方法:

正常思路,应该会获得节点的左子树和右子树的高度,然后比较高度差是否小于1。

可是这样有一个问题,就是节点重复遍历了,影响效率了。

function IsBalanced_Solution(pRoot) {if (pRoot == null) return true;let leftLen = TreeDepth(pRoot.left);let rightLen = TreeDepth(pRoot.right);return Math.abs(rightLen - leftLen) <= 1 && IsBalanced_Solution(pRoot.left) && IsBalanced_Solution(pRoot.right);
}
function TreeDepth(pRoot) {if (pRoot == null) return 0;let leftLen = TreeDepth(pRoot.left);let rightLen = TreeDepth(pRoot.right);return Math.max(leftLen, rightLen) + 1;
}

第二种方法:

改进办法就是在求高度的同时判断是否平衡,如果不平衡就返回-1,否则返回树的高度。

并且当左子树高度为-1时,就没必要去求右子树的高度了,可以直接一路返回到最上层了

function IsBalancedSolution(pRoot) {return TreeDepth(pRoot) !== -1;
}
function TreeDepth(pRoot) {if (pRoot === null) return 0;const leftLen = TreeDepth(pRoot.left);if (leftLen === -1) return -1;const rightLen = TreeDepth(pRoot.right);if (rightLen === -1) return -1;return Math.abs(leftLen - rightLen) > 1 ? -1 : Math.max(leftLen, rightLen) + 1;
}

剑指Offer(57):二叉树的下一个结点

题目描述

给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。

题目分析


1.若该节点存在右子树:则下一个节点为右子树最左子节点(如图节点 B )

2.若该节点不存在右子树:这时分两种情况:

    2.1 该节点为父节点的左子节点,则下一个节点为其父节点(如图节点 D )2.2 该节点为父节点的右子节点,则沿着父节点向上遍历,知道找到一个节点的父节点的左子节点为该节点,则该节点的父节点下一个节点(如图节点 I ,沿着父节点一直向上查找找到 B ( B 为其父节点的左子节点),则 B 的父节点 A 为下一个节点)。

/*function TreeLinkNode(x){this.val = x;this.left = null;this.right = null;this.next = null;
}*/
function GetNext(pNode) {if (pNode === null) {return null;}if (pNode.right !== null) {// 第1种pNode = pNode.right;while (pNode.left !== null) {pNode = pNode.left;}return pNode;}while (pNode.next !== null) {// 第2种if (pNode === pNode.next.left) {return pNode.next;}pNode = pNode.next;}return null;
}

剑指Offer(58):对称的二叉树

题目描述

请实现一个函数,用来判断一颗二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。

function isSymmetrical(pRoot) {if (pRoot === null) {return true;}return compareRoot(pRoot.left, pRoot.right);
}
function compareRoot(left, right) {if (left === null) {return right === null;}if (right === null) {return false;}if (left.val !== right.val) {return false;}return compareRoot(left.left, right.right) && compareRoot(left.right, right.left);
}

剑指Offer(59):按之字顺序打印二叉树

实现思路:广搜的思想,使用两个栈实现,奇数行:从左->右打印,在打印奇数行时,将其子节点按照先左后右的顺序添加到另一个栈中;偶数行:从右->左打印,在打印偶数行时,将其子节点按照先右后左的顺序添加到奇数栈中。

/* function TreeNode(x) {this.val = x;this.left = null;this.right = null;
} */
function Print(pRoot) {const lists = [];if (pRoot === null) {return lists;}const stack1 = [];const stack2 = [];stack2.push(pRoot);let i = 1;while (stack1.length !== 0 || stack2.length !== 0) {const list = [];// 为奇数层if ((i & 1) === 1) {while (stack2.length !== 0) {const tmp = stack2[stack2.length - 1];stack2.pop();list.push(tmp.val);if (tmp.left !== null) stack1.push(tmp.left);if (tmp.right !== null) stack1.push(tmp.right);}}// 为偶数层else {while (stack1.length !== 0) {const tmp = stack1[stack1.length - 1];stack1.pop();list.push(tmp.val);if (tmp.right !== null) stack2.push(tmp.right);if (tmp.left !== null) stack2.push(tmp.left);}}++i;lists.push(list);}return lists;
}

剑指Offer(60):把二叉树打印成多行

题目

从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。

思路

利用队列的特性,并且每一轮都删除queue旧的数据

function Print(pRoot) {// write code hereif (!pRoot) {return []}let result = []let queue = []queue.push(pRoot)while (queue.length > 0) {let len = queue.lengthlet row = []for (let i = 0; i < len; i++) {row.push(queue[i].val)if (queue[i].left) {queue.push(queue[i].left)}if (queue[i].right) {queue.push(queue[i].right)}}queue = queue.slice(len)result.push(row)}return result
}

剑指Offer(61):序列化二叉树

题目描述

请实现两个函数,分别用来序列化和反序列化二叉树

题目分析

首先拿到题目时候,我先想到的是什么是序列化二叉树?序列化主要就是在前后端交互时候需要转换下,毕竟网络传输的是流式数据(二进制或者文本),而不是对象。

所以序列化二叉树就是转化成字符串。
二叉树的反序列化是指:根据某种遍历顺序得到的序列化字符串结果str,重构二叉树。

之前解决重建二叉树问题时候,我们可以知道,两个遍历序列就可以确定一颗二叉树。(比如前序遍历序列和中序遍历序列)。

受此启发,序列化时候我们可以生成一个前序遍历序列和一个中序遍历序列,在反序列化时通过这两个序列重构出原二叉树。

但是当我们细细想下,这个思路有两个个缺点就是:

1.如果二叉树有数值重复的节点,那么必须区分谁是前序遍历序列,谁是后序遍历序列。

2.只有当两个序列所有数据读出后才能开始反序列化。

因此我们可以想,既然是可以边读,边构建二叉树,你是不是想到了自己平时如何构建二叉树的?

我们可以通过深度遍历或者广度遍历序列都行,当然我们最终选择了深度优先遍历,毕竟可以不用额外的空间。

此外还有个技巧就是为了更好地知道遍历某个子树的结束,也就是当我们遍历到null时,我们需要用换位符(比如$)代表,方便反序列化。

此外,我尝试过用字符串做发现不好做,然后转变了下思路,用数组来模拟流,发现就好做了很多。

此外,利用反序列化,我们可以通过数组很快的生成我们想要的二叉树,然后拿去做测试,毕竟一个一个的创建节点,生成二叉树太傻了

const arr = [];
function Serialize(pRoot) {// write code hereif (pRoot === null) {arr.push('a');} else {arr.push(pRoot.val);Serialize(pRoot.left);Serialize(pRoot.right);}
}
function Deserialize() {// write code herelet node = null;if (arr.length < 1) {return null;}const number = arr.shift();if (typeof number === 'number') {node = new TreeNode(number);node.left = Deserialize();node.right = Deserialize();}return node;
}



二叉搜索树(3道):

剑指Offer(23):二叉搜索树的后序遍历序列

题目描述

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

题目分析

二叉搜索树的特点:某一个节点左子树的所有节点的值都比该节点小,右子树的所有节点的值都比该节点大。

后续遍历我们可以知道,最右边的是根节点r。通过根节点r我们可以判断左子树和右子树。

判断左子树中的每个值是否小于r,右子树的每个值是否大于r.

即依次与数组最后一个值比较,一直小于最后一个数的是左子树的,计数,剩下的是右子树的,依次判断剩下的是否全部大于最后一个数,若否,则为false。

使用递归,依次判断每一个子数组是否满足上一个条件

当然我们也可以不用递归,用循环来做,不过需要更高的技巧。

递归版本:

function VerifySquenceOfBST(sequence)
{// write code hereif (sequence.length === 0) return falselet length = sequence.lengthlet root = sequence[length-1]let mid = 0let flag = falsefor (let i = 0; i < length; i++) {if (root < sequence[i]) {mid = iflag = truebreak}}for (let i = mid; i < length; i++){if (root > sequence[i] && flag) {return false}}let left = trueif (mid > 0) {left = VerifySquenceOfBST(sequence.slice(0, mid))}let right = trueif (mid < sequence.length-1){right = VerifySquenceOfBST(sequence.slice(mid, length-1))}return  left && right
}

非递归版本

function VerifySquenceOfBST2(sequence) {let n = sequence.length,i = 0;if (!n) return false;while (n--) {while (sequence[i] < sequence[n]) i++;while (sequence[i] > sequence[n]) i++;if (i < n) return false;i = 0;}return true;
}

剑指Offer(26):二叉搜索树与双向链表(中序遍历)

题目描述

输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。

一次递归(推荐)

二叉搜索树的性质是:左节点 < 当前节点 < 右节点。转换后的双向链表是有序的,这里采用中序递归遍历保证有序性(要生成排序的双向列表,那么只能是中序遍历,因为中序遍历才能从小到大)。

设计的递归函数返回的是:已转换好的双向链表的尾结点,也就是当前节点的 left 指针应该指向的地方。递归函数的实现思路:

检查 left 是否为空,不为空,那么递归调用(传入左子树)
将 left 指针指向已转换好的双向链表的尾结点,并将尾节点的 right 指向当前节点
更新双向链表尾节点(变为当前节点),检查 right 是否为空,不为空,递归调用(传入右子树)
返回转换后的双向链表尾节点

代码实现如下。逻辑在__Convert()函数中,Convert()函数的目的是因为牛客网 oc 条件需要返回转换后链表的首节点。

/* function TreeNode(x) {this.val = x;this.left = null;this.right = null;
} */
function Convert(pRootOfTree) {if (!pRootOfTree) {return null;}__Convert(pRootOfTree, null);let node = pRootOfTree;while (node.left) {node = node.left;}return node;
}function __Convert(pRootOfTree, lastNodeInList = null) {if (!pRootOfTree) {return null;}// step1:左子树if (pRootOfTree.left) {lastNodeInList = __Convert(pRootOfTree.left, lastNodeInList);}// step2:当前节点pRootOfTree.left = lastNodeInList;if (lastNodeInList) {lastNodeInList.right = pRootOfTree;}// step3:右子树lastNodeInList = pRootOfTree;if (pRootOfTree.right) {lastNodeInList = __Convert(pRootOfTree.right, lastNodeInList);}return lastNodeInList;
}

剑指Offer(62):二叉搜索树的第k个结点(中序遍历)

题目描述

给定一棵二叉搜索树,请找出其中的第k小的结点。例如, (5,3,7,2,4,6,8) 中,按结点数值大小顺序第三小结点的值为4。

题目分析

因为这是二叉搜索树,我们可以轻易发现它的中序遍历序列就是从小到大排列,也就是我们可以直接中序遍历,同时计数,就可以得到我们想要的节点了。

二叉搜索树的中序遍历结果是有序的,使用中序遍历,每遍历一个节点,k-1,直到k减到1,即为第K小的节点

不过需要注意的是我们的计数变量k应该在函数外面,不然递归进去后回来时是无法获得已经改变了的k值的。

function KthNode(pRoot, k) {if (pRoot === null || k === 0) {return null;}// 为了能追踪k,应该把KthNodeCore函数定义在这里面,k应该在KthNodeCore函数外面function KthNodeCore(pRoot) {let target = null;if (pRoot.left !== null) {target = KthNodeCore(pRoot.left, k);}if (target === null) {if (k === 1) {target = pRoot;}k--;}if (target === null && pRoot.right !== null) {target = KthNodeCore(pRoot.right, k);}return target;}return KthNodeCore(pRoot);
}

数组(11道):

剑指Offer(1):二维数组中的查找

题目描述

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

题目分析

题目不难,而且给出的限制也很小,我一开始试试用了暴力逐步循环发现也能通过,但是这题目设计的初衷不是简单的让你用暴力循环查找出来,而是要注意是有顺序的。所以我们查找的起始点最好从二维数组中的中间的点开始,不过为了方便,我们一般都是选择最左下角的那个点作为起始点,也就是a[array.lenth][0],比它大就往右边走,比它小就往上面走。话不多说,上代码。

function Find(target, array) {// write code hereconst 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;
}

剑指Offer(6):旋转数组的最小数字

题目描述

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

题目分析

第一种方法:我们发现旋转数组在旋转后,有个分界点,而这个分界点就是最小的那个数。

function minNumberInRotateArray1(rotateArray) {// write code hereif (rotateArray.length === 0) return 0;for (let i = 0; i < rotateArray.length; i++) {if (rotateArray[i] > rotateArray[i + 1]) return rotateArray[i + 1];}return rotateArray[0];
}

第二种方法:分析第一种方法,我们发现时间复杂度为O(n),然而利用二分法的话,时间复杂度是可以做到O(logn)的,不过需要注意边界哦

// eg:[3,4,5,1,2]
function minNumberInRotateArray2(rotateArray) {let left = 0,right = rotateArray.length - 1;   // 4while (right - left > 1) {       let mid= left + (right - left >> 1);    // 2if (rotateArray[mid] > rotateArray[right]) {left = mid;} else {right = mid;}}return Math.min(rotateArray[left], rotateArray[right]);
}

剑指offer(13)调整数组顺序使奇数位于偶数前面 (简单 新建两个数组分别存放奇数和偶数,两个变量作为奇数和偶数的下标)

题目描述:

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

解法1:

这题算是简单题目,新建两个数组,分别用来存放奇数和偶数;将偶数的数组concat到奇数数组后面解决

 function reOrderArray(array) {var odd = [];var even = [];for (var i = 0; i < array.length; i++) {if ((array[i] % 2) === 0) {even.push(array[i]);} else {odd.push(array[i])}}return odd.concat(even);}

解法2:

判断是否为奇数,统计奇数个数,然后新建数组,把所有奇数存进去数组前面,剩下的存进去数组后面。

function reOrderArray(array) {// oddBegin主要是用作奇数的索引,oddCount是用作偶数的索引,newArray用来存储,以空间换时间,复杂度为O(n)let oddBegin = 0,oddCount = 0;const newArray = [];for (let i = 0; i < array.length; i++) {if (array[i] & 1) {oddCount++;}}for (let i = 0; i < array.length; i++) {if (array[i] & 1) {newArray[oddBegin++] = array[i];} else {newArray[oddCount++] = array[i];}}return newArray;

剑指Offer(28):数组中出现次数超过一半的数字(中等 快排思想中partion法 || times变量变化法 || map记录)

题目描述

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

题目分析

这题也有两种做法:

第一种:基于快排思想中的partition函数来做,因为根据题目,那么排序后的数组中间的数就是那个出现次数超过一半的数,那么我只需要利用快排中的partition,找到数组中间的那个数就行。

类似于之前写的查找第K大的数,只不过现在的K值为数组长度的一半

快速排序会递归地进行很多轮,其中每一轮称之为快排的partition算法,下图为第一轮的partition算法的一个示例:

function MoreThanHalfNumSolution(numbers) {const left = 0,right = numbers.length - 1;let key = partition(numbers, left, right);const mid = numbers.length >> 1;while (key !== mid) {if (key > mid) {key = partition(numbers, left, key - 1);} else {key = partition(numbers, key + 1, right);}}let res = numbers[mid];if (!checkMoreThanHalf(numbers, res)) {res = 0;}return res;
}
function partition(a, left, right) {const key = a[left]; // 一开始让key为第一个数while (left < right) {// 扫描一遍while (key <= a[right] && left < right) {// 如果key小于a[right],则right递减,继续比较right--;}[a[left], a[right]] = [a[right], a[left]]; // 交换while (key >= a[left] && left < right) {// 如果key大于a[left],则left递增,继续比较left++;}[a[left], a[right]] = [a[right], a[left]]; // 交换}return left; // 把key现在所在的下标返回
}
function checkMoreThanHalf(numbers, num) {let times = 0;for (let i = 0; i < numbers.length; i++) {if (num === numbers[i]) {times++;}}if (times * 2 <= numbers.length) {return false;}return true;
}

第二种:根据数组特点来做,数组中有一个数字出现的次数超过数组长度的一半,也就是说它出现的次数比其他所有数字出现的次数的和还要多,那么就可以从这里下手啦,具体看代码。

第一种要修改数组,第二种不需要修改数组。除此之外,注意检查是不是符合要求。

function MoreThanHalfNum_Solution(numbers) {let times = 0let result = numbers[0]for (let i = 1; i < numbers.length; i++) {if (times === 0) {result = numbers[i]times = 1continue}if (numbers[i] !== result) {times--} else {times++}}return checkMorethanHalf(numbers, result) ? result : 0
}function checkMorethanHalf(arr, num) {let counts = 0for (let i = 0; i < arr.length; i++) {if (arr[i] === num) {counts++}}if (counts > Math.floor(arr.length / 2)) {return true}return false
}

第三种:
使用哈希Map的键存放数组的元素
使用哈希Map的值存放该元素出现的次数
找出出现次数大于长度一半的元素,返回即可

var majorityElement = function(nums) {// 存储数组的长度的一半let len = nums.length/2;const m = new Map();for (let v of nums) {if (m.has(v)) {m.set(v,m.get(v)+1);} else {m.set(v,1);}};for (let v of m) {if (v[1] > len) {return v[0];}}
};

剑指Offer(30):连续子数组的最大和

题目描述

输入一个整型数组,数组里有正数也有负数。数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。例如:{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为8(从第0个开始,到第3个为止)。你会不会被他忽悠住?(子向量的长度至少是1)

题目分析

可以设定两个数组,其中一个通过不断相加与原数组进行比较,进而得出最大值。

function FindGreatestSumOfSubArray(array){if(array.length == 0){return [];}if(array.length == 1){return array[1]}let max = array[0];let res = array[0];//从array[1]开始,不断相加,与原值相比for(let i = 1 ; i < array.length ; i++){max = Math.max(max + array[i] , array[i]);res = Math.max(max,res);}return res;
}

剑指Offer(32):把数组排成最小的数

题目描述

输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。

题目分析

首先将字符串进行排序,将它们两两拼接起来,比较a+b和b+a哪个大,如果a+b>b+a,那就应该将b放在a的前面,a排在b的后面,依次类推

function PrintMinNumber(numbers) {var result = "";for(let i = 0; i < numbers.length; i++){for(let j = i+1; j < numbers.length; j++){let s1 = numbers[i] + '' + numbers[j]let s2 = numbers[j] + '' + numbers[i]if(s1 > s2){let temp = numbers[i]numbers[i] = numbers[j]numbers[j] = temp}}}for (let m = 0; m < numbers.length; m++) {result = result + numbers[m];}return result;
}

剑指Offer(35):数组中的逆序对(难+ 基于归并排序、临时数组)

题目描述

在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P%1000000007

输入:题目保证输入的数组中没有的相同的数字

数据范围:
• 对于%50的数据,size<=10^4
• 对于%75的数据,size<=10^5
• 对于%100的数据,size<=2*10^5

题目分析

第一反应是采用暴力解法,不过肯定会超时,所以我们需要用时间复杂度更低的解法.

时间复杂度是O(N^2)。

var reversePairs = function(nums) {let res = 0;const length = nums.length;for (let i = 0; i < length; ++i) {for (let j = i + 1; j < length; ++j) {nums[i] > nums[j] && ++res;}}return res;
};

说实话这道题有点难,我也是参考剑指offer上的。那么难点在哪呢?
• 难点一:要想到基于归并排序去解决。
• 难点二:参数的问题,这里很巧妙地用了一个copy数组作为data参数。
• 难点三:合并时,count如何计数。

不过只要注意这些小细节,多花点时间想明白还是能做出来的

归并排序:
(1) 归并排序的流程

(2) 合并两个有序数组的流程

function InversePairs(data) {if (!data || data.length < 2) return 0;const copy = data.slice();let count = 0;count = mergeCount(data, copy, 0, data.length - 1);return count % 1000000007;
}
function mergeCount(data, copy, start, end) {if (start === end) return 0;const mid = end - start >> 1,left = mergeCount(copy, data, start, start + mid), // 注意参数,copy作为data传入right = mergeCount(copy, data, start + mid + 1, end); // 注意参数,copy作为data传入let [p, q, count, copyIndex] = [start + mid, end, 0, end];while (p >= start && q >= start + mid + 1) {if (data[p] > data[q]) {copy[copyIndex--] = data[p--];count = count + q - start - mid;} else {copy[copyIndex--] = data[q--];}}while (p >= start) {copy[copyIndex--] = data[p--];}while (q >= start + mid + 1) {copy[copyIndex--] = data[q--];}return count + left + right;
}

剑指Offer(37):数字在排序数组中出现的次数

题目描述

统计一个数字在排序数组中出现的次数。

思路分析1
这个思路和做法比较简单粗暴。由于是排序数组,所以相同的数字肯定是排在一起的。所以用两个循环,一个从头找一个从尾找,找到第一次出现的位置和最后一次出现的位置。再相减即可。

function GetNumberOfK(data, k)
{// write code herevar start = -1var end = -1for(var i = 0;i < data.length; i++){if(data[i]==k){start = i;break}}for(var j = data.length-1; j>-1; j--){if(data[j]==k){end = jbreak}}if(start != -1 && end != -1){return end - start + 1;}else{return 0}
}

思路分析2

看到有序数组,就要想到二分法。但是与传统的二分法不同,这里同一个值可能出现多次,而我们要找到他出现的第一次和最后一次。所以要多进行一步判断。(注释中有说明)

function GetNumberOfK(data, k){// write code hereif(data.length == 0){return 0;}var first = theFirst(data,0,data.length-1,k)var last = theLast(data,first-1,data.length-1,k)if(first!=-1 && last!=-1){return last-first+1;}else{return 0;}}//找到第一次出现K的位置function theFirst(data,start,end,k){if(start>end){return -1;}var mid = parseInt((start+end)/2)if(data[mid]>k){end = mid - 1;return theFirst(data,start,end,k)}else if(data[mid]<k){start = mid + 1return theFirst(data,start,end,k)}//还要多加一个判断如果mid-1也为k的话,说明第一次出现的位置还更小。else if((mid-1>=0) && (data[mid-1]==k)){return theFirst(data,start,mid-1,k)}else{return mid;}}//找到最后一次出现K的位置function theLast(data,start,end,k){if(start>end){return -1;}var mid = parseInt((start+end)/2)if(data[mid]>k){end = mid - 1;return theLast(data,start,end,k)}else if(data[mid]<k){start = mid + 1return theLast(data,start,end,k)}//还要多加一个判断如果mid+1也为k的话,说明最后一次出现的位置还更大。else if((mid+1<data.length) && (data[mid+1]==k)){return theLast(data,mid+1,end,k)}else{return mid;}}

剑指Offer(40):数组中只出现一次的数字

题目描述

一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。

题目分析

第一种方法:使用js中的indexOf()和lastIndexOf(),只要两个相等,就是只出现一次的数。

function FindNumsAppearOnce(array) {const res = [];for (let i = 0; i < array.length; i++) {if (array.indexOf(array[i]) === array.lastIndexOf(array[i])) {res.push(array[i]);}}return res;
}

第二种方法:使用map记录下每个数的次数,占空间。

function FindNumsAppearOnce2(array) {const map = {},res = [];for (let i = 0; i < array.length; i++) {if (!map[array[i]]) {map[array[i]] = 1;} else {map[array[i]]++;}}for (let i = 0; i < array.length; i++) {if (map[array[i]] === 1) {res.push(array[i]);}}return res;
}

剑指Offer(50):数组中重复的数字

解法一:

用额外的数组或者hash记录,只需要知道出现次数大于1就行了,不需要知道所有数出现次数

function duplicate(numbers) {const map = {};for (let i = 0; i < numbers.length; i++) {if (!map[numbers[i]]) {map[numbers[i]] = 1;} else {return numbers[i];}}return -1;
}

解法二:

题目里写了数组里数字的范围保证在0 ~ n-1 之间,所以可以利用现有数组设置标志,当一个数字被访问过后,可以设置对应位上的数 + n,之后再遇到相同的数时,会发现对应位上的数已经大于等于n了,那么直接返回这个数即可。

function duplicate(numbers, duplication) {for (let i = 0; i < numbers.length; i++) {let index = numbers[i];if (index >= numbers.length) {index -= numbers.length;}if (numbers[index] >= numbers.length) {return index;}numbers[index] = numbers[index] + numbers.length;}return -1;
}

剑指Offer(51):构建乘积数组

题目描述

给定一个数组A[0,1,…,n-1],请构建一个数组B[0,1,…,n-1],其中B中的元素B[i]=A[0]A[1]…*A[i-1]A[i+1]…*A[n-1]。不能使用除法。

function multiply(array) {const B = [],len = array.length;B[0] = 1;// 计算前i - 1个元素的乘积for (let i = 1; i < len; i++) {B[i] = array[i - 1] * B[i - 1];}let tmp = 1;// 计算后N - i个元素的乘积并连接for (let i = len - 2; i >= 0; i--) {tmp *= array[i + 1];B[i] *= tmp;}return B;
}

字符串(8道):

剑指Offer(2):替换空格

题目描述

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

题目分析

我们如果要替换空格,两步:1先知道空格的位置,2替换,但是字符串中有多个空格,所以我们就要循环,替换完之后再去查找字符串空格位置

当然你也可以选择用正则

function replaceSpace(str) {return str.replace(/\s/g, '%20');
}

剑指Offer(27):字符串的排列(递归全排列法、回溯法)

题目描述

输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。

输入描述:输入一个字符串,长度不超过9(可能有字符重复),字符只包括大小写字母。

题目分析

这题还算可以,关于全排列,有两种解法,第一种就是递归全排列法,第二种就是回溯法。

递归全排列法:
就是剑指offer上的做法,也比较容易理解,不过挺少人答的也就是
1.把字符串分为两部分:第一部分为第一个字符,第二部分为第一个字符以后的字符串。
2.然后接下来求后面那部分的全排列。
3.再将第一个字符与后面的那部分字符逐个交换

function Permutation2(str) {let res = [];if (str.length <= 0) return res;arr = str.split(''); // 将字符串转化为字符数组res = permutate2(arr, 0, res);res = [...new Set(res)]; // 去重res.sort(); // 排序return res;
}
function permutate2(arr, index, res) {if (arr.length === index) {return res.push(arr.join(''));}for (let i = index; i < arr.length; i++) {[arr[index], arr[i]] = [arr[i], arr[index]]; // 交换permutate2(arr, index + 1, res);[arr[index], arr[i]] = [arr[i], arr[index]]; // 交换}return res;
}

回溯法
也就是利用树去尝试不同的可能性,不断地去字符串数组里面拿一个字符出来拼接字符串,当字符串数组被拿空时,就把结果添加进结果数组里,然后回溯上一层。(通过往数组加回去字符以及拼接的字符串减少一个来回溯。)

function Permutation(str) {let res = [];const pStr = '';if (str.length <= 0) return res;arr = str.split(''); // 将字符串转化为字符数组res = permutate(arr, pStr, res);return res;
}
function permutate(arr, pStr, res) {if (arr.length === 0) {return res.push(pStr);}const isRepeated = new Set();for (let i = 0; i < arr.length; i++) {if (!isRepeated.has(arr[i])) {// 避免相同的字符交换const char = arr.splice(i, 1)[0];pStr += char;permutate(arr, pStr, res);arr.splice(i, 0, char); // 恢复字符串,回溯pStr = pStr.slice(0, pStr.length - 1); // 回溯isRepeated.add(char);}}return res;
}

剑指Offer(34):第一个只出现一次的字符

题目描述

在一个字符串(1<=字符串长度<=10000,全部由字母组成)中找到第一个只出现一次的字符,并返回它的位置

题目分析

只需要用map记录字符出现的次数就行,比较简单的题

function FirstNotRepeatingChar(str) {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;
}

剑指Offer(43):左旋转字符串

题目描述

汇编语言中有一种移位指令叫做循环左移(ROL),现在有个简单的任务,就是用字符串模拟这个指令的运算结果。对于一个给定的字符序列S,请你把其循环左移K位后的序列输出。例如,字符序列S=”abcXYZdef”,要求输出循环左移3位后的结果,即“XYZdefabc”。

function LeftRotateString(str, n) {if (str === null || str.length === 0) return '';return str.slice(n) + str.slice(0, n);
}

剑指Offer(44):翻转单词顺序序列

例如,“student. a am I”翻转成“I am a student.”

function ReverseSentence(str) {return str.split(' ').reverse().join(' ');
}

剑指Offer(49):把字符串转换成整数

题目描述

将一个字符串转换成一个整数,要求不能使用字符串转换整数的库函数。 数值为0或者字符串不是一个合法的数值则返回0

输入描述:

输入一个字符串,包括数字字母符号,可以为空

输出描述:

如果是合法的数值表达则返回该数字,否则返回0

题目分析
其实就是实现让你实现一个类似Number() 或者 parseInt()的函数

1、边界条件:字符串不存在或空,要return 0
2、判断首位:不为 + - 数字的,直接return 0,若是正负位则要标记
3、判断其余位:不是数字字符直接return 0(通过charCodeAt(0)判断)
4、数字字符串要转换为数字

function StrToInt(str)
{// write code here// 边界条件if (!str||str=='-0'||str=='-') {return 0}// 正常情况let plus = truelet result = ''// 首位判断if (str[0] === '+') {result += ''} else if (str[0] === '-') {result += ''plus = false} else if (str[0].charCodeAt(0) >= 48 && str[0].charCodeAt(0) <= 57) {result += str[0]} else {return 0}// 其余为只要不是数字字符就返回0for (let i = 1; i < str.length; i++) {if (str[i].charCodeAt(0) >= 48 && str[i].charCodeAt(0) <= 57) {result += str[i]} else {return 0}}// 转成数字let front = 1let sum = 0for (let j = result.length - 1; j >= 0; j--) {let current = (result[j].charCodeAt(0) - 48) * frontfront *= 10sum += current}return plus ? sum : -sum
}

剑指Offer(52):正则表达式匹配

题目描述

请实现一个函数用来匹配包括’.‘和’‘的正则表达式。模式中的字符’.‘表示任意一个字符,而’'表示它前面的字符可以出现任意次(包含0次)。 在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串"aaa"与模式"a.a"和"abaca"匹配,但是与"aa.a"和"ab*a"均不匹配

题目分析

说实话这道题有点像编译原理中的状态机,我们做的时候可以结合 画图 来做。

第一种方法是我们比较容易想到的就是用js的原生方法,这个比较简单,不过这题显然是考我们如何去实现原生的方法,也就是第二种方法。

function match(s, pattern) {const reg = new RegExp(`^${pattern}$`);return reg.test(s);
}

第二种方法说实话还是比较难的,因为情况比较复杂,需要理清思路。不过首先我们想到的是这题肯定需要用递归,也就是关键就是如何写这个递归函数,以及需要哪些参数!

理清思路:
当模式中的第二个字符不是“*”时:

如果字符串第一个字符和模式中的第一个字符相匹配,那么字符串和模式都后移一个字符,然后匹配剩余的。
如果 字符串第一个字符和模式中的第一个字符相不匹配,直接返回false。
而当模式中的第二个字符是“*”时:
如果字符串第一个字符跟模式第一个字符不匹配,则模式后移2个字符,继续匹配。如果字符串第一个字符跟模式第一个字符匹配,可以有3种匹配方式:

模式后移2字符,相当于x被忽略;
字符串后移1字符,模式后移2字符;
字符串后移1字符,模式不变,即继续匹配字符下一位,因为
可以匹配多位

function match(s, pattern) {// write code hereif (s == null || pattern == null) {return false}return checkMatch(s, pattern, 0, 0)
}function checkMatch(s, pattern, i, j) {if (i === s.length && j === pattern.length) {return true}if (j === pattern.length && i !== s.length) {return false}// 第二个符号为 *if (pattern[j + 1] && pattern[j + 1] === '*') {// 第一个字符匹配if (s[i] === pattern[j] || (pattern[j] === '.' && i !== s.length)) {return checkMatch(s, pattern, i, j + 2) || checkMatch(s, pattern, i + 1, j) || checkMatch(s, pattern, i + 1, j + 2)// 第一个字符不匹配} else {return checkMatch(s, pattern, i, j + 2)}}if (pattern[j] === s[i] || (pattern[j] === '.' && i !== s.length)) {return checkMatch(s, pattern, i + 1, j + 1)}return false
}

剑指Offer(53):表示数值的字符串(正则)

题目描述

请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串"+100",“5e2”,"-123",“3.1416"和”-1E-16"都表示数值。 但是"12e",“1a3.14”,“1.2.3”,"±5"和"12e+4.3"都不是。

题目分析
正则:

*代表匹配零次或多次

+代表匹配一次或多次

?代表匹配零次或一次

$匹配输入字符串的结束位置

\.代表小数点

\s代表空格,则\s*代表匹配多个空格

\d代表匹配一个数字字符,等价于[0-9],则\d+\.\d可匹配1.或1.0等\d\.\d+可匹配.0或1.0等

[±]代表匹配包含的任一字符+或-,[±]?则说明+或-可有可无,例如0e+4和0e4是一样的,都是0.0

注意若最后不加上$,则测试样例0e不通过,匹配结果是0,加上$则匹配最后的子表达式([eE][±]?\d+)?\s*

例如:’(\d+[eE]\d+)?‘和’(\d+[eE]\d+)?$’,匹配0e,前者为’0’,后者为None

function isNumeric(s) {return s.match(/[+-]?\d*(\.\d*)?([eE][+-]?\d+)?/g)[0] === s;
}
function isNumeric2(s) {return s.search(/^[+-]?\d*(\.\d*)?$/) === 0 || s.search(/^[+-]?\d+(\.\d*)?[Ee]{1}[+-]?\d+$/) === 0;
}

实现全排列

给定一个字符串,输出该字符串所有排列的可能。如输入“abc”,输出“abc,acb,bca,bac,cab,cba”。

遍历字符串的每一个元素,并将字符串中除了该元素的其他元素进行全排列,如’abc’,拿到a后,将bc再次进行全排列,返回的排列好的数组每一项再与a组合在一起得到最终的abc、acb;

function fullpermutate(str) {var result = [];if (str.length > 1) {//遍历每一项for (var m = 0; m < str.length; m++) {//拿到当前的元素var left = str[m];//除当前元素的其他元素组合var rest = str.slice(0, m) + str.slice(m + 1, str.length);//上一次递归返回的全排列var preResult = fullpermutate(rest);//组合在一起for (var i = 0; i < preResult.length; i++) {var tmp = left + preResult[i]result.push(tmp);}}} else if (str.length == 1) {result.push(str);}return result;
}

栈(3道):

剑指Offer(5):用两个栈实现队列

题目描述

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

题目分析

栈是先进后出,队列是先进先出,因此两个栈,一个用来push,一个用来pop,同时注意下两个栈不为空的时候。

const outStack = [],inStack = [];
function push(node) {// write code hereinStack.push(node);
}
function pop() {// write code hereif (!outStack.length) {while (inStack.length) {outStack.push(inStack.pop());}}return outStack.pop();
}

剑指Offer(20):包含min函数的栈

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

var stack = [];function push(node) {stack.push(node);
}function pop() {return stack.pop();
}function top() {return stack[stack.length - 1];
}function min() {return Math.min.apply(this, stack);
}

剑指Offer(21):栈的压入、弹出序列

题目描述

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

题目分析

主要就是理解栈是先进后出。通过画图一步一步分析,就可以知道需要个辅助栈帮我们临时储存已经压入栈中的值。

function IsPopOrder(pushV, popV) {// write code hereconst helpStack = [];let flag = false;while (pushV.length || helpStack.length) {while (helpStack[helpStack.length - 1] === popV[0] && helpStack.length) {helpStack.pop();popV.shift();}if (!popV.length) {flag = true;}if (!pushV.length) {break;}helpStack.push(pushV.shift());}return flag;
}

递归(4道):

剑指Offer(七):裴波那契数列

题目描述

大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项。

n<=39

题目分析

我们都知道斐波那契可以用递归,但是递归重复计算的部分太多了(虽然可以通过),但是这道题更应该用动态规划来做,

动态规划的特点是:最优子结构、无后效性、子问题重叠。

function Fibonacci(n) {// write code here、let f = 0,g = 1;while (n--) {g += f;f = g - f;}return f;
}

剑指Offer(八、九):跳台阶

题目描述

一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法。

题目分析

一:

题目很简单,稍微分析就知道这是斐波那契数列,所以可以动态规划来做

a.如果两种跳法,1阶或者2阶,那么假定第一次跳的是一阶,那么剩下的是n-1个台阶,跳法是f(n-1);

b.假定第一次跳的是2阶,那么剩下的是n-2个台阶,跳法是f(n-2)

c.由a\b假设可以得出总跳法为: f(n) = f(n-1) + f(n-2)

d.然后通过实际的情况可以得出:只有一阶的时候 f(1) = 1 ,只有两阶的时候可以有 f(2) = 2

e.可以发现最终得出的是一个斐波那契数列

二:

青蛙只跳1或2可以得出是一个斐波那契问题,即a[n]=a[n-1]+a[n-2],那么能跳1,2,3个台阶时a[n]=a[n-1]+a[n-2]+a[n-3],…

那么有:

a[n]=a[n-1]+a[n-2]+…+a[1];…①

a[n-1]= a[n-2]+…+a[1];…②

两式相减可知:a[n]=2*a[n-1];

所以编程厉害不厉害除了练,数学一定要好(知道真相的我眼泪掉下来)

一:

function jumpFloor(number) {// write code herelet f = 1,g = 2;while (--number) {g += f;f = g - f;}return f;
}

二:

function jumpFloorII(number) {let i = 1;while (--number) {i *= 2;}return i;
}

三:

function jumpFloor(number)
{if(number === 1){return 1}if(number === 2){return 2}if(number > 2){return jumpFloor(number-1)+jumpFloor(number-2)}
}

剑指Offer(十):矩形覆盖

题目描述

我们可以用21的小矩形横着或者竖着去覆盖更大的矩形。请问用n个21的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法?

题目分析

当然也可以逆向思维

因为可以横着放或竖着放,所以f(n)可以是2*(n-1)的矩形加一个竖着放的21的矩形或2(n-2)的矩形加2横着放的,即f(n)=f(n-1)+f(n-2)

当到了最后,f(1)=1,f(2)=2

function rectCover(number) {// write code hereif (number === 0) return 0;let f = 1,g = 2;while (--number) {g += f;f = g - f;}return f;
}

回溯法(2道):

剑指Offer(65):矩阵中的路径

题目描述

请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则该路径不能再进入该格子。 例如 a b c e s f c s a d e e 矩阵中包含一条字符串"bcced"的路径,但是矩阵中不包含"abcb"路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入该格子。

题目分析

分析题意,我们可以知道这道题用回溯法解决最好,因为我们需要一个一个字母的匹配,当发现不行时,就要回溯上个步骤,选择另一步。

回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。

回溯法的基本思想:

在包含问题的所有解的解空间树中,按照深度优先搜索的策略,从根结点出发深度探索解空间树。当探索到某一结点时,要先判断该结点是否包含问题的解,如果包含,就从该结点出发继续探索下去,如果该结点不包含问题的解,则逐层向其祖先结点回溯。(其实回溯法就是对隐式图的深度优先搜索算法)。

若用回溯法求问题的所有解时,要回溯到根,且根结点的所有可行的子树都要已被搜索遍才结束。

而若使用回溯法求任一个解时,只要搜索到问题的一个解就可以结束。

字典树也有点和回溯法的解空间树类似。

function hasPath(matrix, rows, cols, path) {const pathLength = 0;const visited = new Array(rows * cols);for (let row = 0; row < rows; row++) {for (let col = 0; col < cols; col++) {// 遍历,遍历的点为起点。if (hasPathCore(matrix, rows, cols, row, col, path, pathLength, visited)) {return true;}}}return false;
}
function hasPathCore(matrix, rows, cols, row, col, path, pathLength, visited) {let hasPath = false;if (pathLength === path.length) return true;if (row >= 0 &&row < rows &&col >= 0 &&col < cols &&matrix[row * cols + col] === path[pathLength] &&!visited[row * cols + col]) {++pathLength;visited[row * cols + col] = true;// 因为||为短路运算符,只要第一个满足就会返回,而不会去计算后面的,所以有些路径可以不用去走。hasPath =hasPathCore(matrix, rows, cols, row - 1, col, path, pathLength, visited) ||hasPathCore(matrix, rows, cols, row, col - 1, path, pathLength, visited) ||hasPathCore(matrix, rows, cols, row + 1, col, path, pathLength, visited) ||hasPathCore(matrix, rows, cols, row, col + 1, path, pathLength, visited);if (!hasPath) {--pathLength;visited[row * cols + col] = false;}}return hasPath;
}

剑指Offer(66):机器人的运动范围

题目描述

地上有一个m行和n列的方格。一个机器人从坐标0,0的格子开始移动,每一次只能向左,右,上,下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于k的格子。 例如,当k为18时,机器人能够进入方格(35,37),因为3+5+3+7 = 18。但是,它不能进入方格(35,38),因为3+5+3+8 = 19。请问该机器人能够达到多少个格子?

题目分析

这道题和之前的矩阵中的路径一样都是用回溯法,掌握回溯法后就会觉得比较简单。

回溯法和暴力法有点类似,不过他会用数组或变量去记录已经遍历过的解,避免重复遍历,从而减少了计算量。

function movingCount(threshold, rows, cols) {const visited = [];for (let i = 0; i < rows; i++) {visited.push([]);for (let j = 0; j < cols; j++) {visited[i][j] = false;}}return move(0, 0, rows, cols, visited, threshold);
}
function move(i, j, rows, cols, visited, threshold) {if (i < 0 || i === rows || j < 0 || j === cols || visited[i][j]) {return 0;}let sum = 0;const tmp = `${i}${j}`;for (let k = 0; k < tmp.length; k++) {sum += tmp.charAt(k) / 1; // 转成数字}if (sum > threshold) {return 0;}visited[i][j] = true;return (1 +move(i + 1, j, rows, cols, visited, threshold) +move(i, j + 1, rows, cols, visited, threshold) +move(i - 1, j, rows, cols, visited, threshold) +move(i, j - 1, rows, cols, visited, threshold));
}

其他(15道):

剑指Offer(12):数值的整数次方

题目描述

给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。

const Power = (base, exponent) => Math.pow(base, exponent);

剑指Offer(19):顺时针打印矩阵

题目描述

输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下矩阵: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 则依次打印出数字1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10.

题目分析

首先我们需要把这个复杂的问题分解为简单的问题,打印矩阵也就是一圈一圈的打印出来,所以我们需要解决的问题就是:

1.什么时候该继续打印下一圈即里面那一圈,也就是要找到循环的条件。

2.一圈该如何打印。

那么什么是循环结束的条件呢?也就是我们需要思考从第一圈到第二圈的条件是什么或者说他们有什么共同的特征。我们注意4X4的矩阵,只有两圈,到从第一圈到第二圈,起点从(0,0)变为了(1,1),我们发现4>12,类似的对于一个5X5的矩阵而言,最后一圈只有一个数字,起点坐标为(2,2),满足5>22,同理对于6X6的矩阵也是类似。故可以得出循环的条件就是columns>startX2并且rows>startY2。

然后我们就要实现打印一圈,再分解也就是从左到右,从上到下,从右到左,从下到上。不过要注意下边界条件,也就是最后一圈构成不了一个完整的圈的一些条件,找到了就很好写出代码了。

function printMatrix(matrix) {if (matrix === null) return null;const rows = matrix.length,cols = matrix[0].length;let start = 0,res = [];while (rows > start * 2 && cols > start * 2) {res = res.concat(printMatrixInCircle(matrix, rows, cols, start));start++;}return res;
}
function printMatrixInCircle(matrix, rows, cols, start) {const endX = cols - 1 - start,endY = rows - 1 - start,res = [];for (let i = start; i <= endX; i++) {res.push(matrix[start][i]);}for (let i = start + 1; i <= endY; i++) {res.push(matrix[i][endX]);}for (let i = endX - 1; i >= start && endY > start; i--) {res.push(matrix[endY][i]);}for (let i = endY - 1; i >= start + 1 && endX > start; i--) {res.push(matrix[i][start]);}return res;
}

剑指Offer(29):最小的K个数

题目描述

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

题目分析

基于partition的方法

function GetLeastNumbersSolution(input, k) {if (input.length === 0 || k > input.length || k < 1) return [];const left = 0,right = input.length - 1;let key = partition(input, left, right);while (key !== k - 1) {if (key > k - 1) {key = partition(input, left, key - 1);} else {key = partition(input, key + 1, right);}}const res = input.slice(0, key + 1);return res;
}
function partition(a, left, right) {const key = a[left]; // 一开始让key为第一个数while (left < right) {// 扫描一遍while (key <= a[right] && left < right) {// 如果key小于a[right],则right递减,继续比较right--;}[a[left], a[right]] = [a[right], a[left]]; // 交换while (key >= a[left] && left < right) {// 如果key大于a[left],则left递增,继续比较left++;}[a[left], a[right]] = [a[right], a[left]]; // 交换}return left; // 把key现在所在的下标返回
}

剑指Offer(31):整数中1出现的次数(从1到n整数中1出现的次数)

题目描述
求出1 ~ 13的整数中1出现的次数,并算出100 ~ 1300的整数中1出现的次数?为此他特别数了一下1~13中包含1的数字有1、10、11、12、13因此共出现6次,但是对于后面问题他就没辙了。ACMer希望你们帮帮他,并把问题更加普遍化,可以很快的求出任意非负整数区间中1出现的次数(从1 到 n 中1出现的次数)。

解题思路
将每一位上1出现的次数加起来,就是所求的总次数了。

我们以百位为例子,在 12x45 中,百位为 x ,那么百位前的数字为 12,百位后的数字为 45。此时分为3种情况:

x == 0,这时候后面的数字对百位上1的出现次数是没有影响的,只受前面数字的影响,即: 12 * 100,100为百位的位数。
x == 1,此时既受前面数字的影响也受后面数字的影响,因为在 12 * 100后,1又出现了后面数字+1那么多次(从 12100到12145 ),即 12 * 100 + 45 + 1。
x > 1,此时因为必然包含 12100-12199 共100(百位的位数)个1,所以百位上1出现的次数也与后面的数字没有关系,为 12 * 100 + 100 即 (12 + 1) * 100。

function NumberOf1Between1AndN_Solution(n)
{// write code herevar count = 0;var i = 1;var pre = 0, back = 0, cur = 0;while(n >= i){pre = parseInt(n / (i * 10));back = n - parseInt(n / i) * i;cur = parseInt(n / i) % 10;if(cur == 0){count += pre * i;}else if(cur == 1){count += pre * i + back + 1;}else{count += (pre + 1) * i}i *= 10;}return count;
}

剑指Offer(33):丑数

题目描述

把只包含因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含因子7。 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。

题目分析

主要在于理解丑数的概念,只包含因子2、3和5的数称作丑数,那么我们可以先把因子2、3和5分离出来,那么剩下的就是其他因子,看是否为1,为1的话说明没有其他因子,那就为丑数。不是1的话说明有其他因子,那么就不是丑数。

第一种暴力解法,缺点是连非丑数的也计算,会超时。

第二种用到了动态规划的思想,把前面的丑数存着,生成后面的丑数。t2,t3,t5是判断点,用于判断从何处开始选出并乘以对应因子肯定会大于当前数组中最大丑数,而前面的丑数不用考虑。
:(以空间换时间)

function GetUglyNumberSolution(index) {if (index < 7) return index;const res = [];res[0] = 1;let t2 = 0,t3 = 0,t5 = 0;for (let i = 1; i < index; i++) {res[i] = Math.min(res[t2] * 2, 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];
}

剑指Offer(41):和为S的连续正数序列

题目描述
小明很喜欢数学,有一天他在做数学作业时,要求计算出9~16的和,他马上就写出了正确答案是100。但是他并不满足于此,他在想究竟有多少种连续的正数序列的和为100(至少包括两个数)。没多久,他就得到另一组连续正数和为100的序列:18,19,20,21,22。现在把问题交给你,你能不能也很快的找出所有和为S的连续正数序列? Good Luck!

输出描述:
输出所有和为S的连续正数序列。序列内按照从小至大的顺序,序列间按照开始数字从小到大的顺序

解析思路:
题中序列为差是1 的等差数列,等差数列求和公式为 Sn = (A1 + An) * n / 2 或者 Sn = nA1 + n(n - 1) * d / 2

function FindContinuousSequence(sum)
{// write code herevar left = 1,right = 2,resFin = [],cur;while(left < right){cur = (left + right) * (right - left + 1) / 2;if(cur < sum ){right++;}if(cur > sum){left++;}if(cur == sum){var res = [];for(var i = left; i <= right; i++){res.push(i);}resFin.push(res);left++;}}return resFin;
}

剑指Offer(42):和为S的两个数字

题目描述

输入一个递增排序的数组和一个数字S,在数组中查找两个数,是的他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的。

输出描述:

对应每个测试案例,输出两个数,小的先输出。

题目分析

题目很简单,使用双指针就可以做出来了。此外相距最远,乘积最小。

function FindNumbersWithSum(array, sum) {if (array.length < 2) return [];let left = 0,right = array.length - 1;const res = [];while (left < right) {if (array[left] + array[right] < sum) {left++;} else if (array[left] + array[right] > sum) {right--;} else {res.push(array[left], array[right]);break;}}return res;
}

剑指Offer(45):扑克牌顺子

题目描述

LL今天心情特别好,因为他去买了一副扑克牌,发现里面居然有2个大王,2个小王(一副牌原本是54张_)…他随机从中抽出了5张牌,想测测自己的手气,看看能不能抽到顺子,如果抽到的话,他决定去买体育彩票,嘿嘿!!“红心A,黑桃3,小王,大王,方片5”,“Oh My God!”不是顺子…LL不高兴了,他想了想,决定大\小 王可以看成任何数字,并且A看作1,J为11,Q为12,K为13。上面的5张牌就可以变成“1,2,3,4,5”(大小王分别看作2和4),“So Lucky!”。LL决定去买体育彩票啦。 现在,要求你使用这幅牌模拟上面的过程,然后告诉我们LL的运气如何。为了方便起见,你可以认为大小王是0。

题目分析
重点不能放在有几个零,情况太多了
判断数组最大值和最小值的差是不是4,是的话再判断是否有重复,有重复就错

function IsContinuous(numbers)
{if(numbers.length!==5) return falselet arr = numbers.filter(n=>n!=0)// 判断最大值和最小值能不能连上if(Math.max(...arr)- Math.min(...arr)>4) return falsefor(let i = 0 ; i < arr.length ; i++){// 确保元素不重复if(arr.indexOf(arr[i])!== i){return false}}return true
}

剑指Offer(46):孩子们的游戏(圆圈中最后剩下的数)

题目描述

每年六一儿童节,牛客都会准备一些小礼物去看望孤儿院的小朋友,今年亦是如此。HF作为牛客的资深元老,自然也准备了一些小游戏。其中,有个游戏是这样的:首先,让小朋友们围成一个大圈。然后,他随机指定一个数m,让编号为0的小朋友开始报数。每次喊到m-1的那个小朋友要出列唱首歌,然后可以在礼品箱中任意的挑选礼物,并且不再回到圈中,从他的下一个小朋友开始,继续0…m-1报数…这样下去…直到剩下最后一个小朋友,可以不用表演,并且拿到牛客名贵的“名侦探柯南”典藏版(名额有限哦!!_)。请你试着想下,哪个小朋友会得到这份礼品呢?(注:小朋友的编号是从0到n-1)
如果没有小朋友,请返回-1

题目分析
这道题在数学上叫做约瑟夫环。

function LastRemainingSolution(n, m) {if (n === 0 || m === 0) return -1;const child = [];let del = 0;for (let i = 0; i < n; i++) {child[i] = i;}while (child.length > 1) {const k = m - 1;del = (del + k) % child.length;child.splice(del, 1);}return child[0];
}

剑指Offer(47):求1+2+3+…+n

题目分析

不能用乘除也就不能用公示了,并且不能用循环,那么说只能用递归了。

可是递归要终止条件呀,不能用if-else终止呀,那么只能用逻辑运算符了。

逻辑运算符当中的短路运算符有&&和||,这里只能用&&。

function SumSolution(n) {return n && Sum_Solution(n - 1) + n;
}

剑指Offer(48):不用加减乘除的加法

题目描述

写一个函数,求两个整数之和,要求在函数体内不得使用+、-、*、/四则运算符号。

function Add(num1, num2) {while (num2 !== 0) {const tmp1 = num1 ^ num2;num2 = (num1 & num2) << 1;num1 = tmp1;}return num1;
}

剑指Offer(54):字符流中第一个不重复的字符

题目描述

输出描述:
如果当前字符流没有存在出现一次的字符,返回#字符。

题目分析

我们之前有讲过,一般遇到次数问题,就可以想到用哈希表来统计次数。这题也是如此。

此外这题还可以借助indexOf和lastIndexOf来做,具体留给读者自己解决了。

let map = {};
function Init() {map = {};
}
function Insert(ch) {if (map[ch]) {map[ch] += 1;} else {map[ch] = 1;}
}
function FirstAppearingOnce() {for (const i in map) {if (map[i] === 1) {return i;}}return '#';
}

剑指Offer(63):数据流中的中位数

题目描述

如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。

题目分析

这里只给出下第二种方法,就是插入的时候保持数组一直有序。

const array = [];
function Insert(num) {array.push(num);for (let i = array.length - 2; array[i] > num; i--) {[array[i], array[i + 1]] = [array[i + 1], array[i]];}
}
function GetMedian() {if (array.length & 1 === 1) {return array[(array.length - 1) / 2];}return (array[array.length / 2] + array[array.length / 2 - 1]) / 2;
}

剑指Offer(64):滑动窗口的最大值

题目描述

给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,那么一共存在6个滑动窗口,他们的最大值分别为{4,4,6,6,6,5}; 针对数组{2,3,4,2,6,2,5,1}的滑动窗口有以下6个: {[2,3,4],2,6,2,5,1}, {2,[3,4,2],6,2,5,1}, {2,3,[4,2,6],2,5,1}, {2,3,4,[2,6,2],5,1}, {2,3,4,2,[6,2,5],1}, {2,3,4,2,6,[2,5,1]}。

题目分析

这道题我们需要转换下参考对象,像物理学中一样。

题目描述的是滑动窗口在数组上移动。如果我们以滑动窗口为对象,那么就是数组在滑动窗口上移动。

显然,可以看出滑动窗口就是一个队列,数组中的一个一个的数先进去,先出来。

此外这题还有一个可以优化的一点就是不一定需要把所有数字存进去队列里,只需要把以后有可能成为最大值的数字存进去。

还有一点要注意的是队列里保存的是下标,而不是实际的值,因为窗口移动主要是下标的变化

当然还有其他解法,比如利用两个栈去实现这个队列,从而使得查询时间复杂度降低到O(n)

function maxInWindows(num, size)
{let res = [];if(size == 0){return res;}let begin;let queue = [];for(let i = 0; i < num.length; i++){begin = i - size + 1;//代表滑动窗口的左边界if(queue.length == 0){queue.push(i);}else if(begin > queue[0]){queue.shift();}while((queue.length != 0) && (num[queue[queue.length - 1]] <= num[i])){queue.pop();}queue.push(i);if(begin >= 0){res.push(num[queue[0]])}}return res;
}

剑指offer(11)二进制中1的个数(中等 位运算,n=n&n-1 )

解法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 newNumberOf1(n) {let count = 0;while (n) {n = n & n - 1; // 核心count++;}return count;
}

解法2:

1.如果一个整数与1做与运算的结果是1,则表示该整数最右边一位是1,否则是0。

2.将n二进制形式右移1位,继续与1进行位运算

3.由于负数右移时最高位补1,因此不能采用算术右移,而使用不考虑符号位的逻辑右移

>>>无符号位移,补0,逻辑移动 >>有符号位移,正数补0,负数补1,算术移动

 function NumberOf1(n){let count = 0;while(n !== 0){if(n & 1 === 1) count++;n = n >>> 1;}return count;}

相关知识 :



原码, 反码, 补码 详解

链表(8道):

剑指Offer(3):从尾到头打印链表(简单,栈)

输入一个链表,从尾到头打印链表每个节点的值。

方法一:栈(先进后出,push+pop/unshift)

function printListFromTailToHead(head)
{var stack = [];while(head != null){stack.push(head.val);head = head.next;}var res = [];const len = stack.length; //需要初始stack的长度for(var i = 0; i< len; i++){res.push(stack.pop()); //stack长度会不断变化}return res;// write code here
}
/* function ListNode(x){this.val = x;this.next = null;}*/
function printListFromTailToHead(head) {// write code hereconst res = [];let pNode = head;while (pNode !== null) {res.unshift(pNode.val);pNode = pNode.next;}return res;
}

方法二:reverse()

function printListFromTailToHead(head){var stack = [];while(head != null){stack.push(head.val);head = head.next}return stack.reverse()
}

剑指offer(14)链表中倒数第K个节点 (简单 双指针法)

题目描述 :

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

题目分析 :

建两个结点指针,一个指针为起始节点,另外一个位距离起始节点长度为k的末尾节点。从原链表的头开始不断向后寻找,当末尾节点找到最后时,起始节点所只想的是倒数第k个节点

  function FindKthToTail(head, k) {if (head === null || k === 0) {return null;}let len = 0;let tempNode = head;while (tempNode) {tempNode = tempNode.next;++len;}if (k > len) {return null;}let index = len - k + 1;result = head;for (let i = 1; i < index; ++i) {result = result.next;}return result;}

剑指offer(15)反转链表 (简单,三指针法)

题目描述

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

题目分析

至少需要三个指针pPre(指向前一个结点)、pCurrent(指向当前的结点,在代码中就是pHead)、pNext(指向后一个结点)。

  • 先用next保存pHead的下一个结点信息,保证单链表不会断裂
  • 让pHead从指向next变成指向pre, 到此,完成了pre到pHead的反转,即pre<–pHead
  • 将pre,pHead,next依次向后移动一个结点
  • 循环操作,直到pHead为null,此时pre就是链表的最后一个结点,链表反转完毕,pre为反转后链表的第一个结点
  • 输出pre就是反转之后所得的链表
/* function ListNode(x){this.val = x;this.next = null;}*/
function ReverseList(pHead) {// write code herelet pPre = null,pNext = null;while (pHead !== null) {pNext = pHead.next;pHead.next = pPre;pPre = pHead;pHead = pNext;}return pPre;
}

剑指offer(16)合并两个排序的链表 (简单,双指针,递归)

题目描述

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

题目分析

重点抓住这两个链表都是单挑递增的,因此我们只需要不断地比较他们的头结点就行,明显这是个重复的过程。

可以用递归做,也可以不用递归做,不用递归做只需要用两个指针来一直指向两个链表的“头”结点就行了

/* function ListNode(x){this.val = x;this.next = null;}*/
function Merge(pHead1, pHead2) {let pMergeHead = null;// write code hereif (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;
}

非递归版本:略。

剑指Offer(25):复杂链表的复制(较难)

题目描述

输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针random指向一个随机节点),请对此链表进行深拷贝,并返回拷贝后的头结点。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)

题目分析

这道题有三种解法。

解法一

就是普通的解法,先复制节点,用p.next连接起来。然后再去设置p.random指针指向,不过这个设置又需要从头节点开始查。

所以总的时间复杂度为O(n2)

解法二

用map来保存<N,N>,这样就很容易设置p.random了,比如我们在节点S处和节点S处,我们通过S可以得到N,那么<N,N`>对应,

我们就可以就可以使得S的next指向N了。这是通过空间换时间

解法三

第三种就是比较复杂些,但是不用空间换时间也能达到O(n)
•第一步,对链表的每个节点N创建N‘,并链接在N的后面。
•设置复制出来的p.random。节点N指向S,那么N’指向S’,而N和N’相邻,那么S和S’也相邻。
•把长链表拆开成两个链表。

代码

这里只给出第二种和第三种的参考代码。

解法二:

// 第一种
function RandomListNode(x) {this.label = x;this.next = null;this.random = null;
}
function Clone(pHead) {// write code hereif (pHead === null) {return null;}const map = new Map();let p, p2;p = pHead;p2 = new RandomListNode(pHead.label);const pHead2 = p2;map.set(p, p2);while (p) {if (p.next) p2.next = new RandomListNode(p.next.label);else p2.next = null;p = p.next;p2 = p2.next;map.set(p, p2);}p = pHead;p2 = pHead2;while (p !== null) {p2.random = map.get(p.random);p = p.next;p2 = p2.next;}return pHead2;
}

解法三:

// 第二种
/* function RandomListNode(x){this.label = x;this.next = null;this.random = null;}*/
function Clone2(pHead) {cloneNodes(pHead);connectRandom(pHead);return reconnectNodes(pHead);
}
function cloneNodes(pHead) {// 复制链表let pNode = pHead;while (pNode !== null) {const newNode = new RandomListNode(pNode.label);newNode.next = pNode.next;pNode.next = newNode;pNode = newNode.next;}
}
function connectRandom(pHead) {// 设置random指针let pNode = pHead;while (pNode !== null) {if (pNode.random !== null) {pNode.next.random = pNode.random.next;}pNode = pNode.next.next;}
}
function reconnectNodes(pHead) {// 拆开链表let pNode = pHead;let newNodeHead = null,newNode = null;if (pNode !== null) {newNodeHead = newNode = pNode.next;pNode.next = newNode.next;pNode = newNode.next;}while (pNode !== null) {newNode.next = pNode.next;newNode = newNode.next;pNode.next = newNode.next;pNode = pNode.next;}return newNodeHead;
}

剑指Offer(36):两个链表的第一个公共结点(双指针)

题目描述

输入两个链表,找出它们的第一个公共结点。

题目分析

先在长的链表上跑,直到长的和短的一样长,再一起跑,判断节点相等的时候就可以了。

function FindFirstCommonNode(pHead1, pHead2) {const len1 = getLinkLength(pHead1),len2 = getLinkLength(pHead2);let pLong = pHead1,pShort = pHead2,lenGap = len1 - len2;if (len1 < len2) {pLong = pHead2;pShort = pHead1;lenGap = len2 - len1;}while (lenGap--) {pLong = pLong.next;}while (pLong !== null) {// pLong,pShort一起跑if (pLong.val === pShort.val) {return pLong;}pLong = pLong.next;pShort = pShort.next;}return null;
}
function getLinkLength(pHead) {let length = 0;while (pHead !== null) {pHead = pHead.next;length++;}return length;
}

剑指Offer(55):链表中环的入口结点(双指针)

题目描述

给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。

题目分析

1.一快一慢指针,先找到碰撞点。

2.然后碰撞点到入口节点的距离就是头结点到入口节点的距离。

具体原理

/* function ListNode(x){this.val = x;this.next = null;}*/
function EntryNodeOfLoop(pHead) {let fast = pHead;let slow = pHead;while (fast !== null && fast.next !== null) {slow = slow.next;fast = fast.next.next;if (fast === slow) {// 两者相遇let p = pHead;while (p !== slow) {p = p.next;slow = slow.next;}return p;}}return null;
}

剑指Offer(56):删除链表中重复的结点

题目描述

在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5

题目分析

这道链表的题目不难,意思也很容易清楚,就是删除相邻的重复节点,不过需要注意两点:

1.因为链表是单向的,如果是第一个、第二个节点就重复的话,删除就比较麻烦。因此我们可以额外添加头节点来解决

2.因为重复的节点不一定是重复两个,可能重复很多个,需要循环处理下。

function ListNode(x) {this.val = x;this.next = null;
}
function deleteDuplication(pHead) {if (pHead === null || pHead.next === null) {return pHead;}const Head = new ListNode(0); // 重要,方便处理第一个、第二个节点就是相同的情况。Head.next = pHead;let pre = Head;let cur = Head.next;while (cur !== null) {if (cur.next !== null && cur.val === cur.next.val) {// 找到最后的一个相同节点,因为相同节点可能重复多个while (cur.next !== null && cur.val === cur.next.val) {cur = cur.next;}pre.next = cur.next;cur = cur.next;} else {pre = pre.next;cur = cur.next;}}return Head.next;
}

JS版剑指offer相关推荐

  1. Java算法:牛客网Java版剑指Offer全套算法面试题目整理及电子档,Java算法与数据结构面试题,面试刷题、背题必备!牛客网剑指offer

    剑指offer(java版) 牛客网Java版剑指Offer全套题目67道 资源来源于网络 目录 1.二维数组中的查找 2.替换空格 3.从尾到头打印链表 4.重建二叉树 5.用两个栈实现队列 6.旋 ...

  2. 【Algorithm】用JS刷剑指offer

    1 二维数组的查找 题目描述 在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序.请完成一个函数,输入这样的一个二维数组和一个整数,判断 ...

  3. C++版 - 剑指Offer 面试题36:数组中的逆序对及其变形(Leetcode 315. Count of Smaller Numbers After Self)题解

    剑指Offer 面试题36:数组中的逆序对 题目:在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对.输入一个数组,求出这个数组中的逆序对的总数. 例如, 在数组{7,5, ...

  4. C++版-剑指offer 面试题6:重建二叉树(Leetcode105. Construct Binary Tree from Preorder and Inorder Traversal) 解题报告

    剑指offer 重建二叉树 提交网址:  http://www.nowcoder.com/practice/8a19cbe657394eeaac2f6ea9b0f6fcf6?tpId=13&t ...

  5. C++版 - 剑指offer 面试题39:判断平衡二叉树(LeetCode 110. Balanced Binary Tree) 题解

    剑指offer 面试题39:判断平衡二叉树 提交网址:  http://www.nowcoder.com/practice/8b3b95850edb4115918ecebdf1b4d222?tpId= ...

  6. 用JS刷剑指offer

    1.二叉树的深度 输入一棵二叉树,求该树的深度.从根结点到叶结点依次经过的结点(含根.叶结点)形成树的一条路径,最长路径的长度为树的深度. //DFS,递归 function TreeDepth(pR ...

  7. C++版 - 剑指offer面试题38:数字在已排序数组中出现的次数

    数字在已排序数组中出现的次数 提交网址: http://www.nowcoder.com/practice/70610bf967994b22bb1c26f9ae901fa2?tpId=13&t ...

  8. 编程 跳台阶_Java版剑指offer编程题第8题--跳台阶

    跟learnjiawa一起每天一道算法编程题,既可以增强对常用API的熟悉能力,也能增强自己的编程能力和解决问题的能力.算法和数据结构,是基础中的基础,更是笔试的重中之重. 不积硅步,无以至千里: 不 ...

  9. 一只青蛙跳向三个台阶_Java版剑指offer编程题第9题--变态跳台阶

    跟learnjiawa一起每天一道算法编程题,既可以增强对常用API的熟悉能力,也能增强自己的编程能力和解决问题的能力.算法和数据结构,是基础中的基础,更是笔试的重中之重. 不积硅步,无以至千里: 不 ...

最新文章

  1. C# IEnumerable和IEnumerator的区别,如何实现
  2. 0428(字典,列表,循环)
  3. springboot使用webjars引入jquery
  4. 【PAT甲级】1048 Find Coins (25 分) C++ 全部AC
  5. python3字典写入excel_python3:excel操作之读取数据并返回字典 + 写入的案例
  6. 嵌入式软件架构设计分层思路
  7. c++中的文件读写的操作
  8. udp计算机dll,Udp_SocketBll.dll
  9. 最后一周|高级转录组分析和R语言数据可视化第十二期 (线上线下同时开课)...
  10. chackbox的值 php获取_PHP操作Redis数据库常用方法
  11. zeptojs库解读1之整体框架
  12. python禁用键盘鼠标_在Python中禁用或锁定鼠标和键盘?
  13. 也写Jquery插件,拖动布局
  14. spring mvc国际化_Spring MVC国际化(i18n)和本地化(L10n)示例
  15. c语言双向链表实现航班系统,双向链表C语言实现
  16. excel 文件导入数据库(java)
  17. SiteMesh3简介及使用
  18. 交换机端口镜像配置大全【汇集 22个各种品牌交换机】
  19. RHEL7修改root密码
  20. linux tomcat apr安装,Linux下为Tomcat安装APR

热门文章

  1. k8s:概念以及搭建高可用集群
  2. Linux系统最新的核心版本号是多少,linux系统核心版本号的具体涵义是什么
  3. 单元测试Junit Test 中 Assertions 使用 Java版
  4. 【一周AI新鲜事】“擎天柱”霸气登场/全球创新指数中国排名11位/摩尔定律死了,又活了?...
  5. 基于matlab的梯度下降法实现线性回归
  6. 常见的监测互联网上舆情的方法有哪些?舆情系统方法介绍
  7. 整理的微表情数据库资源 可直接下载到本地电脑 SAMM+SMIC+CASME1+CASME2+CASME^2+CASME3
  8. 24. 09-排序1 排序 数据结构 浙江大学 拼题 PTA
  9. 图片处理——基于openCV实现美颜相机
  10. Java中Vector类的常用方法