1、将一个字符串转换成整数
输入参数的合法性检查:
(1)是否为null;
(2)是否为空串;
(3)是否只含有+和-字符;
(4)是否含有非数字字符。
编程技巧:
(1)使用全局变量作为字符串的合法性标记;
(2)完成主要功能的代码是: number = number * 10 + strArray[i] - '0';
(3)注意字符串是否以+或者-开始。
2、求链表的倒数第k个结点
输入参数的合法性检查:
(1)k是否小于等于0;
(2)输入链表的头结点是否为空或者只有头结点;
(3)k是否大于链表的长度(在解决过程中检查)。
功能实现具体思路(快慢指针法):
设置一个指针从第一个结点开始向后走k - 1步(注意对指针是否为空的判断),如果已经走到链表最后一个结点,但是这时k仍然大于0,说明k大于链表长度,不合法;
然后再设置一个指针从链表的第一个结点开始和第一个指针一起向后走,直至第一个指针来到链表的最后一个结点,此时,第二个指针指向的结点便是倒数第k个结点。
3、将一个二叉排序树转换成有序的双向链表
画图,举例,分解,中序遍历,递归
实现细节:
(1)递归头:if(root == null) return;
(2)由二叉排序树:左 < 根 < 右的特点可知,要想得到有序的双向链表,应该采用中序遍历的方式,先递归调用函数处理左子树得到一个双向链表和一个指向该双向链表最后一个结点的指针(为了实现这个目的,
把已经初始化好的双向链表和指向最后一个结点的指针作为参数传递给函数),然后,将根结点连在得到的链表的表尾,并更新为新的表尾,最后在将链表和指针最为参数传递给函数递归处理右子树。
4、在二维数组中查找一个数,该二维数组每一行、每一列均已递增排序
应该举出具体的例子发现查找的规律,选择右上角或者左下角先进行比较,以右上角为例,如果右上角的那个数大于所要查找的
目标数,那么右上角所在的那一列的数都会比目标数大,从而查找范围缩小一列,如果右上角的那个数小于所要查找的目标数,
那么右上角所在的那一行的数都会比目标数小,从而查找范围缩小一行,如果相等,则查找成功!
5、替换字符串中的空格
先遍历一遍字符串,看有多少空格,由此得出替换之后字符串的长度,然后扩充原先字符串的长度,再设置两个扫描变量p、q,分别
从后向前扫描原字符串和扩充之后的字符串,当p遇到非空格时,直接复制,当p遇到空格时,跳过,对q所指向的字符赋值为替换之后
的字符。此方法的时间复杂度为o(n)!
6、在面试中,如果我们打算修改所给的的输入数据,那么我们最好询问面试官是否允许修改!
7、递归在本质上就是栈结构,如果一个问题可以用栈来实现,那么这个问题必然也能用递归来实现。例如,从尾到头输出一个链表。
(1)、遍历这个链表,并将每一个元素压入栈中,最后再将栈中的元素弹出并输出即可
(2)、建立递归函数,我们每访问到一个结点的时候,先递归输出它后面的结点,然后再输出该结点自身。
8、二叉树的先序、中序、后序遍历的非递归实现
利用栈
(1)、先序:创建一个栈,先将根结点压入栈中,设置一个指向结点的指针,循环,只要栈不为空,就取出栈顶元素并出栈,如果不
为空,就访问它,并依此将它的右、左子结点压入栈中。
(2)、中序:创建一个栈,使得当前指针指向根结点,进入循环(循环条件是栈不为空或者当前指针不为空),首先从根结点开始沿
着左子树前进,在此过程中将遇到的结点压入栈中,直至为空。那么该结点相当于是最左边的没有左子树的根节点,根据左根右的遍历
顺序,现在应该将其出栈,并且访问,然后将指针移向它的右子树,重复上述循环,如果没有右子树,则下次循环直接取栈顶元素,访
问上一层的根节点,继续上述过程!
(3)、后序:
9、如何用两个队列实现一个栈
假设有队列A和B,那么只要实现好栈的入栈、出栈、取栈顶元素即可。
(1)入栈push,只要将元素进入其中一个队列即可,假设为A。
(2)出栈pop,只要将队列的最后一个元素删除即可,将A中的元素按照先进先出的顺序出队,并进入到B队列中,直至最后一个元素
(此元素便是栈顶元素),返回该元素并将其从A队列中直接删除,最后将B队列中的元素再次按照队列的出队顺序出队,并且进入A队列。
10、归并排序
public
class
MergeSort
{
public
static
void
main
(
String
[]
args
) {
int
data
[] = {
23
,
12
,
45
,
34
,
21
,
56
,
36
,
89
,
3
};
mergeSort
(
data
);
for
(
int
x
:
data
) {
System
.
out
.
print
(
x
+
" "
);
}
}
private
static
void
mergeSort
(
int
[]
data
) {
if
(
data
==
null
||
data
.
length
<=
0
)
return
;
int
array
[] =
new
int
[
data
.
length
];
for
(
int
i
=
0
;
i
<
data
.
length
;
i
++)
array
[
i
] =
data
[
i
]; // 注意复制原来的数组元素
mergeSortCore
(
data
,
array
,
0
,
data
.
length
-
1
);
}
private
static
void
mergeSortCore
(
int
[]
data
,
int
[]
array
,
int
start
,
int
end
) {
if
(
start
==
end
)
return
;
int
length
= (
end
-
start
) >>
1
;
mergeSortCore
(
array
,
data
,
start
,
start
+
length
);
mergeSortCore
(
array
,
data
,
start
+
length
+
1
,
end
); // 注意辅助数组和原数组的位置交换
int
i
=
start
+
length
,
j
=
end
,
k
=
end
;
while
(
i
>=
start
&&
j
>=
start
+
length
+
1
) {
if
(
array
[
i
] >
array
[
j
]) {
data
[
k
--] =
array
[
i
--];
}
else
{
data
[
k
--] =
array
[
j
--];
}
}
while
(
i
>=
start
) {
data
[
k
--] =
array
[
i
--];
}
while
(
j
>=
start
+
length
+
1
) {
data
[
k
--] =
array
[
j
--];
}
}
}
11、位运算包括:与、或、异或、左移、右移。
12、给定一系列已知范围的整数,假设最大值为max,总共有上万个整数,要求实现一个时间复杂度为o(n)的排序算法,对这组整数进行排序。
其一,时间复杂度为o(n),其二,数组中的数最大值给定,所以考虑用以空间换取时间的位图(位向量)法实现排序,具体过程如下:
(1)创建一个大小为max + 1的数组,每个元素初始化为0;
(2)扫面给定的整数数组,以当前的数作为下标,修改(1)创建的数组元素的值为当前数出现的次数的累积值;
(3)使用双层循环,遍历(1)所创建的数组,并根据每一个元素对应的当前下标出现的次数对原数组重新复制,便完成了排序。
13、旋转数组的最小数字
二分法,如果中间的那个数大于等于第一个数,那么它一定位于第一个递增子序列中,从而最小值一定位于它的右边,如果中间那个数小于等于
最后的数,那么它一定位于第二个递增子序列中,从而最小值一定位于它的左边,通过以上的比较,我们可以将问题的规模较半,从而时间复
杂度是log(n)。不过,需要注意的是:
(1)如果数组就是个有序数组,那么最小值就是第一个数(通过初始化 mid = start 实现,而循环条件便是array[start] >= array[end],这是该条件不成立,且mid指向第一个元素即为最小数字);
(2)如果第一个数、中间那个数和最后一个数是相等的,就无法确定中间那个数到底属于第一个还是第二个递增子序列,这时候只能用顺序查找的方法。
比如:1 0 1 1 1和1 1 1 0 1
14、斐波拉契数列问题最好使用循环来实现,这样可以避免递归所带来的大量重复计算,提高时间效率。
具体为:设a = 0; b = 1; result = 0; 循环求最终的第n项,循环体为{result = a + b; a = b; b = result;},青蛙跳阶梯可以转化为斐波拉契数列问题,因为
青蛙每次可以跳1层,也可以跳2层,那么n层就可以分解为两步,第一步,分为两种情况,如果跳1层,那么还剩n-1层,与原问题一致,规模较少1,同理
如果第一步跳2层,那么还剩n-2层,规模减少2,第二步再把余下的阶梯跳完,这样f(n) = f(n-1) + f(n-2)
15、关于finally
(1)如果try块或者catch块中有return语句,finally块也会在return语句执行之前被执行;
(2)如果finally块中也有return语句,那么它会覆盖try块或者catch块中的return语句;
(3)方法中的成员变量是基本数据类型时,try块或者catch块中的return语句返回的是该成员变量的拷贝,因此即使finally块对其做修改,也没有用。但如果
是引用类型,那么对其的修改将对返回的结果产生影响。
(4)有两种情况finally块不会被执行:a) 在执行try块之前,程序就出现了异常 b) try块中通过调用System.exit(0)强制退出程序
16、一个整数与它减去1之后进行与运算,二进制的最后一个1会变成0,也就是1的个数会减少1。利用这个基本事实可以解决许多问题,比如,判断一个整数是否
是2的整数次方(如果是,那么二进制表示中只有一个1,所以与上它自身-1得到0);判断一个整数的二进制表示中1的个数(不断的更新m = m & (m - 1),直至m
为0为止,计数器即为1的个数);输入两个整数m和n,问需要修改m的二进制表示中的多少位才能使得与n一样(先做异或运算,然后求运算结果中1的数目)。
17、位运算:与、或、异或、左移、右移
2016/07/04
18、在判断两个小数是否相等的时候,不能直接使用等号==,而应该判断它们差的绝对值|a - b|是否小于或等于一个很小的数,比如10的-7次方
19、使用位运算m >> 1代替m / 2,m << 1代替m * 2,m & 0x1 == 1代替m % 2 == 1判断一个整数是否是奇数,可以大大提高效率
20、全排列可以用递归表达
21、当我们用一个指针遍历链表解决不了问题的时候,可以尝试用两个指针来遍历链表。可以让其中一个指针遍历的速度快一些(比如一次在
链表上走两步),或者先让它在链表走若干步。
22、面试的时候如果没有特别要求,我们可以采用递归的方式遍历二叉树,因为简洁简单。
23、判断一棵二叉树B是否是另一个二叉树A的子结构。
基本思想:先序遍历的递归实现应用于问题的解决过程
(1)用先序遍历的方式在A中查找与B的根结点值相同的结点;
(2)找到这个结点之后判断其左右子树是否与B的根结点的左右子树具有相同的结构,如果有,则返回true,没有,则在该结点的左右子树中寻找与B树具有相同结构的子树。
boolean hasSubtree(pANode, pBNode) {
boolean result = false;
if(pANode != null && pBNode != null) {
if(pANode.value == pBNode.value)
result = doseTree1HasTree2(pANode, pBNode);
if(!result)
result = hasSubtree(pANode.left, pBNode);
if(!result)
result = hasSubtree(pANode.right, pBNode);
}
return result;
}
boolean doseTree1HasTree2(pANode, pBNode) {
if(pANode == null)
return false;
if(pBNode == null)
return true;
if(pANode.valude != pBNode.value)
return false;
return doseTree1HasTree2(pANode.left, pBNode.left) && doseTree1HasTree2(pANode.right, pBNode.right);
}
需要注意的是,用指针对二叉树进行扫描时,一定要判断指针是否为空!!!
24、求一棵二叉树的镜像
(1)画出一棵二叉树及其镜像二叉树,化抽象为具体,总结求解的步骤;
(2)边界条件:如果是一棵空的二叉树,或者,只有根结点时,什么都不做,也引申为递归算法的递归头,如果是空结点或者叶子结点时,递归结束;
(3)采用先序遍历的方式,遍历树中的每一个结点,对于每一个结点,先交换其左右子结点,再递归调用函数求其左右子树的镜像;
(4)非递归算法,无非是使用栈对二叉树进行先根次序遍历,把循环中的visit操作具体为:交换其左右子结点即可。
25、给定一个压栈序列,判断是否可能产生给出的出栈序列
规律:如果下一个弹出的数字刚好是栈顶元素,那么直接弹出。如果下一个弹出的数字不在栈顶,我们把压栈序列中还没有入栈的数字压入辅助栈,
直至把下一个需要弹出的数字压入栈顶为止。如果所有的数字都压入栈了仍然没有找到下一个弹出的数字,那么该序列不可能是一个弹出序列。
2016/07/15
26、实现一个入栈、出栈、求栈中最小元素的时间复杂度均为o(1)的栈
思路:需要一个辅助栈
具体操作为:
(1)push:先将x压入主栈,然后比较x与辅助栈的栈顶元素,如果x < 该元素,那么向辅助栈中压入元素x,否则将辅助栈的栈顶元素再次压入到辅助栈中;
(2)pop:主栈和辅助栈分别弹出一个元素即可;
(3)min:直接返回辅助栈的栈顶元素。
27、不管是广度优先遍历一个有向图还是一棵树,都要用到队列。第一步我们把起始结点(对树而言就是根结点)入队。接下来每一次从
队列的头部取出一个结点,访问这个结点之后把它所能到达的结点(对树而言是子结点)都依次放入队列。重复这个过程,直到队列为空为止。
28、给定一个序列,判断它能否是一棵二叉排序树的先序或者后序遍历
以先序为例,谈谈思路,根据先序遍历的特点,我们可以确定序列中的第一个数便是根结点,再根据二叉排序树的特点:左子树上的结点小于根结点
小于右子树上的结点,由于先序遍历的次序是根左右,因此设置变量扫面之后的序列,直到比根结点大为止,说明已经来到了右子树,重新设置变量
接着扫描,如果遇到比根结点小的,返回false。接下来,就可以递归调用函数检查左右子树(如果有的话)的序列是否满足二叉排序树的性质。注意
边界条件的检查:如果序列为空或者长度小于等于0,返回fasle。递归头是:如果序列中就只有一个元素即l == h,那么返回true。
29、如果要求一棵二叉树的遍历序列,我们可以先找到二叉树的根结点,再基于根结点把整棵树的遍历序列拆分成左子树对应的子序列和右子树对应
的子序列,接下来再递归地处理这两个子序列。
30、二叉树中和为某一值的路径
解决关键词:回溯法、递归、辅助链表(栈)、先序遍历
创建一个栈用来保存当前路径上的结点,使用一个变量记录当前路径中结点的和。先根序列访问这棵二叉树,先将当前结点加入到路径中,并累加它的值,
如果当前结点已经为也叶子节点并且和已经累加到指定的值,就返回或者将路径中的结点值打印出来,否则,如果左子树不为空,则递归调用函数在左子
树上找和为expectedSum - currentSum的路径,同样,如果右子树不为空,则递归调用函数在右子树上找和为expectedSum - currentSum的路径,最后,
为了找到所有的满足条件的路径,需要使用回溯法,向当前结点的父结点回溯,以探测父结点的右子树,或者父结点的父结点的右子树(因为总是先左后右),
从路径栈中弹出一个元素,并将记录和的变量减去当前结点的值。
2016/07/16
31、复制复杂链表
步骤:
(1)根据原始链表的每个结点N创建对应的N',并将N'链接到N的后面(只设置复制结点的数据域和next指针域);
(2)设置复制出来的结点的另一个指针域。如果原始链表上的结点N的另一个指针域指向结点S,则它对应的复制结点N'的另一个指针域指向S的
下一个结点S';
(3)拆分:把奇数位置的链接起来就是原始链表,把偶数位置的链接起来就是复制出来的链表。
32、二叉搜索树与升序的双向链表
思想:分而治之,把分解之后的小问题各个解决,然后把小问题的结果结合起来得到大问题的解。通常分治法思路都可以用递归的代码来实现。
因为二叉搜索树满足左 < 根 < 右,所以可以采用中序遍历的方式解决这个问题,因为二叉搜索树中的结点和双向链表中的结点有相同的指针域
和数据域,所有只要在原始二叉树结点的基础上修改指针域就可以得到最后的链表。
具体过程:
(1)递归调用函数处理左子树,得到左子树转换成的双向链表的第一个结点,遍历走到最后一个结点;
(2)将根结点加入到链表的最后;
(3)递归调用函数处理右子树,得到右子树转换成的双向链表的第一个结点,并将其连到(2)所得到的链表的后面;
递归头:if(pNode.left == null && pNode.right == null) return pNode;
public
class
Solution {
public
TreeNode Convert(TreeNode root) {
if
(root==
null
)
return
null
;
if
(root.left==
null
&&root.right==
null
)
return
root;
// 1.将左子树构造成双链表,并返回链表头节点
TreeNode left = Convert(root.left);
TreeNode p = left;
// 2.定位至左子树双链表最后一个节点
while
(p!=
null
&&p.right!=
null
){
p = p.right;
}
// 3.如果左子树链表不为空的话,将当前root追加到左子树链表
if
(left!=
null
){
p.right = root;
root.left = p;
}
// 4.将右子树构造成双链表,并返回链表头节点
TreeNode right = Convert(root.right);
// 5.如果右子树链表不为空的话,将该链表追加到root节点之后
if
(right!=
null
){
right.left = root;
root.right = right;
}
return
left!=
null
?left:root;
}
}
|
33、给定字符序列,求全排列
分治思想:把整个字符序列分成两部分,第一部分即为序列中的第一个字符,第二部分是除去第一个字符剩下的字符,第一个字符的取值可能是当前字符序列中的任意一个字符,
因此需要遍历,然后在循环体内递归调用函数求后面的字符序列的全排列,用一个变量作为参数来表示当前处理字符序列的第一个字符的位置。递归头为:当当前处理的字符
序列中的第一个字符已经是给定字符序列的最后一个字符时,便可以打印出字符数组。需要注意的是:处理完第一个字符的对应的某一种取值之后,还要把它交换回来。
34、给定字符序列,求所有的组合情况
分治思想,辅助栈用来存储组合中的元素
假设我们想在长度为n的字符串中求m个字符的组合。我们先从头扫描字符串的第一个字符。针对第一个字符,我们有两种选择:第一是把这个字符放到组合中去,接下来
我们需要在剩下的n-1个字符中选取m-1个字符;第二是不把这个字符放到组合中去,接下来我们需要在剩下的n-1个字符中选择m个字符。这两种选择都很容易用递归实现。
private
static
void
f
(
char
[]
array
,
int
begine
,
int
m
,
Stack
<
Character
>
s
) {
if
(
m
==
0
) {
for
(
char
c
:
s
)
System
.
out
.
print
(
c
);
System
.
out
.
println
();
return
;
}
if
(
begine
==
array
.
length
)
return
;
s
.
push
(
array
[
begine
]);
f
(
array
,
begine
+
1
,
m
-
1
,
s
);
s
.
pop
();
f
(
array
,
begine
+
1
,
m
,
s
);
}
35、排列问题(给定8个整数,把它们放在正方体的八个顶点上,要求上面和下面四个整数的和相等的排列、八皇后问题)
八皇后问题剖析:
因为要将八个皇后放在一个8乘8的宫格内,并且要求任意两个皇后不在同一行、同一列、同一条对角线上,因此,每一行必然都会有一个皇后,同样每一列也必然都会有一个皇后,
因此,我们创建一个数组,下标代表每一行(0~7),元素代表该行皇后所在的列,初始化为0~7,以为每一列必然有一个皇后,只要求这8个数的全排列即可,在得到某一种排列
是需要检查是否存在两个皇后位于同一条对角线上,即i - j == array[i] - array[j] || j - i == array[i] - array[j],如果不存在,计数+1。
2016/07/17
36、如果面试题是按照一定的要求摆放若干个数字,我们可以求出这些数字的全排列,然后再一一判断这些排列是不是满足题目给定的要求。
37、解决复杂问题的三种方法:画图、举例子、分解
38、连续子数组的最大和
(1)第一个解决方案:举例发现规律:用sum和max_sum分别表示当前和和最大和,遍历数组,将每一个元素累加到sum中,作如下判断,如果当前sum <= 0
那么从之前的某一个数开始的连续子数组和不会比从当前的数开始的连续子数组的和大,因此舍弃前面的连续子数组,从当前的数开始另作统计,sum = array[i],
否则,如果sum > 0,那么不管怎样,先加上当前数再说,如果当前数>0,那没有问题,更新max_sum,如果当前数小于0,因为是连续子数组,因此也有可能
再加上后面的数,和又变大了。
int maxSum = s0, currSum = s0;
for(i = 1; i < n; i++) {
if(currSum <= 0)
currSum = s[i];
else
currSum += s[i];
if(currSum > maxSum)
maxSum = currSum;
}
return maxSum;
(2)动态规划:
int all = s[n - 1], start = s[n - 1];
for(i = n - 2; i >= 0; i--) {
if(start <= 0)
start = s[i];
else
start += s[i];
if(start > all)
all = start;
}
return all;
39、求1到n中数字1出现的总次数(为了编程方便,我们需要把数字n转换成字符串,然后用字符数组来存储)可举例21345分析
(1)假设输入的n = D1D2D3D4...Dm先求出n的第一位数字first = D1 - '0'
(2)将1到n分成两段:a) 1到D2D3D4...Dm 和 b) D2D3D4...Dm + 1到n
(3)对于b)这一段,分成两部分:1)最高位上1的数目 2)其余位上1的数目,对于最高位上1的数目,看first是否大于1,如果大于1,那么1的数目为:10000...(m - 1个0)~19999...(m - 1个9)共10的m - 1次方个,
如果first等于1那么1的数目为:D2D3D4...Dm + 1个。对于其余位上1的数目:可以按照D1分成D1段,每段先固定某一位为1,有m - 1位可以固定,因此有m - 1中情形,在次基础上,其余m - 2为均可以取0 ~9,
所以共有first * (m - 1) * 10的m - 2次方个1
(4)对于a)这一段,可以递归调用函数求取1的数目
(5)求以上的总和得到最终结果。
40、丑数问题
用空间换取时间,不是检查每一个数是否是丑数,这样会把时间大量消耗在检查非丑数上,有大量的求余操作和除法操作。而是从1开始,按照从小到大的顺序的产生丑数,
把它们放在一个数组中,设置三个变量分别保存*2, *3, *5的结果大于数组中最后一个元素的丑数,为加入最新的丑数做准备,每次从它们当中选择最小的加入到数组中,
然后更新这三个变量。直到数组中的元素个数达到给定序号为止,返回最后一个元素,便是所求的丑数。
2016/07/18
41、哈希表:实现的是映射操作,key -> value。用数组实现的最简易的哈希表:key:数组的下标,value:数组中的元素。利用数组实现简易的哈希表可以解决的问题主要有:
(1)求一个字符串中第一个只出现一次的字符;(因为要统计次数,所以实现哈希表的数组为整型数组)
(2)给两个字符串,在第一个字符串中删除出现在第二个字符串中的字符;(因为只要考虑出现与否,所以实现哈希表的数组为布尔类型的数组)
(3)删除一个字符串中所有重复出现的字符(先创建一个长度为256的布尔类型数组,所有元素初始化为false,再设置一个下标变量,初始化为0,用来保存留下来的字符,遍历字符串,如果哈希表中对应的value
为false,则留下该字符,并且设置哈希表的value为true;如果已经为true表示已经出现过了并保存了下来,直接跳过)
(4)变位词问题(如果两个单词含有相同的字母,并且每个字母出现的次数也相同,那么它们互为变位词)
设置一个长度为256的整型数组,用来实现哈希表,第一次扫描其中一个单词,对于每个字母,value值+1,第二次扫描另外一个单词,对于每个字母,value值-1,两次扫描之后,判断数组中的元素是否全部为0,若是
那么这两个单词就互为变位词。
42、数组中的逆序对
private
static
int
inversePairsCore
(
int
[]
array
,
int
[]
copy
,
int
start
,
int
end
) { //copy数组是原始数组的复制
if
(
start
==
end
) {
array
[
start
] =
copy
[
start
];
return
0
;
}
int
length
= (
end
-
start
) >>
1
;
int
left
=
inversePairsCore
(
copy
,
array
,
start
,
start
+
length
); //注意原始数组和辅助数组位置交换
int
right
=
inversePairsCore
(
copy
,
array
,
start
+
length
+
1
,
end
);
int
i
=
start
+
length
;
int
j
=
end
;
int
k
=
end
;
int
count
=
0
;
while
(
i
>=
start
&&
j
>=
start
+
length
+
1
) {
if
(
array
[
i
] >
array
[
j
]) {
count
+= (
j
-
start
-
length
);
copy
[
k
--] =
array
[
i
--];
}
else
{
copy
[
k
--] =
array
[
j
--];
}
}
while
(
i
>=
start
)
copy
[
k
--] =
array
[
i
--];
while
(
j
>=
start
+
length
+
1
)
copy
[
k
--] =
array
[
j
--];
return
left
+
right
+
count
;
}
43、判断一棵二叉树是否是平衡二叉树(如果二叉树中的任何一个结点的左右子树的深度之差不超过1,那么这个二叉树便是平衡二叉树)
为了避免递归所引起的重复访问,我们采用后序遍历这棵二叉树的方式,在访问左右子树的时候,传入参数深度(指针类型),一方面递归调用函数判断左右子树是否是平衡二叉树,另一方面求出左右子树的深度,据此
判断根结点是否满足平衡性,并计算根结点的深度,递归函数的返回值是布尔类型。
44、给一个整型数组,只有两个数字只出现一次,其余的数字均出现两次,求出这两个数字
思路:先解决一个问题,如果只有一个数字只出现一次,我们怎么求出这个唯一出现的数字呢?
只要依次将数组中的数字做异或运算,最后得到的结果便是唯一出现的数字,因为一个数与自身的异或运算结果为0,与0做异或运算结果为其本身,所以所有的出现两次的数字均在异或的过程中被抵消了,
只有唯一出现的数被保存了下来。再来解决有两个只出现一次的数:同样依次将数组中的数字做异或运算,这次得到的结果应该是这两个唯一出现的数的异或运算结果,肯定不为0,那么二进制表示中至少
有一个1,我们从右向左数第一个1假设在第n位,那么这两个唯一出现的数的二进制表示在第n位上不同,据此可以根据二进制表示的第n位把原来的数组分成两部分,第一,这两个唯一出现的数必被分在不
同的数组中,第二,相同的两个数必被分在同一个数组中。这样,我们再分别把得到的两个数组用只有一个数字唯一出现的方法求出这个数字。
45、输入一个正数s,打印出所有和为s的连续正数序列(至少含有两个数)
思路:我们用两个变量分别表示序列中的最小值small和最大值big,如果从small到big的和大于s的话,增加small的值,如果比s小的话,增加big的值,由于至少包含两个数,假设为small和small + 1那么
这两个数的和为:2small + 1,此时要使得和为s,small的值为:( s - 1 ) / 2,也就是循环结束的条件。
2016/07/20
46、扑克牌的顺子
47、不用加减乘除做加法
思路:考虑十进制加法运算的步骤:各位数字相加;考虑产生的进位;加上进位;加上进位问题又回到了加法,因此可以循环迭代计算。第二,不能使用加减乘除四则运算那么只能模拟计算机里面的加法器。
使用二进制表示的位运算。假设两个数字分别为num1和num2:
(1)由于在二进制加法中:1与1得0进1,1与0得1,0与1得1,0与0的0,正好对应于异或运算,因此第一步先做异或运算,结果保存在sum中,即sum = num1 ^ num2;
(2)考虑进位:进位是由两个1造成的,并且进位之后,这个1向左移1位,因此carry = (num1 & num2 ) << 1
(3)判断进位是否为0,如果不等于0的话,还要将上面两步得到的结果相加,问题回到了(1),因此使用循环迭代执行,每次更新num1 = sum, num2 = carry,直到进位等于0,循环结束。
最后返回num1。
48、求一棵二叉树中根结点到某个结点的路径
解题关键词:回溯法,辅助栈
boolean getPath(pRoot, pNode, path) {
if(pRoot == pNode) {
return true;
}
path.push_back(pRoot);
boolean found = false;
found = getPath(pRoot.left, pNode, path);
found = getPath(pRoot.right, pNode, path);
if(!found) {
path.pop_back();
}
}
49、判断两个二叉树是否对称
50、模拟洗牌程序
private static void xiPaiCore(int[] array) {
int tmp, index, MAX = array.length - 1;
for(int i = 0; i < array.length; i++) {
index = (int) (i + Math.random() * (MAX - i));
tmp = array[i];
array[i] = array[index];
array[index] = tmp;
}
}
51、通配符匹配问题
52、删除链表中重复出现的结点,如果相同结点只保留一个(用哈希表可以使得时间复杂度为o(n))
删除有序链表中重复出现的结点,不保留。
2016/09/10
53、用两个栈来模拟一个队列的时候:
(1)push:只需将元素压入第一个栈即可;
(2)pop:先判断第二个栈是否为空,如果非空,直接弹出栈顶元素即可;如果为空,则需要将第一个栈中的元素依次出栈并压入第二个栈,最后再弹出第二个栈的栈顶元素。
越是没有价值的人,越是喜欢用自己仅剩的那么点价值去威胁别人,却不知道自己随时可能被替代。
54、java中,Queue是一个接口,实现它的类有LinkedList还有PriorityQueue。入队:offer,出队:peek()和poll()
55、层次打印一棵二叉树
import
java.util.*;
class
TreeNode
{
int
val
=
0
;
TreeNode
left
=
null
;
TreeNode
right
=
null
;
public
TreeNode
(
int
val
) {
this
.
val
=
val
;
}
}
public
class
Solution
{
public
static
ArrayList
<
Integer
>
PrintFromTopToBottom
(TreeNode
root
) {
if
(
root
==
null
)
return
null
;
Queue
<TreeNode
>
queue
=
new
LinkedList
<TreeNode
>();
ArrayList
<
Integer
>
result
=
new
ArrayList
<>();
int
currNum
=
1
,
nextNum
=
0
; //用两个变量分别表示当前层还需要打印的结点数目和统计下一层的结点数目
TreeNode
currNode
;
queue
.
offer
(
root
);
while
(!
queue
.isEmpty(
)) {
currNode
=
queue.poll();
result
.
add
(
curr
.
val
);
System
.
out
.
print
(
currNode
.
val
+
" "
);
currNum
--;
if
(
currNode
.
left
!=
null
) {
queue
.
offer
(
currNode
.
left
);
nextNode
++;
}
if
(
currNode.
right
!=
null
) {
queue
.
offer
(
currNode
.
right
);
nextNode
++;
}
if
(
currNum
==
0
) { //如果当前层已经打印完了,则更新为下一层的结点数,并将nextNum置为0,继续统计
下下层的结点数目。注意,必须方法在最后,否则会漏掉当前层的最后一个结点的左右子结点。
System
.
out
.
println
();
currNum
=
nextNum
;
nextNum
=
0
;
}
}
return
result
;
}
}
56、编写函数时:
(1)要对输入参数的合法性进行检查(比如对数组进行合法性检查时:应判断:if( array == null || array.length == 0);
(2)要对输入参数进行边界检查,即特殊输入(如,矩阵,要考虑输入的矩阵只有一行或者一列的情况)
57、求全排列问题时:为了避免重复的情况:在交换当前字符(数字)和后面的某个字符(数字)的时候,先判断两者是否相等,只有不等或者处在当前位置的时候,才去交换。然后递归地处理后面的字符序列。记得递归之后
再交换回来。
58、注意partition函数对缩小查找范围的作用
59、给定一个数组,求数组中的最小值和最大值,要求比较次数为3*N / 2
参数合法性检查:如果数组为空指针或者数组为空,则什么都不做。边界检查:如果数组中仅有一个元素,则最小值和最大值均为该元素。
(1)取双元素法:每次取两个元素,总共取N/2次,做三次比较,先比较这两个元素,然后用小的和当前的最小值比较,大的和当前的最大值比较。
需要注意的是:当N为奇数是,最后i的值为N-1,是最后一个元素,直接拿它和最小值和最大值比较,完了之后break。
(2)分治法:将数组分为左右两部分,分别求左右两部分的最小值和最大值,然后将左边的最小值和右边的最小值作比较得到总的最小值,将左边的最大值
和右边的最大值作比较得到总的最大值。递归实现,递归函数的输入参数是:array, start, end, 存放最小和最大值的引用类型变量。递归头是:start == end,
最小值和最大值均置为array[start]。需要注意的是:递归处理左半边的时候:f(array, start, start + n / 2 -1, ...),注意减1,否则会栈溢出。
60、求数组中的次大值
设置两个变量分别保存最大值和次大值,扫描数组,如果大于最大值,那么更新次大值为当前的最大值,最大值为当前的元素,否则,如果大于次大值,则更新
当前的次大值。
61、构造乘积数组:给定数组A[n],要求一个数组B[n],其中B[i] = A[0] * A[1] * ... * A[i - 1] * A[i + 1] * ... * A[n - 1],也就是A中除去当前元素以外其余元素的乘积。
时间复杂度为o(n)的算法(不过空间复杂度为o(2 * n)):使用动态规划的思想,创建两个数组,分别保存从前向后元素的乘积,和从后向前元素的乘积,即:
A1[n] : A1[i] = A[0] * A[1] * ... * A[i],因为在计算A1[i]时可以直接用A1[i - 1] * A[i],即无后效性,当前状态是对历史的一个完整总结,因此可以使用动态规划的思想解决,
时间复杂度为o(n),A2[n] : A2[i] = A[i] * A[i + 1] * ... * A[n - 1],最终,B[i] = A1[i - 1] * A2[i + 1],第一个元素和最后一个元素特殊一些。