程序员面试金典(java版)

时间:2022-11-06 00:40:58

一、字符串

1.1、确定字符互异

题目描述
请实现一个算法,确定一个字符串的所有字符是否全都不同。这里我们要求不允许使用额外的存储结构。
给定一个string iniString,请返回一个bool值,True代表所有字符全都不同,False代表存在相同的字符。保证字符串中的字符为ASCII字符。字符串的长度小于等于3000。

思路:Parition基于快速排序的partition,可以边排序边找重复,也即是每次partition之后,判断中间key元素与两边元素是否相同,相同则返回false,不同再进行下一轮partition.时间复杂度也是O(nlogn),但要比排序速度快。

import java.util.*;

public class Different {
public boolean checkDifferent(String iniString) {
char[] str = iniString.toCharArray();
return quick_check(str, 0, str.length - 1);
}

public boolean quick_check(char[] str, int low, int high) {
if (low >= high)
return true;
int first = low, last = high;
char key = str[first];

while (first < last) {
while (first < last && str[last] > key)
last--;
if (first < last && str[last] == key) {
return false;
}
str[first] = str[last];
while (first < last && str[first] < key)
first++;
if (first < last && str[first] == key) {
return false;
}
str[last] = str[first];
}
str[first] = key;

return
quick_check(str, low, first - 1)
&& quick_check(str, first + 1, high);
}

}

1.2、原串翻转

题目描述
请实现一个算法,在不使用额外数据结构和储存空间的情况下,翻转一个给定的字符串(可以使用单个过程变量)。
给定一个string iniString,请返回一个string,为翻转后的字符串。保证字符串的长度小于等于5000。
测试样例:
“This is nowcoder”
返回:”redocwon si sihT”

import java.util.*;

public class Reverse {
public String reverseString(String iniString) {
// write code here
char[] str = iniString.toCharArray();
int first = 0;
int end = str.length - 1;
char temp;
while(first < end){
temp = str[first];
str[first] = str[end];
str[end] = temp;
first++;
end --;
}
return new String(str);
}
}

1.3、原串翻转

题目描述
给定两个字符串,请编写程序,确定其中一个字符串的字符重新排列后,能否变成另一个字符串。这里规定大小写为不同字符,且考虑字符串重点空格。
给定一个string stringA和一个string stringB,请返回一个bool,代表两串是否重新排列后可相同。保证两串的长度都小于等于5000。
测试样例:
“This is nowcoder”,”is This nowcoder”
返回:true
“Here you are”,”Are you here”
返回:false

import java.util.*;

public class Same {
public boolean checkSam(String stringA, String stringB) {
// write code here
char[] A = new char[256];
char[] B = new char[256];

for(int i = 0;i < stringA.length();i++){
A[stringA.charAt(i)]++;
}
for(int i = 0;i < stringB.length();i++){
B[stringB.charAt(i)]++;
}

for(int i = 0;i<256;i++){
if(A[i] != B[i]){
return false;
}
}

return true;
}


}

1.4、原串翻转

题目描述
请编写一个方法,将字符串中的空格全部替换为“%20”。假定该字符串有足够的空间存放新增的字符,并且知道字符串的真实长度(小于等于1000),同时保证字符串由大小写的英文字母组成。
给定一个string iniString 为原始的串,以及串的长度 int len, 返回替换后的string。
测试样例:
“Mr John Smith”,13
返回:”Mr%20John%20Smith”
”Hello World”,12
返回:”Hello%20%20World”

import java.util.*;

public class Replacement {
public String replaceSpace(String iniString, int length) {
// write code here
int spaceCount = 0;
char[] str = iniString.toCharArray();
for(int i = 0; i< str.length;i++){
if(str[i] == ' ')
spaceCount++;
}
char[] newStr = new char[length + 2*spaceCount];
int newStrEnd = newStr.length-1;
for(int i = str.length-1;i>=0;i--){
if(str[i] == ' '){
newStr[newStrEnd--] = '0';
newStr[newStrEnd--] = '2';
newStr[newStrEnd--] = '%';
}else{
newStr[newStrEnd--] = str[i];
}
}

return new String(newStr);
}
}

1.5、基本字符串压缩

题目描述
利用字符重复出现的次数,编写一个方法,实现基本的字符串压缩功能。比如,字符串“aabcccccaaa”经压缩会变成“a2b1c5a3”。若压缩后的字符串没有变短,则返回原先的字符串。
给定一个string iniString为待压缩的串(长度小于等于3000),保证串内字符均由大小写英文字母组成,返回一个string,为所求的压缩后或未变化的串。
测试样例
“aabcccccaaa”
返回:”a2b1c5a3”
“welcometonowcoderrrrr”
返回:”welcometonowcoderrrrr”

import java.util.*;

public class Zipper {
public String zipString(String iniString) {
// write code here

char[] str = iniString.toCharArray();
int length = str.length;
char[] newStr = new char[length];

int newStrPtr = 0;
int strPtr = 0;

//注意边界条件
while(strPtr<length && newStrPtr<length-1){
int count = 1;
newStr[newStrPtr++] = str[strPtr];
while(strPtr<length -1 && str[strPtr] == str[strPtr+1]){
strPtr++;
count++;
}
strPtr++;
String count1 = count+"";//如果是10的话,加两个
for(int i = 0;i<count1.length();i++){
newStr[newStrPtr++] = count1.charAt(i);
}

if(strPtr == length){
return new String(newStr);
}
}

return iniString;
}
}

方法二:

import java.util.*;

public class Zipper {
public String zipString(String iniString) {
// write code here
// 测试用例welcometonowcoderrrrr,若按所给方法压缩则结果为:
// w1e1l1c1o1m1e1t1o1n1o1w1c1o1d1e1r5,长度比所给的字符串要长,
// 所以需要返回原来的字符串。
if (iniString == null
|| iniString.length() == 0
|| iniString.isEmpty()) {
return "";
}
StringBuilder zipStr = new StringBuilder();
int count = 1;
for (int i = 0, j = 1; i < iniString.length()
&& j < iniString.length(); i++) {
if (iniString.charAt(i) == iniString.charAt(j++)) {
count++;
} else {
zipStr.append(iniString.charAt(i));
zipStr.append(count);
count = 1;
}
if (j == iniString.length()) {
zipStr.append(iniString.charAt(j - 1));
zipStr.append(count);
}
}
return zipStr.toString().length() > iniString.length() ?
iniString : zipStr.toString();
}
}

1.6、像素翻转(好好看看)

题目描述
有一副由NxN矩阵表示的图像,这里每个像素用一个int表示,请编写一个算法,在不占用额外内存空间的情况下(即不使用缓存矩阵),将图像顺时针旋转90度。
给定一个NxN的矩阵,和矩阵的阶数N,请返回旋转后的NxN矩阵,保证N小于等于500,图像元素小于等于256。
测试样例:
[[1,2,3],[4,5,6],[7,8,9]],3
返回:[[7,4,1],[8,5,2],[9,6,3]]

思路:分层处理

import java.util.*;

public class Transform {
public int[][] transformImage(int[][] mat, int n) {
// write code here
for(int layer = 0;layer<n/2;layer++){
int first = layer;
int last = n-1-layer;
for(int i = first;i<last;i++){
//注意此处,要用偏移量
int offset = i-first;
//逐个保存top上的数字,一个一个进行旋转
int top = mat[first][i];
//左边到上边
mat[first][i] = mat[last-offset][first];
//下到左
mat[last-offset][first] = mat[last][last-offset];
//右到下
mat[last][last-offset] = mat[i][last];
//上到右
mat[i][last] = top;
}
}
return mat;
}
}

1.7、清除行列

题目描述
请编写一个算法,若MxN矩阵中某个元素为0,则将其所在的行与列清零。
给定一个MxN的int[][]矩阵mat和矩阵的阶数n,请返回完成操作后的int[][]矩阵,保证n小于等于300,矩阵中的元素为int范围内。
测试样例:
[[1,2,3],[0,1,2],[0,0,1]]
返回:[[0,0,3],[0,0,0],[0,0,0]]

劣质算法:空间复杂度O(MN)

import java.util.*;

public class Clearer {
public int[][] clearZero(int[][] mat, int n) {
// write code here
int m = mat.length;
int[][] newMat = new int[m][n];
for(int i = 0;i < m;i++)
for(int j = 0;j<n;j++){
newMat[i][j] = mat[i][j];
}
for(int i = 0;i < m;i++)
for(int j = 0;j<n;j++){
if(mat[i][j] == 0){
for(int k = 0;k<n;k++)
newMat[i][k] = 0;
for(int h = 0;h<m;h++)
newMat[h][j] = 0;
}
}

return newMat;
}
}

优质算法:只记录行和列的位置

import java.util.*;

public class Clearer {
public int[][] clearZero(int[][] mat, int n) {
// write code here
int m = mat.length;

int[] rows = new int[m];//记录哪些行为0
int[] cols = new int[n];//记录哪些列为0

for(int i = 0;i < m;i++)
for(int j = 0;j<n;j++){
if(mat[i][j] == 0){
cols[j]=1;
rows[i]=1;
}
}
for(int i = 0;i < m;i++)
for(int j = 0;j<n;j++){
if(cols[j]==1 || rows[i]==1){
mat[i][j] = 0;
}
}

return mat;
}
}

1.8、翻转子串(好好看看思路)

题目描述
假定我们都知道非常高效的算法来检查一个单词是否为其他字符串的子串。请将这个算法编写成一个函数,给定两个字符串s1和s2,请编写代码检查s2是否为s1旋转而成,要求只能调用一次检查子串的函数。
给定两个字符串s1,s2,请返回bool值代表s2是否由s1旋转而成。字符串中字符为英文字母和空格,区分大小写,字符串长度小于等于1000。
测试样例:
“Hello world”,”worldhello ”
返回:false
“waterbottle”,”erbottlewat”
返回:true

思路:若s1是s2的旋转得到,则s2一定是s1s1的子串

import java.util.*;

public class ReverseEqual {
public boolean checkReverseEqual(String s1, String s2) {
// write code here
if (s1.length()!=s2.length()) {
return false;
}
String str=s1+s1;
if (str.contains(s2)) {
return true;
}else {
return false;
}
}
}

二、链表

2.2、链表中倒数第k个结点

题目描述
输入一个链表,输出该链表中倒数第k个结点。

/**
public class ListNode {
int val;
ListNode next = null;

ListNode(int val) {
this.val = val;
}
}*/

public class Solution {
public ListNode FindKthToTail(ListNode head,int k) {
ListNode p1 = head;
for(int i = 0;i<k;i++){
if(p1 == null){
return null;
}
p1=p1.next;
}
while(p1!=null){
head = head.next;
p1 = p1.next;
}
return head;
}
}

2.3、访问单个节点的删除

题目描述
实现一个算法,删除单向链表中间的某个结点,假定你只能访问该结点。
给定带删除的节点,请执行删除操作,若该节点为尾节点,返回false,否则返回true

import java.util.*;

/**
public class ListNode {
int val;
ListNode next = null;

ListNode(int val) {
this.val = val;
}
}*/

public class Remove {
public boolean removeNode(ListNode pNode) {
// write code here
if(pNode.next == null){
return false;
}
pNode.val = pNode.next.val;
pNode.next = pNode.next.next;
return true;
}
}

2.4、链表分割

题目描述
编写代码,以给定值x为基准将链表分割成两部分,所有小于x的结点排在大于或等于x的结点之前
给定一个链表的头指针 ListNode* pHead,请返回重新排列后的链表的头指针。注意:分割以后保持原来的数据顺序不变。

import java.util.*;

/**
public class ListNode {
int val;
ListNode next = null;

ListNode(int val) {
this.val = val;
}
}*/

public class Partition {
public ListNode partition(ListNode pHead, int x) {
// write code here
ListNode temp1 = null;
ListNode head1 = null;
ListNode head2 = null;
ListNode temp2 = null;

while(pHead != null){
ListNode node = pHead.next;//此处保留下一个节点,因为要赋值的节点的next要设置为null
pHead.next = null;//将next为空,这样才能只赋值一个节点
if(pHead.val < x){
if(head1 == null){
head1 = pHead;
temp1 = head1;
}else{
temp1.next = pHead;
temp1 = temp1.next;
}
}else{
if(head2 == null){
head2 = pHead;
temp2 = head2;
}else{
temp2.next = pHead;
temp2 = temp2.next;
}
}
pHead = node;
}
//此处要判断是否为空
if(head1 == null){
return head2;
}
temp1.next = head2;
return head1;
}
}

2.5、链式A+B

题目描述
有两个用链表表示的整数,每个结点包含一个数位。这些数位是反向存放的,也就是个位排在链表的首部。编写函数对这两个整数求和,并用链表形式返回结果。
给定两个链表ListNode* A,ListNode* B,请返回A+B的结果(ListNode*)。
测试样例:
{1,2,3},{3,2,1}
返回:{4,4,4}

import java.util.*;
/**
public class ListNode {
int val;
ListNode next = null;

ListNode(int val) {
this.val = val;
}
}*/

public class Plus {
public ListNode plusAB(ListNode a, ListNode b) {
// write code here
ListNode result = new ListNode(-1);
ListNode temp = result;
int value = 0;//进位
int ge = 0;
while(a.next!= null || b.next!=null){
ge = (a.val + b.val + value)%10;
value = (a.val + b.val + value)/10;
temp.next = new ListNode(ge);
temp = temp.next;
if(a.next == null){
a.val = 0;
b = b.next;
}else if(b.next == null){
b.val = 0;
a = a.next;
}else{
a = a.next;
b = b.next;
}
}

//最后一位
ge = (a.val + b.val + value)%10;
value = (a.val + b.val + value)/10;
temp.next = new ListNode(ge);
temp = temp.next;
if(value==1){
temp.next = new ListNode(1);
}

return result.next;
}
}

2.7、回文链表

题目描述
请编写一个函数,检查链表是否为回文。
给定一个链表ListNode* pHead,请返回一个bool,代表链表是否为回文。
测试样例:
{1,2,3,2,1}
返回:true
{1,2,3,2,3}
返回:false

思路1(错误):反转链表,此方法不通,因为反转完链表,原链表的结构已经发生变化

import java.util.*;

/*
public class ListNode {
int val;
ListNode next = null;

ListNode(int val) {
this.val = val;
}
}*/

public class Palindrome {
public boolean isPalindrome(ListNode pHead) {
// write code here
ListNode reverseNode = reverse(pHead);
//这里就行不通了,因为反转完后,链表的结构发生变化,pHead是尾节点了
while(reverseNode!=null){
if(reverseNode.val != pHead.val){
return false;
}
reverseNode = reverseNode.next;
pHead = pHead.next;
}
return true;
}

public ListNode reverse(ListNode pHead){
ListNode cur = pHead;
ListNode reverseNode = null;
ListNode nextNode = null;
ListNode pre = null;
while(cur != null){
nextNode = cur.next;//保存下一个节点
if(nextNode == null){
reverseNode = cur;
}

cur.next = pre;
pre = cur;
cur = nextNode;
}
return reverseNode;
}
}

思路2:用栈和快慢两指针

import java.util.*;

/**
public class ListNode {
int val;
ListNode next = null;

ListNode(int val) {
this.val = val;
}
}*/

public class Palindrome {
public boolean isPalindrome(ListNode pHead) {
// write code here
Stack<Integer> stack = new Stack<Integer>();
ListNode slow=pHead,fast=pHead;
while(fast!=null && fast.next != null){
stack.push(slow.val);
slow = slow.next;
fast = fast.next.next;
}
//奇数
if(fast!=null){
slow = slow.next;
}

while(slow != null){
if(stack.pop() != slow.val){
return false;
}
slow = slow.next;
}

return true;
}
}

三、栈与队列

3.3、集合栈

题目描述
请实现一种数据结构SetOfStacks,由多个栈组成,其中每个栈的大小为size,当前一个栈填满时,新建一个栈。该数据结构应支持与普通栈相同的push和pop操作。
给定一个操作序列int[][2] ope,每个操作的第一个数代表操作类型,若为1,则为push操作,后一个数为应push的数字;若为2,则为pop操作,后一个数无意义。请返回一个int[][],为完成所有操作后的SetOfStacks,顺序应为从下到上,默认初始的SetOfStacks为空。保证数据合法。

import java.util.*;

public class SetOfStacks {
public ArrayList<ArrayList<Integer>> setOfStacks(int[][] ope, int size) {
ArrayList<ArrayList<Integer>> stackList = new ArrayList<ArrayList<Integer>>();
if (ope == null || ope.length < 1)
return stackList;
// 定义操作的次数
int opeCount = ope.length;
// 定义第一个栈
ArrayList<Integer> curStack = new ArrayList<Integer>();
// 因为操作数大于1,所以先把第一个栈加入集合中
stackList.add(curStack);
for (int i = 0; i < opeCount; i++) {
if (ope[i][0] == 1) // push操作
{
if (curStack.size() == size) // 如果当前栈的size已满
{
// 新建一个栈,curStack指向新的栈
curStack = new ArrayList<Integer>();
// 将该栈加入集合栈
stackList.add(curStack);
}
curStack.add(ope[i][1]); // 数据入当前栈

}
if (ope[i][0] == 2) // pop操作
{
// 如果当前栈的大小大于0
if (curStack.size() > 0)
// 则直接删除,当前栈的最后一个元素
curStack.remove(curStack.size() - 1);
else
// 否则当前栈的size等于0说明栈集合中最后一个栈被pop空了
{
// 把集合中最后一个空栈先删除掉
stackList.remove(stackList.size() - 1);
// curStack指向集合中最后一个栈作为当前栈
curStack = stackList.get(stackList.size() - 1);
// 当前栈执行pop操作,弹出最后一个元素
curStack.remove(curStack.size() - 1);
}
}
}

return stackList;
}
}

3.5、用两个栈实现队列

题目描述
用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。

import java.util.Stack;

public class Solution {
Stack<Integer> stack1 = new Stack<Integer>();
Stack<Integer> stack2 = new Stack<Integer>();

public void push(int node) {
stack1.push(node);
}

public int pop() {
if(stack2.size()>0){
return stack2.pop();
}else{
while(stack1.size()>0){
int temp = stack1.pop();
stack2.push(temp);
}
return stack2.pop();
}
}
}

3.6、双栈排序

题目描述
请编写一个程序,按升序对栈进行排序(即最大元素位于栈顶),要求最多只能使用一个额外的栈存放临时数据,但不得将元素复制到别的数据结构中。
给定一个int[] numbers,其中第一个元素为栈顶,请返回排序后的栈。请注意这是一个栈,意味着排序过程中你只能访问到第一个元素。
测试样例:
[1,2,3,4,5]
返回:[5,4,3,2,1]

思路:时间复杂度为O(N^2),空间复杂度为O(N)

public Stack<Integer> twoStacksSort(int[] numbers) {     
Stack<Integer> stack1 = new Stack<Integer>();//主栈
Stack<Integer> stack2 = new Stack<Integer>();//辅助栈

for(int i = 0;i<numbers.length;i++){
stack1.push(numbers[i]);
}

while(stack1.size()>0){
if(stack2.size()==0){
stack2.push(stack1.pop());
}
if(stack1.peek() > stack2.peek()){
stack2.push(stack1.pop());
}else{
int temp = stack1.pop();
while(stack2.size()>0 && temp < stack2.peek()){
stack1.push(stack2.pop());
}
stack2.push(temp);
}
}

return stack2;
}

3.7、猫狗收容所

题目描述
有家动物收容所只收留猫和狗,但有特殊的收养规则,收养人有两种收养方式,第一种为直接收养所有动物中最早进入收容所的,第二种为选择收养的动物类型(猫或狗),并收养该种动物中最早进入收容所的。
给定一个操作序列int[][2] ope代表所有事件。若第一个元素为1,则代表有动物进入收容所,第二个元素为动物的编号,正数代表狗,负数代表猫;若第一个元素为2,则代表有人收养动物,第二个元素若为0,则采取第一种收养方式,若为1,则指定收养狗,若为-1则指定收养猫。请按顺序返回收养的序列。若出现不合法的操作,即没有可以符合领养要求的动物,则将这次领养操作忽略。
测试样例:
[[1,1],[1,-1],[2,0],[2,-1]]
返回:[1,-1]

思路:此题主要是注意边界 条件

import java.util.*;

public class CatDogAsylum {
public ArrayList<Integer> asylum(int[][] ope) {
ArrayList<Integer> input = new ArrayList<Integer>();
ArrayList<Integer> output = new ArrayList<Integer>();
int rows = ope.length;
for(int i = 0;i<rows;i++){
switch(ope[i][0]){
//有动物进入
case 1:
input.add(ope[i][1]);
break;
//有人领养
case 2:
//第一种领养方式
if(ope[i][1] == 0){
for(int j = 0; j<input.size();j++){
if(input.get(j) != 0){
output.add(input.get(j));
input.remove(j);
break;
}
}
}
//指定收养狗
else if(ope[i][1]==1){
for(int j = 0; j<input.size();j++){
if(input.get(j) > 0){
output.add(input.get(j));
input.remove(j);
break;
}
}
}
//指定收养猫
else if(ope[i][1]== -1){
for(int j = 0; j<input.size();j++){
if(input.get(j) < 0){
output.add(input.get(j));
input.remove(j);
break;
}
}
}
break;
}
}
return output;
}

}

思路2:如果只维护一个队列,第一种方法只需要取出队头的动物则可以,但是第二种方法就比较复杂,需要访问整个队列来找出第一个被访问的猫或者狗。
因此两个队列来实现,一个队列存放放入的狗,一个队列存放放入的猫,对于第二种方法实现起来相当容易,我们只需要根据要选择的猫或者狗从相应的队列中取出便可以,但是第一种方法需要判断那个两个队列头部的是猫先进入收容所,还是狗先进入,这个时候需要一个标志,所以我们每次把动物放入队列的时候,同时将一个递增的序号放入队列,这个序号就是一个时间序列,根据这个序号便可以轻松实现第一种方法。

import java.util.*;

public class CatDogAsylum {
public ArrayList<Integer> asylum(int[][] ope) {
class Animal {
int id, order;

public Animal(int id, int order) {
this.id = id;
this.order = order;
}
}
int order = 0;
LinkedList<Animal> dogs = new LinkedList<>();
LinkedList<Animal> cats = new LinkedList<>();
ArrayList<Integer> list = new ArrayList<>();
for (int i = 0; i < ope.length; i++) {
if (ope[i][0] == 1) {
if (ope[i][1] > 0)
dogs.add(new Animal(ope[i][1], order++));
else if (ope[i][1] < 0)
cats.add(new Animal(ope[i][1], order++));
}

else if (ope[i][0] == 2) {
if (ope[i][1] == 0) {
Animal d = dogs.peek();
Animal c = cats.peek();
boolean flag = false;
if (d != null && c != null) {
if (d.order - c.order < 0)
flag = true;
} else if (d == null && c == null)
continue;
else if (d != null)
flag = true;

if (flag)
list.add(dogs.removeFirst().id);
else
list.add(cats.removeFirst().id);
} else if (ope[i][1] == 1) {
if (dogs.peek() != null)
list.add(dogs.removeFirst().id);
} else if (ope[i][1] == -1) {
if (cats.peek() != null)
list.add(cats.removeFirst().id);
}
}
}
return list;
}
}

四、树与图

4.1、二叉树平衡检查(相当重点)

题目描述
实现一个函数,检查二叉树是否平衡,平衡的定义如下,对于树中的任意一个结点,其两颗子树的高度差不超过1。

思路1:平衡二叉树是通过左右子树的高度来判断是否为平衡二叉树的,所以我们首先想到的是如何求一个树的高度,求一个树的高度很简单,递归求解,每次求出左右子树的最大高度再加1便是父节点的高度,这样递归下去,便可以求出任何一颗树的高度。
既然可以求出任何一个节点的高度,那么通过再次遍历二叉树,判断任何一个节点的左右子树高度相差是否满足平衡二叉树便可实现平衡二叉树的判断。
求一颗平衡树高度的时间复杂度为O(logN),那么在第二次遍历的时候需要求每个节点的高度时间复杂度为O(NlogN)。其实这个过程大部分都是重复判断的,下面的方法是该方法的浓缩
可行,但是效率低,getHeight会反复调用同一个节点的高度,时间复杂度为O(nlgn)

import java.util.*;

/*
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}*/

public class Balance {
public boolean isBalance(TreeNode root) {
// write code here
if(root==null){
return true;
}
if(Math.abs(getHeight(root.left)-getHeight(root.right))>1){
return false;
}else{
return isBalance(root.left)&&isBalance(root.right);
}

}

public int getHeight(TreeNode node){
if(node == null)
return 0;
return Math.max(getHeight(node.left),getHeight(node.right))+1;
}

}

<方法2>:
在一次遍历的过程中,既求一个节点的高度,同时该节点是否满足平衡条件,递归函数中一个引用参数返回子节点的高度,然后父节点的高度便可以通过两个子节点的高度求出来,首先判断两个子节点的高度是否满足平衡二叉树,不满足直接返回,满足的情况下,求出当前节点的高度,继续向上递归。
该方法的时间复杂度只有O(N),空间复杂度为O(H),H为树的高度

import java.util.*;

/*
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}*/

public class Balance {
public boolean isBalance(TreeNode root) {
if(checkHeight(root) == -1){
return false;
}else{
return true;//此处不用计算子节点是否为-1,因为计算root的时候子节点肯定都计算过了
}
}

public int checkHeight(TreeNode root){
if(root==null)
return 0;//高度为0

//先检查左右子树是否平衡,从下往上判断,不平衡就返回-1,这样以免每次都从上计算到最底下
if(checkHeight(root.left)==-1 || checkHeight(root.right)==-1)
return -1;

//再判断此节点是否平衡,平衡返回高度
if(Math.abs(checkHeight(root.left)-checkHeight(root.right))>1)
return -1;

return Math.max(checkHeight(root.left),checkHeight(root.right))+1;
}
}

4.2、有向路径检查

题目描述
对于一个有向图,请实现一个算法,找出两点之间是否存在一条路径。
给定图中的两个结点的指针UndirectedGraphNode* a,UndirectedGraphNode* b(请不要在意数据类型,图是有向图),请返回一个bool,代表两点之间是否存在一条路径(a到b或b到a)。

思路: 这个题目考察的其实是有向图的遍历,图的遍历分为深度优先遍历和广度优先遍历,深度优先遍历用堆栈实现,广度优先遍历用队列实现,在该题目中给出了每个节点的子节点,所以最好用广度优先遍历。
图的广度优先遍历和树的层次遍历类似,但是不是完全相同,因为图是连通的,所以我们必须去标志那个节点被访问过,那个节点没有被访问过,最后如果全部访问完以后,还没有找到a到b的路径,则返回false。
注意知识点:
(1)图中有环,记得标记是否被访问
(2)要分别检测两个方向(a->b,b->a)

import java.util.*;

/*
public class UndirectedGraphNode {
int label = 0;
UndirectedGraphNode left = null;
UndirectedGraphNode right = null;
ArrayList<UndirectedGraphNode> neighbors = new ArrayList<UndirectedGraphNode>();

public UndirectedGraphNode(int label) {
this.label = label;
}
}*/

public class Path {
public boolean checkPath(UndirectedGraphNode a, UndirectedGraphNode b) {
/*
* 这里的left,right好像不太有意义,主要根据邻接矩阵遍历
* 利用队列,首先a自身入队列以及其临街矩阵所有节点入队列,入队列前都进行判断
* 若该点邻居都不是b,则该点出队列,继续上述遍历
* 为了防止环的产生,已经入队列过的点不再入队列,用map管理
*/

return check(a,b) || check(b,a);
}
public boolean check(UndirectedGraphNode a, UndirectedGraphNode b) {
// TODO Auto-generated method stub
if(a == null || b == null){
return false;
}
if(a == b){
return true;
}
Map<UndirectedGraphNode, Boolean> checkedMap = new HashMap<UndirectedGraphNode, Boolean>();
LinkedList<UndirectedGraphNode> searchQueue = new LinkedList<UndirectedGraphNode>();
searchQueue.offer(a);
checkedMap.put(a, true);
while(!searchQueue.isEmpty()){
UndirectedGraphNode currentNode = searchQueue.poll();
if(currentNode.neighbors != null){
for(int i = 0; i < currentNode.neighbors.size(); i++){
UndirectedGraphNode neib = currentNode.neighbors.get(i);
if(neib != null){
if(neib == b){
return true;
}
if(checkedMap.get(neib) == null || !checkedMap.get(neib)){
searchQueue.offer(neib);
checkedMap.put(neib, true);
}
}
}
}
}
return false;
}
}

4.3、树高度最小的BST(重点)

题目描述
对于一个元素各不相同且按升序排列的有序序列,请编写一个算法,创建一棵高度最小的二叉查找树。
给定一个有序序列int[] vals,请返回创建的二叉查找树的高度。

思路1:直接按公式算,序列的中间值作为根节点

思路2:实际此题考查是在建树的过程中顺便计算高度

public class MinimalBST {
class TreeNode{
int val;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int n){
this.val = n;
}
}
public int buildMinimalBST(int[] vals) {
int len = vals.length;
if(len == 0 || vals==null){
return 0;
}
return buildMinimalBST1(vals,0,len-1);
}

public int buildMinimalBST1(int[] vals ,int start,int end) {
if(start>end){
return 0;
}
int mid = (start + end)/2;
int height1 = buildMinimalBST1(vals,start, mid-1);
int height2 = buildMinimalBST1(vals,mid+1, end);
return Math.max(height1,height2) + 1;
}

}

如果是建立二叉树的话

public class MinimalBST {

public TreeNode buildMinimalBST(int[] vals) {
int len = vals.length;
if(len == 0 || vals==null){
return null;
}
return buildMinimalBST1(vals,0,len-1);
}

public TreeNode buildMinimalBST1(int[] vals ,int start,int end) {
if(start>end){
return null;
}
int mid = (start + end)/2;
TreeNode node = new TreeNode(vals[mid]);
node.left = = buildMinimalBST1(vals,start, mid-1);
node.right = = buildMinimalBST1(vals,mid+1, end);

return node;
}

}

4.4、树输出单层结点(重点)

题目描述
对于一棵二叉树,请设计一个算法,创建含有某一深度上所有结点的链表。
给定二叉树的根结点指针TreeNode* root,以及链表上结点的深度,请返回一个链表ListNode,代表该深度上所有结点的值,请按树上从左往右的顺序链接,保证深度不超过树的高度,树上结点的值为非负整数且不超过100000。

思路1:广度优先算法,采用两个队列,一个保存上一层,一个保存下一层

import java.util.*;

/**
public class ListNode {
int val;
ListNode next = null;

ListNode(int val) {
this.val = val;
}
}*/

/*
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}*/

public class TreeLevel {
public ListNode getTreeLevel(TreeNode root, int dep) {
if(root == null || dep<=0){
return null;
}
//LinkedList作为队列,采用广度优先搜索算法,一层一层遍历
LinkedList<TreeNode> cur = new LinkedList<TreeNode>();
cur.offer(root);
int h = 1;//第一层
while(cur.size()>0 && dep>h){
LinkedList<TreeNode> parent = cur; //上一层
cur = new LinkedList<TreeNode>();
while(parent.size()>0){
TreeNode node = parent.poll();
if(node.left!=null){
cur.offer(node.left);
}
if(node.right!=null){
cur.offer(node.right);
}
}
h++;//增加一层与dep判断
}
//将树的dep层转化为链表
ListNode root1 = null;
ListNode temp = null;
while(cur.size()>0){
if(root1 == null){
root1 = new ListNode(cur.poll().val);
temp = root1;
}else{
temp.next = new ListNode(cur.poll().val);
temp = temp.next;
}
}
return root1;
}
}

思路2:递归,即深度优先算法

import java.util.*;

public class TreeLevel {
ListNode head = null, tail = null;

public ListNode getTreeLevel(TreeNode root, int deep) {
if(deep > 0 && root != null){
getTreeLevel1(root, deep);
}
return head;
}

public void getTreeLevel1(TreeNode node, int deep){
if(node == null){
return;
}
if(deep == 1){ //每到下一节点,dep就减1,当dep为1是,则正好是达到深度dep,把当前节点加入到链表中
if(head == null){ //如果链表为空
head = new ListNode(node.val);
tail = head;
}else{ //如果链表不为空
tail.next = new ListNode(node.val);
tail = tail.next;
}
}else{
getTreeLevel1(node.left, deep-1);//递归左节点,深度减1
getTreeLevel1(node.right, deep-1);
}
}
}

4.5、检查是否为BST(重点)

题目描述
请实现一个函数,检查一棵二叉树是否为二叉查找树。

思路1,按照二叉查找树的定义:错误的思路:只考虑每个节点左节点小于根节点小于右节点
正确的是:root的左子树的所有节点都小于root,root小于root右子节点

 public class Checker {
public boolean checkBST(TreeNode root) {
return checkBST1(root,Integer.MIN_VALUE,Integer.MAX_VALUE);
}

public boolean checkBST1(TreeNode root, int min, int max) {
// write code here
if(root==null){//空节点或叶子节点
return true;
}
if(min>root.val || max<root.val)
return false;
//根节点一定是左子树下所有节点最大的,右子树下所有节点最小的,每次更换根节点的值
if(!checkBST1(root.left,min,root.val) || !checkBST1(root.right,root.val,max))
return false;

return true;
}

}

思路2:二叉查找树是中序遍历完是从小到大排列的

import java.util.*;

public class Checker {
int preValue = Integer.MIN_VALUE;
public boolean checkBST(TreeNode root) {
// write code here
if(root==null){//空节点
return true;
}
//中序遍历
if(!checkBST(root.left))
return false;
if(preValue > root.val)
return false;
preValue = root.val;
if(!checkBST(root.right))
return false;

return true;
}

}

4.6、寻找下一个结点