面试题3:数组中重复的数字
题目一:长度为n的数组里的数字在0到n-1的范围
题目一:在一个长度为n的数组里的所有数字都在0到n-1的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。例如,如果输入长度为7的数组{2, 3, 1, 0, 2, 5, 3},那么对应的输出是重复的数字2或者3。
- 算法
- 1.遍历数组,把所有数字归到原位,即使得nums[i] = i
- 2.当遇到nums[i] != i的数字时,判断nums[i]和nums[nums[i]]是否相等
- 2.1 如果相等,那它就是重复的数字
- 2.2 如果不相等,就把当前不应该在这个位置的数字归位,然后循环直到把应该在这个位置的数字归位
public int findRepeatNumber(int[] nums) { for (int i = 0; i < nums.length; i++) { while (nums[i] != i) { if (nums[i] == nums[nums[i]]) { return nums[i]; } int temp = nums[i]; nums[i] = nums[temp]; nums[temp] = temp; } } return -1; }
题目二:长度为n的数组里的数字在1到n-1的范围
题目二:在一个长度为n+1的数组里的所有数字都在1到n的范围内,所以数组中至少有一个数字是重复的。请找出数组中任意一个重复的数字,但不能修改输入的数组。例如,如果输入长度为8的数组{2, 3, 5, 4, 3, 2, 6, 7},那么对应的输出是重复的数字2或者3。
- 算法
- 二分查找法
- 1.每次循环统计数组中值在[start middle]范围的元素的个数
- 2.如果范围大于 middle - start + 1,说明重复的数在左半部分,否则在右半部分;重复查找
public int findRepeatNumber(int[] nums) { int start = 1; int end = nums.length - 1; while (start <= end) { int middle = (start + end) >> 1; int count = countRange(nums, start, middle); if (start == end) { if (count > 1) { return start; } else { break; } } if (count > (middle - start + 1)) { end = middle; } else { start = middle + 1; } } return -1; } private int countRange(int[] nums, int start, int end) { int count = 0; for(int i = 0; i < nums.length; i++) { if (nums[i] >= start && nums[i] <= end) { count++; } } return count; }
面试题4:二维数组中的查找
题目:在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
- 算法
- 1.从矩阵的右上角开始寻找而不是从矩阵的左上角开始寻找,便于判断下一步走向
- 2.大于target的话,列数减一
- 3.小于target的话,行数加一
public boolean findNumberIn2DArray(int[][] matrix, int target) { int rows = matrix.length, cols = matrix[0].length; int row = 0, col = cols - 1; while (row < rows && col >= 0) { if (matrix[row][col] > target) { col--; } else if (matrix[row][col] < target) { row++; } else { return true; } } return false; }
面试题5:替换空格
题目:请实现一个函数,把字符串中的每个空格替换成"%20"。例如输入“We are happy.”,则输出“We%20are%20happy.”。
- 算法
- 先遍历一遍统计空格个数,分配替换空格后新的char数组
public String replaceSpace(String s) { if (s == null) { return s; } int oldLength = s.length(); int numberOfBlank = 0; for (char c : s.toCharArray()) { numberOfBlank += (c == ' ' ? 1 : 0); } if(numberOfBlank == 0) { return s; } int newLength = oldLength + (numberOfBlank << 1); int indexOfOld = oldLength-1; int indexOfNew = newLength-1; StringBuilder sb = new StringBuilder(newLength); while (indexOfOld >= 0) { if (s.charAt(indexOfOld) == ' ') { sb.insert(0, '0'); sb.insert(0, '2'); sb.insert(0, '%'); indexOfNew -= 3; } else { sb.insert(0, s.charAt(indexOfOld)); } indexOfOld--; if (indexOfNew == indexOfOld) { sb.insert(0, s.substring(0, indexOfNew+1)); break; } } return sb.toString(); }
面试题6:从尾到头打印链表
题目:输入一个链表的头结点,从尾到头反过来打印出每个结点的值。
- 算法
- 算法1.第一次遍历统计元素,第二次遍历保存到数组
- 算法2.使用栈保存元素,再逐个弹出
// 不使用栈 public int[] reversePrint(ListNode head) { ListNode node = head; int count = 0; while (node != null) { count++; node = node.next; } int[] res = new int[count]; node = head; for (int i = count - 1; i >= 0; --i) { res[i] = node.val; node = node.next; } return res; } // 使用栈 public int[] reversePrint(ListNode head) { if (head == null) { return new int[0]; } Stack<Integer> stack = new Stack<Integer>(); while (head != null) { stack.push(head.val); head = head.next; } int[] res = new int[stack.size()]; int i = 0; while (!stack.empty()) { res[i++] = stack.pop(); } return res; } // 使用栈或递归打印结果 public void reverseTraverseListNode(ListNode head) { if (head == null) { return; } Stack<Integer> stack = new Stack<>(); while (head != null) { stack.push(head.val); head = head.next; } while (!stack.empty()) { System.out.print(stack.pop()+" "); } } public void reverseTraverseListNode(ListNode head) { if (head != null) { reverseTraverseListNode(head.next); System.out.print(head.val+" "); } }
面试题7:重建二叉树
题目:输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1, 2, 4, 7, 3, 5, 6, 8}和中序遍历序列{4, 7, 2, 1, 5, 3, 8, 6},则重建出图2.6所示的二叉树并输出它的头结点。
1 / \ 2 3 / / \ 4 5 6 \ / 7 8 图 2.6
- 算法
- 类似重建二叉树的统一算法
- 1.找出根节点
- 2.利用根节点计算左子树和右子树中的节点个数
- 3.利用节点个数递归构建左子树和右子树
public TreeNode buildTreeByPreorderAndInorder(int[] preorder, int[] inorder) { if (preorder == null || inorder == null || preorder.length == 0 || inorder.length == 0) { return null; } if (preorder.length != inorder.length) { throw new RuntimeException("Invalid input"); } return buildTreeByPreorderAndInorder(preorder, 0, preorder.length - 1, inorder, 0, inorder.length - 1); } private TreeNode buildTreeByPreorderAndInorder(int[] preorder, int preStart, int preEnd, int[] inorder, int inStart, int inEnd) { TreeNode root = new TreeNode(preorder[preStart]); if (preStart == preEnd) { if (preorder[preStart] == inorder[inStart]) { return root; } else { throw new RuntimeException("Invalid input"); } } int rootIndex = inStart; while (rootIndex < inEnd && inorder[rootIndex] != preorder[preStart]) { rootIndex++; } if (rootIndex == inEnd && inorder[rootIndex] != preorder[preStart]) { throw new RuntimeException("Invalid input"); } int leftLength = rootIndex - inStart; int rightLength = inEnd - rootIndex; if (leftLength > 0) { root.left = buildTreeByPreorderAndInorder(preorder, preStart+1, preStart+leftLength, inorder, inStart, rootIndex-1); } if (rightLength > 0) { root.right = buildTreeByPreorderAndInorder(preorder, preStart+leftLength+1, preEnd, inorder, rootIndex+1, inEnd); } return root; }
面试题8:二叉树的下一个结点
题目:给定一棵二叉树和其中的一个结点,如何找出中序遍历顺序的下一个结点?树中的结点除了有两个分别指向左右子结点的指针以外,还有一个指向父结点的指针。
- 算法:结点有指向父结点的引用
- 1.如果右子树不为空,下一个结点就是右子树中最左下方的结点
- 2.如果右子树为空
- 2.1如果当前结点没有父结点,它就是根节点,下一个结点就是null
- 2.2如果当前结点是父结点的左子结点,加上本身右子树为空,下一个结点就是父结点
- 2.3如果当前结点是父结点的右子结点,需要向上追溯直到当前结点是父结点的左子结点
public class TreeNode2 { public int val; public TreeNode2 left; public TreeNode2 right; public TreeNode2 parent; public TreeNode2(int x) { val = x; } } public TreeNode2 nextNodeOfInorder(TreeNode2 node) { if (node == null) { return null; } if (node.right != null) { TreeNode2 rightTree = node.right; while (rightTree.left != null) { rightTree = rightTree.left; } return rightTree; } else { TreeNode2 cur = node; TreeNode2 parent = node.parent; while (parent != null && cur == parent.right) { cur = parent; parent = parent.parent; } return parent; } }
- 算法:标准二叉树
- 1.如果右子树不为空,下一个结点就是右子树中最左下方的结点
- 2.如果右子树为空
- 2.1如果当前结点没有父结点,它就是根节点,下一个结点就是null
- 2.2如果当前结点是父结点的左子结点,加上本身右子树为空,下一个结点就是父结点
- 2.3如果当前结点是父结点的右子结点,需要向上追溯直到当前结点是父结点的左子结点
- ps:寻找父节点的算法
- 递归算法
- 1.当root的左子树和右子树都不是node时,递归到左右子树寻找,然后判断寻找到与否
- 2.当root的左子树和右子树有一个是node时,父节点就是root
public TreeNode nextNodeOfInorder(TreeNode root, TreeNode node) { if (root == null || node == null) { return null; } if (node.right != null) { TreeNode rightTree = node.right; while (rightTree.left != null) { rightTree = rightTree.left; } return rightTree; } else { TreeNode cur = node; TreeNode parent = parentNode(root, node); while (parent != null && cur == parent.right) { cur = parent; parent = parentNode(root, parent); } return parent; } } private TreeNode parentNode(TreeNode root, TreeNode node) { if (root == null || root.left == null && root.right == null) { return null; } TreeNode parent = root; if (root.left != node && root.right != node) { TreeNode leftTree = parentNode(root.left, node); TreeNode rightTree = parentNode(root.right, node); if (leftTree != null) { parent = leftTree; } else if (rightTree != null) { parent = rightTree; } else { parent = null; } } return parent; }
面试题9:栈和队列相互实现
题目一:用两个栈实现一个队列
题目一:用两个栈实现一个队列。队列的声明如下,请实现它的两个函数appendTail和deleteHead,分别完成在队列尾部插入结点和在队列头部删除结点的功能。
- 算法
- 1.添加时向第一个栈添加
- 2.删除时从第二个栈删除,如果是空,全部元素换栈删除栈顶元素
class CQueue { public CQueue() { } public void appendTail(int value) { } public int deleteHead() { } }
class CQueue <T> { Stack<T> stack1 = new Stack<>(); Stack<T> stack2 = new Stack<>(); public CQueue() { } public void appendTail(T value) { stack1.push(value); } public T deleteHead() { if (stack2.empty()) { while (!stack1.empty()) { stack2.push(stack1.pop()); } } if (stack2.empty()) { throw new RuntimeException("Empty Queue"); } return stack2.pop(); } }
题目二:用两个队列实现一个栈
- 算法
- 1.添加时向非空的那个队列添加
- 2.删除时全部元素改变队列,最后一个元素删除;所以任何时刻一定有一个队列为空
class CStack <T> { Queue<T> queue1 = new LinkedList<>(); Queue<T> queue2 = new LinkedList<>(); public CStack() { } public void appendTail(T value) { if (queue2.isEmpty()) { queue1.add(value); } else { queue2.add(value); } } public T deleteTop() { if (queue1.isEmpty() && queue2.isEmpty()) { throw new RuntimeException("Empty Stack"); } else if (queue1.isEmpty()) { while (queue2.size() > 1) { queue1.add(queue2.poll()); } return queue2.poll(); } else { while (queue1.size() > 1) { queue2.add(queue1.poll()); } return queue1.poll(); } } }