Leetcode终于上线剑指offer的题目了 记下刷题记录
面试题03. 数组中重复的数字
简单题
空间复杂度:O(N)
时间复杂度:O(N)
var findRepeatNumber = function(nums) {
const arr = new Array(nums.length)
for(let n of nums) {
if(arr[n]) {
return n
}
arr[n] = true
}
return false
};
面试题04. 二维数组中的查找
简单题
空间复杂度:O(1) 辅助数据也就是坐标i,j而已。
时间复杂度:O(N+M) 最复杂的情况就是从上到下,从右到左。
const findNumberIn2DArray = function(matrix, target) {
// 左到右递增
// 上到下递增
// 从左上角开始,item > target j--, item < target i++
const n = matrix.length
if(n === 0) return false
const m = matrix[0].length
if(m === 0) return false
if(target < matrix[0][0]) return false
if(target > matrix[n-1][m-1]) return false
let i = 0, j = m-1
while(i=0 && j=0) {
if(matrix[i][j] === target) {
return true
}
if(matrix[i][j] > target) {
j--
continue
}
if(matrix[i][j] < target) {
i++
continue
}
}
return false
};
面试题05. 替换空格
简单题
时间复杂度:O(N)
空间复杂度:O(N)
// 先复习下字符串操作API吧
// replace实现
const replaceSpace = function(s) {
return s.replace(/ /g, '%20')
};
// split + join
const replaceSpace = function(s) {
return s.split(' ').join('%20')
};
// 数组 + join做法
const replaceSpace = function(s) {
const arr = []
for(const c of s) {
c === ' ' ? arr.push('%20') : arr.push(c)
}
return arr.join('')
};
面试题06. 从尾到头打印链表
简单题
递归法和循环压栈逆序输出法,都是O(N)
const reversePrint = function(head) {
// 用递归法
const res = []
const reverse = (node) => {
if(!node) {
return
} else {
reverse(node.next)
res.push(node.val)
}
}
reverse(head)
return res
};
const reversePrint = function(head) {
// 用循环法
const res = []
while(head) {
res.push(head.val)
head = head.next
}
return res.reverse()
};
面试题07. 重建二叉树
中等题
时间复杂度O(N)
空间复杂度O(N) 函数调用栈空间浪费。
var buildTree = function(preorder, inorder) {
// 重建二叉树
// 前序遍历:先中后左最后右,因此第一个是中节点
// 中序遍历:先左后中最后右,因此第一个是左树
const n = preorder.length
if(n === 0) return null
const build = (ps, pe, is, ie) => {
const mid = preorder[ps]
const midNode = new TreeNode(mid)
if(ps === pe || is === ie) {
return midNode
}
// im是中节点的inorder坐标,它同时代表:左子树的长度=im-is,右子树的长度是ie-im
let im
for(let i=is;i<=ie;i++) {
if(inorder[i] === mid) {
im = i
break
}
}
let leftLen = im-is, rightLen = ie-im
// 1,1,0,0
midNode.left = leftLen > 0 ? build(ps+1, ps+1+leftLen-1, is, is+leftLen-1) : null
midNode.right = rightLen > 0 ? build(ps+1+leftLen, ps+1+leftLen+rightLen-1, im+1, im+1+rightLen-1) : null
return midNode
}
return build(0, n-1, 0, n-1)
};
翻了一下过去的提交,发现自己又踩坑了,居然没记住这个结果:就是构建树的过程本来就是一个先序遍历,所以每次只要考虑inorderIndex就行了。
var buildTree = function(preorder, inorder) {
/**
* 其实 这个build的过程本身就是一个先序遍历,因此只需要设置一个全局自增的Index就能走先序遍历的过程了
* 所以 目前需要的下标就是中序遍历的left和right部分
*/
let index = 0
const inorderMap = new Map()
for(let i=0;i {
if(inLeft === inRight) {
return null
}
const rootVal = preorder[index]
const root = new TreeNode(rootVal)
const inorderIndex = inorderMap.get(rootVal)
index++
root.left = recBuild(inLeft, inorderIndex)
root.right = recBuild(inorderIndex+1, inRight)
return root
}
return recBuild(0, inorder.length)
};
面试题09. 用两个栈实现队列
简单题
var CQueue = function() {
this.s1 = []
this.s2 = []
};
/**
* @param {number} value
* @return {void}
*/
CQueue.prototype.appendTail = function(value) {
this.s1.push(value)
};
/**
* @return {number}
*/
CQueue.prototype.deleteHead = function() {
if(this.s2.length) return this.s2.pop()
if(!this.s1.length) return -1
while(this.s1.length) {
this.s2.push(this.s1.pop())
}
return this.s2.pop()
};
面试题10- I. 斐波那契数列
简单题
var fib = function(n) {
const cache = {
0: 0n,
1: 1n
};
return Fibonacci(n) % 1000000007n;
/**
* @param {number} n
* @return {number}
*/
function Fibonacci(n) {
if (cache[n] !== undefined) {
return cache[n];
}
cache[n] = Fibonacci(n - 1) + Fibonacci(n - 2);
return cache[n];
}
};
面试题10- II. 青蛙跳台阶问题
同上
面试题11. 旋转数组的最小数字
简单题
简单题的简单思路
var minArray = function(numbers) {
let i
for(i=numbers.length-1;i>0;i--) {
if(numbers[i]
但是这道题在leetcode原本的题库是困难题,因为它可以花样百出。
var minArray = function(numbers) {
let left = 0, right = numbers.length-1
while(left < right) {
let mid = Math.floor((left+right)/2)
// mid一定在数组的右边
if(numbers[mid]>numbers[right]) {
left = mid+1
// mid一定在数组的左边
} else if (numbers[mid]
面试题12. 矩阵中的路径
中等题
极速地写了一个,没有优化过,但是思路很明显,就是dfs。
const exist = function(board, word) {
const n = board.length
const m = board[0].length
const len = word.length
// 如果字母a,上下左右都有下一个字母,那就得全部都试一试啦,有一个达成和word一样就立马推出。
// travel 目前的位置i,j和目前走到word第几个w
const travel = (i,j,w, visit) => {
if(w===len-1) {
return true
}
// 看看左边?
if(j-1>=0 && board[i][j-1]===word[w+1]) {
let pos = `${i}-${j-1}`
if(!visit[pos]) {
if(travel(i,j-1,w+1,{[pos]:true,...visit})) return true
}
}
// 看看右边?
if(j+1=0 && board[i-1][j]===word[w+1]) {
let pos = `${i-1}-${j}`
if(!visit[pos]) {
if(travel(i-1,j,w+1,{[pos]:true,...visit})) return true
}
}
// 看看下边?
if(i+1
面试题13. 机器人的运动范围
中等题
Leetcode题解里的BFS,比我使用DFS复杂度低了。
空间复杂度O(N),时间复杂度O(N)
var movingCount = function(m, n, k) {
let res = 0;
const directions = [
[1, 0],
[0, 1]
];
const queue = [[0, 0]];
const visited = {
"0-0": true
}; // 标记 (x,y) 是否被访问过
while (queue.length) {
const [x, y] = queue.shift();
// (x, y) 的数位之和不符合要求
// 题目要求节点每次只能走1格,所以无法从当前坐标继续出发
if (bitSum(x) + bitSum(y) > k) {
continue;
}
++res;
for (const direction of directions) {
const newx = direction[0] + x;
const newy = direction[1] + y;
if (
!visited[`${newx}-${newy}`] &&
newx >= 0 &&
newy >= 0 &&
newx < m &&
newy < n
) {
queue.push([newx, newy]);
visited[`${newx}-${newy}`] = true;
}
}
}
return res;
};
function bitSum (n) {
let res = 0
while(n) {
res += n%10
n = Math.floor(n/10)
}
return res
}
面试题16. 数值的整数次方
中等题
先写个普通循环版本,果不其然超时了。
var myPow = function(x, n) {
let res = 1
if(n >= 0) {
while(n--) {
res *= x
}
} else {
while( (n++)!=0 ) {
res /= x
}
}
return res
};
想了下可以用递归来实现二分思想。
如果指数是偶数的情况下:Pow(base,exponent) = Pow(base,exponent/2)*Pow(base,exponent)
指数是奇数的情况下:Pow(base,exponent) = base * Pow(base,exponent/2) * Pow(base,exponent/2)
再区分下奇数偶数的情况,就能成了:
var myPow = function(x, n) {
const pow = (x,n) => {
if(n===0) {
return 1
}
if(n===1) {
return x
}
const exp = Math.floor(n/2)
return n % 2 === 1 ? x*pow(x,exp)*pow(x,exp):pow(x,exp)*pow(x,exp)
}
return n >= 0 ? pow(x,n) : 1/pow(x,-n)
};
突然想到之前学计算机安全导论的时候学过一个叫快速幂的算法,这里用来试试。
var myPow = function(x, n) {
const pow = (x,n) => {
let ans = 1
while(n) {
if(n & 1) {
ans *= x
}
x *= x
n = Math.floor(n/2)
}
return ans
}
return n >= 0 ? pow(x,n) : 1/pow(x,-n)
};
快速幂的思想是:
面试题17. 打印从1到最大的n位数
简单题
直接调用API辣,其实也可以用上面的快速幂计算10的n次幂
var printNumbers = function(n) {
const res = []
const max = 10 ** n - 1
for(let i=1;i<=max;i++) {
res.push(i)
}
return res
};
面试题18. 删除链表的节点
var deleteNode = function(head, val) {
if(head.val === val) {
head = head.next
return head
}
let node = head
while(node.next) {
if(node.next.val === val) {
node.next = node.next.next
break
}
node = node.next
}
return head
};
面试题19. 正则表达式匹配
简单题
回溯法
var isMatch = function(s, p) {
const _isMatch = (i, j) => {
if(i === s.length && j===p.length) {
return true
}
if(i !== s.length && j === p.length) {
return false
}
if(p[j+1] === '*') {
if(s[i]===p[j] || (p[j]==='.' && i !== s.length)) {
return _isMatch(i+1,j) || _isMatch(i,j+2)
} else {
return _isMatch(i,j+2)
}
}
if (s[i]===p[j] || (p[j]==='.' && i !== s.length)){
return _isMatch(i+1, j+1)
} else {
return false
}
}
return _isMatch(0,0)
};
322. 零钱兑换
回溯法剪枝依然超时,所以应该用动态规划
/**
* @param {number[]} coins
* @param {number} amount
* @return {number}
*/
var coinChange = function(coins, amount) {
/**
* 最优化问题
* 可以用回溯法和动态规划法解决
* 优化目标:硬币数量最少
* 先用回溯法莽一波
* 动态规划也可以减少解空间
*/
/**
* rest: 剩余金额
* count: 使用硬币数
*/
let res = Infinity
const sub = (rest, count) => {
// 剪枝
if(count > res) {
return
}
if(rest === 0) {
res = Math.min(count, res)
return
} else if(rest < Math.min(coins)) {
return
}
// 剩余情况
for(const coin of coins) {
sub(rest-coin, count+1)
}
}
sub(amount, 0)
return res === Infinity ? -1 : res
};
var coinChange = function(coins, amount) {
/**
* 最优化问题
* 可以用回溯法和动态规划法解决
* 优化目标:硬币数量最少
* 先用回溯法莽一波
* 动态规划也可以减少解空间
*/
// dp是此金额花费的硬币数
const dp = Array(amount+1).fill(Infinity)
dp[0] = 0
for(let i=1;i<=amount;i++) {
for(let coin of coins) {
if(i-coin>=0) {
dp[i] = Math.min(dp[i], dp[i-coin]+1)
}
}
}
return dp[amount] === Infinity ? -1 : dp[amount]
};
面试题37. 序列化二叉树
var serialize = function(root) {
// 用层次遍历来读入
if(!root) return '[]'
let res = '['
const queue = [root]
while(queue.length) {
const t = queue.shift()
if(t === null) {
res+='null,'
}else {
res+=`${t.val},`
}
// null节点不会再加入它们的子节点而导致出错
if(!t) continue
queue.push(t.left)
queue.push(t.right)
}
return res.slice(0, res.length-1) + ']'
};
/**
* Decodes your encoded data to tree.
*
* @param {string} data
* @return {TreeNode}
*/
var deserialize = function(data) {
if(data === '[]' || !data) return null
data = data.slice(1,data.length-1).split(',')
let index = 0
const head = getTreeNode(data[index++])
const queue = [head]
while(queue.length) {
let node = queue.shift()
node.left = getTreeNode(data[index++])
node.right = getTreeNode(data[index++])
node.left && queue.push(node.left)
node.right && queue.push(node.right)
}
return head
};
const getTreeNode = (value) => {
if(value === 'null') return null
else return new TreeNode(Number(value))
}
/**
* Your functions will be called as such:
* deserialize(serialize(root));
*/
面试题38. 字符串的排列
/**
* @param {string} s
* @return {string[]}
*/
var permutation = function(s) {
if (!s.length) {
return [];
}
const result = [];
_permutation(s.split(""), 0, result);
return result
};
/**
* 回溯遍历得到所有的结果
*
* @param {string[]} strs
* @param {number} start
* @param {string[]} result
*/
function _permutation(strs, start, result) {
if (start === strs.length) {
result.push(strs.join(""));
return;
}
const map = {}
for (let i = start; i < strs.length; ++i) {
if (map[strs[i]]) {
// 进行剪枝
continue;
}
map[strs[i]] = true;
[strs[i], strs[start]] = [strs[start], strs[i]];
_permutation(strs, start + 1, result);
[strs[i], strs[start]] = [strs[start], strs[i]];
}
}
面试题39. 数组中出现次数超过一半的数字
var majorityElement = function(nums) {
/**
* 摩尔投票法
*/
let count = 1
let res = nums[0]
for(let n of nums) {
if(n !== res) {
count--
if(count === 0) {
res = n
count = 1
}
} else {
count++
}
}
return res
};