1.输入一棵二元查找树,将该二元查找树转换成一个排序的双向链表。要求不能创建任何新的结点,只调整指针的指向
解法:中序遍历二叉平衡树,先遍历左子树,然后遍历根节点,然后遍历右子树。所以指针需要这样调整即可:每次需要记录他的刚刚访问过的节点pre,以及正在访问的节点now
preNodenext=nowNode
nowNodepre=preNode
2.定义栈的数据结构,要求添加一个min函数,能够得到栈的最小元素。要求函数min、push以及pop的时间复杂度都是O(1)
想得到最小元素那么就需要比较或者排序,很显然如果排序的话,除了天才排序算法,其他的算法时间复杂度不可能是O(1),那么我们就得想想在push与pop的时候进行比较即可。我的思路是这样的:在栈里面的对象中添加一个属性min,记录的是:如果此对象是栈顶的话,栈里面最小的值。举个例子就明白了:
比如说:5,6,4,2,3,8,6
5进栈,min为5
6进栈,min为5
4进栈,min为4
2进栈,min为2
3进栈,min为2
8进栈,min为2
6进栈,min为2
意思就是:对象默认的min值为自身,A进栈的时候,A的min值必须与栈顶的对象B的min值比较,如果比B的min小,A的min值不变,如果A的min值比B的min值大,更新A的min值为B的min值。
示例代码如下:
- package 面试题;
- public class Entry {
- // 栈中的主要数据
- private int data;
- // 栈中的最小值
- private int min;
- public int getMin() {
- return min;
- }
- public void setMin(int min) {
- this.min = min;
- }
- public Entry(int data, int min) {
- this.data = data;
- this.min = min;
- }
- @Override
- public String toString() {
- return "Entry [data=" + data + ", min=" + min + "]";
- }
- /************ 华丽的分割线:上面是一个实体类,下面都是测试程序了 **********************************/
- public static final int N = 10;// 假定栈的容量是10个数据
- public static Entry[] stack = new Entry[N];// 对象所在的栈
- public static int top = -1;// 栈顶指针:注意进栈是++top,出站时top--
- /**
- * 入栈操作
- */
- public static void push(Entry entry) {
- if (top != -1) {// 不为空才能比较
- if (entry.getMin() > stack[top].getMin()) {// 如果比栈顶的最小值大 ,那么更新入栈数据的最小值为刚才栈顶的最小值
- entry.setMin(stack[top].getMin());
- }
- }
- stack[++top] = entry;
- }
- /**
- * 出栈操作
- */
- public static Entry pop() {
- return stack[top--];
- }
- public static void main(String[] args) {
- int[] test = { 2, 3, 5, 4, 6, 1, 8 };
- // 循环入栈
- for (int i = 0; i < test.length; i++) {
- Entry entry = new Entry(test[i], test[i]);// 默认初始化最小值都是自身
- push(entry);
- }
- for (int i = 0; i < test.length; i++) {
- System.out.println(pop());
- }
- }
- }
Entry [data=8, min=1]
Entry [data=1, min=1]
Entry [data=6, min=2]
Entry [data=4, min=2]
Entry [data=5, min=2]
Entry [data=3, min=2]
Entry [data=2, min=2]
3.有一对兔子,从出生的第三个月开始,以后每个月都生出一对兔子,假设兔子都不死,求1月到20月每个月的兔子总数
斐波那契数列的变形f(n)=f(n-1)+f(n-2)
4.维热纳尔方阵
原 A B C D E F G H I J K L M N O P Q R S T U V W X Y Z1 B C D E F G H I J K L M N O P Q R S T U V W X Y Z A
2 C D E F G H I J K L M N O P Q R S T U V W X Y Z A B
3 D E F G H I J K L M N O P Q R S T U V W X Y Z A B C
4 E F G H I J K L M N O P Q R S T U V W X Y Z A B C D
5 F G H I J K L M N O P Q R S T U V W X Y Z A B C D E
6 G H I J K L M N O P Q R S T U V W X Y Z A B C D E F
7 H I J K L M N O P Q R S T U V W X Y Z A B C D E F G
8 I J K L M N O P Q R S T U V W X Y Z A B C D E F G H
9 J K L M N O P Q R S T U V W X Y Z A B C D E F G H I
10 K L M N O P Q R S T U V W X Y Z A B C D E F G H I J
11 L M N O P Q R S T U V W X Y Z A B C D E F G H I J K
12 M N O P Q R S T U V W X Y Z A B C D E F G H I J K L
13 N O P Q R S T U V W X Y Z A B C D E F G H I J K L M
14 O P Q R S T U V W X Y Z A B C D E F G H I J K L M N
15 P Q R S T U V W X Y Z A B C D E F G H I J K L M N O
16 Q R S T U V W X Y Z A B C D E F G H I J K L M N O P
17 R S T U V W X Y Z A B C D E F G H I J K L M N O P Q
18 S T U V W X Y Z A B C D E F G H I J K L M N O P Q R
19 T U V W X Y Z A B C D E F G H I J K L M N O P Q R S
20 U V W X Y Z A B C D E F G H I J K L M N O P Q R S T
21 V W X Y Z A B C D E F G H I J K L M N O P Q R S T U
22 W X Y Z A B C D E F G H I J K L M N O P Q R S T U V
23 X Y Z A B C D E F G H I J K L M N O P Q R S T U V W
24 Y Z A B C D E F G H I J K L M N O P Q R S T U V W X
25 Z A B C D E F G H I J K L M N O P Q R S T U V W X Y
26 A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
解密方法是把第一行A打头的顺序字母当做明文,比如要加密明文的第一个字母就先找到密钥的第一个字母开头的那一行,再找到明文第一个字母在第一行对应的那一列,这个交叉点就是密文的第一个字母。
明文:EXDVG类似X坐标
密文:HELLO
密匙:DHHQI类似Y坐标
5.输入一个整形数组,数组里有正数也有负数。数组中连续的一个或多个整数组成一个子数组,每个子数组都有一个和。求所有子数组的和的最大值。要求时间复杂度为O(n)。
当我们分析的时候,直接三层循环让他把每一个子数组都求出,然后求和,找出最大值。这样时间复杂度为O(n^3)是相当不合适的。
我们可以这样想:假如数组中全为正数,那么最大和必然为全部数相加
如果数组中有负数,并且加上这个负数,子数组的和小于0,那么最大和的子数组一定不包含这个负数。如果加上这个负数大于0
java代码如下
- package 面试题;
- public class 最大子数组 {
- public static void main(String[] args) {
- int array[] = { 1, -2, 3, -10, -4, 7, 2, -5, 5, 6, -1, -2, -4, 5, 7, 10 };
- int sum = 0;// 子数组的和
- int max = 0;// 临时变量,记录子数组和的最大值
- for (int i = 0; i < array.length; i++) {
- // 如果最大值大于0,那么继续,如果最大值小于0,那么重新进行计数
- if (max > 0) {
- max = max + array[i];
- } else {
- max = array[i];
- }
- // 判断是不是,如果不是最大和,那么更新最大和,同时更新下标
- if (sum < max) {
- sum = max;
- }
- }
- System.out.println("最大值:" + sum);
- }
- }
- package 面试题;
- public class 求最大子数组 {
- public static void main(String[] args) {
- int array[] = { 1, -2, 3, -10, -4, 7, 2, -5, 5, 6, -1, -2, -4, 5, 7, 10};
- int sum = 0;// 子数组的和
- int max = 0;// 临时变量,记录子数组和的最大值
- int start = 0;// 最大和子数组的初始下标
- int end = array.length;// 最大和子数组的结束下标
- for (int i = 0; i < array.length; i++) {
- // 如果最大值大于0,那么继续,如果最大值小于0,那么重新进行计数
- if (max > 0) {
- max = max + array[i];
- } else {
- max = array[i];
- start = i;// 记录start
- }
- // 判断是不是,如果不是最大和,那么更新最大和,同时更新下标
- if (sum < max) {
- sum = max;
- end = i;// 记录更新结束的位置end
- }
- }
- System.out.println("最大值:" + sum);
- for (int i = start; i <= end; i++) {
- System.out.print(array[i] + " ");
- }
- }
- }
1, -2, 3, -10, -4, 7, 2, -5, 5, 6, -1, -2, -4, 5, 7, 10, -100, -1你会发现打印不出数组,因为start已经跑在end之后了。那么怎么解决这个问题呢?很简单,设置一个临时的start就行了,更新最大数值的start数据的时候,同时更新end数据。最终代码如下:
- package 面试题;
- public class 求最大子数组 {
- public static void main(String[] args) {
- int array[] = { 1, -2, 3, -10, -4, 7, 2, -5, 5, 6, -1, -2, -4, 5, 7, 10, -100, -1 };
- int sum = 0;// 子数组的和
- int max = 0;// 临时变量,记录子数组和的最大值
- int startTemp = 0;// 最大和子数组的初始下标
- int startReal = 0;// 记录真实最大子数组的初始下标
- int end = array.length;// 最大和子数组的结束下标
- for (int i = 0; i < array.length; i++) {
- // 如果最大值大于0,那么继续,如果最大值小于0,那么重新进行计数
- if (max > 0) {
- max = max + array[i];
- } else {
- max = array[i];
- startTemp = i;// 记录更新初始位置,startTemp可能会在end之后
- }
- // 判断是不是,如果不是最大和,那么更新最大和,同时真正的更新开始下标和结束下标
- if (sum < max) {
- sum = max;
- startReal = startTemp;
- end = i;// 记录更新结束的位置
- }
- }
- System.out.println("最大值:" + sum);
- for (int i = startReal; i <= end; i++) {
- System.out.print(array[i] + " ");
- }
- }
- }
6.1-->N中,1出现的次数
比如说:1,2,3,4,5,6,7,8,9,10,11,12中1的个数为5分析:
继续分析一个多位数abcde,推导出下面一般情况: 假设N,我们要计算百位上出现1的次数,将由三部分决定:百位上的数字,百位以上的数字,百位一下的数字。
如果百位上的数字为0,则百位上出现1的次数仅由更高位决定,比如12013,百位出现1的情况为100~199,1100~1199,2100~2199,…,11100~11199,共1200个。等于更高位数字乘以当前位数,即12 * 100。
如果百位上的数字大于1,则百位上出现1的次数仅由更高位决定,比如12213,百位出现1的情况为100~199,1100~1199,2100~2199,…,11100~11199,12100~12199共1300个。等于更高位数字加1乘以当前位数,即(12 + 1)*100。
如果百位上的数字为1,则百位上出现1的次数不仅受更高位影响,还受低位影响。例如12113,受高位影响出现1的情况:100~199,1100~1199,2100~2199,…,11100~11199,共1200个,但它还受低位影响,出现1的情况是12100~12113,共114个,等于低位数字113+1。
这种思想我也结合代码理解了好久,最终耗费将近一天时间,把它给消化了。
java代码如下:
- public class 一到N中1的个数 {
- // 算法思想:从个位开始,先计算个位是1的个数,然后计算十位为1的数据,然后计算百位为1...
- public static long CountOne(long n) {
- long count = 0;// 代表个数
- long i = 1;
- long current = 0, after = 0, before = 0;
- while ((n / i) != 0) {
- before = n / (i * 10);// 当前位前面的
- current = (n / i) % 10;// 当前位
- after = n - (n / i) * i;// 当前位后面的
- System.out.println("before:" + before + "\tcurrent:" + current + "\tafter:" + after);
- if (current > 1)
- count = count + (before + 1) * i;// 由最高位决定
- else if (current == 0)
- count = count + before * i;// 由最高位决定
- else if (current == 1)
- count = count + before * i + after + 1;// 由高位与低位决定
- i = i * 10;
- }
- return count;
- }
- // 一定要注意:求的是1出现的个数,不是含有1的整数的个数,不要混淆了
- public static void main(String[] args) {
- int n = 65535;
- System.out.println("1-->" + n + "的个数:" + CountOne(n));
- }
- }
7.根据上排给出的十个数,在下排填出对应的十个数,要求下排每个数都是先前上排那十个数在下排出现的次数.
举一个例子,数值: 0,1,2,3,4,5,6,7,8,9 【上排】
分配: 6,2,1,0,0,0,1,0,0,0 【下排】
0在下排出现了6次,1在下排出现了2次
2在下排出现了1次,3在下排出现了0次
以此类推….
经过分析发现:每一个0N的上排,都有对应的下排,是不是想到八皇后了?没错,跟八皇后很类似!所以我猜想应该也是用的回溯法。那么应该如何实现这个算法呢?是不是仅有对应的一个下排呢?
首先我们用数学分析一下:
由于下排本身对应的是上排在下排出现的次数,那么总共一定会出现N次(因为下排容量是N),所以下排数列的和一定是上排数列的个数N。【脑筋绕不过去的话,可以这么想:如果上排第一个对应的下排第一个5,第二个为6,那么加起来就是出现了11次,但是下排总共才有10个位置,所以。。。明白了???】
算法还没想出来,不过先穷举一下看看规律
##########【1】##########
##########【2】##########
##########【3】##########
##########【4】##########
数值:0,1,2,3
分配:1,2,1,0
数值:0,1,2,3
分配:2,0,2,0
##########【5】##########
数值:0,1,2,3,4
分配:2,1,2,0,0
##########【6】##########
##########【7】##########
数值:0,1,2,3,4,5,6
分配:3,2,1,1,0,0,0
##########【8】##########
数值:0,1,2,3,4,5,6,7
分配:4,2,1,0,1,0,0,0
##########【9】##########
数值:0,1,2,3,4,5,6,7,8
分配:5,2,1,0,0,1,0,0,0
##########【10】##########
数值:0,1,2,3,4,5,6,7,8,9
分配:6,2,1,0,0,0,1,0,0,0
##########【11】##########
数值:0,1,2,3,4,5,6,7,8,9,10
分配:7,2,1,0,0,0,0,1,0,0,0
##########【12】##########
数值:0,1,2,3,4,5,6,7,8,9,10,11
分配:8,2,1,0,0,0,0,0,1,0,0,0
##########【13】##########
数值:0,1,2,3,4,5,6,7,8,9,10,11,12
分配:9,2,1,0,0,0,0,0,0,1,0,0,0
##########【14】##########
数值:0,1,2,3,4,5,6,7,8,9,10,11,12,13
分配:10,2,1,0,0,0,0,0,0,0,1,0,0,0
##########【15】##########
数值:0,1,2,3,4,5,6,7,8,9,10,11,12,13,14
分配:11,2,1,0,0,0,0,0,0,0,0,1,0,0,0
##########【16】##########
数值:0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15
分配:12,2,1,0,0,0,0,0,0,0,0,0,1,0,0,0
##########【17】##########
数值:0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16
分配:13,2,1,0,0,0,0,0,0,0,0,0,0,1,0,0,0
##########【18】##########
数值:0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17
分配:14,2,1,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0
这是怎么来的呢?
设10个变量:x1, x2, x3, x4, x5, x6, x7, x8,x9, x10,并且全部>=0
x1+x2+x3+x4+x5+x6+x7+x8+x9+x10=10;
x1*0+x2*1+x3*2+x4*3+x5*4+x6*5+x7*6+x8*7+x9*8+x10*9=10;
则,直接解一个线性方程组即可,可以直接求出通解。那么就是一个简单的线性代数求解问题了。
8.求数组中第K大的数可以基于快排序思想,步骤如下:
1.随机选择一个支点2.将比支点大的数,放到数组左边;将比支点小的数放到数组右边;将支点放到中间(属于左部分)
3.设左部分的长度为L,
当K < L时,递归地在左部分找第K大的数
当K > L时,递归地在有部分中找第(K - L)大的数
当K = L时,返回左右两部分的分割点(即原来的支点),就是要求的第K大的数
9.现在有很多闭区间,比如[1,10] [2,8][3,11][4,7][12,13][7,15][17,18]已知这个对象(结构体)有start,有end,有length
问题一:哪一个点的区间个数最多问题二:如果两个区间有重叠,可以合并,求出合并后的区间
问题三:如果区间表示的计算机执行某个原子性操作的时间段(注意:原子性操作不可以一旦开始,不能中断),请问最多能执行的时间有多长?
为了方便,我们首先定义一个区间类(C语言就是结构体)
- package 闭区间;
- public class Internal {
- private int start;
- private int end;
- private int length;
- public Internal(int start, int end) {
- this.start = start;
- this.end = end;
- this.length = this.start - this.end;
- }
- public int getStart() {
- return start;
- }
- public int getEnd() {
- return end;
- }
- public int getLength() {
- return length;
- }
- }
算法思想如下
问题一:遇到start就进栈,遇到end就出栈,然后进栈的时候统计栈的个数与max比较即可。(这个可以应用于求当前线程数,进程数)
问题二:遇到start就进栈,遇到end就出栈,如果出栈的时候栈中为空,合并后的区间就为【刚出栈的元素,此end元素】
那么很明显我们需要知道这个坐标是start还是end(解决问题一与问题二)
问题三:我们怎么办呢?你是不是想起广度优先与深度优先呢?没错,跟图那一块的知识点很相似,需要用到递归与回溯。一旦这个工作开始,那么必须等到结束才行。当前任务结束以后,再继续查找下一个可执行任务,递归,一直到没有任务为止,把所有的任务时间相加,最大的那个就是最长的工作时间。
总的代码如下
Internal代码为了问题三的排序,所以需要实现comparable接口,修改如下:
- package 闭区间;
- public class Internal implements Comparable<Internal> {
- private int start;
- private int end;
- private int length;
- public Internal(int start, int end) {
- this.start = start;
- this.end = end;
- this.length = this.end - this.start;
- }
- public int getStart() {
- return start;
- }
- public int getEnd() {
- return end;
- }
- public int getLength() {
- return length;
- }
- @Override
- public String toString() {
- return "Internal [start=" + start + ", end=" + end + ", length=" + length + "]";
- }
- @Override
- public int compareTo(Internal o) {
- if (this.start > o.getStart()) {
- return 1;
- } else if (this.start < o.getEnd()) {
- return -1;
- } else {
- return 0;
- }
- }
- }
- package 闭区间;
- import java.util.ArrayList;
- import java.util.Arrays;
- import java.util.List;
- public class Problem {
- public static final boolean START = true;
- public static final boolean END = false;
- public static void main(String[] args) {
- Internal internal1 = new Internal(1, 6);
- Internal internal2 = new Internal(2, 4);
- Internal internal3 = new Internal(5, 7);
- Internal internal4 = new Internal(5, 6);
- Internal internal5 = new Internal(8, 10);
- List<Internal> allInternal = new ArrayList<Internal>();
- allInternal = Arrays.asList(internal1, internal2, internal3, internal4, internal5);
- // 测试二
- testProblem1And2(allInternal);
- // 测试三
- Internal[] internals = (Internal[]) allInternal.toArray();
- Arrays.sort(internals);
- getMax(internals, 0);
- System.out.println("最大时间为:" + max);
- }
- /************** 华丽的分割线:下面是测试问题三 *******************/
- public static int current = 0;// 当前的总时长
- public static int max = 0;// 最大总时长
- private static void getMax(Internal[] internals, int index) {
- for (int i = index; i < internals.length; i++) {
- current += internals[i].getLength();
- max = max > current ? max : current;
- int next = getNextBegin(i, internals);
- getMax(internals, next);
- current -= internals[i].getLength();
- }
- }
- private static int getNextBegin(int i, Internal[] internals) {
- int j = 0;
- for (j = i + 1; j < internals.length; j++) {
- if (internals[j].getStart() > internals[i].getEnd()) {
- return j;
- }
- }
- return j;
- }
- /************** 华丽的分割线:下面是测试问题一与问题二 *******************/
- private static void testProblem1And2(List<Internal> allInternal) {
- Point[] points = new Point[allInternal.size() * 2];
- for (int i = 0; i < allInternal.size(); i++) {
- Point pointBegin = new Point(START, allInternal.get(i).getStart());
- Point pointEnd = new Point(END, allInternal.get(i).getEnd());
- points[i * 2] = pointBegin;
- points[i * 2 + 1] = pointEnd;
- }
- Arrays.sort(points);
- Point[] stack = new Point[allInternal.size() * 2];
- int max = -1;
- int stackTop = -1;// 栈顶游标
- for (int i = 0; i < points.length; i++) {
- if (points[i].flag) {
- stack[++stackTop] = points[i];// 进栈
- if (stackTop > max) {
- max = stackTop;
- }
- } else {
- Point pop = stack[stackTop--];// 出栈
- if (stackTop < 0) {// 如果栈为空
- System.out.println("合并的区间为:[" + pop.location + "," + points[i].location + "]");
- }
- }
- }
- System.out.println("最大次数为" + (max + 1));
- }
- // 坐标类
- static class Point implements Comparable<Point> {
- public boolean flag;// 是start还是end
- public int location;// 坐标
- public Point(boolean flag, int location) {
- this.flag = flag;
- this.location = location;
- }
- /**
- * 为了数组排序
- */
- @Override
- public int compareTo(Point o) {
- if (this.location > o.location) {
- return 1;
- } else if (this.location < o.location) {
- return -1;
- } else {
- return 0;
- }
- }
- @Override
- public String toString() {
- return "Point [flag=" + flag + ", location=" + location + "]";
- }
- }
- }
10.一个单链表中,如何求出倒数第n个数据?如何求出中间的数据?这是特别常见特别简单的一类问题
常规做法:遍历一遍,存入数组中,直接找到第n个数据考官心中的答案:
设定两个指针,A指针每次移动1位,B指针每次移动2位,当B指针为空的时候,A为指针的中间位置。
设定两个指针,A指针每次移动1位,移动到第n位的时候,B指针开始每次移动1位,当A移动到链表末尾的时候,B指针移动到倒数第n个位置。