diff --git a/README.md b/README.md index ded19a67..adbbcc5d 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,10 @@ - [算法](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/算法.md) 排序、并查集、栈和队列、红黑树、散列表。 + + - [面试算法整理](https://github.com/haiker2011/Interview-Notebook/blob/haiker2011-patch-ownInterview/notes/面试总结.md) + + 主要是对公司面试的算法题目进行总结整理,方便自己查阅。 ### :computer: 操作系统 diff --git a/notes/面试总结.md b/notes/面试总结.md index 479b25b2..0cbc8605 100644 --- a/notes/面试总结.md +++ b/notes/面试总结.md @@ -2,6 +2,16 @@ * [1. 字符串组合](#1-字符串组合) * [2. 整数组合求和](#2-整数组合求和) * [3. 数组中重复的数字](#3-数组中重复的数字) +* [4. 二维数组中的查找](#4-二维数组中的查找) +* [5. 替换空格](#5-替换空格) +* [6. 从尾到头打印链表](#6-从尾到头打印链表) +* [7. 重建二叉树](#7-重建二叉树) +* [8. 二叉树的下一个结点](#8-二叉树的下一个结点) +* [9. 用两个栈实现队列](#9-用两个栈实现队列) +* [10.1 斐波那契数列](#101-斐波那契数列) +* [10.2 跳台阶](#102-跳台阶) +* [10.3 矩形覆盖](#103-矩形覆盖) +* [10.4 变态跳台阶](#104-变态跳台阶) * [参考文献](#参考文献) @@ -178,7 +188,609 @@ private void swap(int[] nums, int i, int j) { } ``` +```python +nums = [int(k) for k in raw_input().split(" ")] +print nums +def duplicate(nums): + if len(nums) <= 0: + return -1, False + for i in range(len(nums)): + while nums[i] != i: + if nums[i] == nums[nums[i]]: + return nums[i], True + t = nums[i] + nums[i] = nums[nums[i]] + nums[nums[i]] = t + # nums[i], nums[nums[i]] = nums[nums[i]], nums[i] + return -1, False + +print duplicate(nums) +``` + +# 4. 二维数组中的查找 + +[NowCoder](https://www.nowcoder.com/practice/abc3fe2ce8e146608e868a70efebf62e?tpId=13&tqId=11154&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) + +## 题目描述 + +在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。 + +```html +Consider the following matrix: +[ + [1, 4, 7, 11, 15], + [2, 5, 8, 12, 19], + [3, 6, 9, 16, 22], + [10, 13, 14, 17, 24], + [18, 21, 23, 26, 30] +] + +Given target = 5, return true. +Given target = 20, return false. +``` + +## 解题思路 + +从右上角开始查找。矩阵中的一个数,它左边的数都比它小,下边的数都比它大。因此,从右上角开始查找,就可以根据 target 和当前元素的大小关系来缩小查找区间。 + +复杂度:O(M + N) + O(1) + +当前元素的查找区间为左下角的所有元素,例如元素 12 的查找区间如下: + +

+ +```java +public boolean Find(int target, int[][] matrix) { + if (matrix == null || matrix.length == 0 || matrix[0].length == 0) + return false; + int rows = matrix.length, cols = matrix[0].length; + int r = 0, c = cols - 1; // 从右上角开始 + while (r <= rows - 1 && c >= 0) { + if (target == matrix[r][c]) + return true; + else if (target > matrix[r][c]) + r++; + else + c--; + } + return false; +} +``` + +```python +target = int(input()) + +# nums = [[1, 4, 7, 11, 15], [2, 5, 8, 12, 19], [3, 6, 9, 16, 22], [10, 13, 14, 17, 24], [18, 21, 23, 26, 30]] + +nums = eval(input()) + +def find(target, matrix): + if len(matrix) == 0 or len(matrix[0]) == 0: + return False + + rows, cols = len(matrix), len(matrix[0]) + r, c = 0, cols - 1 + + while r <= rows - 1 and c >= 0: + if target == matrix[r][c]: + return True + elif target > matrix[r][c]: + r += 1 + else: + c -= 1 + return False + +print (find(target, nums)) +``` + +# 5. 替换空格 + +[NowCoder](https://www.nowcoder.com/practice/4060ac7e3e404ad1a894ef3e17650423?tpId=13&tqId=11155&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) + +## 题目描述 + + +将一个字符串中的空格替换成 "%20"。 + +```text +Input: +"We Are Happy" + +Output: +"We%20Are%20Happy" +``` + +## 解题思路 + +在字符串尾部填充任意字符,使得字符串的长度等于替换之后的长度。因为一个空格要替换成三个字符(%20),因此当遍历到一个空格时,需要在尾部填充两个任意字符。 + +令 P1 指向字符串原来的末尾位置,P2 指向字符串现在的末尾位置。P1 和 P2从后向前遍历,当 P1 遍历到一个空格时,就需要令 P2 指向的位置依次填充 02%(注意是逆序的),否则就填充上 P1 指向字符的值。 + +从后向前遍是为了在改变 P2 所指向的内容时,不会影响到 P1 遍历原来字符串的内容。 + +```java +public String replaceSpace(StringBuffer str) { + int P1 = str.length() - 1; + for (int i = 0; i < P1 + 1; i++) + if (str.charAt(i) == ' ') + str.append(" "); + + int P2 = str.length() - 1; + while (P1 >= 0 && P2 > P1) { + char c = str.charAt(P1--); + if (c == ' ') { + str.setCharAt(P2--, '0'); + str.setCharAt(P2--, '2'); + str.setCharAt(P2--, '%'); + } else { + str.setCharAt(P2--, c); + } + } + return str.toString(); +} +``` + +```python +target = input() + +print (target.replace(" ", "%20")) +``` + + +# 6. 从尾到头打印链表 + +[NowCoder](https://www.nowcoder.com/practice/d0267f7f55b3412ba93bd35cfa8e8035?tpId=13&tqId=11156&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) + +## 题目描述 + +输入链表的第一个节点,从尾到头反过来打印出每个结点的值。 + +

+ +## 解题思路 + +### 使用栈 + +```java +public ArrayList printListFromTailToHead(ListNode listNode) { + Stack stack = new Stack<>(); + while (listNode != null) { + stack.add(listNode.val); + listNode = listNode.next; + } + ArrayList ret = new ArrayList<>(); + while (!stack.isEmpty()) + ret.add(stack.pop()); + return ret; +} +``` + +### 使用递归 + +```java +public ArrayList printListFromTailToHead(ListNode listNode) { + ArrayList ret = new ArrayList<>(); + if (listNode != null) { + ret.addAll(printListFromTailToHead(listNode.next)); + ret.add(listNode.val); + } + return ret; +} +``` + +### 使用头插法 + +利用链表头插法为逆序的特点。 + +头结点和第一个节点的区别: + +- 头结点是在头插法中使用的一个额外节点,这个节点不存储值; +- 第一个节点就是链表的第一个真正存储值的节点。 + +```java +public ArrayList printListFromTailToHead(ListNode listNode) { + // 头插法构建逆序链表 + ListNode head = new ListNode(-1); + while (listNode != null) { + ListNode memo = listNode.next; + listNode.next = head.next; + head.next = listNode; + listNode = memo; + } + // 构建 ArrayList + ArrayList ret = new ArrayList<>(); + head = head.next; + while (head != null) { + ret.add(head.val); + head = head.next; + } + return ret; +} +``` +### 使用 Collections.reverse() + +```java +public ArrayList printListFromTailToHead(ListNode listNode) { +ArrayList ret = new ArrayList<>(); +while (listNode != null) { +ret.add(listNode.val); +listNode = listNode.next; +} +Collections.reverse(ret); +return ret; +} +``` + +```python +def printListFromTailToHead(listNode): + # write code here + l = list() + while listNode: + l.append(listNode.val) + listNode = listNode.next + l.reverse() + return l +``` +# 7. 重建二叉树 + +[NowCoder](https://www.nowcoder.com/practice/8a19cbe657394eeaac2f6ea9b0f6fcf6?tpId=13&tqId=11157&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) + +## 题目描述 + +根据二叉树的前序遍历和中序遍历的结果,重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。 + +```html +preorder = [3,9,20,15,7] +inorder = [9,3,15,20,7] +``` + +

+ +## 解题思路 + +前序遍历的第一个值为根节点的值,使用这个值将中序遍历结果分成两部分,左部分为树的左子树中序遍历结果,右部分为树的右子树中序遍历的结果。 + +```java +// 缓存中序遍历数组每个值对应的索引 +private Map indexForInOrders = new HashMap<>(); + +public TreeNode reConstructBinaryTree(int[] pre, int[] in) { + for (int i = 0; i < in.length; i++) + indexForInOrders.put(in[i], i); + return reConstructBinaryTree(pre, 0, pre.length - 1, 0); +} + +private TreeNode reConstructBinaryTree(int[] pre, int preL, int preR, int inL) { + if (preL > preR) + return null; + TreeNode root = new TreeNode(pre[preL]); + int inIndex = indexForInOrders.get(root.val); + int leftTreeSize = inIndex - inL; + root.left = reConstructBinaryTree(pre, preL + 1, preL + leftTreeSize, inL); + root.right = reConstructBinaryTree(pre, preL + leftTreeSize + 1, preR, inL + leftTreeSize + 1); + return root; +} +``` + +```python +# 返回构造的TreeNode根节点 +def reConstructBinaryTree(self, pre, tin): + # write code here + if not pre or not tin: + return None + root = TreeNode(pre.pop(0)) + index = tin.index(root.val) + root.left = self.reConstructBinaryTree(pre, tin[:index]) + root.right = self.reConstructBinaryTree(pre, tin[index + 1:]) + return root +``` + +# 8. 二叉树的下一个结点 + +[NowCoder](https://www.nowcoder.com/practice/9023a0c988684a53960365b889ceaf5e?tpId=13&tqId=11210&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) + +## 题目描述 + +给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。 + +```java +public class TreeLinkNode { + int val; + TreeLinkNode left = null; + TreeLinkNode right = null; + TreeLinkNode next = null; + + TreeLinkNode(int val) { + this.val = val; + } +} +``` + +## 解题思路 + +① 如果一个节点的右子树不为空,那么该节点的下一个节点是右子树的最左节点; + +

+ +② 否则,向上找第一个左链接指向的树包含该节点的祖先节点。 + +

+ +```java +public TreeLinkNode GetNext(TreeLinkNode pNode) { + if (pNode.right != null) { + TreeLinkNode node = pNode.right; + while (node.left != null) + node = node.left; + return node; + } else { + while (pNode.next != null) { + TreeLinkNode parent = pNode.next; + if (parent.left == pNode) + return parent; + pNode = pNode.next; + } + } + return null; +} +``` + +```python +def GetNext(self, pNode): + # write code here + # pNode is None + if not pNode: + return pNode + if pNode.right: + node = pNode.right + while node.left: + node = node.left + return node + else: + while pNode.next: + parent = pNode.next + if parent.left == pNode: + return parent + pNode = pNode.next + # pNode not have the next node + return None +``` +# 9. 用两个栈实现队列 + +[NowCoder](https://www.nowcoder.com/practice/54275ddae22f475981afa2244dd448c6?tpId=13&tqId=11158&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) + +## 题目描述 + +用两个栈来实现一个队列,完成队列的 Push 和 Pop 操作。 + +## 解题思路 + +in 栈用来处理入栈(push)操作,out 栈用来处理出栈(pop)操作。一个元素进入 in 栈之后,出栈的顺序被反转。当元素要出栈时,需要先进入 out 栈,此时元素出栈顺序再一次被反转,因此出栈顺序就和最开始入栈顺序是相同的,先进入的元素先退出,这就是队列的顺序。 + +

+ +```java +Stack in = new Stack(); +Stack out = new Stack(); + +public void push(int node) { + in.push(node); +} + +public int pop() throws Exception { + if (out.isEmpty()) + while (!in.isEmpty()) + out.push(in.pop()); + + if (out.isEmpty()) + throw new Exception("queue is empty"); + + return out.pop(); +} +``` + +```python +# -*- coding:utf-8 -*- +class Solution: + def __init__(self): + self.stack1 = [] + self.stack2 = [] + def push(self, node): + # write code here + self.stack1.append(node) + def pop(self): + # return xx + if self.stack2 == []: + while self.stack1: + self.stack2.append(self.stack1.pop()) + return self.stack2.pop() + return self.stack2.pop() +``` +# 10.1 斐波那契数列 + +[NowCoder](https://www.nowcoder.com/practice/c6c7742f5ba7442aada113136ddea0c3?tpId=13&tqId=11160&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) + +## 题目描述 + +求斐波那契数列的第 n 项,n <= 39。 + +

+ +## 解题思路 + +如果使用递归求解,会重复计算一些子问题。例如,计算 f(10) 需要计算 f(9) 和 f(8),计算 f(9) 需要计算 f(8) 和 f(7),可以看到 f(8) 被重复计算了。 + +

+ +递归是将一个问题划分成多个子问题求解,动态规划也是如此,但是动态规划会把子问题的解缓存起来,从而避免重复求解子问题。 + +```java +public int Fibonacci(int n) { + if (n <= 1) + return n; + int[] fib = new int[n + 1]; + fib[1] = 1; + for (int i = 2; i <= n; i++) + fib[i] = fib[i - 1] + fib[i - 2]; + return fib[n]; +} +``` + +考虑到第 i 项只与第 i-1 和第 i-2 项有关,因此只需要存储前两项的值就能求解第 i 项,从而将空间复杂度由 O(N) 降低为 O(1)。 + +```java +public int Fibonacci(int n) { + if (n <= 1) + return n; + int pre2 = 0, pre1 = 1; + int fib = 0; + for (int i = 2; i <= n; i++) { + fib = pre2 + pre1; + pre2 = pre1; + pre1 = fib; + } + return fib; +} +``` + +由于待求解的 n 小于 40,因此可以将前 40 项的结果先进行计算,之后就能以 O(1) 时间复杂度得到第 n 项的值了。 + +```java +public class Solution { + private int[] fib = new int[40]; + + public Solution() { + fib[1] = 1; + fib[2] = 2; + for (int i = 2; i < fib.length; i++) + fib[i] = fib[i - 1] + fib[i - 2]; + } + + public int Fibonacci(int n) { + return fib[n]; + } +} +``` + +```python +# -*- coding:utf-8 -*- +class Solution: + + def __init__(self): + self.fib = [0,1] + for i in range(2,40): + self.fib.append(self.fib[i-1]+self.fib[i-2]) + def Fibonacci(self, n): + # write code here + return self.fib[n] +``` +# 10.2 跳台阶 + +[NowCoder](https://www.nowcoder.com/practice/8c82a5b80378478f9484d87d1c5f12a4?tpId=13&tqId=11161&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) + +## 题目描述 + +一只青蛙一次可以跳上 1 级台阶,也可以跳上 2 级。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。 + +## 解题思路 + +```java +public int JumpFloor(int n) { + if (n <= 2) + return n; + int pre2 = 1, pre1 = 2; + int result = 1; + for (int i = 2; i < n; i++) { + result = pre2 + pre1; + pre2 = pre1; + pre1 = result; + } + return result; +} +``` + +```python +# -*- coding:utf-8 -*- +class Solution: + def jumpFloor(self, number): + # write code here + if number == 0: + return number + pre1, pre2 = 1, 1 + #result = 0 + for i in range(number): + pre1, pre2 = pre2, pre1+pre2 + return pre1 +``` +# 10.3 矩形覆盖 + +[NowCoder](https://www.nowcoder.com/practice/72a5a919508a4251859fb2cfb987a0e6?tpId=13&tqId=11163&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) + +## 题目描述 + +我们可以用 2\*1 的小矩形横着或者竖着去覆盖更大的矩形。请问用 n 个 2\*1 的小矩形无重叠地覆盖一个 2\*n 的大矩形,总共有多少种方法? + +## 解题思路 + +```java +public int RectCover(int n) { + if (n <= 2) + return n; + int pre2 = 1, pre1 = 2; + int result = 0; + for (int i = 3; i <= n; i++) { + result = pre2 + pre1; + pre2 = pre1; + pre1 = result; + } + return result; +} +``` + +```python +# -*- coding:utf-8 -*- +class Solution: + def rectCover(self, number): + # write code here + if number == 0: + return number + pre1, pre2 = 1, 1 + #result = 0 + for i in range(number): + pre1, pre2 = pre2, pre1+pre2 + return pre1 +``` +# 10.4 变态跳台阶 + +[NowCoder](https://www.nowcoder.com/practice/22243d016f6b47f2a6928b4313c85387?tpId=13&tqId=11162&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) + +## 题目描述 + +一只青蛙一次可以跳上 1 级台阶,也可以跳上 2 级... 它也可以跳上 n 级。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。 + +## 解题思路 + +```java +public int JumpFloorII(int target) { + int[] dp = new int[target]; + Arrays.fill(dp, 1); + for (int i = 1; i < target; i++) + for (int j = 0; j < i; j++) + dp[i] += dp[j]; + return dp[target - 1]; +} +``` + +```python +# -*- coding:utf-8 -*- +class Solution: + def jumpFloorII(self, number): + # write code here + if number <= 0: + return 0 + return pow(2, number-1) +``` # 参考文献