自用
算法基础阶段
1.排序
1.1经典排序算法
- 插入排序
//插入排序
//时间复杂度最差为O(n^2) 最好为O(n)
public static void insertionSort(int[] arr){
if(arr == null || arr.length < 2){
return;
}
for(int i = 1;i < arr.length;i++){//保证0-i之间有序
for(int j = i-1;j >= 0 && arr[j] > arr[j+1];j--){
swap(arr,j,j+1);
}
}
}
//i和j若为同一个位置则会出错
public static void swap(int[] arr,int i,int j){
arr[i] = arr[i] ^ arr[j];
arr[j] = arr[i] ^ arr[j];
arr[i] = arr[i] ^ arr[j];
}
- 归并排序
//整个过程master公式
//T(N) = 2 * T(N / 2) + O(N)
public static void process(int[] arr,int L,int R){
if(L == R){
return;
}
int mid = L + ((R-L)>>1);
process(arr,L,mid);//左侧部分排好序
process(arr,mid+1,R);//右侧部分排好序
merge(arr,L,mid,R);
}
//归并排序
public static void merge(int[] arr,int L,int M,int R){
//申请一个辅助空间
int[] help = new int[R-L+1];
//设置数组两部分的指示索引
int i = 0;
int p1 = L;
int p2 = M+1;
while(p1 <= M && p2 <= R){
help[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
}
while(p1 <= M){
help[i++] = arr[p1++];
}
while(p2 <= R){
help[i++] = arr[p2++];
}
for (int j = 0; j < help.length; j++) {
arr[L+j] = help[j];
}
}
- 快速排序
//arr[1..r]排好序
public static void quickSort(int[] arr,int L,int R){
if(L < R){
Swap(arr,L+(int)(Math.random()*(R-L+1)),R);
int[] p = partition(arr,L,R);
quickSort(arr,L,p[0]-1);//<区
quickSort(arr,p[1]+1,R);//>区
}
}
//处理arr[1...r]的函数
//返回等于区域(左边界,右边界),所以返回一个长度为2的数组res,res[0] res[1]
public static int[] partition(int[] arr,int L,int R){
int less = L - 1;// <区右边界
int more = R;// >区左边界
while(L < more){//L表示当前数的位置 arr[R] 为划分值
if(arr[L] < arr[R]){
Swap(arr,++less,L++);
}else if(arr[L] > arr[R]){
Swap(arr,--more,L);
}else{
L++;
}
}
Swap(arr,more,R);
return new int[]{less + 1,more};
}
public static void Swap(int[] arr,int i,int j){
}
- 堆排序
堆排序
完全二叉树
可以按照数组顺序对应完全二叉树上的值
数中节点i的左孩子为 2i+1 右孩子为2i+2 父节点为i-1/2
//堆排序
public static void heapSort(int[] arr){
if(arr == null || arr.length < 2){
return;
}
for(int i = 0;i < arr.length;i++){//O(N)
heapInsert(arr,i);//O(logN)
}
int heapSize = arr.length;
swap(arr,0,--heapSize);
while(heapSize > 0){//O(N)
heapify(arr,0,heapSize);//O(logN)
swap(arr,0,--heapSize);//O(1)
}
}
//建立大根堆,向上堆化
public static void heapInsert(int[] arr,int index){
while(arr[index] > arr[(index-1)/2]){//当前位置与父节点位置的数进行比较,若大于父节点则进行交换
swap(arr,index,(index-1)/2);
index = (index - 1) / 2;
}
}
//向下堆化,从任意节点开始进行堆化处理
public static void heapify(int[] arr,int index,int heapsize){
//定义任意节点的左孩子下标
int left = 2 * index + 1;
while(left < heapsize){//若当前节点存在孩子节点
//判断两个孩子节点哪个大,谁大就把下标给largest
int largest = left+1 < heapsize && arr[left] > arr[left+1] ? left : left+1;
//判断孩子节点与父节点哪个大,谁大就把下标值给largest
largest = arr[largest] > arr[index] ? largest : index;
if(largest == index){//说明父节点就是最大值,结束跳出循环
break;
}
swap(arr,largest,index);
index = largest;
left = 2*index + 1;
}
}
public static void swap(int[] arr,int i,int j){
}
- 基数排序
//arr[begin ,...,end]排序
public static void radixSort(int[] arr,int L,int R,int digit){
final int radix = 10;
int i = 0,j = 0;
//有多少个数准备多少个辅助空间
int[] bucket = new int[R - L + 1];
for(int d = 1;d <= digit;d++){
//申请10个空间
//将数位相同的数,进行分片
int[] count = new int[radix];//count[i]表示<=i的数有几个
for(i = L;i <= R;i++){
j = getDigit(arr[i],d);
count[j]++;
}
for(i = 1;i < digit;i++){
count[i] = count[i] + count[i-1];
}
//将出桶数据装入bucket中
for(i = R;i >= L;i--){
j = getDigit(arr[i],d);
bucket[count[j] - 1] = arr[i];
count[j]--;
}
//将bucket数据赋值给arr数组,完成一次出桶操作
for(i = L,j = 0;i <= R;i++,j++){
arr[i] = bucket[j];
}
}
}
//获取数值中某个位置的值
public static int getDigit(int x,int d){
return ((x / ((int)Math.pow(10,d-1)))%10);
}
1.2排序算法相关基础题型
- 经典面试题1
求解数组L-R范围上的最大值
public static int getMax(int[] arr){
return process(arr,0,arr.length-1);
}
//求接arr[L,....,R]范围上最大值
//master公式
//T(N) = 2 * T(N/2) + O(1)
public static int process(int[] arr,int L,int R){
if(L == R){
return arr[L];
}
int mid = L + ((R-L)>>1);//mid = L + (R - L) / 2
int leftMax = process(arr,L,mid);//递归求解左半部最大值
int rightMax = process(arr,mid+1,R);//递归求解右半部最大值
return Math.max(leftMax,rightMax);
}
- 经典面试题2
求解一个数组出现次数为奇数次的两个不同的数
//常见面试题(重)
//求取数组中出现奇数次的两个不同的数
//利用位运算
public static void printOddTimesNum2(int[] arr){
int eor = 0;
for(int i = 0;i < arr.length;i++){
eor ^= arr[i];
}
// eor = a ^ b
// eor != 0
//eor必有一个位置上是1
int rightOne = eor & (~eor + 1);//提取出最右的1**
int onlyOne = 0;//eor'
for(int cur : arr){
if((cur & rightOne) == 1){
onlyOne ^= cur;
}
}
System.out.println(onlyOne + " " + (eor^onlyOne));
}
- 经典面试题3
逆序对问题(归并排序问题的变体)(必出)
即求解右侧比这个数小的数的数量有多少
求解一个数组出现次数为奇数次的两个不同的数
public static int process(int[] arr,int L,int R){
if(L == R){
return 0;
}
int mid = L + ((R - L)>>1);
return process(arr,L,mid) + process(arr,mid+1,R) + merge(arr,L,mid,R);
}
public static int merge(int[] arr,int L,int M,int R){
//申请一个辅助数组
int[] help = new int[R-L+1];
//定义两半部分数据指针
int p1 = L;
int p2 = M + 1;
int i = 0;
int res = 0;
while(p1 <= M && p2 <= R){
res += arr[p2] < arr[p1] ? M - p1 + 1 : 0;
help[i++] = arr[p2] < arr[p1] ? arr[p2++] : arr[p1++];
}
while(p1 <= M){
help[i++] = arr[p1++];
}
while(p2 <= R){
help[i++] = arr[p2++];
}
for (int j = 0; j < help.length; j++) {
arr[L+j] = help[j];
}
return res;
}
- 经典面试题4
小和问题(归并排序问题的变体)
可以看成这个数右侧有多少个比这个数大
1 3 4 2 5
结果 16
public static int process(int[] arr,int L,int R){
if(L == R){
return 0;
}
int mid = L + ((R - L)>>1);
return process(arr,L,mid) + process(arr,mid+1,R) + merge(arr,L,mid,R);
}
public static int merge(int[] arr,int L,int M,int R){
//申请辅助数组
int[] help = new int[R - L + 1];
//定义两半部分数组指针
int p1 = L;
int p2 = M + 1;
int res = 0;
int i = 0;
while(p1 <= M && p2 <= R){
res += arr[p1] < arr[p2] ? (R - p2 + 1) * arr[p1] : 0;
help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
}
while(p1 <= M){
help[i++] = arr[p1++];
}
while(p2 <= R){
help[i++] = arr[p2++];
}
for(i = 0;i < help.length;i++){
arr[L+i] = help[i];
}
return res;
}
- 经典面试题5
堆排序扩展题目
已知一个几乎有序的数组,几乎有序是指,如果吧数组拍好顺序的话,每个元素移动的距离可以不超过k,并且k相对于数组来说比较小。请选择一个合适的排序算法
针对这个数据进行排序
public static void sortArrDistanceLessK(int[] arr,int k){
//默认小根堆
PriorityQueue<Integer> heap = new PriorityQueue<>();//优先级队列
int index = 0;
for(;index <= Math.min(arr.length,k);index++){
heap.add(arr[index]);
}
int i = 0;
for(;index < arr.length;i++,index++){
heap.add(arr[index]);
arr[i] = heap.poll();
}
while(!heap.isEmpty()){
arr[i++] = heap.poll();
}
}
比较器(重)
比较器的实质就是重载比较运算符
比较器可以很好的应用在特殊标准的排序上
比较器可以很好的应用在根据特殊标准排序的结构上
规则
若返回结果为负值,则第一个参数在前面
若返回结果为正值,则第二个参数在前面
若返回结果为0,则谁在前面无所谓
排序算法的稳定性及汇总
同样值的个体之间,如果不因为排序而改变相对次序,就是这个排序是有稳定性的;否则就没有
不具备稳定性的排序:
选择排序、快速排序、堆排序
具备稳定性的排序:
冒泡排序、插入排序、归并排序、一切桶排序思想下的排序
目前没有找到时间复杂度O(NlogN),额外空间复杂度O(1),有稳定的排序
** 排序算法常见的坑 **
1.归并排序的额外空间复杂度可以变成O(1),但是很难,不需要掌握,但算法不再具有稳定性,有兴趣可以搜“归并排序 内部缓存法”
2.“原地归并排序”的帖子都是垃圾,会让归并排序的时间复杂度变成O(N^2)
3.快速排序可以做到稳定性问题,但是非常难,不需要掌握,可以搜“01 stable sort”
4.所有的改进都不重要,因为目前没有找到时间复杂度O(NlogN),额外空间复杂度O(1),又稳定的排序
5.有一道题目,是奇数放在数组左边,偶数放在数组右边,还要求原始的相对次序不变,碰到这个题目,可以怼面试官
2.链表
经典例题
- TreeMap与TreeSet使用
TreeMap: key->value; TreeSet: key
public static void main(String[] args) {
//有序表可以根据key值进行有序组织
TreeMap<Integer,String> treeMap1 = new TreeMap<>();
treeMap1.put(7,"我是7");
treeMap1.put(5,"我是5");
treeMap1.put(4,"我是4");
treeMap1.put(3,"我是3");
treeMap1.put(9,"我是9");
treeMap1.put(2,"我是2");
System.out.println(treeMap1.containsKey(5));
System.out.println(treeMap1.get(5));
System.out.println(treeMap1.firstKey() + ",我最小");
System.out.println(treeMap1.lastKey() + ",我最大");
System.out.println(treeMap1.floorKey(8) + ",在表中所有<=8的数中,我离8最近");
System.out.println(treeMap1.ceilingKey(8) + ",在表中所有>=8的数中,我离8最近");
System.out.println(treeMap1.floorKey(7) + ",在表中所有<=7的数中,我离7最近");
System.out.println(treeMap1.ceilingKey(7) + ",在表中所有>=7的数中,我离7最近");
treeMap1.remove(5);
System.out.println(treeMap1.get(5)+",删了,没有了");
}
- HashMap与HashSet使用
HashMap: Key -> value ;HashSet: Key
public static void main(String[] args) {
Node nodeA = null;
Node nodeB = null;
Node nodeC = null;
//hashSet1的Key是基础类型->int类型
HashSet<Integer> hashSet1 = new HashSet<>();
hashSet1.add(3);
System.out.println(hashSet1.contains(3));
hashSet1.remove(3);
System.out.println(hashSet1.contains(3));
HashMap<Integer,String> mapTest = new HashMap<>();
mapTest.put(1,"zuo");
mapTest.put(1,"cheng");
mapTest.put(2,"2");
System.out.println(mapTest.containsKey(1));
System.out.println(mapTest.get(1));
System.out.println(mapTest.get(4));
mapTest.remove(2);
System.out.println(mapTest.get(2));
System.out.println("==========1==========");
}
- 链表反转
- 打印两个链表的公共部分
- 判断链表是否回文
判断一个链表是否为回文
1 2 3 2 1
快慢指针一定要写熟
public static class Node{
public int value;
public Node next;
public Node(int data){
this.value = data;
}
}
//判断链表回文
public static boolean isPalindrome1(Node head){
Stack<Node> stack = new Stack<Node>();
Node cur = head;
while(cur != null){
stack.push(cur);
cur = cur.next;
}
while (head != null){
if(head.value != stack.pop().value){
return false;
}
head = head.next;
}
return true;
}
//用n/2的方法找快慢指针
public static boolean isPalindrome2(Node head){
if(head == null || head.next == null){
return true;
}
Node right = head.next;
Node cur = head;
while(cur.next != null && cur.next.next != null){
right = right.next;//用于找到mid.next
cur = cur.next.next;
}
Stack<Node> stack = new Stack<Node>();
while(right != null){
stack.push(right);
right = right.next;
}
while(!stack.isEmpty()){
if(head.value != stack.pop().value){
return false;
}
head = head.next;
}
return true;
}
//需要O(1)空间的链表回文判断
public static boolean isPalindrome3(Node head){
if(head == null || head.next == null){
return true;
}
Node n1 = head;
Node n2 = head;
while(n1.next != null && n2.next.next != null){
n1 = n1.next;//n1到达中点
n2 = n2.next.next;//n2到达链表末尾
}
//进行中点后链表逆置
n2 = n1.next;//n2 -> right part first node
n1.next = null;//mid.next -> null
Node n3 = null;
while(n2 != null){//right part convert
n3 = n2.next;//n3 -> save next node
n2.next = n1;//next of right node convert
n1 = n2;//n1 move
n2 = n3;//n2 move
}
n3 = n1;//n3 -> save last node
n2 = head;// n2 -> left first node
boolean res = true;
while(n1 != null && n2 != null){//检查链表是否回文
if(n1.value != n2.value){
res = false;
break;
}
n1 = n1.next;//left to mid
n2 = n2.next;//right to mid
}
//将又半部分逆置的再还原
n1 = n3.next;
n3.next = null;
while(n1 != null){
n2 = n1.next;
n1.next = n3;
n3 = n1;
n1 = n2;
}
return res;
}
- 复制链表
复制含有随机指针节点的链表
【题目】一种特殊的单链表节点类描述如下
class Node{
int value;
Node next;
Node rand;
Node(int val){
value = val;
}
}
rand指针是单链表节点结构中新增的指针,rand可能指向链表中的任意一个节点,也可能指向null。给定一个由Node节点类型组成的无环单链表的头节点
head,请实现一个函数完成这个链表的复制,并返回复制的新链表的头节点。
【要求】时间复杂度O(N),额外空间复杂度O(1)
public static class Node{
public int value;
public Node next;
public Node rand;
public Node(int data){
this.value = data;
}
}
//方法1:利用哈希表来拷贝
public static Node copyListWithRand1(Node head){
HashMap<Node,Node> map = new HashMap<Node,Node>();
Node cur = head;
while(cur != null){
map.put(cur,new Node(cur.value));
cur = cur.next;
}
cur = head;
while (cur != null){
//cur 老
//map.get(cur) 新
map.get(cur).next = cur.next;
map.get(cur).rand = cur.rand;
}
return map.get(head);
}
//方法2:利用链表的位置关系来拷贝
public static Node copyListWithRand2(Node head){
if(head == null){
return null;
}
Node cur = head;
Node next = null;
//复制节点并连接每个节点
//1 -> 2 -> 3
//1 -> 1' -> 2 -> 2' ->3
while(cur != null){
next = cur.next;
cur.next = new Node(cur.value);
cur.next.next = next;
cur = next;
}
cur = head;
Node curCopy = null;
//设置复制节点的rand指针
//1 -> 1' -> 2 -> 2'
while(cur != null){
next = cur.next.next;//当前节点的下一个真实节点
curCopy = cur.next;//当前节点的复制节点
curCopy.rand = cur.rand != null ? cur.rand.next : null;
cur = next;
}
Node res = head.next;
cur = head;
//分离拷贝的数组
while(cur != null) {
next = cur.next.next;
curCopy = cur.next;
curCopy.next = next != null ? next.next : null;
cur = next;
}
return res;
}
- 链表区域划分
将链表中的数值按照给定的一个数K进行划分,小于K的方左边,等于K的放中间,大于K的放右边
public static class Node{
public int value;
public Node next;
public Node(int data){
this.value = data;
}
}
//方法1思想:将链表中的数值依次取出存于数组中,在数组中进行partition操作(同荷兰国旗问题),然后将排好序的数据,依次传入链表中
//方法2思想:建立三个链表区,小于K的一个链表、等于K的一个链表、大于K的一个链表,最后将三个链表进行连接
public static Node listPartition2(Node head,int pivot){
Node sH = null;//small head
Node sT = null;//small tail
Node eH = null;//equal head
Node eT = null;//equal tail
Node mH = null;//big head
Node mT = null;//big tail
Node next = null;//save next node
//every node distributed to three lists
while(head != null){
next = head.next;//保存下一个节点
head.next = null;
if(head.value < pivot){
if(sH == null){
sH = head;
sT = head;
}else{
sT.next = head;
sT = head;
}
}else if(head.value == pivot){
if(eH == null){
eH = head;
eT = head;
}else{
eT.next = head;
eT = head;
}
}else{
if(mH == null){
mH = head;
mT = head;
}else{
mT.next = head;
mT = head;
}
}
head = next;
}
if(sT != null){
sT.next = eH;
eT = eT == null ? sT : eT;//下一步,谁去连大于区域的头,谁就变成eT
}
//上面的if,不管跑了没有,et
//all reconnect
if(eT != null){//如果小于区域和等于区域,不是都没有
eT.next = mH;
}
return sH != null ? sH : (eH != null ? eH : mH);
}
- 链表相交问题
两个链表相交的一系列问题
【题目】给定两个可能有环也可能无环的单链表,头节点head1和head2.请实现一个函数,如果两个链表相交,请返回相交的第一个节点。若不相交,返回null
【要求】如果两个链表长度之和为N,时间复杂度请达到O(N),额外空间复杂度请达到O(1)
存在的可能情况
情况一,若两个单链表均无环
1、求两个链表的,end和链表长度
2、判断两个链表各自的end是否一致,不一致直接返回false
3、若一致,则先让长链表走差值步,然后两个链表再一块走,进行逐步判断链表指向节点是否相同
情况二,若一个单链表为有环链表,另一个为无环链表
不存在
情况三,两个单链表均有环
public static class Node{
public int value;
public Node next;
public Node(int data){
this.value = data;
}
}
//主函数调用
public static Node getIntersectNode(Node head1,Node head2){
if(head1 == null || head2 == null){
return null;
}
Node loop1 = getLoopNode(head1);
Node loop2 = getLoopNode(head2);
if(loop1 == null && loop2 == null){
return noLoop(head1,head2);
}
if(loop1 != null && loop2 != null){
return bothLoop(head1,loop1,head2,loop2);
}
return null;
}
//两个有环链表,返回第一个相交点,如果不想则返回null
public static Node bothLoop(Node head1,Node loop1,Node head2,Node loop2){
Node cur1 = null;
Node cur2 = null;
if(loop1 == loop2){
cur1 = head1;
cur2 = head2;
int n = 0;
while(cur1 != loop1){
n++;
cur1 = cur1.next;
}
while(cur2 != loop2){
n--;
cur2 = cur2.next;
}
cur1 = n > 0 ? head1 : head2;
cur2 = cur1 == head1 ? head2 : head1;
n = Math.abs(n);
while(n != 0){
n--;
cur1 = cur1.next;
}
while(cur1 != cur2){
cur1 = cur1.next;
cur2 = cur2.next;
}
return cur1;
}else{
cur1 = loop1.next;
while (cur1 != loop1){
if(cur1 == loop2){
return loop1;
}
}
return null;
}
}
//若两个无环单链表,相交,判定两个单链表的相交第一个节点
// 1、求两个链表的,end和链表长度
// 2、判断两个链表各自的end是否一致,不一致直接返回false
// 3、若一致,则先让长链表走差值步,然后两个链表再一块走,进行逐步判断链表指向节点是否相同
public static Node noLoop(Node head1,Node head2){
if(head1 == null || head2 == null){
return null;
}
Node cur1 = head1;
Node cur2 = head2;
int n = 0;//统计两个链表的差值
while(cur1.next != null){
n++;
cur1 = cur1.next;
}
while(cur2.next != null){
n--;
cur2 = cur2.next;
}
if(cur1 != cur2){
return null;
}
cur1 = n > 0 ? head1 : head2;//谁长,谁的头变成cur1
cur2 = cur1 == head1 ? head2 : head1;//谁短,谁的头变成cur2
n = Math.abs(n);//取绝对值
while(n != 0){
n--;
cur1 = cur1.next;
}
while(cur1 != cur2){
cur1 = cur1.next;
cur2 = cur2.next;
}
return cur1;
}
//找到单链表第一个入环节点,如果无环,返回null
//快慢指针相遇
//若链表有环,则快指针一次走两步,慢指针一次走一步,则快慢指针必在环上相遇,然后将快指针重置于链表头,令其一次走一步,
// 当快慢指针再次相遇时则是单链表入环节点
public static Node getLoopNode(Node head){
if(head == null || head.next == null || head.next.next == null){
return null;
}
Node n1 = head.next;//n1为慢指针,一次走一步
Node n2 = head.next.next;//n2为快指针,一次走两步
//若链表存在环,则快慢指针必在环中相遇
while(n1 != n2){
if(n1.next == null || n2.next.next == null){
return null;
}
n2 = n2.next.next;
n1 = n1.next;
}
//快慢指针环中相遇后,将快指针置于链表头,然后一次走一步
n2 = head;//n2 -> walk again from head
//若n1与n2再次相遇,则相遇处即为链表入环的第一个节点
while(n1 != n2){
n1 = n1.next;
n2 = n2.next;
}
return n1;
}
//判断单链表是否有环,且返回第一个入环节点(利用HashSet解决)
public static Node getLoopNode1(Node head){
if(head == null || head.next == null || head.next.next == null){
return null;
}
HashSet<Node> hashSet = new HashSet<>();
Node cur = head;
while(!hashSet.contains(cur)){
hashSet.add(cur);
cur = cur.next;
}
return cur == null ? null : cur;
}
3.二叉树
- 二叉树的遍历
//二叉树节点
public static class Node{
public int value;
public Node left;
public Node right;
public Node(int data){
this.value = data;
}
}
//递归解法:先序遍历(根左右)
public static void preOrderRecur(Node head){
if(head == null){
return;
}
System.out.print(head.value + " ");
preOrderRecur(head.left);
preOrderRecur(head.right);
}
//先序遍历的非递归解法
// 1、准备一个栈,每次从栈中弹出一个节点cur
// 2、打印(处理)cur
// 3、先右后左(如果有)压入栈中
// 4、如此重复操作
public static void preOrderNoRecur(Node head){
if(head != null){
Stack<Node> stack = new Stack<Node>();
stack.add(head);
while (!stack.isEmpty()){
head = stack.pop();
System.out.print(head.value + " ");
//有右孩子的话,先压右
if(head.right != null){
stack.push(head.right);
}
//有左孩子的话,再压左
if(head.left != null){
stack.push(head.left);
}
}
}
System.out.println();
}
//递归解法:中序遍历(左根右)
public static void inOrderRecur(Node head){
if(head == null){
return;
}
inOrderRecur(head.left);
System.out.print(head.value + " ");
inOrderRecur(head.right);
}
//中序遍历的非递归解法
// 1、每棵子树整棵树左边界进栈
// 2、依次弹出的过程中,打印
// 3、对弹出节点的右树压栈
public static void inOrderNoRecur(Node head){
if(head != null){
Stack<Node> stack = new Stack<Node>();
while(!stack.isEmpty() || head != null){
if(head != null){//将左边界压入栈中
stack.push(head);
head = head.left;
}else{
head = stack.pop();
System.out.print(head.value + " ");
head = head.right;
}
}
}
System.out.println();
}
//递归解法:后序遍历(左右根)
public static void posOrderRecur(Node head){
if(head == null){
return;
}
posOrderRecur(head.left);
posOrderRecur(head.right);
System.out.print(head.value + " ");
}
//后序遍历的非递归解法
// 1、准备两个栈,第一个栈按照“根左右”的顺序压栈,只要栈不为空,则出栈到收集栈中
public static void posOrderNoRecur(Node head){
if(head != null){
Stack<Node> s1 = new Stack<Node>();
Stack<Node> s2 = new Stack<Node>();
s1.push(head);
while (!s1.isEmpty()){
head = s1.pop();
s2.push(head);
if(head.left != null){
s1.push(head.left);
}
if(head.right != null){
s1.push(head.right);
}
}
while(!s2.isEmpty()){
System.out.print(s2.pop().value + " ");
}
}
System.out.println();
}
- 判断一棵二叉树是否为平衡二叉树
如何判断一棵二叉树是否是平衡二叉树?(二叉树题目套路)
递归可以解决一切树型DP问题
public static class Node{
public int value;
public Node left;
public Node right;
}
public static boolean isBalanced(Node head){
return process(head).isBalanced;
}
public static class ReturnType{
public boolean isBalanced;
public int height;
public ReturnType(boolean isB,int hei){
isBalanced = isB;
height = hei;
}
}
public static ReturnType process(Node x){
if(x == null){
return new ReturnType(true,0);
}
ReturnType leftData = process(x.left);
ReturnType rightData = process(x.right);
int height = Math.max(leftData.height,rightData.height) + 1;
boolean isBalanced = leftData.isBalanced && rightData.isBalanced && Math.abs(leftData.height - rightData.height) < 2;
return new ReturnType(isBalanced,height);
}
- 判断二叉树是否为搜索二叉树
二叉树的相关概念及其实现判断
如何判断一棵二叉树是否是搜索二叉树?
(搜索二叉树:每一棵树,他的左子树都比它小,它的右子树都比他大)
中序遍历,若为升序则肯定为搜索二叉树
public static class Node{
public int value;
public Node left;
public Node right;
public Node(int data){
this.value = data;
}
}
//搜索二叉树的判定
public static int preValue = Integer.MIN_VALUE;
public static boolean isBST(Node head){
if(head == null){
return true;
}
boolean isLeftBst = isBST(head.left);
if(!isLeftBst){
return false;
}
if(head.value <= preValue){
return false;
}else{
preValue = head.value;
}
return isBST(head.right);
}
//判断搜索二叉树的非递归写法
public static boolean isBST2(Node head){
if(head != null){
int preValue = Integer.MIN_VALUE;
Stack<Node> stack = new Stack<Node>();
while(!stack.isEmpty() || head != null){
if(head != null){
stack.push(head);
head = head.left;
}else{
head = stack.pop();
if(head.value <= preValue){
return false;
}else{
preValue = head.value;
}
head = head.right;
}
}
}
return true;
}
public static class ReturnData{
public boolean isBST;
public int min;
public int max;
public ReturnData(boolean is,int mi,int ma){
isBST = is;
min = mi;
max = ma;
}
}
public static ReturnData process(Node x){
if(x == null){
return null;
}
ReturnData leftData = process(x.left);
ReturnData rightData = process(x.right);
int min = x.value;
int max = x.value;
if(leftData != null){
min = Math.min(min,leftData.min);
max = Math.max(max,leftData.max);
}
if(rightData != null){
min = Math.min(min,rightData.min);
max = Math.max(max,rightData.max);
}
boolean isBST = true;
if(leftData != null && (!leftData.isBST || leftData.max >= x.value)){
isBST = false;
}
if(rightData != null && (!rightData.isBST || x.value >= rightData.min)){
isBST = false;
}
return new ReturnData(isBST,min,max);
}
- 判断一棵二叉树是否为满二叉树
public static class Node{
public int value;
public Node left;
public Node right;
public Node(int data){
this.value = data;
}
}
public static boolean isF(Node head){
if(head == null){
return true;
}
Info data = f(head);
return data.nodes == (1 << data.height - 1);
}
public static class Info{
public int height;
public int nodes;
public Info(int h,int n){
height = h;
nodes = n;
}
}
public static Info f(Node x){
if(x == null){
return new Info(0,0);
}
Info leftData = f(x.left);
Info rightData = f(x.right);
int height = Math.max(leftData.height,rightData.height) + 1;
int nodes = leftData.nodes + rightData.nodes + 1;
return new Info(height,nodes);
}
- 判断一棵树为完全二叉树
如何判断一棵二叉树是完全二叉树?
二叉树按宽度遍历
情况
1.若任意节点有右孩子,无左孩子则直接false
2.在情况1不违规的情况下,如果遇到第一个左右孩子不双全的,则后续节点则均为叶子节点
public static class Node{
public int value;
public Node left;
public Node right;
}
//判断二叉树是否为完全二叉树
public static boolean isCBT(Node head){
if(head == null){
return true;
}
LinkedList<Node> queue = new LinkedList<>();
//是否遇到过左右两个孩子不双全的节点
boolean leaf = false;
Node l = null;
Node r = null;
queue.add(head);
while(!queue.isEmpty()){
head = queue.poll();
l = head.left;
r = head.right;
//如果遇到了不双全的节点后,又发现当前节点居然有孩子
if((leaf && (l != null || r != null)) || (l == null && r != null)){
return false;
}
if(l != null){
queue.add(l);
}
if(r != null){
queue.add(r);
}
if(l == null || r == null){
leaf = true;
}
}
return true;
}
- 二叉树的序列化与反序列化
二叉树的序列化和反序列化
就是内存里的一棵树如何变成字符串形式(序列化),又如何从字符串形式变成内存里的树(反序列化)
如何判断一棵二叉树是不是另一颗二叉树的子树
public static class Node{
public int value;
public Node left;
public Node right;
public Node(int val){
value = val;
}
}
//序列化
// 以head为头的树,请序列化成字符串返回
public static String serialByPre(Node head){
if(head == null){
return "#_";
}
String res = head.value + "_";
res += serialByPre(head.left);
res += serialByPre(head.right);
return res;
}
//反序列化
public static Node reconByPreString(String preStr){
String[] values = preStr.split("_");
Queue<String> queue = new LinkedList<String>();
for(int i = 0;i != values.length;i++){
queue.add(values[i]);
}
return reconPreOrder(queue);
}
public static Node reconPreOrder(Queue<String> queue){
String value = queue.poll();
if(value.equals("#")){
return null;
}
Node head = new Node(Integer.valueOf(value));
head.left = reconPreOrder(queue);
head.right = reconPreOrder(queue);
return head;
}
- 获取二叉树的宽度和深度
//二叉树节点
public static class Node{
public int value;
public Node left;
public Node right;
public Node(int data){
this.value = data;
}
}
public static int getWide(Node head){
if(head == null){
return 0;
}
Queue<Node> queue = new LinkedList<>();
queue.add(head);
HashMap<Node,Integer> levelMap = new HashMap<>();//标注节点所在的层
levelMap.put(head,1);//头节点在第1层
int curLevel = 1;//当前所在层
int curLevelNodes = 0;//当前层节点个数
int max = Integer.MIN_VALUE;//
while(!queue.isEmpty()){
Node cur = queue.poll();
int curNodeLevel = levelMap.get(cur);//获取当前节点所在的层数
if(curNodeLevel == curLevel){
curLevelNodes++;
}else{
max = Math.max(max,curLevelNodes);
curLevel++;
curLevelNodes = 1;
}
if(head.left != null){
levelMap.put(cur.left,curNodeLevel+1);
queue.add(head.left);
}
if(head.right != null){
levelMap.put(cur.right,curNodeLevel+1);
queue.add(head.right);
}
}
return max;
}
//二叉树宽度优先遍历
//借助队列来实现
// 1、首先将头节点插入队列中
// 2、然后出队打印,检查被打印节点的左孩子存在不,存在则进队,不存在则检查右孩子,若存在则进队
// 3、继续循环,直至队列为空
public static void kuanduPrio(Node head){
if(head == null){
return;
}
Queue<Node> queue = new LinkedList<>();
queue.add(head);
while(!queue.isEmpty()){
Node cur = queue.poll();
System.out.println(cur.value + " ");
if(cur.left != null){
queue.add(cur.left);
}
if(cur.right != null){
queue.add(cur.right);
}
}
System.out.println();
}
- 查找节点的祖先节点
给定两个二叉树的节点node1和node2,找到他们的最低公共祖先节点
public static class Node{
public int value;
public Node left;
public Node right;
public Node(int data){
this.value = data;
}
}
//o1和o2一定属于head为头的树
//返回o1和o2的最低公共祖先
public static Node lca(Node head,Node o1,Node o2){
HashMap<Node,Node> fatherMap = new HashMap<>();
fatherMap.put(head,head);
process(head,fatherMap);
HashSet<Node> set1 = new HashSet<>();
Node cur = o1;
while(cur != fatherMap.get(cur)){
set1.add(cur);
cur = fatherMap.get(cur);
}
set1.add(head);
cur = o2;
//不确定对不对
while(cur != fatherMap.get(cur)){
if(set1.contains(cur)){
return cur;
}else{
cur = fatherMap.get(cur);
}
}
return head;
}
public static void process(Node head,HashMap<Node,Node> fatherMap){
if(head == null){
return;
}
fatherMap.put(head.left,head);
fatherMap.put(head.right,head);
process(head.left,fatherMap);
process(head.right,fatherMap);
}
//解法二
//1、o1为o2的祖先节点,或者o2为o1的祖先节点
//2、o1与o2不互为祖先节点
public static Node lowestAncestor(Node head,Node o1,Node o2){
if(head == null || head == o1 || head == o2){
return head;
}
Node left = lowestAncestor(head.left,o1,o2);
Node right = lowestAncestor(head.right,o1,o2);
if(left != null && right != null){
return head;
}
//左右两棵树,并不都有返回值
return left != null ? left : right;
}
- 查找二叉树的后继节点
在二叉树中找到一个节点的后继节点
【题目】现在有一种新的二叉树节点类型如下:
public class Node{
public int value;
public Node left;
public Node right;
public Node parent;
public Node(int val){
value = val;
}
}
该结构比普通二叉树节点结构多了一个指向父节点的parent指针
假设有一棵Node类型的节点组成的二叉树,树中每个节点的parent指针都正确地指向自己的父节点,头节点的parent指向null
只给一个在二叉树中的某个节点node,请实现返回node的后继节点的函数
在二叉树的中序遍历的序列中,node的下一个节点叫做node的后继节点。
public class Node{
public int value;
public Node left;
public Node right;
public Node parent;
public Node(int val){
value = val;
}
}
//求解后继节点
// 1、若节点x有右树,则右树一定为其后继节点
// 2、x无右树,则
public static Node getSuccessorNode(Node node){
if(node == null){
return node;
}
if(node.right != null){
return getLeftMost(node.right);//右树上的最左节点
}else{
Node parent = node.parent;
while(parent != null && parent.left != node){
node = parent;
parent = node.parent;
}
return parent;
}
}
public static Node getLeftMost(Node node){
if(node == null){
return node;
}
while (node.left != null){
node = node.left;
}
return node;
}
- 折纸问题
折纸问题
请把一段纸条竖着放在桌子上,然后从纸条的下边向上方对折1次,压出折痕后展开。
此时折痕是凹下去的,即折痕突起的方向指向纸条的背面
如果从纸条的下边向上方持续对折2次,压出折痕后展开,此时又三条折痕,从上到下依次是下折痕、下折痕和上折痕。
给定一个输入参数N,代表纸条都从下边向上方连续对折N次
请从上到下打印所有折痕的方向
例如:N=1时,打印:down N=2时,打印:down down up
1凹
2凹 2凸
3凹 3凸 3凹 3凸
所以,若想打印所有折痕,则中序遍历这棵二叉树
public static void printAllFolds(int N){
printProcess(1,N,true);//1凹
}
//递归过程,来到了某一个节点
//i是节点的层数,N一共的层数,down == true 凹 down == false 凸
public static void printProcess(int i,int N,boolean down){
if(i > N){
return;
}
printProcess(i+1,N,true);
System.out.println(down ? "凹" : "凸");
printProcess(i+1,N,false);
}
public static void main(String[] args) {
int N = 3;
printAllFolds(N);
}
4.图
4.1图的一种结构定义
- 点
public class Node {
public int value;//点上的值
public int in;//点的入度
public int out;//点的出度
public ArrayList<Node> nexts;
public ArrayList<Edge> edges;
public Node(int value){
this.value = value;
in = 0;
out = 0;
nexts = new ArrayList<>();
edges = new ArrayList<>();
}
}
- 边
public class Edge {
public int weight;
public Node from;
public Node to;
public Edge(int weight,Node from,Node to){
this.weight = weight;
this.from = from;
this.to = to;
}
}
- 图
public class Graph {
public HashMap<Integer,Node> nodes;//点集
public HashSet<Edge> edges;//边集
public Graph(){
nodes = new HashMap<>();
edges = new HashSet<>();
}
}
- 图的生成器
matrix 所有的边
N * 3 的矩阵
[weight,from节点上面的值,to节点上面的值]
public class GraphGenerator {
public static Graph createGraph(Integer[][] matrix){
Graph graph = new Graph();
for(int i = 0;i < matrix.length;i++){
Integer from = matrix[i][0];
Integer to = matrix[i][1];
Integer weight = matrix[i][2];
if(!graph.nodes.containsKey(from)){
graph.nodes.put(from,new Node(from));
}
if(!graph.nodes.containsKey(to)){
graph.nodes.put(from,new Node(to));
}
Node fromNode = graph.nodes.get(from);
Node toNode = graph.nodes.get(to);
Edge newEdge = new Edge(weight,fromNode,toNode);
fromNode.nexts.add(toNode);
fromNode.out++;
toNode.in++;
fromNode.edges.add(newEdge);
graph.edges.add(newEdge);
}
return graph;
}
}
4.2图的一些操作
- 图的宽度优先遍历
图的宽度优先遍历(bfs)
1.利用队列实现
2.从源节点开始依次按宽度进队列,然后弹出
3.每弹出一个点,把该节点所有没有进过队列的邻接点放入队列
4.直到队列变空
//从node出发,进行宽度优先遍历
public static void bfs(Node node){
if(node == null){
return;
}
Queue<Node> queue = new LinkedList<>();
HashSet<Node> set = new HashSet<>();//只要一个点已经进队列了,则这个点在set中一定有注册,用于防止点重复进入队列
queue.add(node);
set.add(node);
while(!queue.isEmpty()){
Node cur = queue.poll();
System.out.println(cur.value);
for(Node next : cur.nexts){
if(!set.contains(next)){
set.add(next);
queue.add(next);
}
}
}
}
- 图的深度优先遍历
深度优先遍历
1.利用栈实现
2.从源节点开始把节点按照深度放入栈中,然后弹出
3.每弹出一个点,把该节点下一个没有进过栈的邻接点放入栈
4.直到栈变空
public static void dfs(Node node){
if(node == null){
return;
}
Stack<Node> stack = new Stack<>();
HashSet<Node> set = new HashSet<>();
stack.add(node);
set.add(node);
System.out.println(node.value);
while(!stack.isEmpty()){
Node cur = stack.pop();
for(Node next: cur.nexts){
if(!set.contains(next)){
stack.push(cur);
stack.push(next);
set.add(next);
System.out.println(next.value);
break;
}
}
}
}

浙公网安备 33010602011771号