牛客网 -- 刷题剑指offer (持续更新...)
这里我将记录我在刷牛客网的剑指offer系列题中,对题目的理解与解法,以一个普通人的视角,阐述题解.
数组
1.构建乘积数组
题目描述: 给定一个数组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]。不能使用除法。(注意:规定B[0] = A[1] * A[2] * ... * A[n-1],B[n-1] = A[0] * A[1] * ... * A[n-2];)
题目关键点理解: 由题,n肯定大于等于2,不然不会有A[n - 2],所以就不用考虑特殊情况了
//方法一:
function multiply_one(array) {
/*
* 遍历B,暴力法求解数组B
* O(n^2)
* 测试运行时间:11ms
* 占用内存:5240k
* */
let B = new Array(array.length)
for (let i = 0; i < B.length; i ++){
B[i] = 1
for (let j = 0; j < array.length; j ++) {
if (i === j) continue;
B[i] *= array[j]
}
}
return B
}
//方法二:
function multiply_two(array) {
/*
* 使用空间换时间
* 使用两个数组分别存储
* order = [A[0], A[0]*A[1], A[0]*A[1]*A[2], ..., A[0]*A[1]*...*A[n - 1]]
* reverseOrder = [A[0]*A[1]*...*A[n - 1], ..., A[2]*A[3]*...*A[n - 1], ..., A[n - 2]*A[n - 1], A[n - 1]]
* 所以 B[i] = order[i - 1] * reverseOrder[i + 1]
* 需要遍历两遍n
* O(n)
* */
let order = []
let reverseOrder = []
order[0] = array[0]
reverseOrder[array.length - 1] = array[array.length - 1]
for (let i = 1,j = array.length - 2; i < array.length;i ++,j --){
order[i] = array[i] * order[i - 1]
reverseOrder[j] = array[j] * reverseOrder[j + 1]
}
let B = []
for (let i = 0;i < array.length;i ++) {
let left = order[i - 1] === undefined ? 1 : order[i - 1]
let right = reverseOrder[i + 1] === undefined ? 1 : reverseOrder[i + 1]
B[i] = left * right
}
return B
}
2.数组中重复的数字
题目描述: 在一个长度为n的数组里的所有数字都在0到n-1的范围内。数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字2。
题目关键点理解: 只需要找到第一个重复的值
//方法一:
function multiply_one(array) {
/*
* 思路,哈希表。判断该数字是否已经出现过
* */
let set = new Set()
let res = false
for (let i = 0;i < numbers.length; i ++) {
if (set.has(numbers[i])) {
duplication[0] = numbers[i]
res = true
break;
}else{
set.add(numbers[i])
}
}
return res
}
3.二维数组中的查找
题目描述: 在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序,请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
题目关键点理解: 注意关键字,顺序排序
//方法一:
function Find(target, array) {
/*
* 思路:如果存在一个数在这个每一行每一列的递增二维矩阵中
* 那么它一定满足这一行 array[i][0] <= target && target <= array[i][length]
* 只要满足这样条件的每行,使用查找算在该行进行查找
* 每一行n个元素,总共又m行
* 我使用的二分查找,所以时间复杂度:O(m*log2n)
* */
let binary = function (target, array,index,len) {
if (index < len){
if (array[index] === target){
return index
}
if (array[len] === target) {
return len
}
let mid = Math.floor((index + len) / 2)
if (array[mid] === target) {
return mid
}else if (array[mid] < target) {
return binary(target,array,mid + 1,len)
}else{
return binary(target,array,0,mid - 1)
}
}
return - 1
}
let len = array[0].length - 1
let res = false
for (let i = 0;i <= array.length - 1; i ++){
if (target >= array[i][0] && target <= array[i][len]) {
if (binary(target,array[i],0,len) !== -1) {
res = true
break;
}
}
}
return res
}
//方法二对查找进行了优化
function Find_new(target, array) {
/*
* 利用JS set数据结构。快速查找
* */
let len = array[0].length - 1
let res = false
for (let i = 0;i <= array.length - 1; i ++){
if (target >= array[i][0] && target <= array[i][len]) {
let tem = new Set(array[i])
if (tem.has(target)) {
res = true
break;
}
}
}
return res
}
字符串
1.字符流中第一个不重复的字符
题目描述:请实现一个函数用来找出字符流中第一个只出现一次的字符。例如,当从字符流中只读出前两个字符"go"时,第一个只出现一次的字符是"g"。当从该字符流中读出前六个字符“google"时,第一个只出现一次的字符是"l"。如果当前字符流没有存在出现一次的字符,返回#字符。
题目理解:从题目和所给三个函数可以看出,Init是初始化一个存储空间,Insert是给该存储空间存储字符,FirstAppearingOnce找到该存储空间第一个只出现的字符
function Init() {
//不使用关键字声明,定义一个全局的对象
obj = {}
}
//Insert one char from stringstream
function Insert(ch) {
//使用对象模拟哈希表,快速判断该字符是否重复出现
if (obj[ch]) {
obj[ch] ++
}else{
obj[ch] = 1
}
}
//return the first appearence once char in current stringstream
function FirstAppearingOnce() {
//根据哈希表,返回第一个值为1的key
for (let key of Object.keys(obj)){
if (obj[key] === 1){
return key
}
}
return '#'
}
//Init -> Insert -> FirstAppearingOnce
2.表示数值的字符串
题目描述:请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串"+100","5e2","-123","3.1416"和"-1E-16"都表示数值。但是"12e","1a3.14","1.2.3","+-5"和"12e+4.3"都不是。
题目理解:正则匹配即可
function isNumeric(s) {
/*
* 符号可能有可能没有
* e或者E后面必定跟着一个数字
* js小数点前后必须跟着一个值,比如12.表示12,.12表示0.12
* */
return s.match(/[+-]?(\d+([.]\d*)?|\d*([.]\d+)?)([eE][+-]?\d+)?/g)[0] === s
}
3.正则表达式匹配
题目描述
请实现一个函数用来匹配包括'.'和''的正则表达式。模式中的字符'.'表示任意一个字符,而''表示它前面的字符可以出现任意次(包含0次)。在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串"aaa"与模式"a.a"和"abaca"匹配,但是与"aa.a"和"ab*a"均不匹配
理解题意即可,题目的意思其实就是,pattern作为正则能不能去匹配s
function match(s, pattern) {
var reg=new RegExp("^" + pattern + "$");
return reg.test(s);
}
4.替换空格
题目描述 请实现一个函数,将一个字符串中的每个空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。
function replaceSpace(str) {
//简单正则
return str.replace(/\s/g,'%20')
}
链表
1.从头到尾打印链表
题目描述:输入一个链表,按链表从尾到头的顺序返回一个ArrayList。
题目理解:从尾到头
function printListFromTailToHead(head){
/*
* 循环链表,当指针指向位null跳出
* 结果数组从头插入值
* */
// write code here
let result = []
while(head !== null){
result.unshift(head.val)
head = head.next
}
return result
}
2.删除链表中重复的值
题目描述:在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5 题目理解:重复的节点不保留,所以结果中只能是没有重复值的
function deleteDuplication(pHead) {
/*
* rescode记录没有重复的值
* deletcode记录重复的值
* 最后遍历rescode构成结果链表
* */
let rescode = new Set()
let deletcode = new Set()
while (pHead !== null){
if (rescode.has(pHead.val) || deletcode.has(pHead.val)){
rescode.delete(pHead.val)
deletcode.add(pHead.val)
}else{
rescode.add(pHead.val)
}
pHead = pHead.next
}
if (rescode.size > 0) {
let resNode = {}
let tem = resNode
for (let key of rescode) {
let obj = {val:key,next:null}
tem.next = obj
tem = tem.next
}
return resNode.next
}else{
return null
}
}
3.链表中环的入口节点
题目描述:给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。
function EntryNodeOfLoop(pHead) {
/*
* 利用数组存储每个节点地址
* 每次到下个节点时判断该节点地址是否已经被存储,如果是,那么跳出循环
* */
let nodeCode = []
while (pHead !== null) {
if (nodeCode.indexOf(pHead) !== -1) {
break;
}else{
nodeCode.push(pHead)
pHead = pHead.next
}
}
return pHead === null ? null : pHead
}
栈和队列
1.用两个栈实现队列
题目描述:用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。
题目理解:栈表示入队列,b栈表示出队列,但是栈是后进先出,队列是先进先出。所以表示b的顺序是和a栈顺序相反。于是需要出队列时,a栈一个一个出,b栈将出的值一个一个进。最后b栈再出一个,那么就是a栈中最先进的那一个了
js模拟栈使用 push和pop
let outStack = [];
let isStack = [];
function push(node)
{
isStack.push(node);
}
function pop()
{
if(!outStack.length){
while(isStack.length){
outStack.push(isStack.pop());
}
}
return outStack.pop();
}
2.滑动窗口的最大值
题目描述:给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,那么一共存在6个滑动窗口,他们的最大值分别为{4,4,6,6,6,5};
function maxInWindows(num, size) {
/*
* 使用队列模拟滑动操作。若进队列数 >= 出队列数 那么此时滑动窗口最大值为Math.max(max,进队列数)
* 若进队列数 < 出队列数 ,则需要判断出队列数是否为上一轮窗口的最大值,若是,则需要在窗口中重新寻找最大值
* */
if (size === 0) return []
let queue = []
let reslut = []
let max = -Infinity
for (let i = 0; i <= num.length; i ++) {
if (queue.length < size) {
queue.push(num[i])
max = Math.max(max,num[i])
}else{
reslut.push(max)
let tem = queue.shift()
queue.push(num[i])
if (num[i] >= tem) {
max = Math.max(num[i],max)
}else {
if (tem === max) {
max = queue[0]
for (let j = 1;j < queue.length; j ++) {
max = Math.max(max,queue[j])
}
}
}
}
}
return reslut
}
查找和排序
1.旋转数组的最小数字
题目描述:把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。
function minNumberInRotateArray(rotateArray) {
/*
* 一个一个遍历,找到第一个小于rotateArray[0]的数
* */
let result = 0
for (let i = 1; i < rotateArray.length; i ++) {
if (rotateArray[i] < rotateArray[0]) {
result = rotateArray[i]
break;
}
}
return result
}
function minNumberInRotateArray_1(rotateArray) {
/*
* 二分查找,找到旋转的那个点
* */
if (rotateArray.length === 0) return 0
let left = 0
let right = rotateArray.length - 1
while (left < right) {
let mid = Math.floor((left + right) / 2)
if (rotateArray[mid] < rotateArray[0]) {
right = mid
}else{
left = mid + 1
}
}
return rotateArray[left]
}
递归和循环
1.斐波那契数列
题目描述:大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项(从0开始,第0项为0,第1项是1)。n<=39
function Fibonacci(n) {
//递归 --- 因为大量的重复计算,会超时
if (n < 2) return n
return Fibonacci(n - 1) + Fibonacci(n - 2)
}
function Fibonacci(n) {
//循环
let arr = [0,1]
let index = 2
while (index <= n){
arr[index] = arr[index - 1] + arr[index - 2]
index ++
}
return arr[n]
}
2.跳台阶
题目描述:一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)。
function jumpFloor(number) {
/*
* 动态规划:dp[i] 表示青蛙跳i级台阶有dp[i]种跳法
* 所以:dp[i] = dp[i - 1] + dp[i - 2]
* */
let dp = [0,1,2]
for (let i = 3; i <= number; i ++) {
dp[i] = dp[i - 1] + dp[i - 2]
}
return dp[number]
}
3.变态跳台阶
题目描述:一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
function jumpFloorII(number) {
//动态规划,找找规律
let dp = [0,1,2]
for (let i = 3; i <= number; i ++) {
dp[i] = 2*dp[i - 1]
}
return dp[number]
}
4.矩形覆盖
题目描述:题目描述我们可以用2*1的小矩形横着或者竖着去覆盖更大的矩形。请问用n个2*1的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法?比如n=3时,2*3的矩形块有3种覆盖方法:
function rectCover(number) {
/*
* 因为一个小矩形有两种放的方式,横着和竖着,竖着放占一个空间,横着放必须用两个小矩形才能填充满
* */
let dp = [0,1,2,3]
for (let i = 4; i <= number; i ++) {
dp[i] = dp[i - 1] + dp[i - 2]
}
return dp[number]
}
位运算
1.二进制中1的个数
题目描述:输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。0
function NumberOf1(n) {
/*
* 右移运算符
* “>>”运算符执行有符号右移位运算
* “>>>”运算符执行无符号右移位运
* &运算符,比较二进制中对应位是否都为1,如果都为1,结果中该位也为1
* */
let result = 0
while (n !== 0) {
if ((n & 1) > 0){
result ++
}
n = n >>> 1
}
return result
}
树
1.对称的二叉树
题目描述:请实现一个函数,用来判断一颗二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。
题目理解:需要二叉树与该二叉树的镜像相同,也就是说该二叉树的遍历结果与该二叉树镜像的遍历结果应该是一致的
function isSymmetrical(pRoot) {
/*
* 前序遍历:先输出该节点值,然后遍历左节点、再遍历右节点
* 那么它的镜像遍历方法就应该对称,先输出该节点值,然后遍历右节点、再遍历左节点
* 注意:节点为空也需要遍历进去,否者会出错
* */
let ergodic = function (root,arr,index) {
if (root === null) arr.push(null)
else {
arr.push(root.val)
if (index === 'left') {
ergodic(root.left,arr,index)
ergodic(root.right,arr,index)
}
if (index === 'right') {
ergodic(root.right,arr,index)
ergodic(root.left,arr,index)
}
}
return arr
}
let left = ergodic(pRoot,[],'left')
let right = ergodic(pRoot,[],'right')
let len = left.length - 1
while (len >= 0) {
if (left.pop() === right.pop()) {
len --
}else{
return false
}
}
return true
}
2.重建二叉树
题目描述:输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。
function reConstructBinaryTree(pre, vin) {
/*
* 一层一层找根节点
* */
let result = null
if (pre.length > 1) {
let rootNode = pre[0]
let vinLeft = vin.slice(0,vin.indexOf(pre[0]))
let preLeft = pre.splice(1,vinLeft.length)
let vinRight = vin.slice(vin.indexOf(pre[0]) + 1)
let preRight = pre.splice(1)
result = {
val:pre[0],
left:reConstructBinaryTree(preLeft,vinLeft),
right:reConstructBinaryTree(preRight,vinRight)
}
}else if (pre.length === 1) {
result = {
val:pre[0],
left:null,
right:null
}
}
return result
}
3.二叉树的下一个节点
题目描述:给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。
function GetNext(pNode) {
let result = null
if (pNode === null) return result
if (pNode.right !== null){
// 存在右子节点,找到右子节点的最左节点
result = pNode.right
while (result.left !== null) {
result = result.left
}
}else{
// 没有右子节点
// 1. 当前节点是父节点的左节点
// 2. 当前节点是父节点的右节点,往上找父节点的父节点......直到某一个节点是父节点的左节点,取父节点
result = pNode
while (result !== null) {
if (result.next === null) {
result = result.next
break;
}
if (result === result.next.left) {
result = result.next
break;
}
result = result.next
}
}
return result
}
4.按之字形顺序打印二叉树
题目描述:请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。
function Print(pRoot) {
let result = []
if (pRoot === null) return result
let arr = [pRoot]
let order = true
while (arr.length > 0) {
let tem = []
let temRrs = []
for (let i = 0; i < arr.length; i ++) {
temRrs.push(arr[i].val)
if (arr[i].left !== null){
tem.push(arr[i].left)
}
if (arr[i].right !== null){
tem.push(arr[i].right)
}
}
arr = tem
if (!order) temRrs.reverse()
result.push(temRrs)
order = !order
}
return result
}
5.把二叉树打印成多行
题目描述:从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。
function Print(pRoot) {
let result = []
if (pRoot === null) return result
let arr = [pRoot]
while (arr.length > 0) {
let tem = []
let temRrs = []
for (let i = 0; i < arr.length; i ++) {
temRrs.push(arr[i].val)
if (arr[i].left !== null){
tem.push(arr[i].left)
}
if (arr[i].right !== null){
tem.push(arr[i].right)
}
}
arr = tem
result.push(temRrs)
}
return result
}
6.序列化二叉树
题目描述:请实现两个函数,分别用来序列化和反序列化二叉树二叉树的序列化是指:把一棵二叉树按照某种遍历方式的结果以某种格式保存为字符串,从而使得内存中建立起来的二叉树可以持久保存。序列化可以基于先序、中序、后序、层序的二叉树遍历方式来进行修改,序列化的结果是一个字符串,序列化时通过 某种符号表示空节点(#),以 !表示一个结点值的结束(value!)。二叉树的反序列化是指:根据某种遍历顺序得到的序列化字符串结果str,重构二叉树。例如,我们可以把一个只有根节点为1的二叉树序列化为"1,",然后通过自己的函数来解析回这个二叉树
解题思路:可以根据本系列中的重建二叉树题建立先序和中心字符串,然后跑代码就行了,本题我使用层序来进行序列化和反序列化,好像弄复杂了
function Serialize(pRoot) {
let result = ''
let arr = [pRoot]
while (arr.length > 0) {
let temS = ''
let temA = []
while (arr.length > 0) {
let tem = arr.shift()
if (tem === null){
temS += '#!'
} else{
temS += tem.val + '!'
temA.push(tem.left,tem.right)
}
}
result += temS + '@'
arr = temA
}
return result
}
function Deserialize(s) {
let arr = s.split('@')
arr.pop()
for (let i = 0; i < arr.length; i ++) {
arr[i] = arr[i].split('!')
arr[i].pop()
}
let pRoot = arr[0][0] === '#' ? null : {val:arr[0][0],left:null,right:null}
let levArr = [pRoot]
for (let i = 1; i < arr.length; i ++) {
let tem = []
for (let j = 0; j < arr[i].length; j ++) {
let index = Math.floor( j / 2)
if (arr[i][j] !== '#') {
if (j % 2 === 1){
levArr[index].right = {val:arr[i][j],left:null,right:null}
tem.push(levArr[index].right)
} else{
levArr[index].left = {val:arr[i][j],left:null,right:null}
tem.push(levArr[index].left)
}
}
}
levArr = tem
}
return pRoot
}
7.二叉搜索树的第K个节点
题目描述:给定一棵二叉搜索树,请找出其中的第k小的结点。例如, (5,3,7,2,4,6,8) 中,按结点数值大小顺序第三小结点的值为4。
注意返回的是结点不是该结点的值
function KthNode(pRoot, k) {
/*
* 思路:先随便遍历,再排序(库函数)
* */
if (pRoot === null) return null
let nodeArr = [pRoot]
let nodeValArr = []
while (nodeArr.length > 0) {
let tem = nodeArr.shift()
nodeValArr.push(tem)
if (tem.left !== null) nodeArr.push(tem.left)
if (tem.right !== null) nodeArr.push(tem.right)
}
nodeValArr.sort((a,b)=> a.val - b.val)
return nodeValArr[k - 1]
}
8.数据流中的中位数
题目描述:如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。
Insert持续插入数据,GetMedian解析数据,所以需要定义一个全局变量data,由题,需要每次都维护data保持顺序
let data = []
function Insert(num){
if (data.length === 0) {
data.push(num)
}else{
let index = data.length - 1
while (1){
if (data[index] <= num || index === -1) {
data[index + 1] = num
break;
}else{
data[index + 1] = data[index]
}
index --
}
}
}
function GetMedian(){
let result = undefined
if (data.length % 2 === 1) {
//奇数
result = data[Math.floor(data.length / 2)]
}else{
//偶数
result = (data[Math.floor(data.length / 2)] + data[Math.ceil(data.length / 2)]) / 2
}
return result
}
代码的完整性
1.数值的整数次方
题目描述:给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。保证base和exponent不同时为0
function Power(base, exponent) {
//就是实现Math.Pow(x,n),为了保证时间限制,不能for循环O(n)
if(base === 0) return 0
if (exponent < 0) {base = 1 / base;exponent = Math.abs(exponent)}
if (exponent === 0) return 1
if (exponent === 1) return base
let index = 1
let result = 1
let tem = base
while (exponent > 1) {
if (index * 2 >= exponent) {
exponent -= index
index = 1
result = tem * result
tem = base
}else{
tem = tem * tem
index *= 2
}
}
result *= base
return result
}
2.调整数组顺序使奇数位位于偶数前面
题目描述:输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。
function reOrderArray(array) {
//一个简单的思路:两个队列分别存储奇数和偶数
//left 存奇数 right 存偶数
let left = []
let right = []
for (let i = 0; i < array.length; i ++){
if (array[i] % 2 === 0) right.push(array[i])
else left.push(array[i])
}
return [...left,...right]
}
代码的鲁棒性
1.反转链表
题目描述:输入一个链表,反转链表后,输出新链表的表头。
function ReverseList(pHead) {
/*
* 原地反转,需要始终记录原链表当前指针指向的下一个节点
* 然后当前节点反转
* */
if (pHead === null) return null
let tem = null
let node = pHead.next
while (node !== null){
pHead.next = tem
tem = pHead
pHead = node
node = pHead.next
}
pHead.next = tem
return pHead
}
2.链表中倒数第K个节点
题目描述:输入一个链表,输出该链表中倒数第k个结点。
function FindKthToTail(head, k) {
//空间换时间,存储每个节点
let arr = []
while (head !== null){
arr.push(head)
head = head.next
}
return arr[arr.length - k]
}
function FindKthToTail_new(head, k) {
//双指针,第一个指针遍历k次,然后第二个指针再和第一个指针一起遍历到第一个指针指向null
if(head == null || k <= 0) return null; //容错处理
let p1 = head
let p2 = head
for (let i = 1; i < k; i ++){
if (p1.next != null) {
p1 = p1.next;
}else { //防止k过大,超出链表长度
return null;
}
}
while (p1.next !== null){
p1 = p1.next;
p2 = p2.next;
}
return p2;
}
3.合并两个排序的列表
题目描述:输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。
function Merge(pHead1, pHead2) {
let result = {next:null}
let node = result
while (pHead1 !== null || pHead2 !== null) {
if (pHead1 === null) {
node.next = pHead2
break;
}
if (pHead2 === null) {
node.next = pHead1
break;
}
if (pHead1.val > pHead2.val) {
node.next = pHead2
pHead2 = pHead2.next
}else{
node.next = pHead1
pHead1 = pHead1.next
}
node = node.next
}
return result.next
}
4.树的子结构
题目描述:输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)
function HasSubtree(pRoot1, pRoot2) {
/*
* isSubtree判断pRoot2是否为pRoot1的子结构,因为空树不是任意一个树的子结构所以不能原地递归
* 循环pRoot1,当当前结点值===pRoot2的结点值,进入isSubtree判断,返回true,跳出循环
* */
if (pRoot1 === null || pRoot2 === null) return false
let isSubtree = function (pRoot1, pRoot2) {
if (pRoot2 === null) return true
if (pRoot1 === null) return false
return pRoot1.val === pRoot2.val && isSubtree(pRoot1.left,pRoot2.left) && isSubtree(pRoot1.right,pRoot2.right)
}
let arr = [pRoot1]
let result = false
while (arr.length > 0) {
let tem = arr.shift();
if (tem.val === pRoot2.val) {
if (isSubtree(tem, pRoot2)) {
result = true
break;
}
}
if (tem.left !== null) arr.push(tem.left)
if (tem.right !== null) arr.push(tem.right)
}
return result
}
面试思路
1.二叉树的镜像
题目描述:操作给定的二叉树,将其变换为源二叉树的镜像。
function Mirror(root) {
/*
* 递归交换
* */
if (root === null) return root
let tem = root.left
root.left = root.right
root.right = tem
if (root.left !== null) Mirror(root.left)
if (root.right !== null) Mirror(root.right)
return root
}
画图让抽象形象化
1.顺时针打印矩阵
题目描述:输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下4 X 4矩阵: 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.
function printMatrix(matrix) {
/*
* 指针顺时针旋转
* */
if (matrix.length === 0) return []
let left = 0,top = 0,right = matrix[0].length - 1,bottom = matrix.length - 1
let i = 0,j = 0
let result = []
let len = matrix[0].length * matrix.length
while (result.length < len){
result.push(matrix[i][j])
if (j < right && i === top) {
j ++
}else if (j === right && i < bottom) {
i ++
}else if (j > left && j <= right && i === bottom){
j --
} else if (j === left && i <= bottom && i > top) {
i --
if (i === top) {
left ++
top ++
right --
bottom --
i ++
j ++
}
}
}
return result
}
举例让抽象具体化
1.包含min函数的栈
题目描述:定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的min函数(时间复杂度应为O(1))。 注意:保证测试中不会当栈为空的时候,对栈调用pop()或者min()或者top()方法。
let stack = {
arr:[],
min:Infinity
}
function push(node) {
stack.arr.push(node)
stack.min = Math.min(node,stack.min)
}
function pop() {
let tem = stack.arr.pop()
if (tem === stack.min) {
stack.min = Infinity
for (let i = 0; i < stack.arr.length; i ++) {
stack.min = Math.min(stack.arr[i],stack.min)
}
}
}
function top() {
return stack.arr[0]
}
function min() {
return stack.min
}
2.栈的压入、弹出序列
题目描述:输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序, 序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。(注意:这两个序列的长度是相等的)
function IsPopOrder(pushV, popV) {
/*
* 定义一个栈模拟压入和弹出,如果最后栈清空,那么返回true否则返回false
* */
let stack = []
let pop = popV.shift()
for (let i = 0;i < pushV.length; i ++) {
stack.push(pushV[i])
if (pop === pushV[i]) {
while (stack.length > 0){
let tem = stack.pop()
if (tem === pop) pop = popV.shift()
else {
stack.push(tem)
break;
}
}
}
}
return stack.length === 0 ? true : false
}
3.从上忘下打印二叉树
题目描述:从上往下打印出二叉树的每个节点,同层节点从左至右打印。
function PrintFromTopToBottom(root) {
/*
* 定义一个临时结点数组存下一层的结点,再定一个值数组存当前层的值
* */
if (root === null) return []
let arr = [root]
let result = []
while (arr.length > 0) {
let valA = []
let tem = []
for (let i = 0; i < arr.length; i ++) {
valA.push(arr[i].val)
if (arr[i].left !== null) tem.push(arr[i].left)
if (arr[i].right !== null) tem.push(arr[i].right)
}
arr = tem
result.push(valA)
}
return result
}
4.二叉搜索树的后序遍历序列
题目描述:输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出Yes,否则输出No。假设输入的数组的任意两个数字都互不相同。
function VerifySquenceOfBST(sequence) {
/*
* 二叉查找树(Binary Search Tree),(又:二叉搜索树,二叉排序树)它或者是一棵空树,或者是具有下列性质的二叉树:
* 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
* 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
* 所以左子树最大值小于根结点,右子树最小值大于根节点
* 双指针,left指针寻炸左子树,right指针寻找右子树,如果最后没有遍历完,则不是一个二叉搜索树
* */
if (sequence.length === 0) return false
if (sequence.length < 3) return true
let left = []
let isLeft = false
let right = []
let isRight = false
let tem = sequence.pop()
let i = 0,j = sequence.length - 1
while (i <= j) {
if (tem > sequence[i]) {
left.push(sequence[i])
i ++
}else{
isLeft = true
}
if (tem < sequence[j]) {
right.unshift(sequence[j])
j --
}else{
isRight = true
}
if (isLeft && isRight) return false
}
return (left.length === 0 ? true : VerifySquenceOfBST(left)) && (right.length === 0 ? true : VerifySquenceOfBST(right))
}
5.二叉树中和为某一值的路径
题目描述:输入一颗二叉树的根节点和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。
function FindPath(root, expectNumber) {
/*
* 必须到叶结点
* 广度优先搜索
* */
let result = []
if (root === null) return result
let nodeArray = new Map()
nodeArray.set(root,[expectNumber,[]])
while (nodeArray.size > 0) {
let temArray = new Map()
for(let [node,tem] of nodeArray.entries()){
tem[1].push(node.val)
let temNumber = tem[0] - node.val
if (node.left === null && node.right === null && temNumber === 0)result.push(tem[1]) //保证是叶节点
if (node.left !== null) temArray.set(node.left,[temNumber,[...tem[1]]]);
if (node.right !== null) temArray.set(node.right,[temNumber,[...tem[1]]]);
}
nodeArray = temArray
}
return result
}
分解让复杂问题简单化
1.二叉搜索树与双向链表
题目描述:输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。
function Convert(pRootOfTree) {
/*
* 中序遍历,缓存每个结点,遍历结点,修改指针
* */
if (pRootOfTree === null) return null
let stack = []
let middleOrder = function (node,stack) {
if (node.left !== null) stack.push(...middleOrder(node.left,[]))
stack.push(node)
if (node.right !== null) stack.push(...middleOrder(node.right,[]))
return stack
}
middleOrder(pRootOfTree,stack)
for (let i = 0; i < stack.length; i ++) {
stack[i].right = stack[i + 1] || null
stack[i].left = stack[i - 1] || null
}
return stack[0]
}
1.复杂链表的复制
题目描述:输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针random指向一个随机节点),请对此链表进行深拷贝,并返回拷贝后的头结点。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)
function Clone(pHead) {
/*
* pNode:存储pHead每个节点
* newNode:存储新创建的每个节点
* */
let node = {next:null}
let result = node
let pNode = []
let newNode = []
while (pHead !== null) {
node.next = {next:null,random:null}
node = node.next
node.label = pHead.label
pNode.push(pHead)
newNode.push(node)
pHead = pHead.next
}
//按创建顺序,对应排序,赋值random引用节点
for (let i = 0; i < pNode.length; i ++) {
newNode[i].random = newNode[pNode.indexOf(pNode[i].random)]
}
return result.next
}