面试题51:在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是重复的数字2或者3。
- 思路:寻找重复元素,很容易想到建立哈希表来完成,遍历一遍数组就可以将每个元素映射到哈希表中。如果哈希表中已经存在这个元素则说明这就是个重复元素。这种方法可以很方便的在O(n)时间内完成对重复元素的查找。可是题目要求在O(1)的空间。因此采用哈希表这种解法肯定在空间复杂度上是不符合要求的。题目中数组中所以数字都在[0, n-1]区间范围内,因此哈希表的大小为n。因此我们实际要做的就是对n个范围为0到n-1的数进行哈希,而哈希表的大小刚好为n。对排序算法比较熟悉的同学不难发现这与一种经典的排序算法(基数排序)非常类似。而基数排序的时间空间复杂度刚好符合题目要求。因此尝试使用基数排序来解这道面试题。
- 代码实现
-
- public class TestMain {
- public static void main(String[] args) {
- TestMain t = new TestMain();
- System.out.println(t.IsReplication(new int[] { 1, 3, 6, 0, 7, 3, 4, 2 }));
- }
- public int IsReplication(int array[]) {
- int tem;// 用于交换
- if (array.length <= 0) {
- return -1;
- } // if
- for (int i = 0; i < array.length;) {
- if (array[i] != i) {
- if (array[i] != array[array[i]]) {
- tem = array[i];
- array[i] = array[array[i]];
- array[tem] = tem;
- } else {
- return array[i];
- }
- }else {
- i++;
- }
- }
- return -1;
- }
- }
面试题52:给定一个数组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]。 其中A[i] = 1。不能使用除法。
- 思路:使用矩阵法求解,将矩阵分为上三角矩阵和下三角矩阵,分别求乘积
- 代码实现
-
- public class TestMain {
- public static void main(String[] args) {
- TestMain t = new TestMain();
- int[] B = t.multiply(new int[] { 2, 3, 4 });
- for (int i = 0; i < B.length; i++) {
- System.out.println(B[i]);
- }
- }
- public int[] multiply(int[] A) {
- int length = A.length;
- int[] B = new int[length];
- if (length != 0) {
- B[0] = 1;
- // 计算下三角连乘
- for (int i = 1; i < length; i++) {
- B[i] = B[i - 1] * A[i - 1];
- }
- int temp = 1;
- // 计算上三角连乘
- for (int j = length - 2; j >= 0; j--) {
- temp *= A[j + 1];
- B[j] *= temp;
- }
- }
- return B;
- }
- }
面试题53:请实现一个函数用来匹配包括.‘和'的正则表达式。模式中的字符. '表示任意一个字符 ,而"表示它前面的字符可以出现任意次(包含0次) (未看)
- 思路:当字符串只有一一个字符时,进行判断,否则就有两种递归情况,( 1 )当模式中的第二个字符不是“”时:如果字符串第一个字符和模式中的第一个字符相匹配或是点那么 字符串和模式都后移一个字符, 然后匹配剩余的;如果字符串第一个字符和模式中的第一 个字符相不匹配, 直接返回false。(2 )当模式中的第二个字符是“时:如果字符串第一个字符跟模式第一 个字符不匹配,则模式后移2个字符,继续匹配;如果字符串第一个字符跟模式第一个字符匹配或是点,可以有3种匹配方式: 1 >模式后移2字符,相当于x被忽略; 2>字符串后移1字符,模式后移2字符; 3>字符串后移1字符,模式不变,即继续匹配字符下- -位,因为可以匹配多位;
- 代码实现
-
- public boolean match(char[] str, char[] pattern) {
- if (str == null || pattern == null)
- return false;
- // 若字符串的长度为1
- if (str.length == 1) {
- if (pattern.length == 1) {
- if (str[0] == pattern[0] || pattern[0] == '.')
- return true;
- return false;
- }
- }
- int sindex = 0;
- int pindex = 0;
- return matchIndex(str, sindex, pattern, pindex);
- }
- public boolean matchIndex(char[] str, int sindex, char[] pattern, int pindex) { // str和pattern同时到达末尾,则匹配成功
- if (sindex == str.length && pindex == pattern.length)
- return true;
- // 若pattern先到尾,而str没有到达末尾,则匹配失败
- if (sindex != str.length && pindex == pattern.length)
- return false;
- // 若pattern第二个字符是*
- if (pindex + 1 < pattern.length && pattern[pindex + 1] == '*') {
- if (sindex != str.length && pattern[pindex] == str[sindex]
- || sindex != str.length && pattern[pindex] == '.') {
- return matchIndex(str, sindex + 1, pattern, pindex + 2) || matchIndex(str, sindex, pattern, pindex + 2)
- || matchIndex(str, sindex + 1, pattern, pindex);
- } else {
- return matchIndex(str, sindex, pattern, pindex + 2);
- }
- }
- // 若pattern第二个字符不是*
- if (sindex != str.length && pattern[pindex] == str[sindex] || sindex != str.length && pattern[pindex] == '.')
- return matchIndex(str, sindex + 1, pattern, pindex + 1);
- return false;
- }
面试题54:请实现一个函数用来判断字符串是否表示数值(包括整数和小数)
- 思路:逐个字符进行判断,e或E和小数点最多出现一次,而e或E的前一个必须是数字,且不能是第一个或最后一个字符,符号的前一个字符不能是e或E。也可用正则表达式判断!
- 代码实现
-
- public boolean isNumeric(char[] str) {
- if (str == null)
- return false;
- int index = 0;
- int ecount = 0;
- int point = 0;
- // 如果第一个字符是符号就跳过
- if (str[0] == '-' || str[0] == '+')
- index++;
- for (int i = index; i < str.length; i++) {
- if (str[i] == '-' || str[i] == '+') {
- if (str[i - 1] != 'e' && str[i - 1] != 'E')
- return false;
- continue;
- }
- if (str[i] == 'e' || str[i] == 'E') {
- ecount++;
- if (ecount > 1)
- return false;
- if (i == 0 || str[i - 1] < 48 || str[i - 1] > 57 || i == str.length - 1)
- return false;
- point++;
- continue;
- }
- if (str[i] == '.') {
- point++;
- if (point > 1)
- return false;
- continue;
- }
- // 出现非数字且不是e/E则返回false(小数点和符号用continue跳过了)
- if ((str[i] < 48 || str[i] > 57) && (str[i] != 'e') && (str[i] != 'E'))
- return false;
- return true;
- }
- }
面试题55:请实现一个函数用来找出字符流中第一个只出现一次的字符。
- 思路:借助辅助空间进行判断,如字符数组。
- 代码实现
-
- public class Solution {
- // Insert one char from stringstream
- // 数组存储每个字符出现的次数
- char[] cn = new char[256];
- StringBuffer sb = new StringBuffer();
- public void Insert(char ch) {
- ++cn[ch];
- sb.append(ch + "");
- }
- // return the first appearence once char in current stringstream
- public char FirstAppearingOnce() {
- char[] t = sb.toString().toCharArray();
- for (int i = 0; i < t.length; i++) {
- if (cn[t[i]] == 1) {
- return t[i];
- }
- }
- return '#';
- }
- }
面试题56:一个链表中包含环,请找出该链表的环的入口结点。
- 思路:定义快慢两个指针,相遇后(环中相汇点)将快指针指向pHead 然后一起走,每次往后挪一位,相遇的 节点即为所求。详细分析:相遇即p1==p2时,p2所经过节点数为2x,p1所经过节点数为x,设环中有n个节点,p2 比p1多走一圈有2x=n+x; n=x;可以看出p1实际走了一个环的步数,再让p2指向链表头部,p1位置不变,p1,p2 每次走一步直到p1==p2; 此时p1指向环的入口。(用HashSet来解决)
- 实现代码
-
- public ListNode EntryNodeOfLoop2(ListNode pHead) {
- ListNode fast = pHead;
- ListNode slow = pHead;
- while (fast != null && fast.next != null) {
- fast = fast.next.next;
- slow = slow.next;
- // 当快指针 与 慢指针相遇时
- if (fast == slow) {
- fast = pHead;
- // 再次相遇
- while (fast != slow) {
- fast = fast.next;
- slow = slow.next;
- }
- return fast;
- }
- }
- return null;
- }
面试题57:在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。
- 思路:先新建一个头节点,然后向后查找值相同的节点,重复查找后删除
- 代码实现
-
- public class Solution {
- public ListNode deleteDuplication(ListNode pHead) {
- if (pHead == null) {
- return null;
- }
- ListNode preNode = null;
- ListNode node = pHead;
- while (node != null) {
- if (node.next != null && node.val == node.next.val) {
- int value = node.val;
- while (node.next != null && node.next.val == value) {
- node = node.next;
- }
- if (preNode == null) {
- pHead = node.next;
- } else {
- preNode.next = node.next;
- }
- } else {
- preNode = node;
- }
- node = node.next;
- }
- return pHead;
- }
- }
面试题58:给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。
- 思路:若节点右孩子存在,则设置一个指针从该节点的右孩子出发,一直沿着指向左子结点的指针找到的叶子节点即为下一个节点;若节点不是根节点。如果该节点是其父节点的左孩子,则返回父节点;否则继续向上遍历其父节点的父节点,重复之前的判断,返回结果
- 代码实现 //next指向的是父节点
-
- public TreeLinkNode GetNext(TreeLinkNode pNode) {
- if (pNode == null)
- return null;
- if (pNode.right != null) {
- pNode = pNode.right;
- while (pNode.left != null) {
- pNode = pNode.left;
- }
- return pNode;
- }
- while (pNode.next != null) {
- // 找第一个当前节点是父节点左孩子的节点
- if (pNode.next.left == pNode)
- return pNode.next;
- pNode = pNode.next;
- }
- return null;
- }
面试题59:请实现一个函数,用来判断一颗二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样 的,定义其为对称的。
- 思路:利用递归进行判断,若左子树的左孩子等于右子树的右孩子且左子树的右孩子等于右子树的左孩子,并且左右子树节点的值相等,则是对称的。
- 代码实现
-
- public boolean isSymmetrical(TreeNode pRoot) {
- if (pRoot == null)
- return true;
- return isCommon(pRoot.left, pRoot.right);
- }
- public boolean isCommon(TreeNode leftNode, TreeNode rightNode) {
- if (leftNode == null && rightNode == null)
- return true;
- if (leftNode != null && rightNode != null)
- return leftNode.val == rightNode.val && isCommon(leftNode.left, rightNode.right)
- && isCommon(leftNode.right, rightNode.left);
- return false;
- }
面试题60:请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序 打印,第三行按照从左到右的顺序打印,依此类推。
- 思路:利用两个栈的辅助空间分别存储奇数偶数层的节点,然后打印输出。或使用链表的辅助空间来实现,利用链表的反向迭实现逆序输出。
- 代码实现
-
- public ArrayList<ArrayList<Integer>> Print(TreeNode pRoot) {
- ArrayList<ArrayList<Integer>> res = new ArrayList<>();
- if (pRoot == null)
- return res;
- Stack<TreeNode> s1 = new Stack<>(); // s1表示奇数,从右向左输出
- Stack<TreeNode> s2 = new Stack<>(); // s2表示偶数,从左向右输出
- s1.push(pRoot);
- int level = 1;
- while (!s1.empty() || !s2.empty()) {
- if (level % 2 != 0) {
- ArrayList<Integer> list = new ArrayList<>();
- while (!s1.empty()) {
- TreeNode node = s1.pop();
- if (node != null) {
- list.add(node.val);
- s2.push(node.left);
- s2.push(node.right);
- }
- }
- if (!list.isEmpty()) {
- res.add(list);
- level++;
- }
- } else {
- ArrayList<Integer> list = new ArrayList<>();
- while (!s2.empty()) {
- TreeNode node = s2.pop();
- if (node != null) {
- list.add(node.val);
- s1.push(node.right);
- s1.push(node.left);
- }
- }
- if (!list.isEmpty()) {
- res.add(list);
- level++;
- }
- }
- }
- return res;
- }
面试题61:从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。
- 思路:利用辅助空间链表或队列来存储节点,每层输出。
- 代码实现
-
- public ArrayList<ArrayList<Integer>> Print(TreeNode pRoot) {
- ArrayList<ArrayList<Integer>> res = new ArrayList<>();
- if (pRoot == null)
- return res;
- LinkedList<TreeNode> queue = new LinkedList<>();
- queue.add(pRoot);
- ArrayList<Integer> list = new ArrayList<>();
- int start = 0;
- int end = 1;
- while (!queue.isEmpty()) {
- TreeNode node = queue.pop();
- list.add(node.val);
- start++;
- if (node.left != null)
- queue.offer(node.left);
- if (node.right != null)
- queue.offer(node.right);
- if (start == end) {
- start = 0;
- end = queue.size();
- res.add(new ArrayList<>(list));
- list.clear();
- }
- }
- return res;
- }
面试题62:请实现两个函数,分别用来序列化和反序列化二叉树
- 思路:序列化:前序遍历二叉树存入字符串中;反序列化:根据前序遍历重建二叉树。
- 代码实现
-
- public String Serialize(TreeNode root) {
- StringBuffer sb = new StringBuffer();
- if (root == null) {
- sb.append("#,");
- return sb.toString();
- }
- sb.append(root.val + ",");
- sb.append(Serialize(root.left));
- sb.append(Serialize(root.right));
- return sb.toString();
- }
-
- public int index = -1;
- public TreeNode Deserialize(String str) {
- index++;
- int len = str.length();
- String[] strr = str.split(",");
- TreeNode node = null;
- if (index >= len)
- return null;
- if (!strr[index].equals("#")) {
- node = new TreeNode(Integer.valueOf(strr[index]));
- node.left = Deserialize(str);
- node.right = Deserialize(str);
- }
- return node;
- }
面试题63:给定一颗二叉搜索树,请找出其中的第k大的结点
- 思路:二叉搜索树按照中序遍历的顺序打印出来正好就是排序好的顺序,第k个结点就是第K大的节点,分别递归查找左右子树的第K个节点,或使用非递归借用栈的方式查找,当count=k时返回根节点
- 代码实现
-
- int count = 0;
- public TreeNode KthNode(TreeNode pRoot, int k) {
- if (pRoot == null || k < 1)
- return null;
- count++;
- if (count == k) {
- return pRoot;
- }
- TreeNode leftNode = KthNode(pRoot.left, k);
- if (leftNode != null)
- return leftNode;
- TreeNode rightNode = KthNode(pRoot.right, k);
- if (rightNode != null)
- return rightNode;
- return null;
- }
面试题64:如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位 于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。
- 思路:创建优先级队列维护大顶堆和小顶堆两个堆,并且小顶堆的值都大于大顶堆的值,2个堆个数的差值小 于等于1,所以当插入个数为奇数时:大顶堆个数就比小顶堆多1,中位数就是大顶堆堆头;当插入个数为偶数 时,使大顶堆个数跟小顶堆个数一样,中位数就是 2个堆堆头平均数。也可使用集合类的排序方法。
- 代码实现
-
- int count = 0;
- PriorityQueue<Integer> minHeap = new PriorityQueue<>();
- PriorityQueue<Integer> maxHeap = new PriorityQueue<>(16, new Comparator<Integer>() {
- @Override
- public int compare(Integer o1, Integer o2) {
- return o2.compareTo(o1);
- }
- });
- public void Insert(Integer num) {
- count++;
- // 当数据的个数为奇数时,进入大根堆
- if ((count & 1) == 1) {
- minHeap.offer(num);
- maxHeap.offer(minHeap.poll());
- } else {
- maxHeap.offer(num);
- minHeap.offer(maxHeap.poll());
- }
- }
- public Double GetMedian() {
- if (count == 0)
- return null;
- // 当数据个数是奇数时,中位数就是大根堆的顶点
- if ((count & 1) == 1) {
- return Double.valueOf(maxHeap.peek());
- } else {
- return Double.valueOf((minHeap.peek() + maxHeap.peek())) / 2;
- }
- }
面试题65:给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值
- 思路:两个for循环,第一个for循环滑动窗口,第二个for循环滑动窗口中的值,寻找最大值。还可以使用时间复杂度更低的双端队列求解。
- 代码实现
-
- public ArrayList<Integer> maxInWindows(int[] num, int size) {
- ArrayList<Integer> list = new ArrayList<>();
- if (num == null || size < 1 || num.length < size)
- return list;
- int length = num.length - size + 1;
- for (int i = 0; i < length; i++) {
- int current = size + i;
- int max = num[i];
- for (int j = i; j < current; j++) {
- if (max < num[j]) {
- max = num[j];
- }
- }
- list.add(max);
- }
- return list;
- }
面试题66:请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中 的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵 中的某一个格子,则该路径不能再进入该格子。
- 思路:回溯法,双层for循环,判断每一个点,每次递归调用上下左右四个点,用flag标志是否已经匹配 (return),进行判断点的位置是否越界,是否已经正确匹配,判断矩阵的路径与模式串的第index个字符是否匹配。
- 代码实现
-
- public boolean hasPath(char[] matrix, int rows, int cols, char[] str) {
- int flag[] = new int[matrix.length];
- for (int i = 0; i < rows; i++) {
- for (int j = 0; j < cols; j++) {
- if (helper(matrix, rows, cols, i, j, str, 0, flag))
- return true;
- }
- }
- return false;
- }
- private boolean helper(char[] matrix, int rows, int cols, int i, int j, char[] str, int k, int[] flag) {
- int index = i * cols + j;
- if (i < 0 || i >= rows || j < 0 || j >= cols || matrix[index] != str[k] || flag[index] == 1)
- return false;
- if (k == str.length - 1)
- return true;
- flag[index] = 1;
- if (helper(matrix, rows, cols, i - 1, j, str, k + 1, flag)
- || helper(matrix, rows, cols, i + 1, j, str, k + 1, flag)
- || helper(matrix, rows, cols, i, j - 1, str, k + 1, flag)
- || helper(matrix, rows, cols, i, j + 1, str, k + 1, flag)) {
- return true;
- }
- flag[index] = 0;
- return false;
- }
面试题67:地上有一个m行和n列的方格。一个机器人从坐标0,0的格子开始移动,每一次只能向左,右,上,下四个方 向移动一格,但是不能进入行坐标和列坐标的数位之和大于k的格子。
- 思路:利用递归实现,每次只能走上下左右四个点,进行判断点的位置是否越界,点数之和是否大于K,是否已经走过了。
- 代码实现
-
- public int movingCount(int threshold, int rows, int cols) {
- int flag[][] = new int[rows][cols]; // 记录是否已经走过 return helper(0, 0,
- // rows, cols, flag, threshold);
- }
- private int helper(int i, int j, int rows, int cols, int[][] flag, int threshold) {
- if (i < 0 || i >= rows || j < 0 || j >= cols || numSum(i) + numSum(j) > threshold || flag[i][j] == 1)
- return 0;
- flag[i][j] = 1;
- return helper(i - 1, j, rows, cols, flag, threshold) + helper(i + 1, j, rows, cols, flag, threshold)
- + helper(i, j - 1, rows, cols, flag, threshold) + helper(i, j + 1, rows, cols, flag, threshold) + 1;
- }
- private int numSum(int i) {
- int sum = 0;
- while (i > 0) {
- sum += i % 10;
- i = i / 10;
- }
- return sum;
- }