面试题58:删除链表中重复的结点
在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5
代码:
public class Solution {
public ListNode deleteDuplication(ListNode pHead)
{
if(pHead==null || pHead.next==null) return pHead;
ListNode pH=null; //要返回的节点
if(pHead.val != pHead.next.val) {
pH = pHead;
}else {
ListNode p = pHead;
while(p.next.next != null) {
if(p.val != p.next.val && p.next.val != p.next.next.val) {
pH = p.next;
break;
}
p = p.next;
}
if(p.next.next == null) {
if(p.val != p.next.val) {
return p.next;
}else {
return null;
}
}
}
ListNode p = pH;
ListNode current = pH.next;
pH.next=null;
int num = pH.val;
while(current.next != null) {
if(current.val != num && current.val != current.next.val) {
p.next = current;
p = p.next;
num = current.val;
current = current.next;
p.next = null;
continue;
//num = current.next.val;
}
num = current.val;
current = current.next;
}
if(num != current.val) {
p.next = current;
}
return pH;
}
}
面试题59:二叉树的下一个结点
给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。
代码:(未使用递归的二叉树问题)
/*
public class TreeLinkNode {
int val;
TreeLinkNode left = null;
TreeLinkNode right = null;
TreeLinkNode next = null;
TreeLinkNode(int val) {
this.val = val;
}
}
*/
public class Solution {
public TreeLinkNode GetNext(TreeLinkNode pNode)
{
if(pNode == null ) return null;
if(pNode.right != null) {
pNode = pNode.right;
while(pNode.left!=null) {
pNode = pNode.left;
}
return pNode;
}
while(pNode.next != null){
if(pNode.next.left == pNode){
return pNode.next;
}
pNode = pNode.next;
}
return null;
}
}
面试题60:对称的二叉树
请实现一个函数,用来判断一颗二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。
public class Question60 {
boolean isSymmetrical(TreeNode pRoot)
{
if(pRoot==null) return true;//牛客上认为空指针也是对称的
return isS(pRoot.left,pRoot.right);
}
boolean isS(TreeNode p1,TreeNode p2) {
if(p1==null && p2==null) return true;
if(p1==null || p2==null) return false;
if(p1.val != p2.val) return false;
return isS(p1.left,p2.right)&& isS(p1.right,p2.left);
}
}
面试题61:按之字形顺序打印二叉树
请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。
import java.util.ArrayList;
public ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
ArrayList<ArrayList<Integer> > a =new ArrayList<ArrayList<Integer> >();
if(pRoot == null) return a;
int num = 0;
ArrayList<TreeNode> current = new ArrayList<TreeNode>();//装下一级用的
current.add(pRoot);
while(current.size() != 0) {
num++;
ArrayList<Integer> little = new ArrayList<Integer>();
ArrayList<TreeNode> current1 = new ArrayList<TreeNode>();
for(int i=0;i<current.size();i++) {
TreeNode p = current.get(i);
if(p.left != null) current1.add(p.left);
if(p.right != null) current1.add(p.right);
little.add(p.val);
}
//注意按照之子形打印
if(num % 2 == 0) {
ArrayList<Integer> s = new ArrayList<Integer>();
for(int i=little.size()-1;i>=0;i--) {
s.add(little.get(i));
}
a.add(s);
}else {
a.add(little);
}
current = current1;
}
return a;
} //注:这道题和之前的一道题很像,都可以用队列这个数据结构来处理,但是本次采用的微微有点不同
面试题62:把二叉树打印成多行
从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。 (这道题和上面的那道题一样,甚至比上面那个还简单!)
import java.util.ArrayList;
ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
ArrayList<ArrayList<Integer> > a =new ArrayList<ArrayList<Integer> >();
if(pRoot == null) return a;
ArrayList<TreeNode> current = new ArrayList<TreeNode>();//装下一级用的
current.add(pRoot);
while(current.size() != 0) {
ArrayList<Integer> little = new ArrayList<Integer>();
ArrayList<TreeNode> current1 = new ArrayList<TreeNode>();
for(int i=0;i<current.size();i++) {
TreeNode p = current.get(i);
if(p.left != null) current1.add(p.left);
if(p.right != null) current1.add(p.right);
little.add(p.val);
}
a.add(little);
current = current1;
}
return a;
}
面试题63:序列化二叉树
请实现两个函数,分别用来序列化和反序列化二叉树
//序列化
String Serialize(TreeNode root) {
StringBuffer sb = new StringBuffer();
if(root == null){
sb.append("#,");
return sb.toString();
}
sb.append(root.val + ",");
sb.append(Serialize(root.left));
sb.append(Serialize(root.right));
return sb.toString();
}
//反序列化
int index=-1;
TreeNode Deserialize(String str) {
index++;
int len = str.length();
if(index >= len){
return null;
}
String[] strr = str.split(",");
TreeNode node = null;
if(!strr[index].equals("#")){
node = new TreeNode(Integer.valueOf(strr[index]));
node.left = Deserialize(str);
node.right = Deserialize(str);
}
return node;
}
面试题64:二叉搜索树的第k个结点
给定一棵二叉搜索树,请找出其中的第k小的结点。例如, (5,3,7,2,4,6,8) 中,按结点数值大小顺序第三小结点的值为4。
TreeNode KthNode(TreeNode pRoot, int k)
{
if(pRoot == null) return null;
if(k<=0) return null;
ArrayList<TreeNode> a = new ArrayList<TreeNode>();
a = zhongxu(pRoot,a);
if(a.size()==0) return null;
if(k > a.size()) {
return null;
}else {
return a.get(k-1);
}
}
ArrayList<TreeNode> zhongxu(TreeNode p,ArrayList<TreeNode> a){
if(p == null) return null;
zhongxu(p.left,a);
a.add(p);
zhongxu(p.right,a);
return a;
}
面试题65:数据流中的中位数
如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。
import java.util.Comparator;
import java.util.PriorityQueue;
public class Question65 {
private PriorityQueue<Integer> small = new PriorityQueue<Integer>();
private PriorityQueue<Integer> big = new PriorityQueue<Integer>(11,new Comparator<Integer>() {
public int compare(Integer num1,Integer num2) {
return num2.compareTo(num1);
}
});
int count=0;
public void Insert(Integer num) {
count++;
//总数为奇数时,大顶堆堆顶就是中位数
if((count & 1) == 0) {//判断偶数的高效写法
if(!big.isEmpty() && num < big.peek()) {
big.offer(num);
num = big.poll();
}
small.offer(num);
}else {
if(!small.isEmpty() && num > small.peek()) {
small.offer(num);
num = small.poll();
}
big.offer(num);
}
}
public Double GetMedian() {
if(count == 0) throw new RuntimeException("No available number");
double result;
if((count & 1)==1) {
result = big.peek();
}else {
result = (small.peek() + big.peek())/2.0;
}
return result;
}
}
注意事项:
1. Java的PriorityQueue 是从JDK1.5开始提供的新的数据结构接口,默认内部是自然排序,结果为小顶堆,也可以自定义排序器,比如下面反转比较,完成大顶堆。 PriorityQueue里面默认是小顶堆。
2. 最小堆:是一种经过排序的完全二叉树,其中任一非终端节点的数据值均不大于其左子节点和右子节点的值。大顶堆:根节点的关键字既大于或等于左子树的关键字值,又大于或等于右子树的关键字值。总而言之,最小的或者最大的就在根节点上
3. 关于PriroityQueue的常用API:
poll() 获取并移除堆顶的元素
peek()获取堆顶的元素,但是不移除
offer(num)将num添加进堆中
size() 获取堆中元素的个数
面试题66:滑动窗口的最大值
给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,那么一共存在6个滑动窗口,他们的最大值分别为{4,4,6,6,6,5}; 针对数组{2,3,4,2,6,2,5,1}的滑动窗口有以下6个: {[2,3,4],2,6,2,5,1}, {2,[3,4,2],6,2,5,1}, {2,3,[4,2,6],2,5,1}, {2,3,4,[2,6,2],5,1}, {2,3,4,2,[6,2,5],1}, {2,3,4,2,6,[2,5,1]}。
代码1:暴力算法
public ArrayList<Integer> maxInWindows(int [] num, int size)
{
ArrayList<Integer> contain = new ArrayList<Integer>();
if(num == null || num.length==0 || size>num.length || size<=0) return contain;
for(int i=0;i<=num.length-size;i++) {
int max = num[i]; //记录当前窗口的最大值
for(int n=i;n<size+i;n++) {
if(num[n]>max) {
max = num[n];
}
}
contain.add(max);
}
return contain;
}
代码2:优化后的
面试题67:矩阵中的路径
请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则之后不能再次进入这个格子。 例如 a b c e s f c s a d e e 这样的3 X 4 矩阵中包含一条字符串”bcced”的路径,但是矩阵中不包含”abcb”路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入该格子。
public class Question67 {
public boolean hasPath(char[] matrix, int rows, int cols, char[] str)
{
//标志位,初始化为false
boolean[] flag = new boolean[matrix.length];
for(int i=0;i<rows;i++){
for(int j=0;j<cols;j++){
//循环遍历二维数组,找到起点等于str第一个元素的值,再递归判断四周是否有符合条件的----回溯法
if(judge(matrix,i,j,rows,cols,flag,str,0)){
return true;
}
}
}
return false;
}
//judge(初始矩阵,索引行坐标i,索引纵坐标j,矩阵行数,矩阵列数,待判断的字符串,字符串索引初始为0即先判断字符串的第一位)
private boolean judge(char[] matrix,int i,int j,int rows,int cols,boolean[] flag,char[] str,int k){
//先根据i和j计算匹配的第一个元素转为一维数组的位置
int index = i*cols+j;
//递归终止条件
if(i<0 || j<0 || i>=rows || j>=cols || matrix[index] != str[k] || flag[index] == true)
return false;
//若k已经到达str末尾了,说明之前的都已经匹配成功了,直接返回true即可
if(k == str.length-1)
return true;
//要走的第一个位置置为true,表示已经走过了
flag[index] = true;
//回溯,递归寻找,每次找到了就给k加一,找不到,还原
if(judge(matrix,i-1,j,rows,cols,flag,str,k+1) ||
judge(matrix,i+1,j,rows,cols,flag,str,k+1) ||
judge(matrix,i,j-1,rows,cols,flag,str,k+1) ||
judge(matrix,i,j+1,rows,cols,flag,str,k+1) )
{
return true;
}
//走到这,说明这一条路不通,还原,再试其他的路径
flag[index] = false;
return false;
}
}
知识点:
1. 本题是回溯法的相关问题:回溯法(探索与回溯法)是一种选优搜索法,又称为试探法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。
2. 回溯法是从上到下进行深度搜索,如果深度搜索没有进行到底而不满足决策函数了,那么不好意思,请回去,然后再从最近的可以岔开的地方选择另一条路,继续之前的深度搜索,如果搜索到底,那么再通过for循环进行广度搜索。所以它也是深度搜索和广度搜索并行的。求出的解也一定是最优解。回溯法是比动态规划法更加一般的算法,如n皇后,子集和数问题!其实回溯法就是对隐式图的深度优先搜索算法。
面试题68:机器人的运动
地上有一个m行和n列的方格。一个机器人从坐标0,0的格子开始移动,每一次只能向左,右,上,下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于k的格子。 例如,当k为18时,机器人能够进入方格(35,37),因为3+5+3+7 = 18。但是,它不能进入方格(35,38),因为3+5+3+8 = 19。请问该机器人能够达到多少个格子?
public class Question68 {
public int movingCount(int threshold, int rows, int cols) {
int flag[][] = new int[rows][cols]; //记录是否已经走过
return helper(0, 0, rows, cols, flag, threshold);
}
private int helper(int i, int j, int rows, int cols, int[][] flag, int threshold) {
if (i < 0 || i >= rows || j < 0 || j >= cols || numSum(i) + numSum(j) > threshold || flag[i][j] == 1) return 0;
flag[i][j] = 1;
return helper(i - 1, j, rows, cols, flag, threshold)
+ helper(i + 1, j, rows, cols, flag, threshold)
+ helper(i, j - 1, rows, cols, flag, threshold)
+ helper(i, j + 1, rows, cols, flag, threshold)
+ 1;
}
private int numSum(int i) {
int sum = 0;
do{
sum += i%10;
}while((i = i/10) > 0);
return sum;
}
}