看了一下,LeetCode
上剑指OFFER
的题目大概有70道左右,难度大多为简单到中等。但是如果放开思路,深究多种解法,难度也并不低。总之适合我等菜鸟,先记录在此,并立下🏳️🏳️🏳️ 🚩🚩🚩:
这些算法题,我要全部吃掉!!!
当然,这里只记录一些常规的,或者比较不错的算法,其中也有自己的思考,但是文字注释并不多,很适合下次光临回味的那种。每一篇blog
大概3~4
道题的记录量,那么,话不多说。
287. 寻找重复数
给定一个包含 n + 1 个整数的数组 nums
,其数字都在 1
到 n
之间(包括 1
和 n
),可知至少存在一个重复的整数。假设只有一个重复的整数,找出这个重复的数。
示例 1:
输入: [1,3,4,2,2]
输出: 2
示例 2:
输入: [3,1,3,4,2]
输出: 3
说明:
- 不能更改原数组(假设数组是只读的)。
- 只能使用额外的
O(1)
的空间。 - 时间复杂度小于
O(n2)
。 - 数组中只有一个重复的数字,但它可能不止重复出现一次。
如果没有说明中的限制,这里有很多种做法。比如开辟一个哈希表,或者开辟一个数组,其索引对应的就是其应该正确放置的值。这些方法就保证了时间优先的原则。
var findRepeatNumber = function(nums) {
// set
const s = new Set();
for (let num of nums) {
if (s.has(num)) {
return num;
}
s.add(num);
}
};
此外,也可以利用O(nlogn)
的时间复杂度将数组排序,那么重复的数字也就很明显了。
如果对空间排序有要求,就像题目所说的那样,就可以通过抽屉原理来解决,抽屉原理是啥?
抽屉原理的一般含义为:“如果每个抽屉代表一个集合,每一个苹果就可以代表一个元素,假如有n+1个元素放到n个集合中去,其中必定有一个集合里至少有两个元素。
对此,我们可以通过不断将元素放置到其正确的抽屉处,如此循环。这样就能保证在一段时间内发现一个抽屉具有两个元素,从而找到重复的数字。具体思路如下:
从头扫描数组,遇到下标为i
的元素如果不等于i
的话(假设等于j
),就将其与下标为j
的元素互换,每一次互换就确保了某个数字正确归位,因此,如果数组中存在重复的元素,则必然会引起冲突
,即当前元素值与待交换的值相等,这样就找到了重复的元素。
var findRepeatNumber = function(nums) {
// 抽屉原理
const n = nums.length;
for (let i = 0; i < n; i++) {
let val = nums[i]
while (val !== i) {
if (val === nums[val]) {
return val
}
// python 这里有坑
[nums[val], nums[i]] = [nums[i], nums[val]];
}
}
};
面试题07. 重建二叉树
输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。
例如,给出
前序遍历 preorder = [3,9,20,15,7]
中序遍历 inorder = [9,3,15,20,7]
返回如下的二叉树:
3
/ \
9 20
/ \
15 7
限制:
0 <= 节点个数 <= 5000
方法一:递归
整体来看逻辑比较复杂,但是用递归进行分而治之能很简单地解决。算法的基本思路在于将重建二叉树的复杂度平摊到各个子树的递归构建上,要构建子树,就需要确定子树相应的前序遍历和中序遍历。对此就可以利用前序遍历和中序遍历的特点来求解。首先preorder
的第一个节点为当前根节点,而中序遍历的遍历策略为左=>根=>右
,因此利用inorder
求上面根节点的索引就不难找出相应的左右子树范围,递归建树即可,停止条件为当前仅当列表中只有一个节点,直接返回。
var buildTree = function(preorder, inorder) {
if (preorder.length < 1) return null;
const root = new TreeNode(preorder[0]);
if (preorder.length === 1) return root;
const ind = inorder.findIndex(node => node === root.val);
const leftInorder = inorder.slice(0, ind);
const rightInorder = inorder.slice(ind+1);
root.left = buildTree(preorder.slice(1, 1+ind), leftInorder);
root.right = buildTree(preorder.slice(1+ind), rightInorder);
return root;
};
面试题05. 替换空格
请实现一个函数,把字符串 s
中的每个空格替换成"%20"。
示例 1:
输入:s = "We are happy."
输出:"We%20are%20happy."
限制:
0 <= s 的长度 <= 10000
方法一:快捷方法
最简单的方法就是通过循环遍历原字符串,一旦遇到空格则在后续str += '20%'
即可,问题不大。当然也可以做顺风车,直接利用字符串中成熟的API
就可以得到结果:
str.split(' ').join('20%');
方法二:双指针从后向前填充法
对于源字符串而言,只要存在一个空格字符,就将其变为20%
,也就是每一个空格就会使得结果字符串的长度多出2个。出于对原字符串就地修改的想法,首先遍历原字符串找到空格个数,然后在原字符串的末尾扩充相应的空白待填空间,设置两个指针p1, p2
分别指向原字符串末尾和扩充后的字符串末尾,然后两个指针向前移动,如果p1
指针指向了原字符中的空格字符,则在p2
指针处依次填充0、2、%
字符,否则就简单将p1
指针的字符赋值给p2
即可,直到p1,p2
的指向相同,则退出。
var replaceSpace = function(s) {
const n = s.length;
if (n === 0) return '';
// 统计空格个数
let res = [],
cnt = 0;
for (let i of s) {
if (i === ' ') {
cnt += 1;
}
res.push(i);
}
// JS 字符 immutable 性质
// 转换为数组操作
res = [...res, ...new Array(2*cnt).fill(' ')];
let [p1, p2] = [n-1, res.length-1];
while (p1 >= 0 && p2 > p1) {
if (res[p1] === ' ') {
res[p2--] = '0';
res[p2--] = '2';
res[p2--] = '%';
p1--;
continue;
}
res[p2--] = res[p1--];
}
return res.join('');
};
虽然此方法的初衷在于减小空间复杂度。但是因为JavaScript
的immutable
性质,似乎只有通过开辟结果数组来辅助实现,或许我还没有想到新的方法。。。
面试题09. 用两个栈实现队列
用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail
和 deleteHead
,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead
操作返回 -1 )
示例 1:
输入:
["CQueue","appendTail","deleteHead","deleteHead"]
[[],[3],[],[]]
输出:[null,null,3,-1]
示例 2:
输入:
["CQueue","deleteHead","appendTail","appendTail","deleteHead","deleteHead"]
[[],[],[5],[2],[],[]]
输出:[null,-1,null,null,5,2]
提示:
1 <= values <= 10000
最多会对 appendTail、deleteHead 进行 10000 次调用
将其中一个栈stack1
作为数据压入存放的栈,将第二个栈stack2
作为数据弹出的栈。基本思路为:当执行所谓队列的appendTail
操作的时候,stack1
执行相应逻辑。如果执行deleteHead
,分两种情况讨论,如果stack2
为空的话,则考虑将stack1
中的所有数据pop
到stack2
中,然后弹出stack2
顶部即可,这样就实现了队列的弹出顺序,当然stack1
里也没有数据的时候,就如题意返回-1
即可。
var CQueue = function() {
this.stack1 = [];
this.stack2 = [];
};
/**
* @param {number} value
* @return {void}
*/
CQueue.prototype.appendTail = function(value) {
this.stack1.push(value);
};
/**
* @return {number}
*/
CQueue.prototype.deleteHead = function() {
if (this.stack2.length === 0) {
if (this.stack1.length > 0) {
while (this.stack1.length > 0) {
const val = this.stack1.pop();
this.stack2.push(val);
}
} else {
return -1;
}
}
const top = this.stack2.pop();
return top;
};
当然这里利用两个栈实现了队列,换一个思想,我们也可以通过两个队列来实现一个栈…具体怎么操作,闭上眼睛,感受一下。