解决面试题的思路(上)
二叉树的镜像
题目描述
操作给定的二叉树,将其变换为源二叉树的镜像。
分析
下图中右边的树就是左边的树的镜像。
镜像树和原树之间的关系是:根节点一样,左右节点交换位置。
如上图所示,第一次交换之后,其实是6和1左右两棵子树之间交换了位置。由此类推,只要一个结点存在孩子结点,就交换两个子节点或是子树(有一个为空也可以)。上图中第二次实现了结点1的左右子结点的交换,第三次交换实现了结点6的左右子结点交换。最终就得到了镜像树。
Java代码
public class Solution {
public void Mirror(TreeNode root) {
if(root == null) //根节点为空
return;
if(root.left == null && root.right == null) //左右两个子结点都为空
return;
// 交换左右结点
TreeNode p = root.left;
root.left = root.right;
root.right = p;
if(root.left != null) //左结点若不为空,则同理处理左子树
Mirror(root.left);
if(root.right != null) //右结点若不为空,则同理处理右子树
Mirror(root.right);
}
}
顺时针打印矩阵
题目描述
输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下4 X 4矩阵:
则依次打印出数字1, 2, 3, 4, 8, 12, 16, 15, 14, 13, 9, 5, 6, 7, 11, 10。
分析
顺时针打印矩阵,可以通过画图的方式开分析问题。假设矩阵的行数为lines,列数为columns。设左上角的元素坐标为
(
0
,
0
)
(0,0)
(0,0)。
最一般的情况如图(a)所以,顺时针打印一圈就是先从左往右,再从上往下,再从右往左,最后从下往上打印一圈,一共是四步。
可以总结其中的规律:在打印第
i
i
i圈的时候,
(
i
=
0
,
1
,
2
,
.
.
.
)
(i=0,1,2,...)
(i=0,1,2,...)
即打印第i个循环时,令start = i,endX=lines-1-start,endY=columns-1-start则:
step | x | y |
---|---|---|
1 | start | start → endY |
2 | (start+1) → endX | endY |
3 | endX | (endY-1) → start |
4 | endX → (start-1) | start |
下面考虑终止条件,即循环打印的次数count。
c
o
u
n
t
=
[
m
i
n
(
l
i
n
e
,
c
o
l
u
m
n
)
+
1
]
/
2
count = [\mathrm{min}(line, column)+1] / 2
count=[min(line,column)+1]/2
此外,还要考虑打印最后一圈时的情况。
打印最后一圈不可能为4步,只可能小于4步。图(b)、( c )、(d)分别展示了最后一个循环打印3步、2步、1步的情况。可见,只要没有打印结束,总要打印第1步。
- 是否打印第2步则要看是否满足endX>startX,若满足,则执行;
- 是否打印第3步则要看是否满足endX>startX且endY>startY,若满足,则执行;
- 是否答应第4步则要看是否满足endY>startY且endX-startX>=2,若满足,则执行。
其中,startX=startY=start。
Java代码
import java.util.ArrayList;
public class Solution {
public ArrayList<Integer> printMatrix(int [][] matrix) {
ArrayList<Integer> list = new ArrayList<Integer>();
int lines = matrix.length;
int columns = matrix[0].length;
int count = (Math.min(lines, columns)-1) / 2;
for(int i=0; i<count; i++){
int start = i;
int endX = lines - 1 - start;
int endY = columns - 1 - start;
// 从左向右
for(int k=start; k<=endY; k++)
list.add(matrix[start][k]);
// 从上向下
if(endX > start)
for(int k=start+1; k<=endX; k++)
list.add(matrix[k][endY]);
// 从右向左
if(endX>start && endY>start)
for(int k=endY-1; k>=start; k--)
list.add(matrix[endX][k]);
// 从下向上
if(endY>start && endX-start>1)
for(int k=endX-1; k>start; k--)
list.add(matrix[k][start]);
}
return list;
}
}
复杂链表的复制
题目描述
输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点或者null),返回结果为复制后复杂链表的head。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)。
链表结点:
public class RandomListNode {
int label;
RandomListNode next = null;
RandomListNode random = null;
RandomListNode(int label) {
this.label = label;
}
}
分析
如下图是一个由五个结点复杂链表。
第一步:复制每一个结点,如下图所示:
第二步:复制random指针
第三步:拆分链表
Java代码
public class Solution {
public RandomListNode Clone(RandomListNode pHead)
{
if(pHead == null)
return null;
CloneNode(pHead);
CloneRandomPointer(pHead);
return(SplitLink(pHead));
}
// 第一步
public void CloneNode(RandomListNode pHead){
RandomListNode pCurrent = pHead;
while(pCurrent != null){
RandomListNode cloneNode = new RandomListNode(pCurrent.label);
cloneNode.next = pCurrent.next;
pCurrent.next = cloneNode;
pCurrent = cloneNode.next;
}
}
// 第二步
public void CloneRandomPointer(RandomListNode pHead){
RandomListNode pCurrent = pHead;
while(pCurrent != null){
if(pCurrent.random != null)
pCurrent.next.random = pCurrent.random.next;
else
pCurrent.next.random = null;
pCurrent = pCurrent.next.next;
}
}
// 第三步
public RandomListNode SplitLink(RandomListNode pHead){
RandomListNode pCurrent = pHead;
RandomListNode pHeadClone = pHead.next;
while(pCurrent != null){
RandomListNode pCurClone = pCurrent.next;
pCurrent.next = pCurClone.next;
pCurClone.next = (pCurClone.next==null) ? null : pCurClone.next.next;
pCurrent = pCurrent.next;
}
return pHeadClone;
}
}
二叉搜索树和双向链表
题目描述
输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。结果返回头结点。
结点的定义为:
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
分析
已知,搜索二叉树的特点是:左子结点的值总是小于父结点的值,右子结点的值总是大于父结点的值。转换以后的双向链表从左到右的顺序与中序遍历一样,所以使用中序遍历的方法来处理。
其关键就是:
curLast.right = pRootOfTree;
pRootOfTree.left = curLast;
curLast = pRootOfTree;
Java代码
public class Solution {
TreeNode curLast = null; //指示当前处理的结点,即以生成的双向链表的最后一个结点
TreeNode listHead = null; // 标志双向链表的头结点
public TreeNode Convert(TreeNode pRootOfTree) {
ConvertSub(pRootOfTree);
return listHead;
}
public void ConvertSub(TreeNode pRootOfTree){
if(pRootOfTree == null)
return;
ConvertSub(pRootOfTree.left);
if(curLast == null){
curLast = pRootOfTree;
listHead = pRootOfTree;
}
else{
curLast.right = pRootOfTree;
pRootOfTree.left = curLast;
curLast = pRootOfTree;
}
ConvertSub(pRootOfTree.right);
}
}
字符串的排列
题目描述
输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。
输入一个字符串,长度不超过9(可能有字符重复),字符只包括大小写字母。
分析
利用递归的思想来解决。
首先将一个字符串看成两个部分,
- 第①部分:第一个字符;
- 第②部分:除第一个字符外的所有字符;
将第一个字符与第二部分中的所有字符交换,如下图所示:
交换之后的字符串用同样的方法处理。
如题中的例子"abc",整个过程如下图所示:
最后一行就是排列出来的所有可能的结果。
此外还要考虑重复的清空,所以将结果添加到List中去的时候,还要检查是否重复。先使用TreeSet存放元素会减少查重的工作。
Java代码
import java.util.*;
public class Solution {
public ArrayList<String> Permutation(String str) {
ArrayList<String> res = new ArrayList<String>();
//若字符串不存在,或字符串为空
if(str==null || str.length()==0)
return res;
//字符串不为空
char[] array = str.toCharArray(); //字符串中的元素处理不方便,故转换成数组
TreeSet<String> set = new TreeSet<String>(); // 使用TreeSet可以避免重复
Permutation(array, 0, set);
res.addAll(set); // 将TreeSet中的所有结果添加到res中
return res;
}
public void Permutation(char[] array, int begin, TreeSet<String> set){
// 若字符串不存在或为空,或者begin元素错误
if(array==null || array.length==0 || begin<0 || begin>array.length-1)
return;
// 递归结束标志,begin指向数组的最后一个元素
if(begin == array.length-1)
set.add(String.valueOf(array)); // 将该结果添加到TreeSet中
// 递归未结束
else{
// 将第二部分中的每一个元素和第一个元素进行交换,对新字符串进行递归处理
for(int i=begin; i<array.length; i++){
swap(array, begin, i);
Permutation(array, begin+1, set);
swap(array, begin, i);//这一步不可省略!!!交换结束后还要交换回来进行下一次处理。
}
}
}
public void swap(char[] arr, int i, int j){
char temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}