声明:我写这个的意图是我在看书的过程中,就我掌握的内容做一个笔记,没有抄袭的意图。再次说明一下,我找工作的过程中并不顺利,没有像那些牛人们拿到一大把的Offer,我只是希望通过我自己的努力,不断提高自己。想要真正掌握这些还是得买本书好好读一下。
注:还没有完结,先放出来,不断更新
1、面试题3:二维数组中的查找
题目大致为:
一个二维数组,每一行按照从左到右递增,每一列按照从上到下递增,查找数组中是否存在某个数。如数组:
1 2 8 9
2 4 9 12
4 7 10 13
6 8 11 15
思路:
这道题有其特殊性,从右上角或者左下角开始查找的方向是确定的。这句话是说比如是查找7,我们从右上角开始,9大于7,则减少列下标,查找13的话就增加行下表,查找的方向是确定的,这样就容易实现了。
Java代码:
package org.algorithm.pointtooffer;
/**
* 面试题3:二维数组的查找
*
* @author dell
*
*/
public class Item03 {
public static void main(String args[]) {
// 测试用的例子
int A[][] = { { 1, 2, 8, 9 }, { 2, 4, 9, 12 }, { 4, 7, 10, 13 },
{ 6, 8, 11, 15 } };
System.out.println(find(A, 7));
}
/**
* 二维数组的查找
* @param array 已知的数组
* @param number 待查找的数
* @return
*/
public static boolean find(int array[][], int number) {
boolean flag = false;
int rows = array.length;// 行数
int columns = array[0].length;// 列数
int row = 0;
int column = columns - 1;
while (row < rows && column >= 0) {
// 比较二维数组中的元素与number的关系
if (array[row][column] == number) {
flag = true;
break;// 跳出循环
} else if (array[row][column] > number) {
// 列变小
column--;
} else {
// 行变大
row++;
}
}
return flag;
}
}
2、面试题4:替换空格
题目大致为:
实现一个函数,把字符串中的每个空格替换成"%20"。
思路:
在Java和C中,对字符串的处理略有不同,在C中字符串是以字符数组的形式存储的,并且在字符串或者字符数组中都有一个结束符"\0";而在Java中,却没有这样的结束符,所以本题在Java下的处理与C中也不一样。
在C中,思路为:先遍历一遍,找到空格的个数,这样便可以计算新的字符串的长度=旧的字符串的长度+空格数*2,然后从尾部向前遍历,遇到非空格,则复制到新的位置,否则直接添加新字符。
在Java中,字符替换主要有两种:replace(char oldChar, char newChar)和replaceAll(String regex, String replacement)。
为简单起见,我加了点限制条件,用Java实现本题。
Java实现
package org.algorithm.pointtooffer;
/**
* 面试题4:替换空格
*
* @author dell
*
*/
public class Item04 {
public static void main(String args[]) {
String s = "We are happy.";
char c_old[] = s.toCharArray();
// 为简单起见,我们假设给它一个新的空间,空间的大小组以存下替换后的字符
char c_new[] = new char[100];
for (int i = 0; i < c_old.length; i++) {
c_new[i] = c_old[i];
}
// 输出新的数组
System.out.println(replaceBlank(c_new, c_old.length));
}
/**
* 计算新的字符串
* @param c带空格的字符数组
* @param length是指第一个字符到最后一个字符的长度,不是字符数组的长度
* @return
*/
public static String replaceBlank(char c[], int length) {
// 查找空格的数目
int blank = 0;
for (int i = 0; i < length; i++) {
if (c[i] == ' ') {
blank++;
}
}
// 重新计算新的数组的大小
int length_new = length + blank * 2;
// 从尾到头查找
int j = length - 1;
int k = length_new - 1;
while (j >= 0 && k >= 0) {
if (c[j] != ' ') {
c[k--] = c[j];
} else {
c[k--] = '0';
c[k--] = '2';
c[k--] = '%';
}
j--;
}
return new String(c);
}
}
3、面试题5:从尾到头打印链表
题目大致为:
输入一个链表的头结点,从未到头反过来打印每个结点的值。
思路:
题目的要求是进行从尾到头输出,而链表的查找只能是顺序查找,栈的结构满足这样的条件:先进后出。同样,也可以使用递归的方式求解。
Java代码:
链表类:
package org.algorithm.pointtooffer;
/**
* 链表的节点
*
* @author dell
*
*/
public class ListNode {
private int value;
private ListNode next;
public ListNode(int value) {
this.value = value;
}
public ListNode(int value, ListNode next) {
this.value = value;
this.next = next;
}
public void setValue(int value) {
this.value = value;
}
public void setNext(ListNode next) {
this.next = next;
}
public int getValue() {
return this.value;
}
public ListNode getNext() {
return this.next;
}
}
Java实现:
package org.algorithm.pointtooffer;
import java.util.Stack;
/**
* 面试题5:从尾到头打印链表
*
* @author dell
*
*/
public class Item05 {
/**
* 测试
*
* @param args
*/
public static void main(String args[]) {
// 构建链表
ListNode head = new ListNode(0);
ListNode node_one = new ListNode(1);
ListNode node_two = new ListNode(2);
ListNode node_three = new ListNode(3);
ListNode node_four = new ListNode(4);
head.setNext(node_one);
node_one.setNext(node_two);
node_two.setNext(node_three);
node_three.setNext(node_four);
node_four.setNext(null);
System.out.println("第一种方式,递归实现:");
printListReverse_1(head);
//换行
System.out.println();
System.out.println("第二种方式,非递归实现:");
printListReverse_2(head);
}
/**
* 用递归实现
*
* @param head
*/
public static void printListReverse_1(ListNode head) {
if (head != null) {
if (head.getNext() != null) {
printListReverse_1(head.getNext());
}
System.out.print(head.getValue() + "、");
}
}
/**
* 用栈实现
*
* @param head
*/
public static void printListReverse_2(ListNode head) {
Stack<Integer> s = new Stack<Integer>();
ListNode p = head;
// 进栈
while (p != null) {
s.push(p.getValue());
p = p.getNext();
}
// 出栈
while (!s.isEmpty()) {
System.out.print(s.pop() + "、");
}
System.out.println();
}
}
4、面试题6:重建二叉树
题目大致为:
已知前序遍历序列和中序遍历序列,要求重建二叉树
二叉树的结点定义:
package org.algorithm.pointtooffer;
public class BinaryTreeNode {
private int value;
private BinaryTreeNode left;
private BinaryTreeNode right;
public BinaryTreeNode(int value) {
this.value = value;
}
public BinaryTreeNode(int value, BinaryTreeNode left, BinaryTreeNode right) {
this.value = value;
this.left = left;
this.right = right;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
public BinaryTreeNode getLeft() {
return left;
}
public void setLeft(BinaryTreeNode left) {
this.left = left;
}
public BinaryTreeNode getRight() {
return right;
}
public void setRight(BinaryTreeNode right) {
this.right = right;
}
}
思路:
如上图所示,在前序遍历的序列中第一个就是树的根结点,此时再在中序遍历的序列里查找这个根结点,则中序遍历的序列里根结点左侧的就是左子树,右侧的就是右子树,再对左右子树进行同样的操作,此时可以使用递归实现,这样便能构造出这个二叉树。
Java代码:
package org.algorithm.pointtooffer;
/**
* 面试题6:重建二叉树
*
* @author dell
*
*/
public class Item06 {
public static void main(String args[]) {
// 二叉树的前序遍历
int preOrder[] = { 1, 2, 4, 7, 3, 5, 6, 8 };
// 二叉树的中序遍历
int inOrder[] = { 4, 7, 2, 1, 5, 3, 8, 6 };
BinaryTreeNode root = constructTree(preOrder, inOrder);
printPostOrder(root);
}
public static BinaryTreeNode constructTree(int preOrder[], int inOrder[]) {
// 根据前序遍历创建根结点
BinaryTreeNode root = new BinaryTreeNode(preOrder[0]);
root.setLeft(null);
root.setRight(null);
int leftNum = 0;//左子树的结点个数
// 在中序中找到根节点
for (int i = 0; i < inOrder.length; i++) {
if (inOrder[i] == root.getValue()) {
break;
} else {
leftNum++;
}
}
int rightNum = preOrder.length - 1 - leftNum;
// 左子树不为空
if (leftNum > 0) {
//构造左子树的前序和中序遍历序列
int leftPreOrder[] = new int[leftNum];
int leftInOrder[] = new int[leftNum];
for (int i = 0; i < leftNum; i++) {
leftPreOrder[i] = preOrder[i + 1];
leftInOrder[i] = inOrder[i];
}
//递归构造左子树
BinaryTreeNode leftRoot = constructTree(leftPreOrder, leftInOrder);
root.setLeft(leftRoot);
}
//右子树不为空
if (rightNum > 0) {
//构造右子树的前序和中序遍历序列
int rightPreOrder[] = new int[rightNum];
int rightInOrder[] = new int[rightNum];
for (int i = 0; i < rightNum; i++) {
rightPreOrder[i] = preOrder[leftNum + i + 1];
rightInOrder[i] = inOrder[leftNum + i + 1];
}
//递归构造右子树
BinaryTreeNode rightRoot = constructTree(rightPreOrder,
rightInOrder);
root.setRight(rightRoot);
}
return root;
}
/**
* 二叉树的后序遍历(递归实现)
* @param root 树的根结点
*/
public static void printPostOrder(BinaryTreeNode root) {
if (root != null) {
printPostOrder(root.getLeft());
printPostOrder(root.getRight());
System.out.print(root.getValue() + "、");
}
}
}
面试题7:用两个栈实现队列
题目大致为:
appendTail和deleteHead。
思路:
栈的特性是:后进先出,而队列的特性是:先进先出。这里使用两个栈实现队列有点负负得正的意思。栈1负责添加,而栈2负责删除。
Java代码:
package org.algorithm.pointtooffer;
import java.util.Stack;
/**
* 面试题7:用两个栈实现队列
*
* @author dell
*
*/
/**
* 构造一个类
*
* @author dell
*
* @param <T>泛型
*/
class CQueue<T> {
// 两个栈
private Stack<T> stack1;
private Stack<T> stack2;
public CQueue() {
this.stack1 = new Stack<T>();
this.stack2 = new Stack<T>();
}
/**
* 模拟在队列末尾插入
*
* @param node
*/
public void appendTail(T node) {
stack1.push(node);
}
/**
* 模拟删除队列的头
*
* @return
*/
public T deleteHead() {
if (stack2.size() == 0) {
if (stack1.size() == 0) {
try {
throw new Exception("队列为空");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
// stack1 不为空,将stack1中的元素放入stack2中
while (stack1.size() > 0) {
stack2.push(stack1.pop());
}
}
return stack2.pop();
}
}
public class Item07 {
// 测试
public static void main(String args[]) {
// 在队列中增加元素
CQueue<Integer> cq = new CQueue<Integer>();
for (int i = 0; i < 5; i++) {
cq.appendTail(i);
}
// 依次取出
for (int i = 0; i < 5; i++) {
System.out.print(cq.deleteHead() + "、");
}
System.out.println();
// 此时为空,再取一次,看会不会报错
cq.deleteHead();
}
}
面试题8:旋转数组的最小数字
题目大致为:
一个递增排序的数组的一个旋转(把一个数组最开始的若干元素搬到数组的末尾,称之为数组的旋转),输出旋转数组的最小元素。
思路:
其实旋转过后的数组是部分有序的,这样的数组依然可以使用折半查找的思路求解
Java代码:
package org.algorithm.pointtooffer;
/**
* 面试题8:旋转数组的最小数字
*
* @author dell
*
*/
public class Item08 {
public static void main(String args[]) {
int A[] = { 3, 4, 5, 1, 2 };// 数组A是数组{1,2,3,4,5}的旋转数组
System.out.println(findMin(A));
}
public static int findMin(int array[]) {
int low = 0;
int high = array.length - 1;
int middle = low;
while (array[low] >= array[high]) {
// 数组中就只有两个数,最小的为后者
if (high - low == 1) {
middle = high;
break;
}
// 查找中间位置
middle = (high + low) / 2;
if (array[middle] >= array[low]) {
low = middle;
} else if (array[middle] <= array[high]) {
high = middle;
}
}
return array[middle];
}
}
面试题9:斐波那契数列
菲波那切数列是每一个学C语言的人都特别熟悉的一个问题。
思路:
用递归实现的过程中会出现重复计算的情况,此时,可以利用动态规划的思想,保存中间结果,这样可以避免不必要的计算。
Java代码:
package org.algorithm.pointtooffer;
/**
* 面试题9:斐波那契数列
*
* @author dell
*
*/
public class Item09 {
public static void main(String args[]) {
int n = 3;
System.out.println(fibonacci(n));
}
public static int fibonacci(int n) {
if (n == 0) {
return 0;
} else if (n == 1) {
return 1;
} else {
//由zero和one保存中间结果
int zero = 0;
int one = 1;
int fN = 0;
for (int i = 2; i <= n; i++) {
fN = one + zero;
zero = one;
one = fN;
}
return fN;
}
}
}
面试题10:二进制中1的个数
题目大致为:
实现一个函数,输入一个整数,输出该数二进制表示中1的个数。
思路:
把一个整数减去1,再和原整数做与运算,会把最右边一个1编程0,那么一个整数的二进制表示中有多少个1,就可以进行多少次这样的操作。
Java实现
package org.algorithm.pointtooffer;
/**
* 面试题10:二进制中1的个数
*
* @author dell
*
*/
public class Item10 {
public static void main(String args[]) {
int n = 9;
System.out.println("9的二进制表示中1的个数为:" + numberOf1(n));
}
/**
* 利用了与的操作
* @param n
* @return
*/
public static int numberOf1(int n) {
int count = 0;
while (n != 0) {
count++;
n = (n - 1) & n;
}
return count;
}
}
面试题11:数值的整数次方
题目大致为:
实现函数double power(double base, int exponent),求base的exponent次方。不得使用库函数,同时不需要考虑大数问题。
思路:
可以考虑对指数折半,这样只需要计算一半的值,若指数是奇数,则-1再折半,否则直接折半。
Java实现
package org.algorithm.pointtooffer;
/**
* 面试题11:数值的整数次方
*
* @author dell
*
*/
public class Item11 {
public static void main(String args[]) {
int base = 2;
int exponent_1 = 9;
int exponent_2 = 10;
System.out.println("当exponent为奇数:" + power(base, exponent_1));
System.out.println("当exponent为偶数:" + power(base, exponent_2));
}
/**
* 整数次方
*
* @param base底
* @param exponent指数
* @return
*/
public static double power(double base, int exponent) {
if (exponent == 0) {
return 1;
}
if (exponent == 1) {
return base;
}
if (exponent >> 1 == 0) {// 偶数
int exponent_1 = exponent >> 1;
double tmp = power(base, exponent_1);
return tmp * tmp;
} else {// 非偶数
int exponent_2 = exponent - 1;// -1后就是偶数
double tmp = power(base, exponent_2);
return tmp * base;// 最后还有先开始减掉的一个base
}
}
}
面试题12:打印1到最大的n位数
面试题13:在O(1)时间删除链表结点
题目大致为:
给定单向链表的头指针和一个结点指针,定义一个函数在 O(1) 时间删除该结点。
思路:
想要在 O(1) 时间内删除链表的指定结点,要遍历的话得 O(n) ,则肯定不能遍历。若是要删除的结点不是尾结点,那么可以将后面的那个值复制到该指针处,并将后面指针所指空间删除,用复制+删除后面的实现删除,时间复杂度为 O(1) 。对于尾结点,需要遍历,那么时间复杂度是 O(n) ,但是总的时间复杂度为 [(n-1)*O(1)+O(n)]/n ,结果是 O(1) 。
链表结构:
package org.algorithm.pointtooffer;
/**
* 链表的节点
*
* @author dell
*
*/
public class ListNode {
private int value;
private ListNode next;
public ListNode(int value) {
this.value = value;
}
public ListNode(int value, ListNode next) {
this.value = value;
this.next = next;
}
public void setValue(int value) {
this.value = value;
}
public void setNext(ListNode next) {
this.next = next;
}
public int getValue() {
return this.value;
}
public ListNode getNext() {
return this.next;
}
}
Java实现
package org.algorithm.pointtooffer;
/**
* 面试题13:在O(1)时间删除链表的节点
*
* @author dell
*
*/
public class Item13 {
public static void main(String args[]) {
// 构建链表
ListNode head = new ListNode(1);
ListNode node_2 = new ListNode(2);
ListNode node_3 = new ListNode(3);
ListNode node_4 = new ListNode(4);
ListNode node_5 = new ListNode(5);
ListNode node_6 = new ListNode(6);
ListNode node_7 = new ListNode(7);
head.setNext(node_2);
node_2.setNext(node_3);
node_3.setNext(node_4);
node_4.setNext(node_5);
node_5.setNext(node_6);
node_6.setNext(node_7);
node_7.setNext(null);
// 输出原始链表
System.out.println("原始链表:");
printList(head);
System.out.println("----------------");
// 删除结点node_3
deleteNode(head, node_3);
System.out.println("删除node_3后链表:");
printList(head);
System.out.println("----------------");
// 删除结点head
deleteNode(head, head);
System.out.println("删除head后链表:");
printList(head);
System.out.println("----------------");
}
/**
* 狸猫换太子删除结点
*
* @param head头指针
* @param toBeDeleted要删除的指针
*/
public static void deleteNode(ListNode head, ListNode toBeDeleted) {
if (head == null || toBeDeleted == null) {
return;
}
// 找到要删除的节点的下一个节点
if (toBeDeleted.getNext() != null) {
ListNode p = toBeDeleted.getNext();// p为toBeDeleted的下一个节点
toBeDeleted.setValue(p.getValue());
// 删除p节点
toBeDeleted.setNext(p.getNext());
} else if (head == toBeDeleted) {
// 如果头结点就是要删除的节点
head = null;
} else {
// 删除尾节点
ListNode currentNode = head;// 用于遍历链表
while (currentNode.getNext() != toBeDeleted) {
currentNode = currentNode.getNext();
}
currentNode.setNext(null);
}
}
/**
* 打印链表
*
* @param head头指针
*/
public static void printList(ListNode head) {
ListNode current = head;
while (current != null) {
System.out.print(current.getValue() + "、");
current = current.getNext();
}
System.out.println();
}
}
面试题14:调整数组顺序使奇数位于偶数前面
题目大致为:
对于一个数组,实现一个函数使得所有奇数位于数组的前半部分,偶数位于数组的后半部分。
思路:
可以使用双指针的方式,一个指针指向数组的开始,一个指针指向数组的尾部,如果头部的数为偶数且尾部的数是奇数则交换,否则头部指针向后移动,尾部指针向前移动,直到两个指针相遇
Java代码:
package org.algorithm.pointtooffer;
import java.util.Arrays;
/**
* 面试题14:调整数组顺序使奇数位于偶数前面
*
* @author dell
*
*/
public class Item14 {
public static void main(String args[]) {
int A[] = { 1, 2, 3, 4, 5 };
rejustArray(A);
System.out.println(Arrays.toString(A));
}
/**
* 调整数组的顺序
* @param array 数组
*/
public static void rejustArray(int array[]) {
// 两个位置,头和尾
int low = 0;
int high = array.length - 1;
// 两个位置直到相遇
while (low < high) {
// 如果low位置上的为偶数,high位置上的为奇数,交换
if (array[low] % 2 == 0 && array[high] % 2 == 1) {
int tmp = array[low];
array[low] = array[high];
array[high] = tmp;
low++;
high--;
} else if (array[low] % 2 == 1) {// 如果low位置上的为奇数,low后移
low++;
} else {// high位置上的为偶数,high前移
high--;
}
}
}
}
面试题15:链表中倒数第k个结点
题目大致为:
在一个链表中,查找倒数的第k个数。
思路:
使用双指针的方式,前一个指针先走k步(中间隔k-1个结点),后一个指针才开始走,直到第一个指针走到尾,后一个指针指向的就是要找的倒数第k个数。值得注意的是:1、k是否超过链表长度且k必须为正整数;2、链表是否为空。
链表结构
package org.algorithm.pointtooffer;
/**
* 链表的节点
*
* @author dell
*
*/
public class ListNode {
private int value;
private ListNode next;
public ListNode(int value) {
this.value = value;
}
public ListNode(int value, ListNode next) {
this.value = value;
this.next = next;
}
public void setValue(int value) {
this.value = value;
}
public void setNext(ListNode next) {
this.next = next;
}
public int getValue() {
return this.value;
}
public ListNode getNext() {
return this.next;
}
}
Java代码:
package org.algorithm.pointtooffer;
/**
* 面试题15:链表中倒数第k个结点
*
* @author dell
*
*/
public class Item15 {
public static void main(String args[]) {
// 构建链表
ListNode head = new ListNode(1);
ListNode h1 = new ListNode(2);
ListNode h2 = new ListNode(3);
ListNode h3 = new ListNode(4);
ListNode h4 = new ListNode(5);
ListNode h5 = new ListNode(6);
head.setNext(h1);
h1.setNext(h2);
h2.setNext(h3);
h3.setNext(h4);
h4.setNext(h5);
h5.setNext(null);
// 查找倒数第k个
ListNode p = findKthToTail(head, 3);
System.out.println(p.getValue());
}
public static ListNode findKthToTail(ListNode head, int k) {
// 首先判断链表是否存在,k是否大于0
if (head == null || k <= 0) {
return null;
}
ListNode prePoint = head;// 第一个指针
ListNode postPoint = head;// 第二个指针
for (int i = 0; i < k - 1; i++) {
if (prePoint.getNext() != null) {
prePoint = prePoint.getNext();
} else {
return null;
}
}
while (prePoint.getNext() != null) {
prePoint = prePoint.getNext();
postPoint = postPoint.getNext();
}
return postPoint;
}
}
面试题16:反转链表
题目大致为:
对于一个链表,反转该链表并返回头结点。
思路:
主要是指针的操作,但是要注意不能断链。这里可以使用非递归的方式求解。
链表结构
package org.algorithm.pointtooffer;
/**
* 链表的节点
*
* @author dell
*
*/
public class ListNode {
private int value;
private ListNode next;
public ListNode(int value) {
this.value = value;
}
public ListNode(int value, ListNode next) {
this.value = value;
this.next = next;
}
public void setValue(int value) {
this.value = value;
}
public void setNext(ListNode next) {
this.next = next;
}
public int getValue() {
return this.value;
}
public ListNode getNext() {
return this.next;
}
}
Java代码:
package org.algorithm.pointtooffer;
/**
* 面试题16:反转链表
*
* @author dell
*
*/
public class Item16 {
/**
* 测试函数
*
* @param args
*/
public static void main(String args[]) {
// 构建链表
ListNode head = new ListNode(0);
ListNode node_one = new ListNode(1);
ListNode node_two = new ListNode(2);
ListNode node_three = new ListNode(3);
ListNode node_four = new ListNode(4);
head.setNext(node_one);
node_one.setNext(node_two);
node_two.setNext(node_three);
node_three.setNext(node_four);
node_four.setNext(null);
// 打印
ListNode reservedHead = reverseList(head);
ListNode tmp = reservedHead;
while (tmp != null) {
System.out.print(tmp.getValue() + "、");
tmp = tmp.getNext();
}
}
/**
* 非递归实现
*
* @param head头指针
* @return
*/
public static ListNode reverseList(ListNode head) {
ListNode reservedHead = null;
ListNode pNode = head;
ListNode pPrev = null;
while (pNode != null) {
ListNode pNext = pNode.getNext();
if (pNext == null) {
reservedHead = pNode;
}
pNode.setNext(pPrev);
pPrev = pNode;
pNode = pNext;
}
return reservedHead;
}
}
面试题17:合并两个排序的链表
题目大致为:
输入两个递增排序的链表,合并这两个链表并使得新链表中的结点仍然按照递增排序的。
思路:
主要是链表中值的比较,取较小的结点插入到新的链表中。
链表结构
package org.algorithm.pointtooffer;
/**
* 链表的节点
*
* @author dell
*
*/
public class ListNode {
private int value;
private ListNode next;
public ListNode(int value) {
this.value = value;
}
public ListNode(int value, ListNode next) {
this.value = value;
this.next = next;
}
public void setValue(int value) {
this.value = value;
}
public void setNext(ListNode next) {
this.next = next;
}
public int getValue() {
return this.value;
}
public ListNode getNext() {
return this.next;
}
}
Java实现
package org.algorithm.pointtooffer;
/**
* 面试题17:合并两个排序的链表
*
* @author dell
*
*/
public class Item17 {
public static void main(String args[]) {
// 构建链表1
ListNode head1 = new ListNode(1);
ListNode node1_2 = new ListNode(3);
ListNode node1_3 = new ListNode(5);
ListNode node1_4 = new ListNode(7);
head1.setNext(node1_2);
node1_2.setNext(node1_3);
node1_3.setNext(node1_4);
node1_4.setNext(null);
// 构建链表2
ListNode head2 = new ListNode(2);
ListNode node2_2 = new ListNode(4);
ListNode node2_3 = new ListNode(6);
ListNode node2_4 = new ListNode(8);
head2.setNext(node2_2);
node2_2.setNext(node2_3);
node2_3.setNext(node2_4);
node2_4.setNext(null);
System.out.println("链表1:");
printList(head1);
System.out.println("-------------");
System.out.println("链表2:");
printList(head2);
System.out.println("-------------");
System.out.println("合并后的链表:");
ListNode head = mergeList(head1, head2);
printList(head);
System.out.println("-------------");
}
/**
* 合并两个链表
* @param head1链表1
* @param head2链表2
* @return
*/
public static ListNode mergeList(ListNode head1, ListNode head2) {
ListNode head = null;// 合并后的头指针
// 如果有一个为空,则为另一个链表
if (head1 == null) {
head = head2;
}
if (head2 == null) {
head = head1;
}
// 两个都不为空
if (head1 != null && head2 != null) {
// node_1和node_2是用于遍历
ListNode node_1 = head1;
ListNode node_2 = head2;
if (node_1.getValue() < node_2.getValue()) {
head = node_1;
head.setNext(mergeList(node_1.getNext(), node_2));
} else {
head = node_2;
head.setNext(mergeList(node_1, node_2.getNext()));
}
}
return head;
}
/**
* 打印链表
*
* @param head头指针
*/
public static void printList(ListNode head) {
ListNode current = head;
while (current != null) {
System.out.print(current.getValue() + "、");
current = current.getNext();
}
System.out.println();
}
}
面试题18:树的子结构
面试题19:二叉树的镜像
题目大致为:
给定一棵二叉树,将其每一个结点的左右子树交换,这就叫做镜像。
思路:
先对其根节点的左右子树处理,交换左右子树,此时再递归处理左右子树。这里要注意分为三种情况:1、树为空;2、只有根结点;3、左右子树至少有一个不为空。
Java实现:
package org.algorithm.pointtooffer;
/**
* 面试题19:二叉树的镜像
*
* @author dell
*
*/
public class Item19 {
public static void main(String args[]) {
// 构建二叉树
BinaryTreeNode root = new BinaryTreeNode(8);
BinaryTreeNode t1 = new BinaryTreeNode(6);
BinaryTreeNode t2 = new BinaryTreeNode(10);
BinaryTreeNode t3 = new BinaryTreeNode(5);
BinaryTreeNode t4 = new BinaryTreeNode(7);
BinaryTreeNode t5 = new BinaryTreeNode(9);
BinaryTreeNode t6 = new BinaryTreeNode(11);
root.setLeft(t1);
root.setRight(t2);
t1.setLeft(t3);
t1.setRight(t4);
t2.setLeft(t5);
t2.setRight(t6);
t3.setLeft(null);
t3.setRight(null);
t4.setLeft(null);
t4.setRight(null);
t5.setLeft(null);
t5.setRight(null);
t6.setLeft(null);
t6.setRight(null);
// 求镜像
mirrorRecursively(root);
// 前序遍历输出
printPreOrder(root);
}
/**
* 分三种情况:1、树为空;2、只有根结点;3、左右子树至少有一个不为空 对根结点的左右子树的处理方法与根结点的处理一致,可以采用递归的方式求解
*
* @param root
*/
public static void mirrorRecursively(BinaryTreeNode root) {
// 树为空
if (root == null) {
return;
}
// 只有一个根结点
if (root.getLeft() == null && root.getRight() == null) {
return;
}
// 左右子树至少有一个不为空
BinaryTreeNode treeTmp = root.getLeft();
root.setLeft(root.getRight());
root.setRight(treeTmp);
// 递归求解左右子树
if (root.getLeft() != null) {
mirrorRecursively(root.getLeft());
}
if (root.getRight() != null) {
mirrorRecursively(root.getRight());
}
}
/**
* 递归方式实现前序遍历输出
* @param root
*/
public static void printPreOrder(BinaryTreeNode root) {
if (root != null) {
System.out.print(root.getValue() + "、");
printPreOrder(root.getLeft());
printPreOrder(root.getRight());
}
}
}
面试题20:顺时针打印矩阵
面试题21:包含min函数的栈
题目大致为:
定义栈的数据结构,在给类型中实现一个能够得到栈的最小元素的 min 函数。在该栈中,调用 min 、 push 及 pop 的时间复杂度都是 O(1)。
思路:
可以建一个辅助的栈,在插入的过程中,插入栈1,同时在插入辅助栈的过程中要求与栈中的元素比较,若小于栈顶元素,则插入该元素,若大于栈顶元素,则继续插入栈顶元素。
Java实现:
package org.algorithm.pointtooffer;
import java.util.Stack;
/**
* 面试题21:包含min函数的栈
*
* @author dell
*
*/
class StackWithMin {
private Stack<Integer> stack;
private Stack<Integer> stackHelp;// 一个辅助的栈
public StackWithMin() {
stack = new Stack<Integer>();
stackHelp = new Stack<Integer>();
}
/**
* 直接插入stack中,在插入stackHelp时,如果为空则直接插入,或者要判断与顶部元素的大小,若小于则插入,若大于则继续插入顶部元素
*
* @param t
* 待插入元素
*/
public void push(int t) {
stack.push(t);
// 插入辅助的栈
if (stackHelp.size() == 0 || t < stackHelp.peek()) {
stackHelp.push(t);
} else {
stackHelp.push(stackHelp.peek());
}
}
/**
* 出栈,要求stack和stackHelp均不为空
*
* @return
*/
public int pop() {
assert (stack.size() > 0 && stackHelp.size() > 0);
stackHelp.pop();
return stack.pop();
}
/**
* 取得最小值,最小值一定是stackHelp的栈顶元素
*
* @return
*/
public int min() {
assert (stack.size() > 0 && stackHelp.size() > 0);
return stackHelp.peek();
}
}
public class Item21 {
public static void main(String args[]) {
StackWithMin s = new StackWithMin();
s.push(3);
s.push(4);
s.push(2);
System.out.println(s.min());
s.push(1);
System.out.println(s.min());
}
}
面试题22:栈的压入、弹出序列
题目大致为:
输入两个整数序列,第一个序列表示栈的压入顺序,判断第二个序列是否为该栈的弹出顺序。
思路:
主要分为这样的几种情况:首先判断两个序列的长度是否相等,若相等且大于0,则利用辅助栈模拟入栈和出栈。如果栈为空,则入栈,此时若栈顶元素与出栈序列的第一个元素相等,则出栈,否则继续入栈,最后判断栈是否为空且出栈序列所有的元素都遍历完。
Java代码:
package org.algorithm.pointtooffer;
import java.util.Stack;
/**
* 面试题22:栈的压入、弹出序列
*
* @author dell
*
*/
public class Item22 {
public static void main(String args[]) {
// 测试用例
// 第一组
int pushArray_1[] = { 1, 2, 3, 4, 5 };
int popArray_1[] = { 4, 5, 3, 2, 1 };
System.out.println("第一组:" + isPopOrder(pushArray_1, popArray_1));
// 第二组
int pushArray_2[] = { 1, 2, 3, 4, 5 };
int popArray_2[] = { 4, 3, 5, 1, 2 };
System.out.println("第二组:" + isPopOrder(pushArray_2, popArray_2));
// 第三组,主要长度不等
int pushArray_3[] = { 1, 2, 3, 4, 5 };
int popArray_3[] = { 4, 5, 3 };
System.out.println("第三组:" + isPopOrder(pushArray_3, popArray_3));
}
/**
* 判断序列popArray是否为pushArray的出栈序列
*
* @param pushArray
* 入栈序列
* @param popArray
* 出栈序列
* @return boolean
*/
public static boolean isPopOrder(int pushArray[], int popArray[]) {
boolean flag = false;
// 能够执行的条件是这样的序列不为空,而且两个序列的长度是相等的
if (pushArray.length > 0 && pushArray.length == popArray.length) {
// 构造一个辅助栈,模拟入栈和出栈
Stack<Integer> stack = new Stack<Integer>();
int i = 0;
int j = 0;
// 保证入栈序列全进入栈
while (i < pushArray.length) {
// 当栈非空时,若栈顶元素与出栈序列中的元素相同,则出栈
if (stack.size() > 0 && stack.peek() == popArray[j]) {
stack.pop();
j++;
} else {// 若不相同或者栈为空,则在入栈序列中继续增加
stack.push(pushArray[i]);
i++;
}
}
// 此时栈中还有元素需要与出栈序列对比
while (stack.size() > 0) {
// 若果相等就出栈
if (stack.peek() == popArray[j]) {
stack.pop();
j++;
} else {//若不相等就直接退出
break;
}
}
// 最终如果栈是空的,而且popArray中的所有数都遍历了,则是出栈序列
if (stack.isEmpty() && j == popArray.length) {
flag = true;
}
}
return flag;
}
}
面试题23:从上往下打印二叉树
题目大致为:
从上往下打印出儿茶树的每个结点,同一层的结点按照从左到右的顺序打印。
思路:
可以使用队列的方式存储,先将根结点入队,若队列不为空,则取出队列的头,若这个结点有左孩子,则左孩子入队,若有右孩子,则右孩子入队。
二叉树的定义
package org.algorithm.pointtooffer;
public class BinaryTreeNode {
private int value;
private BinaryTreeNode left;
private BinaryTreeNode right;
public BinaryTreeNode(int value) {
this.value = value;
}
public BinaryTreeNode(int value, BinaryTreeNode left, BinaryTreeNode right) {
this.value = value;
this.left = left;
this.right = right;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
public BinaryTreeNode getLeft() {
return left;
}
public void setLeft(BinaryTreeNode left) {
this.left = left;
}
public BinaryTreeNode getRight() {
return right;
}
public void setRight(BinaryTreeNode right) {
this.right = right;
}
}
Java实现
package org.algorithm.pointtooffer;
import java.util.LinkedList;
import java.util.Queue;
/**
* 面试题23:从上往下打印二叉树
*
* @author dell
*
*/
public class Item23 {
public static void main(String args[]) {
// 构建二叉树
BinaryTreeNode root = new BinaryTreeNode(8);
BinaryTreeNode t1 = new BinaryTreeNode(6);
BinaryTreeNode t2 = new BinaryTreeNode(10);
BinaryTreeNode t3 = new BinaryTreeNode(5);
BinaryTreeNode t4 = new BinaryTreeNode(7);
BinaryTreeNode t5 = new BinaryTreeNode(9);
BinaryTreeNode t6 = new BinaryTreeNode(11);
root.setLeft(t1);
root.setRight(t2);
t1.setLeft(t3);
t1.setRight(t4);
t2.setLeft(t5);
t2.setRight(t6);
t3.setLeft(null);
t3.setRight(null);
t4.setLeft(null);
t4.setRight(null);
t5.setLeft(null);
t5.setRight(null);
t6.setLeft(null);
t6.setRight(null);
// 层次遍历
System.out.println("层次遍历序列:");
printFromTopToBottom(root);
}
/**
* 层次遍历
*
* @param root根结点
*/
public static void printFromTopToBottom(BinaryTreeNode root) {
// 使用队列的形式
Queue<BinaryTreeNode> queue = new LinkedList<BinaryTreeNode>();
// 根结点入队
queue.add(root);
// 队列非空
while (!queue.isEmpty()) {
// 去除队列的头
BinaryTreeNode treeNode = queue.poll();
System.out.print(treeNode.getValue() + "、");
// 左孩子不为空
if (treeNode.getLeft() != null) {
queue.add(treeNode.getLeft());
}
// 右孩子不为空
if (treeNode.getRight() != null) {
queue.add(treeNode.getRight());
}
}
}
}
面试题24:二查搜索树的后续遍历序列
题目大致为:
输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则返回 true ,否则返回 false 。假设输入的数组的任意两个数字都互不相同。
思路:
主要考察的是二叉搜索树和后序遍历的性质,其中后序遍历的最后一个数是根结点,在二叉搜索树中,左子树的值都小于根结点,右子树的值都大于跟结点,这样便能构造左右子树的序列,用同样的方法分别处理左右子树序列,这便是一个递归的问题。
树的结构
package org.algorithm.pointtooffer;
public class BinaryTreeNode {
private int value;
private BinaryTreeNode left;
private BinaryTreeNode right;
public BinaryTreeNode(int value) {
this.value = value;
}
public BinaryTreeNode(int value, BinaryTreeNode left, BinaryTreeNode right) {
this.value = value;
this.left = left;
this.right = right;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
public BinaryTreeNode getLeft() {
return left;
}
public void setLeft(BinaryTreeNode left) {
this.left = left;
}
public BinaryTreeNode getRight() {
return right;
}
public void setRight(BinaryTreeNode right) {
this.right = right;
}
}
Java实现
package org.algorithm.pointtooffer;
import java.util.Arrays;
/**
* 面试题24:二叉搜索树的后序遍历序列
*
* @author dell
*
*/
public class Item24 {
public static void main(String args[]) {
int array[] = { 5, 7, 6, 9, 11, 10, 8 };
System.out.println(verifySquenceOfBST(array));
}
public static boolean verifySquenceOfBST(int squence[]) {
// 判断序列是否满足要求
int length = squence.length;
if (squence == null || length <= 0) {
return false;
}
// 序列的最后一个数是二查搜索树的根结点
int root = squence[length - 1];
int i;
for (i = 0; i < length; i++) {
// 当只有根结点的时候要用等于去判断
if (squence[i] >= root) {
break;
}
}
// 判断右子树是否满足要求
for (int j = i; j < length - 1; j++) {
if (squence[j] < root) {
return false;
}
}
// 左子树结点的个数
int leftNum = i;
// 构造左子树的序列
int left[] = Arrays.copyOfRange(squence, 0, leftNum);
// 构造右子树的序列
int right[] = Arrays.copyOfRange(squence, leftNum, length - 1);
boolean leftBool = true;
boolean rightBool = true;
// 当左子树的序列存在时
if (left.length > 0) {
leftBool = verifySquenceOfBST(left);
}
// 当右子树的序列存在时
if (right.length > 0) {
rightBool = verifySquenceOfBST(right);
}
return (leftBool && rightBool);
}
}
面试题25:二叉树中和为某一值的路径
面试题26:复杂链表的复制
面试题27:二查搜索树与双向链表
面试题28:字符串的排列
面试题29:数组中出现次数超过一半的数字
题目大致为:
数组中有一个数字出现的次数超过数组长度的一般,找出这个数字。
思路:
在遍历数组的过程中纪录两个量,一个是数组中的数字,一个是次数,当下一个数字和我们保存的一致时则次数加1,当不一致时次数减1,当次数为0时,重置两个量。数组中的数字为当前访问的值,次数为1。这里主要是利用了出现的次数超过了一半,其实就是超过一半数出现的次数减去其他的数出现的次数始终是大于0的。
Java实现
package org.algorithm.pointtooffer;
/**
* 面试题29:数组中出现次数超过一半的数字
*
* @author dell
*
*/
public class Item29 {
public static void main(String args[]) {
int testArray[] = { 1, 2, 3, 2, 2, 2, 5, 4, 2 };
System.out.println("超过一半的数字为:" + moreThanHalfNum(testArray));
}
public static int moreThanHalfNum(int array[]) {
// 一种巧妙的解法
int length = array.length;// 数组的长度
int result = array[0];
int times = 0;
for (int i = 1; i < length; i++) {
if (times == 0) {
result = array[i];
times = 1;
} else if (array[i] == result) {
times++;
} else {
times--;
}
}
return result;
}
}
面试题30:最小的k个数
题目大致为:
输入 n 个整数,找出其中最小的 k 个数。
思路:
使用类似二叉查找树的形式实现,控制好树中的结点个数为 k 。
Java代码
package org.algorithm.pointtooffer;
import java.util.Iterator;
import java.util.TreeSet;
/**
* 面试题30:最小的k个数
*
* @author dell
*
*/
public class Item30 {
public static void main(String args[]) {
// 测试的例子
int array[] = { 4, 5, 1, 6, 2, 7, 3, 8 };
final int k = 4;
TreeSet<Integer> set = getLeastNumbers(array, k);
// 输出
Iterator<Integer> it = set.iterator();
System.out.println("最小的" + k + "个数为:");
while (it.hasNext()) {
System.out.print(it.next() + "、");
}
}
public static TreeSet<Integer> getLeastNumbers(int array[], int k) {
TreeSet<Integer> set = new TreeSet<Integer>();
// 判断k和array的合法性
if (array == null || k <= 0) {
return null;
}
for (int i = 0; i < array.length; i++) {
if (set.size() < k) {// 如果TreeSet中的元素小于K个,则直接插入
set.add(array[i]);
} else {// TreeSet中的元素大于K个
if (set.last() > array[i]) {// 最大的元素大于array[i]
set.pollLast();// 移除
set.add(array[i]);// 加入新的
}
}
}
return set;
}
}
面试题31:连续字数组的最大和
题目大致为:
输入一个整型数组,数组里有正数也有负数。数组中一个或者连续的多个整数组成一个字数组。求所有字数组的和的最大值。要求时间复杂度为 O(n) 。
思路:
因为时间复杂度为O(n),则只能遍历一次数组,这里同时使用两个变量currentSum和finalGreatSum,其中currentSum保存的是当前的和,若currentSum<0,则从下一个位置从新记录,finalGreatSum记录的是历史的最小值,只有当currentSum>finalGreatSum时用currentSum替换finalGreatSum。
Java代码
package org.algorithm.pointtooffer;
/**
* 面试题31:连续字数组的最大和
*
* @author dell
*
*/
public class Item31 {
public static void main(String args[]) {
// 测试
int array[] = { 1, -2, 3, 10, -4, 7, 2, -5 };
int result = findGreatSumOfSubArray(array);
System.out.println("子数组的最大和为:" + result);
}
public static int findGreatSumOfSubArray(int array[]) {
// 用currentSum记录当前的和
int currentSum = 0;
// 用finalGreatSum记录历史最佳
int finalGreatSum = 0;
for (int i = 0; i < array.length; i++) {
currentSum += array[i];
// 如果currentSum>0则记录
if (currentSum > 0) {
// 如果currentSum>finalGreatSum则替换finalGreatSum
if (currentSum > finalGreatSum) {
finalGreatSum = currentSum;
}
} else {
currentSum = 0;
}
}
return finalGreatSum;
}
}
面试题32:从1到n整数中1出现的次数
面试题33:把数组排成最小的数
面试题34:丑数
题目大致为:
丑数的定义为:只包含因子2,3和5的数。求按从小到大的顺序的第1500个丑数。约定:1当做第一个丑数。
思路:
设置三个指针分别代表该位置*2,*3和*5,并将这三个数中的最小值插入数组中,若当前位置的值*对应的因子<=刚插入的值,便将该指针后移,直到新的位置上的值*对应的因子>刚插入的值。
Java实现
package org.algorithm.pointtooffer;
/**
* 面试题34:丑数
*
* @author dell
*
*/
public class Item34 {
public static void main(String args[]) {
int index = 7;
System.out.println("第" + index + "个丑数为:" + getUglyNum(7));
}
/**
* 根据index计算丑数
*
* @param index丑数的下标
* @return丑数
*/
public static int getUglyNum(int index) {
// 检查index
if (index <= 0) {
return 0;
}
// 为了便于存储,可以建立数组保存中间结果
int tmp[] = new int[index];
// 保存第一个
tmp[0] = 1;
// 记录三组数的位置
int multi2 = 0;
int multi3 = 0;
int multi5 = 0;
int nextUglyNum = 1;
while (nextUglyNum < index) {
int min = findMin(tmp[multi2] * 2, tmp[multi3] * 3, tmp[multi5] * 5);
tmp[nextUglyNum] = min;
// 重新计算multi2,multi3,multi5
while (tmp[multi2] * 2 <= min) {
multi2++;
}
while (tmp[multi3] * 3 <= min) {
multi3++;
}
while (tmp[multi5] * 5 <= min) {
multi5++;
}
nextUglyNum++;
}
return tmp[index - 1];
}
/**
* 计算三个数的最小值
*
* @param a
* @param b
* @param c
* @return
*/
public static int findMin(int a, int b, int c) {
int minTmp = (a < b ? a : b);
return (minTmp < c ? minTmp : c);
}
}
面试题35:第一个只出现一次的字符
题目大致为:
在字符串中找出第一个只出现一次的字符。
思路:
在Java中可以把字符串转换成字符数组处理,可以使用 HashMap 的数据结构存储,其中 key 为字符, value 为对应出现的次数,这样通过两次遍历字符数组就可以找出,其中,第一次是构建 HashMap ,第二次是对每个字符判断其 HashMap 中对应的 value 的值是否为1。
Java实现
package org.algorithm.pointtooffer;
import java.util.HashMap;
/**
* 面试题35:第一个只出现一次的字符
*
* @author dell
*
*/
public class Item35 {
public static void main(String args[]) {
// 测试
String s = "abaccdeff";
char c[] = s.toCharArray();
System.out.println("第一个只出现一次的字符为:" + first(c));
}
/**
* 查找第一次只出现一次的字符
*
* @param c待查找的字符数组
* @return
*/
public static char first(char c[]) {
char tmp = 0;
// 可以使用Hash表,key存储的是字符,value存储的是出现的次数
HashMap<Character, Integer> map = new HashMap<Character, Integer>();
for (int i = 0; i < c.length; i++) {
// hash表中已经存在key
if (map.containsKey(c[i])) {
// 修改其value
int value = map.get(c[i]);// 根据key得到value
map.remove(c[i]);
map.put(c[i], value + 1);
} else {
map.put(c[i], 1);
}
}
// 插入完毕后依次搜索
for (int i = 0; i < c.length; i++) {
if (map.get(c[i]) == 1) {
tmp = c[i];
break;// 退出循环
}
}
return tmp;
}
}
面试题36:数组中的逆序对
面试题37:两个链表的第一个公共结点
题目大致为:
输入两个链表,找出它们的第一个公共结点。
思路:
第一个公共结点开始往后都是公共结点,所以在末尾向前遍历,就可以找到第一个公共结点。利用上面的思想,可以先计算两个链表的长度,计算两个链表的长度差,然后先遍历较长的链表,等到剩余长度相等时开始同时遍历,这样就能较快地找到相同的结点,时间复杂度为 O(m+n) ,其中 m , n 分别为两个链表的长度。
Java实现
package org.algorithm.pointtooffer;
/**
* 面试题37:两个链表的第一个公共结点
*
* @author dell
*
*/
public class Item37 {
public static void main(String args[]) {
// 构建链表
ListNode head1 = new ListNode(1);
ListNode node_2 = new ListNode(2);
ListNode node_3 = new ListNode(3);
ListNode head2 = new ListNode(4);
ListNode node_5 = new ListNode(5);
ListNode node_6 = new ListNode(6);
ListNode node_7 = new ListNode(7);
head1.setNext(node_2);
node_2.setNext(node_3);
node_3.setNext(node_6);
node_6.setNext(node_7);
node_7.setNext(null);
head2.setNext(node_5);
node_5.setNext(node_6);
ListNode result = findFirst(head1, head2);
System.out.println("第一个公共结点:" + result.getValue());
}
/**
* 查找第一个公共结点
* @param head1链表1的头指针
* @param head2链表2的头指针
* @return
*/
public static ListNode findFirst(ListNode head1, ListNode head2) {
ListNode p1 = head1;
ListNode p2 = head2;
int list_1_len = 0;
int list_2_len = 0;
// 分别计算两个链表的长度
while (p1 != null) {
list_1_len++;
p1 = p1.getNext();
}
while (p2 != null) {
list_2_len++;
p2 = p2.getNext();
}
// 长度差
int nLength = list_1_len - list_2_len;
ListNode pLong = head1;
ListNode pShort = head2;
if (list_1_len < list_2_len) {
pLong = head2;
pShort = head1;
nLength = list_2_len - list_1_len;
}
// 长的先走nLength步
for (int i = 0; i < nLength; i++) {
pLong = pLong.getNext();
}
// 此时长度相等,一起向前走,并判断他们的值是否相等
while (pLong != null && pShort != null && pLong != pShort) {
pLong = pLong.getNext();
pShort = pShort.getNext();
}
return pLong;
}
}
面试题38:数字在排序数组中出现的次数
题目大致为:
统计一个数字在排序数组中出现的次数。
思路:
由于是排序数组,要查找其中的一个数字,最简便的方法便是折半查找,这样我们可以先查找最先出现的位置和最后出现的位置,数出中间的个数便为总共的出现次数。
Java实现
package org.algorithm.pointtooffer;
/**
* 面试题38:数字在排序数组中出现的次数
*
* @author dell
*
*/
public class Item38 {
public static void main(String args[]) {
// 测试
int array[] = { 1, 2, 3, 3, 3, 3, 4, 5 };
int k = 3;// 待查找的数
System.out.println(k + "的个数为:" + getNumOfK(array, k));
}
/**
* 通过计算初始位置和结束位置,计算出所有的个数
*
* @param array
* @param k
* @return
*/
public static int getNumOfK(int array[], int k) {
int num = 0;
if (array != null) {
int index_start = getFirstK(array, k, 0, array.length - 1);// 初始位置
int index_end = getLastK(array, k, 0, array.length - 1);// 结束位置
if (index_start > -1 && index_end > -1) {
num = index_end - index_start + 1;
}
}
return num;
}
/**
* 查找k在数组中的最先出现的位置
*
* @param array初始数组
* @param k待查找的值
* @return
*/
public static int getFirstK(int array[], int k, int low, int high) {
// 判断low和high
if (low > high) {
return -1;
}
// 折半查找的思想,找中间位置
int middle = (low + high) / 2;
// 查找是否为k
if (array[middle] == k) {// 查找的值是为k,剩下的是判断是否是第一个k
// 是第一个
if ((middle > 0 && array[middle - 1] != k) || middle == 0) {
return middle;
}
// 不是第一个
else {
high = middle - 1;
}
} else if (array[middle] > k) {// 在左侧
high = middle - 1;
} else {// 在右侧
low = middle + 1;
}
return getFirstK(array, k, low, high);
}
/**
* 类似getFirstK,查找最后一个为K
*
* @param array
* @param k
* @return
*/
public static int getLastK(int array[], int k, int low, int high) {
// 判断low和high
if (low > high) {
return -1;
}
int middle = (low + high) / 2;
// 查找是否为k
if (array[middle] == k) {// 查找的值是为k,剩下的是判断是否是最后一个k
// 是最后一个
if ((middle > 0 && array[middle + 1] != k)
|| middle == array.length - 1) {
return middle;
}
// 不是最后一个
else {
low = middle + 1;
}
} else if (array[middle] > k) {// 在左侧
high = middle - 1;
} else {// 在右侧
low = middle + 1;
}
return getLastK(array, k, low, high);
}
}
面试题39:二叉树的深度
题目大致为:
输入一棵二叉树的根结点,求该树的深度。其中,从根结点到叶结点一次经过的结点形成树的一条路径,最长路径的长度为树的深度。
思路:
树的遍历可以利用递归实现,查找深度其实也是遍历的一种,分别遍历左右子树,找到左右子树中结点的个数。
树的结构
package org.algorithm.pointtooffer;
public class BinaryTreeNode {
private int value;
private BinaryTreeNode left;
private BinaryTreeNode right;
public BinaryTreeNode(int value) {
this.value = value;
}
public BinaryTreeNode(int value, BinaryTreeNode left, BinaryTreeNode right) {
this.value = value;
this.left = left;
this.right = right;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
public BinaryTreeNode getLeft() {
return left;
}
public void setLeft(BinaryTreeNode left) {
this.left = left;
}
public BinaryTreeNode getRight() {
return right;
}
public void setRight(BinaryTreeNode right) {
this.right = right;
}
}
Java实现
package org.algorithm.pointtooffer;
/**
* 面试题39:二叉树的深度
*
* @author dell
*
*/
public class Item39 {
public static void main(String args[]) {
BinaryTreeNode root = new BinaryTreeNode(1);
BinaryTreeNode node_2 = new BinaryTreeNode(2);
BinaryTreeNode node_3 = new BinaryTreeNode(3);
BinaryTreeNode node_4 = new BinaryTreeNode(4);
BinaryTreeNode node_5 = new BinaryTreeNode(5);
BinaryTreeNode node_6 = new BinaryTreeNode(6);
BinaryTreeNode node_7 = new BinaryTreeNode(7);
root.setLeft(node_2);
root.setRight(node_3);
node_2.setLeft(node_4);
node_2.setRight(node_5);
node_3.setLeft(null);
node_3.setRight(node_6);
node_4.setLeft(null);
node_4.setRight(null);
node_5.setLeft(node_7);
node_5.setRight(null);
node_6.setLeft(null);
node_6.setRight(null);
node_7.setLeft(null);
node_7.setRight(null);
// 计算深度
System.out.println("二叉树的深度为:" + treeDepth(root));
}
/**
* 计算二叉树的深度
*
* @param root根结点
* @return深度
*/
public static int treeDepth(BinaryTreeNode root) {
// 先判断树是否存在
if (root == null) {
return 0;
}
// 递归实现左右子树的深度
int leftDepth = treeDepth(root.getLeft());
int rightDepth = treeDepth(root.getRight());
// 找到最大值
return (leftDepth > rightDepth ? (leftDepth + 1) : (rightDepth + 1));
}
}
补充:判断一棵树是否为平衡二叉树
题目大致为:
输入一棵二叉树的根结点,判断该树是不是平衡二叉树。其中,如果某二叉树中任意结点的左右子树的深度相差不超过1,那么它就是一棵平衡二叉树。
面试题40:数组中只出现一次的数字
面试题41:和为s的两个数字VS和为s的连续正数序列
题目一大致为:
输入一个递增排序的数组和一个数字s,在数组中查找两个数,使得他们的和正好是s。
思路:
对于有序数组,用两个指针分别指向头和尾部,然后将他们的和与s比较,若等于s,退出;若<s,则头指针向后移;若>s,则尾指针向前移;直到两个指针相遇。
Java代码
package org.algorithm.pointtooffer;
/**
* 面试题41:和为s的两个数字VS和为s的连续正数序列
*
* @author dell
*
*/
public class Item41 {
public static void main(String args[]) {
int array[] = { 1, 2, 4, 7, 11, 15 };
int s = 15;
int result[] = new int[2];// 存储两个解
boolean flag = findNumWithSum(array, result, s);
if (flag == true) {// 存在这样的解,输出
System.out.println("一组解为:" + result[0] + "、" + result[1]);
} else {
System.out.println("不存在");
}
}
/**
* 和为s的两个数组
*
* @param array原始数组
* @param result结果数组
* @param s和
* @return
*/
public static boolean findNumWithSum(int array[], int result[], int s) {
int length = array.length;
boolean flag = false;
// 条件检查,要保证能存储两个数
if (length <= 0 || result.length != 2) {
return flag;
}
// 两个指针
int low = 0;
int high = length - 1;
while (low < high) {
// 如果相等
if (array[low] + array[high] == s) {
// 记录下
result[0] = array[low];
result[1] = array[high];
flag = true;// 表示找到了
break;
}
// 如果>
else if (array[low] + array[high] > s) {
high--;// 减小
}
// 如果小于
else {
low++;// 增加
}
}
return flag;
}
}
题目二大致为:
输入一个正数s,打印出所有和为s的连续正数序列(至少含有两个数)。
思路:
s至少要为3,这样才能满足至少含有两个数,若s>3,这时可以查找low和high之间的和,若=s,则输出;若<s,则增加high,若>s,则增加low,这样就减少了他们之间的数,直到low<(1+s)/2;即小于中间值,因为两个中间值的和可能为s。
Java代码
package org.algorithm.pointtooffer;
/**
* 题目二:和为s的连续序列
*
* @author dell
*
*/
public class Item41_1 {
public static void main(String args[]) {
System.out.println("打印序列:");
findSquenceWithSum(15);
}
/**
* 查找序列
*
* @param s和
*/
public static void findSquenceWithSum(int s) {
if (s < 3) {
System.out.println("不存在");
return;
}
int high = 2;// 至少有两个,那么最小也得为3
int low = 1;
int currentSum = low + high;// 记录当前的和
int end = (1 + s) / 2;
while (low < end) {
// 若==s,则返回
if (currentSum == s) {
printSquence(low, high);
}
// 大于,要减小
while (currentSum > s && low < end) {
currentSum -= low;
low++;
if (currentSum == s) {
printSquence(low, high);
}
}
high++;
currentSum += high;
}
}
/**
* 打印函数
*
* @param low下界
* @param high上界
*/
public static void printSquence(int low, int high) {
// 判断是否符合要求
if (high - low <= 0) {// 只有一个数或者high<low
System.out.println("不存在");
return;
}
for (int i = low; i <= high; i++) {
System.out.print(i + "、");
}
System.out.println();// 换行
}
}
面试题42:翻转单词顺序VS左旋转字符串
题目一大致为:
输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变。为简单起见,标点符号和普通字母一样处理。
思路:
两次翻转,对于字符串 "I am a student." ,首先进行第一次翻转,即翻转完为: ".tneduts a ma I" ,然后再对每个单词翻转,最终为: "student. a am I" 。
Java实现
package org.algorithm.pointtooffer;
/**
* 面试题42:翻转单词顺序VS左旋转字符串
*
* @author dell
*
*/
public class Item42 {
public static void main(String args[]) {
String s = "I am a student.";
System.out.println(reverseSentence(s));
}
/**
* 翻转整个字符串
*
* @param s
* @return
*/
public static String reverseSentence(String s) {
// 将字符串转换成字符数组
char c[] = s.toCharArray();
// 先将整个字符数组翻转
reverse(c, 0, c.length - 1);
// 再翻转每一个单词
int lengthOfTerm = 0;// 单词的长度
for (int i = 0; i < c.length; i++) {
if (c[i] == ' ') {
int end = i - 1;// 末尾的位置
int start = end - lengthOfTerm + 1;
reverse(c, start, end);// 翻转单词
lengthOfTerm = 0;// 重新置0,做下一次的统计
} else {
lengthOfTerm++;// 增加单词的个数
}
}
return new String(c);
}
/**
* 通用的对每个字符数组翻转
*
* @param c
* @param start字符数组的开始
* @param end字符数组的结束
*/
public static void reverse(char c[], int start, int end) {
// 不满足要求的输入
if (c == null || start > end) {
return;
}
// 只有一个字符
if (start == end) {
return;
}
while (start < end) {
char tmp = c[start];
c[start] = c[end];
c[end] = tmp;
start++;
end--;
}
}
}
题目二大致为:
字符串的左旋转操作时把字符串前面的若干个字符转移到字符串的尾部。如输入字符串为 "abcdefg" 和数字2,则返回 "cdefgab" 。
思路:
类似上面的两次翻转。
Java实现
package org.algorithm.pointtooffer;
/**
* 题目二:左旋转字符串
*
* @author dell
*
*/
public class Item42_1 {
public static void main(String args[]) {
String s = "abcdefg";
int k = 2;
System.out.println(leftRotateString(s, k));
}
/**
* 左旋转
*
* @param s原始字符串
* @param k旋转的个数
* @return
*/
public static String leftRotateString(String s, int k) {
// 先检查s和k的合法性
if (s == null || k <= 0) {
return s;
}
// 将字符串转换成字符数组
char c[] = s.toCharArray();
// 翻转整个字符串
reverse(c, 0, c.length - 1);
// 找到k的位置
reverse(c, 0, c.length - 1 - k);
reverse(c, c.length - k, c.length - 1);
return new String(c);
}
/**
* 通用的对每个字符数组翻转
*
* @param c
* @param start字符数组的开始
* @param end字符数组的结束
*/
public static void reverse(char c[], int start, int end) {
// 不满足要求的输入
if (c == null || start > end) {
return;
}
// 只有一个字符
if (start == end) {
return;
}
while (start < end) {
char tmp = c[start];
c[start] = c[end];
c[end] = tmp;
start++;
end--;
}
}
}
面试题43:n个骰子的点数
面试题44:扑克牌的顺序
题目大致为:
从扑克牌中随机抽出5张牌,判断是不是一个顺子,即这5张牌是不是连续的。2~10为数字本身,A为1,J为11,Q为12,K为13,而大、小王可以看成是任意数字。
思路:
实则本题是判断一个数组是否是连续的,将大、小王看成是0,0可以充当任何的数。这样我的思路是:先对数组排序,排序后统计出0的个数,在计算非0数之间的缺少的数字个数的总和,若是在这个过程中发现前后两个的差为0则为相同的元素,则不符合题意;在满足题意的情况下,若是0的个数大于等于缺少的数字个数的总和,那么满足条件,否则不满足条件。
Java代码:
package org.algorithm.pointtooffer;
import java.util.Arrays;
/**
* 面试题44:扑克牌的顺序
*
* @author dell
*
*/
public class Item44 {
public static void main(String args[]) {
// 模拟随机抽牌,大小王为0,A为1,J为11,Q为12,K为13,其实就是个数组,判断数组是否是顺序的
// 测试1:正好填补
int array_1[] = { 0, 0, 1, 4, 5 };
System.out.println(isContinuous(array_1));
// 测试2:不能填补
int array_2[] = { 0, 1, 4, 5, 6 };
System.out.println(isContinuous(array_2));
// 测试3:有相同元素
int array_3[] = { 0, 1, 3, 3, 4, };
System.out.println(isContinuous(array_3));
}
public static boolean isContinuous(int array[]) {
// 由于数组的规模很小,则可以直接使用库函数完成
// 作者有句话很重要:通常我们认为不同级别的时间复杂度只有当n足够大的时候才有意义
Arrays.sort(array);
int numOfZero = 0;// 统计0的个数
int sumOfError = 0;
for (int i = 0; i < array.length - 1; i++) {
if (array[i] == 0) {
numOfZero++;
} else {// 非零的情况
// 若有相同的元素
if (array[i + 1] - array[i] == 0) {
return false;
} else {
sumOfError += (array[i + 1] - array[i] - 1);
}
}
}
if (numOfZero >= sumOfError) {// 0能填补空缺
return true;
} else {
return false;
}
}
}
面试题45:圆圈中最后剩下的数字
题目大致为:
0,1,...,n-1 这 n 个数字排成一个圆圈,从数字 0 开始每次从这个圆圈里删除第 m 个数字。求出这个圆圈里剩下的最后一个数字。
思路:
第一种办法就是通过环形链表模拟这样的删除过程;但是作者推导出了这样的关系式,具体关系式可以见书 P231 。
Java代码
package org.algorithm.pointtooffer;
/**
* 面试题45:圆圈中最后剩下的数字
*
* @author dell
*
*/
public class Item45 {
public static void main(String args[]) {
System.out.println(lastRemaining(5, 3));
}
/**
* 比较巧妙的办法是推导出一个推导公式
*
* @param n
* :n个数字
* @param m
* :删除第m个数字
* @return
*/
public static int lastRemaining(int n, int m) {
if (n < 1 || m < 1) {
return -1;
}
int last = 0;
for (int i = 2; i <= n; i++) {
last = (last + m) % i;
}
return last;
}
}
面试题46:求1+2+...+n
题目大致为:
求1+2+...+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。
思路:
本题书上用C++实现,其中解法一:利用构造函数求解。但是在Java中声明对象数组必须对数组中的对象初始化才能开辟空间。所以我这题不知道利用Java怎么实现。书中的其他几种方式主要是利用C++的一些特性。如果有人知道,可以告诉我,谢谢。
面试题47:不用加减乘除做加法
题目大致为:
写一个函数,求两个整数之和,要求在函数体内不得使用+、-、*、/四则运算符号。
思路:
使用位运算。分三步:第一、不考虑进位,用异或模拟加法;第二、用与运算并左移一位模拟进位;第三、重复前面两步。
Java实现
package org.algorithm.pointtooffer;
/**
* 面试题47:不用加减乘除做加法
*
* @author dell
*
*/
public class Item47 {
public static void main(String args[]) {
int a = 1;
int b = 2;
System.out.println(add(a, b));
}
public static int add(int num1, int num2) {
int sum = 0;
int carry = 0;
do {
sum = num1 ^ num2;// 第一步,异或
carry = (num1 & num2) << 1;// 第二步,进位
// 第三步,相加
num1 = sum;
num2 = carry;
} while (num2 != 0);
return num1;
}
}
面试题48:不能被继承的类
题目大致为:
用C++设计一个不能被继承的类。
思路:
在Java中只要把类定义为final就OK。Java和C++语言有差异的地方还有很多。
面试题49:把字符串转换成整数
面试题50:树中两个结点的最低公共祖先
面试题51:数组中重复的数字
面试题52:构建乘积数组
面试题53:正则表达式匹配
面试题54:
面试题55:
面试题56:链表中环的入口结点
题目大致为:
一个链表中包含环,如何找出环的入口结点?
思路:
对于上图中的链表,首先得判断是否有环存在,在环存在的情况下求出环中结点的个数,最后类似求链表的倒数第K个结点求出入口结点。这样的过程可以使用快慢指针的方式求解。
链表结构
package org.algorithm.pointtooffer;
/**
* 链表的节点
*
* @author dell
*
*/
public class ListNode {
private int value;
private ListNode next;
public ListNode(int value) {
this.value = value;
}
public ListNode(int value, ListNode next) {
this.value = value;
this.next = next;
}
public void setValue(int value) {
this.value = value;
}
public void setNext(ListNode next) {
this.next = next;
}
public int getValue() {
return this.value;
}
public ListNode getNext() {
return this.next;
}
}
Java实现
package org.algorithm.pointtooffer;
/**
* 面试题56:链表中环的入口结点
*
* @author dell
*
*/
public class Item56 {
public static void main(String args[]) {
// 生成链表
ListNode head = new ListNode(1);
ListNode node_2 = new ListNode(2);
ListNode node_3 = new ListNode(3);
ListNode node_4 = new ListNode(4);
ListNode node_5 = new ListNode(5);
ListNode node_6 = new ListNode(6);
head.setNext(node_2);
node_2.setNext(node_3);
node_3.setNext(node_4);
node_4.setNext(node_5);
node_5.setNext(node_6);
node_6.setNext(node_3);
// 找到环的入口结点
System.out.println("环的入口结点为:" + getEntry(head).getValue());
}
/**
* 求出环内结点的个数
* @param head
* @return
*/
public static int numOfCycle(ListNode head) {
// 链表是否存在
if (head == null) {
return -1;// -1表示不存在环
}
// 建立快慢指针
ListNode fastNode = head;
ListNode slowNode = head;
int num = 0; // 环中结点的个数
while (fastNode != null && slowNode != null) {
if (fastNode.getNext() != null
&& fastNode.getNext().getNext() != null) {
fastNode = fastNode.getNext().getNext();
} else {
return -1;// -1表示不存在环
}
slowNode = slowNode.getNext();
// 相遇表示存在环
if (fastNode == slowNode) {
break;// 跳出循环
}
}
// 计算环中结点的个数
num++;
slowNode = slowNode.getNext();
while (slowNode != fastNode) {
slowNode = slowNode.getNext();
num++;
}
return num;
}
/**
* 得到入口结点
* @param head
* @return
*/
public static ListNode getEntry(ListNode head) {
if (head == null) {
return null;
}
ListNode fastNode = head;
ListNode slowNode = head;
int k = numOfCycle(head);
// 无环
if (k == -1) {
return null;
}
// 快指针先走k步,这边是关键
for (int i = 0; i < k; i++) {
fastNode = fastNode.getNext();
}
// 同时走
while (fastNode != slowNode) {
fastNode = fastNode.getNext();
slowNode = slowNode.getNext();
}
return fastNode;
}
}
面试题57:删除链表中重复的结点
题目大致为:
在一个排序的链表中,如何删除重复的结点?
思路:
原始的链表:
删除重复结点后的链表
链表中结点的删除,最关键的就是不能断链。在删除的过程中,被删除结点的前一个结点的指针必须保存,这样才不会断链,所以必须存在一个指针preNode。
面试题58:
面试题59:
面试题60:把二叉树打印成多行
题目大致为:
从上往下按层打印二叉树,同一层的结点按从左到右的顺序打印,每一层打印到一行。
思路:
实际上就是二叉树的层次遍历问题,可以使用队列存储。
二叉树结构
package org.algorithm.pointtooffer;
public class BinaryTreeNode {
private int value;
private BinaryTreeNode left;
private BinaryTreeNode right;
public BinaryTreeNode(int value) {
this.value = value;
}
public BinaryTreeNode(int value, BinaryTreeNode left, BinaryTreeNode right) {
this.value = value;
this.left = left;
this.right = right;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
public BinaryTreeNode getLeft() {
return left;
}
public void setLeft(BinaryTreeNode left) {
this.left = left;
}
public BinaryTreeNode getRight() {
return right;
}
public void setRight(BinaryTreeNode right) {
this.right = right;
}
}
Java实现
package org.algorithm.pointtooffer;
import java.util.LinkedList;
import java.util.Queue;
/**
* 面试题60:把二叉树打印成多行
*
* @author dell
*
*/
public class Item60 {
public static void main(String args[]) {
// 构建二叉树
BinaryTreeNode root = new BinaryTreeNode(8);
BinaryTreeNode t1 = new BinaryTreeNode(6);
BinaryTreeNode t2 = new BinaryTreeNode(10);
BinaryTreeNode t3 = new BinaryTreeNode(5);
BinaryTreeNode t4 = new BinaryTreeNode(7);
BinaryTreeNode t5 = new BinaryTreeNode(9);
BinaryTreeNode t6 = new BinaryTreeNode(11);
root.setLeft(t1);
root.setRight(t2);
t1.setLeft(t3);
t1.setRight(t4);
t2.setLeft(t5);
t2.setRight(t6);
t3.setLeft(null);
t3.setRight(null);
t4.setLeft(null);
t4.setRight(null);
t5.setLeft(null);
t5.setRight(null);
t6.setLeft(null);
t6.setRight(null);
// 逐层打印
printMulti(root);
}
/**
* 逐层打印
* @param root根结点
*/
public static void printMulti(BinaryTreeNode root) {
Queue<BinaryTreeNode> queue = new LinkedList<BinaryTreeNode>();
queue.add(root);
int num = 1;// 表示当前lever层上的结点个数
while (!queue.isEmpty()) {// 队列非空
int nextNum = 0;// 临时变量,用来记录下一层的结点个数
// 取出当前层并打印,随后加入下一层的结点
for (int i = 0; i < num; i++) {
BinaryTreeNode tmp = queue.poll();// 取出队列头
// 左孩子不为空
if (tmp.getLeft() != null) {
queue.add(tmp.getLeft());
nextNum++;
}
// 右孩子不为空
if (tmp.getRight() != null) {
queue.add(tmp.getRight());
nextNum++;
}
System.out.print(tmp.getValue() + "\t");
}
System.out.println();
num = nextNum;
}
}
}