24 小时编程面试准备挑战
第一天(2020年1月4日)
我不太相信用60分钟的编程挑战来衡量开发者的技能。大多数情况下,这些问题的设计都使得如果你掌握了算法,15分钟就能解决。我记不清自己何时在如此短的时间内解决过任何棘手的实际问题。任何复杂的问题都需要时间来解决。
然而,我周一,也就是1月6日,有两场编程面试。所以,与其抱怨面试过程,我决定从明天,也就是2020年1月4日早上8点开始,一直到1月5日晚上8点,连续24小时学习数据结构和算法。
我会定期更新我的进度,发布笔记和代码。以下是我计划探讨的主题:
- 数组和字符串
- 链表
- 栈和队列
- 树木
- 图表
- 位操作
- 递归
- 动态规划
- 搜索和排序
我计划阅读Gayle Laakmann McDowell 的《破解编程面试》和Michael McMillan 的《JavaScript 数据结构与算法:将经典计算方法引入 Web》 。
我会阅读其他博客文章,尽可能多地解决编程问题,并与开发者社区分享。
请分享一下你所知道的好的面试准备资源。我希望这篇文章和讨论能成为一个非常有用的参考平台,帮助那些时间紧迫、准备编程面试的人。
第一天(2020年1月4日)
我早上 10 点开始了 24 小时编程面试准备挑战。一杯香浓的咖啡已经准备好,我即将攻克编程面试难题。以下是我今天要学习的主题。
- 数组
- 细绳
- 链表
- 堆栈
- 队列
- 树木
- 图表
- 搜索
我会认真学习这些主题,记录所有有用的资源,解决每个主题下 5 到 10 道题,并每两小时向社区汇报一次进度。我相信社区可以成为我很好的监督伙伴。
请分享一下您关于如何快速掌握数据结构的最佳想法。非常感谢您的任何建议。
下次更新时间为中午12点。
下午 12:00 更新
- 我读了这本书的第一章
Cracking the Coding Interview。 - 以下是我解决过的编程问题:
数组和字符串
1. 唯一性判断:实现一个算法来判断一个字符串是否包含所有唯一字符。如果不能使用其他数据结构该怎么办?
function uniqueCharacters(str) {
const ASCII_CHARS = 256;
if(str.length > ASCII_CHARS) {
return false;
}
const chars = Array(ASCII_CHARS).fill(false);
for(let char of str){
const index = char.charCodeAt(0);
if (chars[index] === true) {
return false;
}
chars[index] = true;
}
return true;
}
console.log(uniqueCharacters('abcd10jk')); // true
console.log(uniqueCharacters('hutg9mnd!nk9')); // false
2. 检查排列:给定两个字符串,编写一个方法来判断其中一个字符串是否是另一个字符串的排列。
function isPermutation(str1, str2) {
if (str1.length !== str2.length) {
return false;
}
const map = new Map();
for(const char of str1) {
if(map.has(char)) {
map.set(char, map.get(char) + 1);
} else {
map.set(char, 1);
}
}
for(const char of str2) {
if(map.has(char)) {
const value = map.get(char);
if(value === 0) {
return false;
} else {
map.set(char, map.get(char) - 1);
}
}
}
for(const value of map.values()) {
if(value !== 0) {
return false;
}
}
return true;
}
console.log(isPermutation("god", "dog")); // true
3. URLify:编写一个方法,将字符串中的所有空格替换为'%20'。
function replaceSpaces(str, trueLength) {
let output = "";
let chars = 0;
for(let char of str) {
if(char !== " ") {
chars++;
}
}
let spaces = trueLength - chars;
for(let char of str) {
if(char === " " && spaces > 0) {
output += "%20";
spaces--;
} else if(char !== " ") {
output += char;
}
}
while(spaces > 0) {
output += "%20";
spaces--;
}
return output;
}
console.log(replaceSpaces("Mr 3ohn Smith", 13)); // "Mr%203ohn%20Smith"
3. 回文排列:给定一个字符串,编写一个函数来检查它是否是某个回文的排列。
function isPermutationOfPalindrome(str) {
let charCount = 0;
let map = new Map();
for(let char of str) {
if (char === " ") {
continue;
}
if(map.has(char)) {
map.delete(char);
} else {
map.set(char, true);
}
charCount++;
}
if(charCount % 2 === 0) {
return map.size === 0;
} else {
return map.size === 1;
}
}
console.log(
isPermutationOfPalindrome("tacoa cat") === true,
isPermutationOfPalindrome("atco cat") === true,
isPermutationOfPalindrome(" rac ecar rara ") === true
);
4. 字符串压缩:实现一个使用重复字符计数执行基本字符串压缩的方法。
function compress (str) {
const map = new Map();
let result = '';
for(const char of str) {
if(map.has(char)) {
map.set(char, map.get(char) + 1);
} else {
map.set(char, 1);
}
}
for(let [key, value] of map) {
result += key + value;
}
return str.lenght >= result.lenght ? str : result;
}
console.log(compress('aabcccccaaa')); // "a5b1c5"
5. 旋转矩阵:给定一个用 NxN 矩阵表示的图像,其中图像中的每个像素为 4 字节,编写一个方法将图像旋转 90 度。
function rotateMatrix(arr) {
let n = arr.length - 1;
for (let i = 0; i < n; i++) {
for (let j = 0; j < n - i; j++) {
let temp = arr[i][j];
arr[i][j] = arr[n - j][n - i]; // top row
arr[n - j][n - i] = temp; // right column
}
}
return arr;
}
console.log(rotateMatrix([
[1,2,3],
[4,5,6],
[7,8,9]
]));
/*
[
[9, 6, 3],
[8, 5, 2],
[7, 4, 1]
]
*/
6. 字符串旋转;假设你有一个 isSubstring 方法,用于检查一个单词是否是另一个单词的子字符串。给定两个字符串 s1 和 s2,编写代码,仅使用一次 isSubstring 调用来检查 s2 是否是 s1 的旋转(例如,“waterbottle” 是“P'erbottlewat” 的旋转)。
const isSubstring = (str1, str2) => str1.includes(str2);
function isRotation(str1, str2) {
if(!str1 || !str2) {
return false;
}
if(str1.length !== str2.length) {
return false;
}
return isSubstring(str1 + str2, str2);
}
console.log(isRotation("waterbottle", "erbottlewat")); // true
console.log(isRotation("aacdf", "acda")); // false
7. 查找给定字符串或数组中的第一个唯一字符
function findFirstUniqueCharacter(str) {
if(!str) return;
const map = new Map();
for(let char of str) {
if(map.has(char)) {
map.set(char, map.get(char) + 1);
} else {
map.set(char, 1);
}
}
for(let [key, value] of map) {
if(value === 1) {
return key;
}
}
return "None";
}
console.log(findFirstUniqueCharacter('foobar')); // f
console.log(findFirstUniqueCharacter('aabbccdef')); // d
console.log(findFirstUniqueCharacter('aabbcc')); // 'No Unique Character Found'
下午3:30更新
链表
链表Node类
class Node {
constructor(value, next = null) {
this.value = value;
this.next = next;
}
}
链表print函数
function print(node) {
while(node !== null) {
console.log('->' + node.value);
node = node.next;
}
}
1. 删除重复项:编写代码从无序链表中删除重复项。
function removeDups(node) {
if(node === null) return null;
let nodes = new Map();
let current = node.next;
let prev = node;
nodes.set(node.value, 1);
while(current !== null) {
if(nodes.get(current.value) === 1) {
prev.next = current.next;
} else {
nodes.set(current.value, 1);
prev = current;
}
current = current.next;
}
return node;
}
let node1 = new Node(1);
let node2 = new Node(2);
let node3 = new Node(3);
let node4 = new Node(2);
let node5 = new Node(1);
node1.next = node2;
node2.next = node3;
node3.next = node4;
node4.next = node5;
node5.next = null;
print(removeDups(node1));
/*
"->1"
"->2"
"->3"
*/
2. 返回第 K 个元素到最后一个元素:实现一个算法来查找单链表的第 k 个元素到最后一个元素。
function KthToLast(root, n) {
if(!root) return null;
let current = root;
let follower = root;
for(let i = 0; i < n; i++) {
current = current.next;
}
while(current.next !== null) {
current = current.next;
follower = follower.next;
}
return follower;
}
let node1 = new Node(1);
let node2 = new Node(2);
let node3 = new Node(3);
let node4 = new Node(4);
let node5 = new Node(5);
node1.next = node2;
node2.next = node3;
node3.next = node4;
node4.next = node5;
node5.next = null;
console.log(KthToLast(node1, 2).value); // 3
3. 删除链表中间部分
function removeDups(head) {
if(head === null) return null;
let fast = head;
let slow = head;
let prev = null;
while(fast !== null && fast.next !== null) {
fast = fast.next.next;
prev = slow;
slow = slow.next;
}
prev.next = slow.next;
return head;
}
let nodeA = new Node('a');
let nodeB = new Node('b');
let nodeC = new Node('c');
let nodeD = new Node('d');
let nodeE = new Node('e');
let nodeF = new Node('f');
nodeA.next = nodeB;
nodeB.next = nodeC;
nodeC.next = nodeD;
nodeD.next = nodeE;
nodeE.next = nodeF;
nodeF.next = null;
print(removeDups(nodeA));
/*
"->a"
"->b"
"->c"
"->e"
"->f"
*/
4. 根据给定值对链表进行分区,如果我们不关心链表元素的构成,stable
function partition(head, x) {
let tail = head;
let current = head;
while(current !== null) {
let next = current.next;
if(current.value < x) {
current.next = head;
head = current;
} else {
tail.next = current;
tail = current;
}
current = next;
}
tail.next = null;
return head;
}
let nodeA = new Node(3);
let nodeB = new Node(5);
let nodeC = new Node(8);
let nodeD = new Node(2);
let nodeE = new Node(10);
let nodeF = new Node(2);
let nodeH = new Node(1);
nodeA.next = nodeB;
nodeB.next = nodeC;
nodeC.next = nodeD;
nodeD.next = nodeE;
nodeE.next = nodeF;
nodeF.next = nodeH;
nodeH.next = null;
print(partition(nodeA, 5));
/*
"->1"
"->2"
"->2"
"->3"
"->5"
"->8"
"->10"
*/
5. 给定一个链表,其中每个节点有两个指针,一个指向下一个节点,一个指向链表中的随机节点,克隆该链表。
function clone(node) {
if(node === null) return node;
let map = new Map();
let copy = new Node(node.value);
let current = node;
let newHead = copy;
map.set(current, copy);
current = current.next;
while(current !== null) {
let temp = new Node(current.value);
map.set(current, temp);
copy.next = temp;
copy = temp;
current = current.next;
}
current = node;
copy = newHead;
while(current !== null) {
if(current.randome !== null) {
copy.random = map.get(current.random);
} else {
copy.random = null;
}
current = current.next;
copy = copy.next;
}
return newHead;
}
6. 给定一个链表,判断它是否包含环。
function hasCycle(node) {
if(node === null) return false;
let slow = node;
let fast = node.next;
while(fast !== null && fast.next !== null) {
if(fast === slow) return true;
slow = slow.next;
fast = fast.next.next;
}
return false;
}
let node1 = new Node(1);
let node2 = new Node(2);
let node3 = new Node(3);
let node4 = new Node(4);
let node5 = new Node(5);
node1.next = node2;
node2.next = node3;
node3.next = node4;
node4.next = node5;
node5.next = node3;
console.log(hasCycle(node1)); // true
7. 如何一次性找到链表的中间元素?
function findMiddle(node) {
if(node === null) return null;
let fast = node.next;
let slow = node;
while(fast !== null && fast.next !== null) {
fast = fast.next.next;
slow = slow.next;
}
console.log(slow.value);
}
let node1 = new Node(5);
let node2 = new Node(6);
let node3 = new Node(7);
let node4 = new Node(1);
let node5 = new Node(2);
node1.next = node2;
node2.next = node3;
node3.next = node4;
node4.next = node5;
node5.next = null;
findMiddle(node1); // 7
8. 将两个数相加
// Input: (2 -> 4 -> 3) + (5 -> 6 -> 4)
// Output: 7 -> 0 -> 8
// Explanation: 342 + 465 = 807.
const addTwoNumbers = function(l1, l2) {
let head = new Node(0);
let carry = 0;
let current = head;
while(l1 !== null || l2 !== null ) {
const x = l1 !== null ? l1.value : 0;
const y = l2 !== null ? l2.value : 0;
const sum = carry + x + y;
carry = Math.floor(sum / 10);
current.next = new Node(sum % 10);
current = current.next;
if(l1 != null) l1 = l1.next;
if(l2 != null) l2 = l2.next;
}
if (carry > 0) {
current.next = new ListNode(carry);
}
return head.next;
};
const node13 = new Node(3);
const node12 = new Node(4);
const node11 = new Node(2);
node11.next = node12;
node12.next = node13;
const l1 = node11;
const node23 = new Node(4);
const node22 = new Node(6);
const node21 = new Node(5);
node22.next = node23;
node21.next = node22;
const l2 = node21;
print(addTwoNumbers(l1, l2));
/*
"->7"
"->0"
"->8"
*/
晚上7:45更新
堆栈
1. 给定一个栈,使用不超过一个额外的栈对栈中的元素进行排序。
Array.prototype.peek = function() {
return this[this.length -1];
}
Array.prototype.isEmpty = function() {
return this.length <= 0;
}
function sortStack(stack) {
if(!stack || stack.length ===0) return stack;
let newStack = [];
newStack.push(stack.pop());
while(!stack.isEmpty()) {
const temp = stack.pop();
while(!newStack.isEmpty() && temp > newStack.peek()) {
stack.push(newStack.pop());
}
newStack.push(temp);
}
return newStack;
}
console.log(sortStack([5, 1, 3, 2, 4])); // [5, 4, 3, 2, 1]
2. 评估 Javascript 中的逆波兰表示法
function reversePolishNotation(seq) {
if(seq.length <= 2) {
return;
}
const operands = ['+', '-', '*', '/'];
const stack = [];
let i = 0;
stack.push(seq[i]);
i++;
while(i <= seq.length) {
let item = seq[i];
let index = operands.indexOf(item);
if(index < 0) {
stack.push(item);
} else {
const a = parseInt(stack.pop(), 10);
const b = parseInt(stack.pop(), 10);
if(index === 0) {
stack.push(a + b);
}
if(index === 1) {
stack.push(a - b);
}
if(index === 2) {
stack.push(a * b);
}
if(index === 3) {
stack.push(b/a);
}
}
i++;
}
return parseInt(stack[0], 0);
}
console.log(reversePolishNotation(["2", "1", "+", "3", "*"])) // 9
console.log(reversePolishNotation(["4", "13", "5", "/", "+"])) // 6
console.log(reversePolishNotation(["10", "6", "9", "3", "+", "-11", "*", "/", "*", "17", "+", "5", "+"])) // 22
console.log(reversePolishNotation(["2", "1"])) // undefined
3. 使用 JavaScript 实现栈
function Stack() {
this.top = null;
}
Stack.prototype.push = function (val) {
let node = {
data : val,
next : null
};
node.next = this.top;
this.top = node;
};
Stack.prototype.pop = function () {
if(this.top === null) {
return null;
} else {
const top = this.top;
this.top = this.top.next;
return top.data;
}
};
Stack.prototype.peek = function() {
if(this.top === null) {
return null;
} else {
return this.top.data;
}
}
var S1 = new Stack();
S1.push(5);
S1.push(6);
S1.push(7);
S1.push(8);
console.log(S1.pop()); // 8
console.log(S1.pop()); // 7
4. 使用两个栈的队列(JavaScript)
function Queue() {
this.pushStack = new Stack();
this.popStack = new Stack();
}
Queue.prototype.enqueue = function(value) {
this.pushStack.push(value);
}
Queue.prototype.dequeue = function() {
let popStack = this.popStack;
let pushStack = this.pushStack;
if(popStack.peek()) {
const deq = popStack.pop();
return deq;
}
while(pushStack.peek()) {
popStack.push(pushStack.pop());
}
}
const q1 = new Queue();
q1.enqueue(3);
q1.enqueue(4);
q1.enqueue(5);
q1.enqueue(6);
q1.enqueue(7);
q1.dequeue();
q1.dequeue();
q1.dequeue();
console.log(q1);
5.
function stepsToSolveTowerOfHanoi(height, from, to, via) {
if (height >= 1) {
// Move a tower of height - 1 to buffer peg using destination peg
stepsToSolveTowerOfHanoi(height - 1, from, via, to);
// Move the remaining disk to the destination peg.
console.log(`Move disk ${height} from Tower ${from} to Tower ${to}`);
// Move the tower of `height-1` from the `buffer peg` to the `destination peg` using the `source peg`.
stepsToSolveTowerOfHanoi(height - 1, via, to, from);
}
return;
}
stepsToSolveTowerOfHanoi(3, "A", "C", "B");
/*
"Move disk 1 from Tower A to Tower C"
"Move disk 2 from Tower A to Tower B"
"Move disk 1 from Tower C to Tower B"
"Move disk 3 from Tower A to Tower C"
"Move disk 1 from Tower B to Tower A"
"Move disk 2 from Tower B to Tower C"
"Move disk 1 from Tower A to Tower C"
*/
6. 判断一个字符串是否由有效的括号组成。
function isMatchingBrackets(str) {
if(str.length <= 1) {
return false;
}
let stack = [];
const map = {
'{': '}',
'[': ']',
'(': ')'
};
for(let char of str) {
if(char === '(' || char === '[' || char === '{') {
stack.push(char);
} else {
const last = stack.pop();
if (char !== map[last]) {
return false;
}
}
}
return stack.length === 0;
}
console.log(isMatchingBrackets("(){}")); // true
console.log(isMatchingBrackets("[{()()}({[]})]({}[({})])((((((()[])){}))[]{{{({({({{{{{{}}}}}})})})}}}))[][][]")); // true
console.log(isMatchingBrackets("({(()))}}")); // false

