面试题3 -- 数组中重复的数字
题目一:找出数组中重复的数字
在一个长度为n的数组里的所有数字都在0~n-1范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是重复的数字2或者3。
时间复杂度O(n),空间复杂度O(n)的解法
public class Solution {
public boolean duplicate(int[] numbers, int length, List<Integer> duplication) {
if(nums == null || nums.length == 0 || nums.length == 1) {
return false;
}
boolean duplicate = false;
Map<Integer, Integer> dict = new HashMap<>(length);
for(int num : numbers) {
if(dict.containsKey(num)) {
duplication.add(num);
duplicate = true;
} else {
dict.put(num, 1);
}
}
return duplicate;
}
}
时间复杂度O(n),空间复杂度O(1)的解法
public class Solution {
public boolean duplicate(int[] numbers, int length, List<Integer> duplication) {
if (numbers == null || numbers.length == 0 || numbers.length == 1) {
return false;
}
for (int i = 0; i < length; i++) {
if (numbers[i] < 0 || numbers[i] >= length) {
return false;
}
}
boolean duplicate = false;
for(int i = 0; i < length; i++) {
while(numbers[i] != i) {
int target = numbers[i];
if(numbers[target] == target) {
duplicate = true;
duplication.add(target);
break;
} else {
int temp = numbers[target];
numbers[target] = numbers[i];
numbers[i] = temp;
}
}
}
return duplicate;
}
}
题目二:不修改数组找出重复的数字
在一个长度为n+1的数组里的所有数字都在1~n的范围内,所以数组中至少有一个数字是重复的。请找出数组中任意一个重复的数组,但不能修改输入的数组。例如,如果输入长度为8的数组{2,3,5,4,3,2,6,7},那么对应的输出是重复的数字2或者3。
首先,很容易想到时间复杂度O(n)、空间复杂度O(n)的hash解法,同上。
之后,书中提到了可以利用 二分法 的思想,结合具体的例子 举例 分析。长度为8的数组,所有数字都在1~7,通过中间数字4把它分成1~4和5~7,分别统计各个区间的个数,然后发现1~4区间多了,则再对1~4区间进行二分...
public int getDuplication(int[] numbers, int length) {
if (numbers == null || numbers.length <= 1) {
return -1;
}
int left = 1;
int right = length - 1;
int mid = (left + right) >> 1;
while(left < right) {
int leftCount = 0;
int rightCount = 0;
for (int i = 0; i < length; i++) {
if(numbers[i] >= left && numbers[i] <= mid) {
leftCount++;
} else if(numbers[i] > mid && numbers[i] <= right) {
rightCount++;
}
}
if (leftCount > mid - left + 1) {
right = mid;
} else if (rightCount > right - mid) {
left = mid+1;
}
mid = (left + right) >> 1;
}
return mid;
}
面试题4 -- 二维数组中的查找
题目:在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
1 2 8 9
2 4 9 12
4 7 10 13
6 8 11 15
此题Java与书中C++的实现方式没有太多区别,一种“俄罗斯方块”开心消消乐的思想的。笔者一开始想到了二分查找,但是显然不行,不再赘述。
public class Solution {
public boolean find(int[][] matrix, int number) {
if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
return false;
}
int row = 0;
int col = matrix[0].length - 1;
while(row < matrix.length && col >= 0) {
if(number < matrix[row][col]) {
col--;
} else if(number > matrix[row][col]) {
row++;
} else {
return true;
}
}
return false;
}
}
面试题5 -- 替换空格
题目:请事先的一个函数,把字符串中的每个空格替换成"%20"。例如,输入"We are happy.",则输出"We%20are%20happy."。
这个题的重点在于 是否是在原来的字符串上进行替换。如果不是原地修改,那这道题就没什么意义了。
C++中的字符串就是字符数组,即一串字符的其实指针,通过'\0'表示结尾。如"We are happy.",实际是"We are happy.\0"。
这点Java和C++略有不同,Java中的String是JDK封装好的不可修改的字符数组。无法实现原地修改。所以我们直接改用char[]
public class Solution {
public void replaceBlank(char[] str, int length) {
if (str == null || str.length == 0) { // 一定不要忘记考虑边界情况!!!写逻辑之前先记个TODO,写完正文回来补
return;
}
int spaces = 0;
for (int i = 0; i < str.length; i++) {
if (str[i] == ' ') {
spaces++;
}
}
int pOld = length - 1;
int pNew = length - 1 + (spaces * 2);
while(pOld >= 0) {
if (str[pOld] != ' ') {
str[pNew--] = str[pOld--];
} else {
str[pNew--] = '0';
str[pNew--] = '2';
str[pNew--] = '%';
pOld--;
}
}
}
}
面试题5相关题目
有两个排序的数组A1和A2,内存在A1的末尾有足够多的空余空间容纳A2。请实现一个函数,把A2中的所有数字插入A1中,并且所有的数字是排序的。
思路和上面一样,也是考虑 从后往前 遍历。
public class MergeSortSolution {
public void mergeSort(int[] a1, int[] a2, int len1, int len2) {
if(a1 == null || a2 == null) {
return;
}
int p = len1 + len2 - 1;
int p1 = len1 - 1;
int p2 = len2 - 1;
while(p1 >= 0 && p2 >= 0) {
if(a1[p1] > a2[p2]) {
a1[p--] = a1[p1--];
} else {
a1[p--] = a2[p2--];
}
}
while(p1 >= 0) { a1[p--] = a1[p1--] };
while(p2 >= 0) { a1[p--] = a2[p2--] };
}
}
题目6 -- 从尾到头打印链表
题目:输入一个链表的头节点,从尾到头反过来打印出每个节点的值。
典型的“后进先出”,用自定义的 栈 或者递归方法栈实现。考察的是栈这种数据结构的理解与运用。
public void revertPrint(ListNode head) {
Deque<Integer> stack = new LinkedList<>();
ListNode node = head;
while (node != null) {
stack.push(node.key);
node = node.next;
}
while (!stack.isEmpty()) {
System.out.println(stack.pop());
}
}
public void revertPrint2(ListNode head) {
if (head == null) {
return;
}
revertPrint(head.next);
System.out.println(head.key);
}
题目7 -- 重建二叉树
题目:输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如,输入前序遍历{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建如图所示的二叉树并输出它的头节点。
考察的是二叉树,题目是本科数据结构这门课的经典题型,前序遍历 + 中序遍历 ==> 二叉树。如下图,根据前序遍历的信息获取根节点,根据中序遍历的信息获取左右字数,然后递归的处理完。
public BinaryTreeNode reBuildBinaryTree(int[] preorder, int[] inorder) {
if (preorder == null || inorder == null || preorder.length != inorder.length) {
return null;
}
return doRebuildBinaryTree(preorder, inorder, 0, 0, preorder.length);
}
private BinaryTreeNode doRebuildBinaryTree(int[] preorder, int[] inorder, int left1, int left2, int length) {
if (length <= 0) {
return null;
}
int root = preorder[left1];
for (int i = 0; i < length; i++) {
int mid = left2 + i;
if (root == inorder[mid]) {
BinaryTreeNode node = new BinaryTreeNode();
node.value = root;
node.left = doRebuildBinaryTree(preorder, inorder,
left1 + 1, left2, mid - left2);
node.right = doRebuildBinaryTree(preorder, inorder,
left1 + mid - left2 + 1, mid + 1, left2 + length - 1 - mid);
return node;
}
}
// find no root, input data is wrong
throw new IllegalArgumentException("find no root, input data is wrong.");
}
题目8 -- 二叉树的下一个节点
题目:给定一颗二叉树和其中的一个节点,如何找出中序遍历的下一个节点?树中的节点除了有两个分别指向左右子节点的指针,还有一个指向父节点的指针。
此题的关键点在于有一个指向父节点的指针,因此可以考虑结合例子分析中序遍历的过程,分情况讨论中序遍历中下一个节点的可能性。
- 当前节点有右指针,则下一个节点是其右孩子的最后一个左孩子
- 当前节点没有右指针,但是当前节点是一个左指针,则下一个节点是其双亲
- 当前节点没有右指针,并且当前节点自己是一个右指针,则需要一直顺着其双亲往上找,找到第一个是左节点的双亲,然后返回其双亲。没有找到这样的节点的话,则返回null表示当前节点已经是中序遍历的末尾了。
public class Solution {
public BinaryTreeNode findInOrderNext(BinaryTreeNode root, BinaryTreeNode node) {
if (root == null || node == null) {
return null;
}
if (node.right != null) { // 场景1:有右孩子
BinaryTreeNode next = node;
while(next.left != null) {
next = next.left;
}
return next;
} else if(isLeftChild(node)) { // 场景2:是左孩子
return node.parent;
} else { // 场景3:自己是右孩子,且没有右孩子
BinaryTreeNode father = node.parent;
while(father != null) {
if (isLeftChild(father)) {
return father.parent;
}
father = father.parent;
}
return null;
}
}
private boolean isLeftChild(BinaryTreeNode node) {
if (node == null || node.parent == null) {
return false;
}
return node == node.parent.left;
}
}
题目9 -- 用两个栈实现队列
题目:用两个栈实现一个队列。队列的声明如下,请实现它的两个函数appendTail和deleteHead,分别完成在队列尾部插入节点和在队列头部删除节点的功能。
/**
* 两个栈实现一个队列
**/
public class StackQueue<T> {
private Deque<T> appendStack;
private Deque<T> popStack;
public StackQueue() {
appendStack = new LinkedList<>();
popStack = new LinkedList<>();
}
/**
* maybe OOM
**/
public void appendTail(@Nullable T elem) {
appendStack.push(elem);
}
public T deleteHead() {
if (popStack.isEmpty()) {
while(!appendStack.isEmpty()) {
T elem = appendStack.pop();
popStack.push(elem);
}
}
if (popStack.isEmpty()) {
throw new IllegalStateException("queue is empty.");
}
return popStack.pop();
}
public static void main(String[] args) {
StackQueue<Integer> queue = new StackQueue<>();
queue.appendTail(1);
queue.appendTail(2);
System.out.println(queue.deleteHead());
queue.appendTail(3);
System.out.println(queue.deleteHead());
System.out.println(queue.deleteHead());
System.out.println(queue.deleteHead());
System.out.println(queue.deleteHead()); // exception
}
}
相关题目:用两个队列实现一个栈。
/**
* 两个队列实现一个栈
**/
public class QueueStack<T> {
private Queue<T> queue1;
private Queue<T> queue2;
public QueueStack() {
queue1 = new LinkedList<>();
queue2 = new LinkedList<>();
}
public void push(T elem) {
Queue<T> curQueue = queue2.isEmpty() ? queue1 : queue2;
curQueue.offer(elem);
}
public T pop() {
Queue<T> curQueue = queue2.isEmpty() ? queue1 : queue2;
Queue<T> anotherQueue = curQueue == queue1 ? queue2 : queue1;
if (curQueue.isEmpty()) {
throw new IllegalStateException("stack is empty.");
}
while(curQueue.size() > 1) {
T elem = curQueue.poll();
anotherQueue.offer(elem);
}
return curQueue.poll();
}
public static void main(String[] args) {
QueueStack<Integer> stack = new QueueStack<>();
stack.push(1);
System.out.println(stack.pop());
stack.push(2);
stack.push(3);
System.out.println(stack.pop());
System.out.println(stack.pop());
System.out.println(stack.pop());
}
}