第二章 面试需要的基础知识
/** * 面试题3(一) 找到数组中任意一个相同的数字 * 思路: * 以整数的值为散列值,通过交换将该值放到相应的位置上 * 总结: * 小型正整数的值可以直接作为散列值(hashcode),存放到相应的位置,以O(n)的时间复杂度实现排序 * @param arr * @return */ public int findSameNumber(int[] arr) { //非法输入 if (arr == null || arr.length <= 1) return -1; //下面假设输入中符合题目要求 for (int i = 0; i < arr.length; i++) { while (arr[i] != i) { if (arr[arr[i]] != arr[i]) //不相等则交换 exch(arr, i, arr[i]); else return arr[i]; //相等,则找到了相同的数字 } } return -1; } private void exch(int[] arr, int a, int b) { int temp = arr[a]; arr[a] = arr[b]; arr[b] = temp; } public static void main(String[] args) { int[] ints = {2, 3, 1, 0, 2, 5, 3}; System.out.println(new Solution().findSameNumber(ints)); }
/** * 面试题4 在二维数组中查找是否包含数组 * 思路:不断地将问题的范围缩小 * 总结:找规律,找出可以用循环或者递归的方法解决 * * @param arr * @param num * @return */ public boolean findNumber(int[][] arr, int num) { //非法输入 if (arr == null) { return false; } for (int i = 0; i < arr.length; i++) { if (arr[i] == null) return false; } //从右上角开始 int length = arr.length; int row = 0; int col = length - 1; while (row < length && col >= 0) { if (arr[row][col] < num) row++; else if (arr[row][col] > num) col--; else return true; } return false; }
/** * 面试题5 将空格替换为%02 * 解法:从后到前替换,将字符一次移动到位,避免了数组的频繁移动 * 注意:位运算优先级低于加减 * @param str */ public String replaceBlank(String str) { //非法输入 int length = str.length(); if (str == null || length == 0) return null; //转化成数组 char[] chars = str.toCharArray(); int count = 0; //字符串中空格的数量 for (int i = 0; i < length; i++) { if (chars[i] == ' ') count++; } char[] tempChars = new char[length + (count << 1)]; int p = length - 1; //两个指针 int q = tempChars.length - 1; while (p >= 0) { if (chars[p] != ' ') { tempChars[q--] = chars[p]; } else { tempChars[q--] = '0'; tempChars[q--] = '2'; tempChars[q--] = '%'; } p--; } return String.valueOf(tempChars); } public static void main(String[] args) { Solution solution = new Solution(); String str = "we are world."; System.out.println(solution.replaceBlank(str)); }
/** * 面试题6:反向打印链表 * 解法一:用递归的逆序遍历的方式 * 解法二:顺序访问链表,将需要打印的值存入栈中 * * @param node */ public void printReverse(Node node) { //采用解法一 //非法输入 if (node == null) return; printReverse(node.next); System.out.println(node.val); } public void printReverseUseStack(Node node) { //采用解法二 if (node == null) return; Stack<Integer> s = new Stack<>(); //使用栈 for (Node i = node; i != null; i = i.next) { //顺序遍历链表 s.push(i.val); } while (!s.empty()) { //出栈 System.out.println(s.pop()); } }
/** * 面试题7 * 根据前序遍历和中序遍历的结果数组重建二叉树 * 解法:根据不同遍历的特点 找到根结点,然后再递归地处理子树 * * @param pre * @param in * @return */ public TreeNode construct(int[] pre, int[] in) { //非法输入 if (pre == null || in == null || pre.length == 0 || in.length == 0) return null; try { return constructCore(pre, in, 0, pre.length - 1, 0, in.length - 1); } catch (Exception e) { e.printStackTrace(); } return null; } /** * @param pre * @param in * @param pp1 前序数组的范围指针 * @param pp2 * @param ip1 中序数组的范围指针 * @param ip2 * @return */ private TreeNode constructCore(int[] pre, int[] in, int pp1, int pp2, int ip1, int ip2) throws Exception { if (pp1 > pp2 || ip1 > ip2) return null;//无子树,返回null TreeNode node = new TreeNode(); //根节点 node.val = pre[pp1]; int nodeIndexInInOder = -1; //根结点在中序遍历中的索引 for (int i = ip1; i <= ip2; i++) { //从中序遍历中寻找根结点 if (in[i] == node.val) { nodeIndexInInOder = i; break; } } if (nodeIndexInInOder == -1) throw new Exception("Invalide Input");//在中序中没有找到 node.left = constructCore(pre, in, pp1 + 1, pp1 + (nodeIndexInInOder - ip1), ip1, nodeIndexInInOder - 1); node.right = constructCore(pre, in, pp1 + 1 + (nodeIndexInInOder - ip1), pp2, nodeIndexInInOder + 1, ip2); return node; }
/** * 面试题8 二叉树的下一个结点 * 分析:有右子树的情况下,下一个节点是右子树的最左侧(最小)结点; * 无右子树的情况下,下一个结点是沿着父结点向上查找,第一个遇到的左链接父结点。如果查找到父节点都没有找到下一结点则无下一节点。 */ public BTreeNode findNextNode(BTreeNode node) { //非法输入处理 if (node == null) return null; //有右子树的情况 if (node.right != null) return min(node.right); //取最小的结点 //没有右子树的情况 while (node.parent != null) { //有父结点,沿着父结点向上走 if (node.parent.left == node) return node.parent; node = node.parent; } return null; //没有找到父节点 } private BTreeNode min(BTreeNode node) { if (node.left == null) return node; return min(node.left); }
/** * 面试题9:用两个栈实现队列 * 分析:一个栈用来入,一个栈用来出 */ class queueStack { //用两个栈来实现 //为了方便使用了Integer private Stack<Integer> sIn; private Stack<Integer> sOut; public queueStack() { sIn = new Stack<>(); sOut = new Stack<>(); } //实现尾部添加 public void appendTail(Integer val) { sIn.push(val); } //实现头部删除 public Integer deleteHead() { if (sOut.empty()) { //出栈是空的情况 while (!sIn.empty()) sOut.push(sIn.pop()); } if (sOut.empty()) return null; //出栈依旧是空 return sOut.pop(); } }
/** * 面试题9:用两个队列实现栈操作 * 分析:交替地从一个队列复制到另外一个队列,复制过去时留下最后添加的元素用于弹出 */ class StackQueue { private LinkedQueue<Integer> que1; private LinkedQueue<Integer> que2; private boolean isQue1 = true;//true表示当前使用Que1 //入栈 public void push(Integer val) { if (isQue1) que1.enqueue(val); else que2.enqueue(val); } //出栈 public Integer pop() { if (isQue1) { if (que1.isEmpty()) return null; while (que1.count != 1) que2.enqueue(que1.dequeue()); isQue1 = false; return que1.dequeue(); } else { if (que2.isEmpty()) return null; while (que2.count != 1) que1.enqueue(que2.dequeue()); isQue1 = true; return que2.dequeue(); } } }
/** * 面试题10 斐波那契数 * 分析:一般来说,循环算法占用的空间比递归的方法更加少 * 普通青蛙 * 分析:可以变为斐波那契数列问题 * 变态青蛙 * 分析:通过数学的分析,可以得到n台阶跳法的函数表达式 */ public int fibonacci(int n) { if (n < 0) return -1;//-1表示错误输入 if (n == 0) return 0; if (n == 1) return 1; //用数组保存前两个计算结果 int[] result = {0, 1}; int num = 2; while (num != n) { int currentResult = result[0] + result[1]; result[0] = result[1]; result[1] = currentResult; } return result[0] + result[1]; }
/**面试题12 回溯法从二维数组中寻找字符串 * @ClassName Solution * @Author wangyudi * @Date 2019/7/14 16:13 * @Version 1.0 * @Description */ class TreeNode { int val = 0; TreeNode left = null; TreeNode right = null; public TreeNode(int val) { this.val = val; } } public class Solution { public boolean findPath(char[][] matrix, String str) { //deal with invalid input if (str == null || matrix == null) return false; //下面默认输入是一个有效的矩形二维数组 int rows = matrix.length; int cols = matrix[0].length; boolean[] visited = new boolean[rows * cols]; //存放该数组对应元素是否已被访问信息 char[] charStr = str.toCharArray(); int pathIndex = 0;//记录需要寻找字符串的始端 boolean result = false; for (int row = 0; row < rows; row++) { for (int col = 0; col < cols; col++) { if (findPathCore(matrix, charStr, pathIndex, row, col, rows, cols, visited)) { result = true; break; } } } return result; } /** * 查找路径的迭代函数 * @param matrix * @param charStr * @param pathIndex * @param row * @param col * @param rows * @param cols * @param visited * @return */ private boolean findPathCore(char[][] matrix, char[] charStr, int pathIndex, int row, int col, int rows, int cols, boolean[] visited) { //1 判断是否已经找到字符串 if (pathIndex == charStr.length) return true; boolean result = false; //2 判断二维数组的字符是否对应字符串中的字符 if (row >= 0 && row < rows && col >= 0 && col < cols && charStr[pathIndex] == matrix[row][col] && visited[row * cols + col] == false) { //对应 //寻找下一个子字符串,并修改访问信息 pathIndex++; visited[row * cols + col] = true; //从该元素所在位置的四个方向中寻找下一个字符串 result = findPathCore(matrix, charStr, pathIndex, row + 1, col, rows, cols, visited) || findPathCore(matrix, charStr, pathIndex, row - 1, col, rows, cols, visited) || findPathCore(matrix, charStr, pathIndex, row, col + 1, rows, cols, visited) || findPathCore(matrix, charStr, pathIndex, row, col - 1, rows, cols, visited); //四个方向都没有找到相应的子字符串,则说明该位置不正确 //修改访问信息,然后回归到上一个位置 if (!result) { visited[row * cols + col] = false; } } return result; } // Test input and output public static void main(String[] args) { Solution solution = new Solution(); char[] char1 = {'a', 'b', 't', 'g'}; char[] char2 = {'c', 'f', 'c', 's'}; char[] char3 = {'j', 'd', 'e', 'h'}; char[] char4 = {'a', 'b', 't', 'g'}; char[][] chars = {char1, char2, char3, char4}; String str = "bfcc"; System.out.println(solution.findPath(chars, str)); } }
//面试题14 剪绳子 public class Solution { /** * 求如何剪绳子,绳段的乘积最大 * 法一:动态规划 * * @param length 绳的长度 * @return */ public int maxProduct1(int length) { //deal with invalid input if (length <= 0) return -1; if (length == 1) return 1; if (length == 2) return 2; if (length == 3) return 3; //存储不同长度下绳段乘积最大值 int[] products = new int[length + 1]; products[1] = 1; products[2] = 2; products[3] = 3; products[4] = 4; int max = 0;//存放不同长度下绳段乘积最大值 int temp = 0; for (int i = 5; i <= length; i++) { for (int j = 1; j <= i >> 1; j++) { temp = products[j] * products[i - j]; if (max < temp) { max = temp; } } products[i] = max; } return products[length]; } /** * 贪婪算法 * 思路:长度大于5时,尽可能地剪3;小于5不剪 */ public int maxProduct2(int length) { if (length <= 0) return -1; if (length < 5) return length; int countOf3 = 0; while (length >= 5) { length -= 3; countOf3++; } return (int) Math.pow(3, countOf3) * length; } public static void main(String[] args) { Solution solution = new Solution(); System.out.println(solution.maxProduct1(15)); System.out.println(solution.maxProduct2(15)); } }
/* 面试题15 二进制中1的个数 自身 & (自身-1) 可以使最低位的1置为0 Java中int类型的负数的二进制用补码的形式存储 下面算法不适合负数 */ public int numberOf1(int num) { int count = 0; while (num != 0) { num = num & (num - 1); count++; } return count; } public static void main(String[] args) { Solution solution = new Solution(); int a = 60; System.out.println(solution.numberOf1(a)); }
/**面试题17 * 以O(1)删除列表中的一个指定结点 * 情况分类:不是尾节点, 是尾结点有前结点, 是尾结点无前节点 * * @param first * @param toBeDeleteNode */ public void deleteNode(Node first, Node toBeDeleteNode) throws Exception { //invalid input if (first == null || toBeDeleteNode == null) return; //不是尾 if (toBeDeleteNode.next != null) { toBeDeleteNode.val = toBeDeleteNode.next.val; toBeDeleteNode.next = toBeDeleteNode.next.next; } else { Node i = null; for (i = first; i.next != toBeDeleteNode; i = i.next) { } //有前 if (i != null) { i.next = null; } //无前 else first = null; } }
/** * 面试题18:删除连续重复的节点 * * @param first */ public Node deleteSameNode(Node first) { //invalid input if (first == null) return null; Node preNode = null; //reserve preNode Node node = first; while (node != null) { if (node.next != null && node.val == node.next.val) if (preNode == null) { node = deleteSameNodeInList(node); first = node; //值类型参数不能直接改变 } else { node = deleteSameNodeInList(node); preNode.next = node; } else { preNode = node; node = node.next; } } return first; } private Node deleteSameNodeInList(Node node) { Node result = node; while (result.next != null && result.val == result.next.val) { result = result.next; } return result.next; }
/** * 面试题19 匹配字符串 * * @param str * @param strFormat * @return */ public boolean match(char[] str, char[] strFormat) { if (str == null || strFormat == null) return false; return matchCore(str, 0, strFormat, 0); } private boolean matchCore(char[] str, int strIndex, char[] strFormat, int strFormatIndex) { if (strIndex == str.length && strFormatIndex == strFormat.length) { return true; } if (strIndex >= str.length || strFormatIndex >= strFormat.length) return false; //"." if (strFormat[strFormatIndex] == '.') return matchCore(str, strIndex + 1, strFormat, strFormatIndex + 1); //"a" else if (strFormatIndex == strFormat.length - 1 || strFormat[strFormatIndex + 1] != '*') { if (str[strIndex] != strFormat[strFormatIndex]) return false; else return matchCore(str, strIndex + 1, strFormat, strFormatIndex + 1); } //"a*" else { if (str[strIndex] == strFormat[strFormatIndex] || strFormat[strFormatIndex] == '.') return matchCore(str, strIndex, strFormat, strFormatIndex + 2) || matchCore(str, strIndex + 1, strFormat, strFormatIndex + 2) || matchCore(str, strIndex + 1, strFormat, strFormatIndex); else return matchCore(str, strIndex, strFormat, strFormatIndex + 2); } }
/** * 面试题21:将奇数调整到偶数的前面 * 思路:快速排序法partition方法 * 可以将arr[--hi]&0x01)==0判断方法提取为抽象方法(模板方法) * * @param arr */ public void reorderOddEven(int[] arr) { if (arr == null || arr.length == 0) return; int lo = -1; int hi = arr.length; while (hi > lo) { while ((arr[++lo] & 0x01) != 0) if (lo == arr.length - 1) break; while ((arr[--hi] & 0x01) == 0) if (hi == 0) break; if (hi > lo) { int temp = arr[lo]; arr[lo] = arr[hi]; arr[hi] = temp; } } }
/** * 面试题22:查找链表中倒数第 k 个结点 * * @param head * @param k * @return */ public ListNode FindKthToTail(ListNode head, int k) { if (head == null || k <= 0) return null; //invalid input ListNode lo = head; ListNode hi = head; for (int i = 0; i < k - 1; i++) { if (hi.next == null) return null; // doesn't have Penultimate k_Th hi = hi.next; } while (hi.next != null) { lo = lo.next; hi = hi.next; } return lo; }
/** * 面试题23:找到链表中环的入口结点 * 思路: * 先确定有环 * 确定换中结点个数 * 确定环的入口结点 * * @param head * @return */ public ListNode EntryNodeOfTheLoop(ListNode head) { if (head == null) return null; ListNode p1 = head; ListNode p2 = head; ListNode meetNode = null; do { if (p1.next == null) break; else p1 = p1.next; if (p2.next == null || p2.next.next == null) break; else p2 = p2.next.next; if (p1 == p2) meetNode = p1; } while (p1 != p2); // no loop if (meetNode == null) return null; // have loop; get the number of node in the loop p1 = meetNode; p2 = meetNode.next; int count = 1; while (p1 != p2) { p2 = p2.next; ++count; } p1 = head; p2 = head; for (int i = 0; i < count; i++) { p2 = p2.next; } while (p1 != p2) { p1 = p1.next; p2 = p2.next; } return p1; }
/** * 面试题24:将链表翻转 * 思路:从最后一个结点开始翻转 * * @param head * @return */ public ListNode ReverseList(ListNode head) { if (head == null) return null; if (head.next == null) return head; ListNode prevNode = null; ListNode curNode = head; ListNode storNode = curNode.next; while (curNode != null) { curNode.next = prevNode; prevNode = curNode; curNode = storNode; storNode = curNode == null ? null : curNode.next; } return prevNode; }
/** * 面试题25:合并两个链表 * 思路:类似归并排序中的归并算法 * * @param list1 * @param list2 * @return */ public ListNode Merge(ListNode list1, ListNode list2) { if (list1 == null && list1 == null) return null; if (list1 == null) return list2; if (list2 == null) return list1; ListNode result = null; ListNode min = null; ListNode cur = null; while (list1 != null && list2 != null) { if (list1.val < list2.val) { min = list1; list1 = list1.next; } else { min = list2; list2 = list2.next; } if (result == null) { result = min; cur = min; } else { cur.next = min; cur = min; } } if (list1 != null) cur.next = list1; if (list2 != null) cur.next = list2; return result; } /** * 面试题25:合并两个链表 * 思路:递归的方法 * @param list1 * @param list2 * @return */ public ListNode merge(ListNode list1, ListNode list2) { if(list1==null&& list2==null) return null; if (list1 == null) return list2; if (list2 == null) return list1; ListNode head = null; if(list1.val<list2.val){ head = list1; head.next = merge(list1.next,list2); } else { head=list2; head.next = merge(list1,list2.next); } return head; }