数据结构与算法(部分)

位运算

异或

交换(前提,a和b在内存里是两块独立的区域(值可以一样))(不建议使用)

int a=n;
int b=m;
a=a^b;       //a=n^m
b=a^b;       //b=n^m^m=n^0=n
a=a^b;       //a=n^m^n=m^0=m

//在数组中要保证 i!=j 否则会被磨成0
//交换arr的i和j位置上的值
public 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];
}

1)在一个整形数组arr[]中,只有一个数出现了奇数次,其他都出现了偶数次,怎么找到这个奇数次的数

(时间复杂度为O(N) 空间复杂度为O(1) )

int err=0;
for(int i=0;i<arr.length;++i){
    err=arr[i]^err;
}
return err;

/*
[2,1,2,1,3,3,1,1,3]
在异或运算中,可以看成
[1,1,1,1,2,2,3,3,3]
[   0   , 0 ,  3  ]
*/

2)在一个整形数组arr[]中,只有两个数出现了奇数次,其他都出现了偶数次,怎么找到这两个奇数次的数

(时间复杂度为O(N) 空间复杂度为O(1) )

思路:假设err第八位为1

再创建一个err’继续异或数组,此时err’异或第八位不是1(0)的数,

a(b)==err’

int err=0;
for(int i=0;i<arr.length;++i){
    err^=arr[i];
}
//err=a^b
//err!=0
//err必然有一个位置上是1
int rightOne = err&(~err+1);//取出最右边的1
/*
err=1001111
~err=0110000
~err+1=0110001
err&(~err+1)=0000001
*/
int onlyOne=0;//err'
for(int i=0;i<arr.length;++i){
    if(arr[i]&rightOne!=0){
        onlyOne^=arr[i];
    }
}

System.out.print(onlyOne+" "+(err^onlyOne));

排序

选择排序

时间复杂度O(N^2)

额外空间复杂度O(1)

核心思想:找到数组中最小的值,放到第一位,然后以此类推

if(arr==null||arr.length<2){
    return;
}
for(int i=0;i<arr.length;++i){
    int maxIdex=i;
    for(int j=i+1;j<arr.length;++i){//找i~N-1上最小值的下标
        minIndex=arr[j]<arr[minIndex]?j:minIndex;
    }
    swap(arr,i,minIdex);
}
public static void swap(int[] arr,int i,int j){
    int temp=arr[i];
    arr[i]=arr[j];
    arr[j]=temp;
}

冒泡排序

时间复杂度O(N^2)

额外空间复杂度O(1)

核心思想:

[5,8,6,84,3,853,5,54,6,3,31,1]

  • [5]
  • [5,8]
  • [5,8,84]
  • [3,5,8,84]
  • [1,3,3,5,5,6,8,31,54,84,853]
if(arr==null||arr.length<2){
    return;
}
for(int i=arr.length-1;i>0;--i){
    for(int j=0;j<i;++i){//0~i
        if(arr[j]>arr[j+1]){
            swap(arr,i,i+1);
        }
    }
}

插入排序

时间复杂度O(N^2)~O(N)

额外空间复杂度O(1)

核心思想:将一个记录插入到已经排好序的有序表中,从而一个新的、记录数增1的有序表

if(arr==null||arr.length<2){
    return;
}
//0~0有序
//0~i想有序
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,i,i+1);
    }
}

二分查找法

求中值:mid=L+(R-L)/2

//arr[L....R]范围上求最大值
public static int process(int[] arr,int L,int R){
    if(L==R){//arr[L...R]范围上只有一个数,直接返回,base case
        return arr[L];
    }
    int mid=L+((R-L)>>1);//中点
    int leftMax=process(arr,L,mid);
    int rightMax=process(arr,mid,R);
    return Math.max(leftMax,rightMax);
}

master公式的使用

T(N)=a*T(N/b)+O(N^d)

  • log(a,b)>d ==>复杂度为O(N^log(b,a))
  • log(a,b)=d ==>复杂度为O(N^log(b,a))
  • log(a,b)<d ==>复杂度为O(N^d)

归并排序

  1. 整体就是一个简单递归,左边排好序、右边排好序,让整体有序
  2. 让整体有序的过程里用了排外序方法
  3. 利用master公式来求解时间复杂度
  4. 归并排序的实质

时间复杂度 O(N*logN)

额外空间复杂度O(N)

public static void mergeSort(int[] arr){
    if(arr==null||arr.length<2){
        return arr;
    }
    process(arr,0,arr.length-1)
}
public static int process(int[] arr,int L,int R){
    if(L==R){//arr[L...R]范围上只有一个数,直接返回,base case
        return arr[L];
    }
    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 mid,int R){
    int[] help=new int[R-L+1];
    int i=0;
    int p1=L;
    int p2=mid+1;
    while(p1<=mid&&p2<=R){
        help[i++]=arr[p1]<=arr[p2]?arr[p1++]:arr[p2++];
    }
   //两个while只能中一个
   while(p1<=mid){
       help[i++]=arr[p1++];
   }
   while(p2<=R){
       help[i++]=arr[p2++];
   }
   for(i=0;i<help.length;i++){
       arr[L+i]=help[i];
   }
}

小和问题:

​ 在一个数组中,每一个数左边比当前数小的数累加起来,叫做这个数组的小和,求一个数组的小和

例:[1,3,4,2,5] 1左边比1小的数,没有;3左边比3小的数,1;4左边比4小的数,1、3;2左边比2小的数,1;5左边比5小的数,1、3、4、2;所有小和为1+1+3+1+1+3+4+2=16

public static int smallSum(int[] arr){
    if(arr==null||arr.length<2){
        return 0;
    }
    return process(arr,0,arr.length-1);
}
public static int process(int[] arr,int l,int r){
    if(l==r){
        return 0;
    }
    int mid=l+((r-1)>>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 i=0;
    int p1=L;
    int p2=m+1;
    int res=0;
    while(p1<=m&&p2<=r){
        res+=arr[p1]<arr[p2]?(r-p2+1)*arr[p1]:0;//(r-p2+1)右组当前多少个数比arr[p1]大
        help[i++]=arr[p1]<arr[p2]?arr[p1++]:arr[p2++];
    }
    while(p1<m){
        help[i++]=arr[p1++];
    }
    while(p2<m){
        help[i++]=arr[p2++];
    }
    for(i=0;i<help.length;i++){
        arr[L+i]=help[i];
    }
    return res;
}

快速排序

public static void quickSort(int[] arr){
    if(arr==null||arr.length<2){
        return;
    }
    quickSort(arr,0,arr.length-1);
}
//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]的函数
//默认以arr[r]做划分,arr[r]->p   <p  ==p   >p
//返回等于区域(左边界,右边界),所以返回一个长度为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};
}

堆排序

时间复杂度O(N*logN)

  1. 堆结构就是用数组实现的完全二叉树结构
  2. 完全二叉树中如果每课子树的最大值都在顶部就是大根堆
  3. 完全二叉树中如果每课子树的最小值都在顶部就是小根堆
  4. 堆结构的heap Insert与heapify操作
  5. 堆结构的增大和减少
  6. 优先级队列结构,就是堆结构

排序

建立大根堆

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;
    sawp(arr,0,--heapSize);
    while(heapSize>0){//o(N)
        heapify(arr,0,heapSize);//O(logN)
        swap(arr,0,--heapSize);//O(1)
    }
}

插入

//某个数在index位置,能否往下移动
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;
    }
}

取出最大值,并变回大根堆

//某个数在index位置,能否往下移动
public static void heapify(int[] arr,int index,int heapSize){
    int left=index*2+1;//左孩子的下标
    while(left<heapSize){//下方还有孩子的时候
        //判断有无右孩子,并判断两个孩子中,谁的值大,把下标给largest
        int largest=left+1<heapSize && arr[left+1]>arr[left]?arr[left+1]:arr[left];
        //父和较大的孩子之间,谁的值大,把下标给largest
        largest=arr[argest]>arr[index]?largest:index;
        if(largest==index){
            break;
        }
        swap(arr,largest,index);
        index=largest;
        left=index*2+1;
    }
}

在这里插入图片描述

PriorityQueue heap=new PriorityQueue<>(); 底层代码就是小根堆

public void sortedArrDistanceLessk(){
    //默认小根堆
    PriorityQueue<Interger> 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();
    }
}

比较器

  1. 比较器的实质就是重载比较器
  2. 比较器可以很好的应用在特殊标准的排序上
  3. 比较器可以很好的应用在根据特殊标准排序的结构上
//返回负数的时候,第一个参数排在前面
//返回正数的时候,第二个参数排在前面
//返回0的时候,无所谓谁在前面
public int compare(Student o1,Student o2){
    return o1.id-o2.id;
}

桶排序

在这里插入图片描述

public static void radixSort(int[] arr){
    if(arr==null||arr.length<2){
        return;
    }
    radixSort(arr,0,arr.length-1,maxbits(arr));
}
public static int maxbits(int[] arr){
    int max=Interger.MIN_VALUE;
    for(int i=0;i<arr.length;i++){
        max=Math.max(max,arr[i]);
    }
    int res=0;
    while(max!=0){
        res++;
        max/=10;
    }
    return res;
}
//arr[begin..end]排序
public static void radixSort(int[] arr,int L,int R,int digit){
    final int radix=0;
    int i=0,j=0;
    //有多少数准备多少个辅助空间
    int[] bucket=new int[R-L+1];
    for(int d=1;d<digit;d++){//有多少位就进出几次
        /*10个空间
        count[0]当前位(d位)是0的数字有多少个
        count[1]当前位(d位)是(0,1)的数字有多少个
        count[2]当前位(d位)是(0,1,2)的数字有多少个
        count[i]当前位(d位)是(0~i)的数字有多少个
        */
        int[] count=new int[radix];//count[0..9]
        for(i=L;i<=R;i++){
            j=getDigit(arr[i],d);
            count[j]++;
        }
        for(i=1;i<radix;i++){
            count[i]=count[i]+count[i-1];
        }
        for(i=R;i>=L;i--){
            j=getDigit(arr[i],d);
            bucket[count[j]-1]=arr[i];
            count[j]--;
        }
        for(i=L,j=0;i<=R;i++,j++){
            arr[i]=bucket[j];
        }
    }
}

排序算法的稳定性及其汇总

同样值的个体之间,如果不因为排序而改变相对次序,就是这个排序是具有稳定性的;否则没有

稳定对基础类型没什么用,对非基础类型有用

不稳定排序:

  • 选择排序
  • 快速排序
  • 堆排序

稳定排序

  • 冒泡排序
  • 插入排序
  • 归并排序
  • 一切桶排序思想下的排序

目前没有找到时间复杂度O(N*logN),额外空间复杂度O(1),又稳定的排序

时间复杂度空间复杂度稳定性
选择O(N^2)O(1)×
冒泡O(N^2)O(1)
插入O(N^2)O(1)
归并O(N*logN)O(N)
快排(随)O(N*logN)O(logN)×
O(N*logN)O(1)×
  1. 能用快排就用快排

  2. 有空间要求用堆排

  3. 归并具有稳定性

  4. 基于比较的排序能不能做到时间在 O(N*logN)以下?

    目前没有

  5. 能不能做到时间复杂度 O(N*logN),空间在O(N)以下,具有稳定性?

    目前没有

常见的坑:

  1. 归并排序的额外空间复杂度可以变成O(1),但是非常难,不需要掌握,有兴趣可以搜”归并排序 内部缓存法“
  2. ”原地归并排序“的帖子都是垃圾,会让归并排序的时间复杂度变成O(N^2)
  3. 快速排序可以做到稳定性问题,但是会非常难,不需要掌握,可以搜”01 stable sort"
  4. 所有的改进都不重要,因为目前没有找到时间复杂度O(N*logN),额外空间复杂度O(1),又稳定的排序
  5. 有一道题目,是奇数放到数组左边,偶数放到数组右边,还要求原始的相对次序不变,碰到这个问题,可以怼面试官

链表

哈希表

  1. 哈希表在使用层面上可以理解为一种集合结构
  2. 如果只有key,没有伴随数据value,可以使用HashSet结构
  3. 如果即有key,又有伴随数据value,可以使用HashMap结构
  4. 有无伴随数据,是HashSet和HashMap唯一的区别,底层的实际结构是一回事
  5. 使用哈希表,增(put)、删(remove)、改(put)、查(get)的操作,可以认为时间复杂度为O(1),但是常数时间比较大
  6. 放入哈希表的东西,如果是基础类型,内部按值传递,内存占用就是这个东西的大小
  7. 放入哈希表的东西,如果不是基础类型,内部按引用传递,内存占用是这个东西的内存地址的大小

有序表

  1. 有序表在使用层面上可以理解为一种集合结构
  2. 如果只有key,没有伴随数据value,可以使用TreeSet结构
  3. 如果既有key,又有伴随数据value,可以使用TreeMap结构
  4. 有无伴随数据,是TreeSet和TreeMap唯一的区别,底层的实际结构是一回事
  5. 有序表和哈希表的区别是,有序表把key按照顺序组织起来,而哈希表完全不组织
  6. 红黑树、AVL树、size-balance-tree和跳表等都属于有序表结构,只是底层具体实现 不同
  7. 放入哈希表的东西,如果是基础类型,内部按值传递,内存占用就是这个东西的大小
  8. 放入哈希表的东西,如果不是基础类型,必须提供比较器,内部按引用传递,内存占 用是这个东西内存地址的大小
  9. 不管是什么底层具体实现,只要是有序表,都有以下固定的基本功能和固定的时间复 杂度

有序表的固定操作

  • void put(K key, V value):将一个(key,value)记录加入到表中,或者将key的记录 更新成value
  • V get(K key):根据给定的key,查询value并返回
  • void remove(K key):移除key的记录
  • boolean containsKey(K key):询问是否有关于key的记录
  • K firstKey():返回所有键值的排序结果中,最左(最小)的那个
  • K lastKey():返回所有键值的排序结果中,最右(最大)的那个
  • K floorKey(K key):如果表中存入过key,返回key;否则返回所有键值的排序结果中, key的前一个
  • K ceilingKey(K key):如果表中存入过key,返回key;否则返回所有键值的排序结果中, key的后一个
  • 以上所有操作时间复杂度都是O(logN),N为有序表含有的记录数

题目

题目给定两个可能有环也可能无环的单链表,头节点head1和head2。请实 现一个函数,如果两个链表相交,请返回相交的 第一个节点。如果不相交,返 回null

要求如果两个链表长度之和为N,时间复杂度请达到O(N),额外空间复杂度 请达到O(1)。

解决第一个问题,判断有无环:

  • 1、用哈希表

    用哈希表解决——set

    将结点一个一个入到哈希表中,判断有无相同结点,有就是一个环并且是第一个相同的结点就是入环结点

  • 2、快慢指针

    快慢指针

    • 快慢指针都在第一个节点

    • 快指针走两步,慢指针走一步,当他们相遇了就是有环,他们不会在环里转超过两圈

    • 相遇了之后,快指针回到第一个结点,一次走一步,慢指针停在原地,接着一次走一步,他们在相遇一定是环的第一个节点loop

      (设环外m,环内n节点,相遇时慢指针在环内走k步。此时慢走m+k步,快比慢多走m+k(快在绕圈圈等着慢),结论(m+k)%n=0。快回起点走m到环入口,此时慢在环内走了k+m步%n=0,即在环入口相遇)

判断是否相交

  • 判断两个都是无环是否相交:

    • 判断是否再同一个内存里面

    • 假设head1,head2

      head1: len1=m,end1

      head2: len2=n,end2

    • 让长节点走差值,然后短节点在开始走,最后相交的一定是第一个节点

  • 判断一个有环,一个无环是否相交

    不可能相交的

  • 两个都有环

    • 三种情况:在这里插入图片描述

    • loop1==loop2:第二种情况

    • 判断loop1再转一圈之后,是否遇到了loop2,遇到了就说明是第三种情况(返回loop1或者loop2都对),没有遇到就是第一种情况

二叉树

  • 先序(头左右)

    1. 每次从栈中弹出一个节点cur
    2. 打印(处理)cur
    3. 先把cur的右孩子压到栈里面然后再左孩子(如果有)
    4. 循环
  • 后序(左右头)

    • 两个栈——栈,收集栈
    1. 弹,当前节点记作cur
    2. cur放入收集栈中
    3. 先压左,再压右
    4. 周而复始
    5. 打印收集栈
  • 中序(左头右)

    1. 每棵子树,整棵树左边进栈
    2. 依次弹的过程中,打印
    3. 对弹出节点左右树循环

    递归

    public static void inOrderRecur(Node head){
        if(head==null){
            return;
        }
        inOrderRecur(head.left);
        System.out.print(head.value+" ");
        inOrderRecur(head.right);
    }
    

    非递归

    public static void inOrderRecur(Node head){
        System.out.print("in-order");
        if(head!=null){
            Stack<Node> stack=new Stack<>();
            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;
                }
            }
        }
    }
    

如何直观打印一颗二叉树

prinThree(head)

二叉树的宽度优先遍历

返回二叉树最大宽度

public static void w(Node head){
    if(head==null){
        return;
    }
    Queue<Node> queue=new LinkedList<>();
    queue.add(head);
    HashMap<Node,Integer> leveMap=new HashMap<>();//存放每个节点再那一层
    levelMap.put(head,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);//上一层可以结算了,更新max
            curLevel++;
            curLevelNodes=1;
        }
        if(cur.left!=null){
            leveMap.put(cur.left,curNodeLevel+1);
            queue.add(cur.left);
        }
        if(cur.right!=null){
            leveMap.put(cur.right,curNodeLevel+1);
            queue.add(cur,right);
        }
    }
    max=Math.max(max,curLevelNodes);
    return max;	
}

二叉树的相关概念及其实现判断

如何判断一棵树是不是搜索二叉树

搜索二叉树:对于每一个子树来说,左孩子比父节点小,右孩子比父节点大

中序遍历全部都是升序就是搜索二叉树

public statc int perValue=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{
        perValue=head.value;
    }
    return isBST(head.right);
}
public static boolean inOrderRecur(Node head){
    System.out.print("in-order");
    int perValue=Integer.MIN_VALUE;
    if(head!=null){
        Stack<Node> stack=new Stack<>();
        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{
        		perValue=head.value;
    			}
                head=head.right;
            }
        }
    }
    return ture;
}
public static class ReteurnData{
    public boolean isBST;
    public int min;
    public int max;
    public ReteurnData(boolean isB,int min,int max){
        isBST=isB;
        min=min;
        max=max;
    }
} 
public static ReteurnData process(Node x){
    if(x==null){
        return null;
    }
    ReteurnData leftData=process(x.left);
    ReteurnData rightData=process(x.right);
    
    int min=x.value;
    int max=x.value;
    if(leftData!=null){
        min=Math.min(min,leftDdata.min);
        max=Math.max(max,leftDdata.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||rightData.min>=x.value)){
        isBST=false;
    }
    return new ReteurnData(isBST,min,max);
}

怎么判断一颗二叉树是完全二叉树

  • 任一节点,,有右孩子没有左孩子 false
  • 在第一个不违规的情况下,如果遇到了第一个左右孩子不全,后续全是叶节点
public static boolean isCBT(Node head){
    if(head==null){
        return true;
    }
    List<Node> queue=new LinkedList<>();
    //是否遇到左右孩子不全情况
    boolean leaf=false;
    Node l=null;
    Node r=null;
    list.add(head);
    while(queue.isEmpty){
        Node head=queue.pop();
        l=head.left;
        r=head.right;
        if((leaf && (r!=null || l!=null)) || (r!=null && l==null)){
            return false;
        }
        if(r==null && l!=null){
            leaf=true;
        }
        if(l!=null){
            queue.add(l)
        }
        if(r!=null){
            queue.add(r);
        }
    }
    return true;
}

如何判断一棵树是不是满二叉树

​ 最大深度:c

​ 节点数:N

N=2^c-1

public static boolean isF(Node head){
    if(head==null){
        return true;
    }
    Info data=process(head);
    return data.nodes==(1<<(data.height)-1);
}

public static class Info{
    public int height;
    public int nodes;
    public Info(int hei,int nod){
        height=hei;
        nodes=nod;
    }
}
public static Info ptocess(Node x){
    if(x==null){
        return new Info(0,0);
    }
    Info lefeData=process(x.left);
    Info rightData=process(x.right);
    int height=Math.max(leftData.height,rightData.heighgt)+1;
    int nodes=leftData.nodes+rightData.nodes+1;
    return new Indo(height,nodes);
}

如何判断一棵树是不是平衡二叉树(二叉树题目套路)

  • 左子树是平衡二叉树
  • 右子树是平衡二叉树
  • |左高-右高|<=1
public static boolean isBalanced(Node head){
    return process(head).isBalanced;
}
public static class ReturnType{
    public boolean isBalanced;
    public int heignt;
    public ReturnType(boolean isB,int heignt){
        isBalanced=isB;
        heignt=heignt;
    }
}
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.height&&rightData.height&&Math.abs(leftData.height-rightData.height)<2;
    return new ReturnType(isBalanced,height);
}

给定两个二叉树的节点node1和node2,找到他们的最低公共祖先节点

//o1,o2一定属于head为头的二叉树
//返回o1,o2的最低公共祖先
public static Node lca(Node head,Node o1,Node 02){
    HashMap<Node,Node> fatherMap=new HashMap<>();
    fatherMap.put(head,head);
    process(head,fatherMap);
    HashSet<Node> set01=new HashSet<>();
    set01.add(o1);
    Node cur=o1;
    while(cur!=fatherMap.get(cur)){
        set01.add(cur);
        cur=fatherMap.get(cur);
    }
    set.add(head);
    cur=o2;
    while(cur!=fatherMap.get(cur)){
        cur=fatherMap.get(cur);
        if( set01.contains(cur)){
            return cur;
        }
    }
}

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);
    
}

在K二MP叉算树法中扩找展到题一目个节二点的后继节点

【题目】 现在有一种新的二叉树节点类型如下:

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的后继节点。

  • x有右树 右树上面的最左节点
  • x无右树 一路往上找,那个节点是它父节点的左孩子,那这个父节点就是x的后继节点
public static Node getSuccessorNode(Node node){
    if(node==null){
        return node;
    }
    if(node.right!=null){
        return getLeftMost(node.left);
    }else{//无右子树
        Node parent=node.parent;
        while(parent!=null&&parent.left!=node){//当前节点是其父亲节点右孩子
            node=parent;
            parent=node.parent;
        }
        return parent;
    }
}
public static NodegetLeftMost(Node node){
    if(node==null){
        return node;
    }
    while(node.left!=null){
        node=node.left;
    }
    return node;
} 

二叉树的序列化和反序列化

就是内存里的一棵树如何变成字符串形式,又如何从字符串形式变成内存里的树 如何判断一颗二叉树是不是另一棵二叉树的子树?


	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.offer(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;
	}

折纸问题

请把一段纸条竖着放在桌子上,然后从纸条的下边向上方对折1次,压出折痕后 展开。

此时折痕是凹下去的,即折痕突起的方向指向纸条的背面。

如果从纸条的下边向上方连续对折2次,压出折痕后展开,此时有三条折痕,从 上到下依次是下折痕、下折痕和上折痕。

给定一个输入参数N,代表纸条都从下边向上方连续对折N次。

请从上到下打印所有折痕的方向。 例如:N=1时,打印: down N=2时,打印: down down up

public static void printAllFolds(int N) {
		printProcess(1, N, true);
	}

public static void printProcess(int i, int N, boolean down) {
    if (i > N) {
        return;
    }
    printProcess(i + 1, N, true);
    System.out.println(down ? "down " : "up ");
    printProcess(i + 1, N, false);
}

public static void main(String[] args) {
    int N = 1;
    printAllFolds(N);
}

Node

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 GraphGenerator {

	public static Graph createGraph(Integer[][] matrix) {
		Graph graph = new Graph();
		for (int i = 0; i < matrix.length; i++) {
			Integer weight = matrix[i][0];
			Integer from = matrix[i][1];
			Integer to = matrix[i][2];
			if (!graph.nodes.containsKey(from)) {
				graph.nodes.put(from, new Node(from));
			}
			if (!graph.nodes.containsKey(to)) {
				graph.nodes.put(to, 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;
	}

}

宽度优先遍历

  1. 利用队列实现
  2. 从源结点开始依次按照宽度进队列,然后弹出
  3. 每弹出一个点,把该结点下一个没有进过栈的邻接点放入队列
  4. 直到队列变空
package class06;

import java.util.HashSet;
import java.util.LinkedList;
import java.util.Queue;

public class Code01_BFS {

	public static void bfs(Node node) {
		if (node == null) {
			return;
		}
		Queue<Node> queue = new LinkedList<>();
		HashSet<Node> map = new HashSet<>();
		queue.add(node);
		map.add(node);
		while (!queue.isEmpty()) {
			Node cur = queue.poll();
			System.out.println(cur.value);
			for (Node next : cur.nexts) {
				if (!map.contains(next)) {
					map.add(next);
					queue.add(next);
				}
			}
		}
	}
}

广度优先遍历

  1. 利用栈实现
  2. 从源结点开始把节点按照深度放入栈,然后弹出
  3. 每弹出一个点,把该节点下一个没有进栈的邻接点放入栈
  4. 直到栈变空
package class06;

import java.util.HashSet;
import java.util.Stack;

public class Code02_DFS {

	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;
				}
			}
		}
	}

}

拓扑排序算法

适用范围:要求有向图,且有入度为0的节点,且没有环

package class06;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

public class Code03_TopologySort {

	// directed graph and no loop
	public static List<Node> sortedTopology(Graph graph) {
        //key:某一个node
        //value:剩下的入度
		HashMap<Node, Integer> inMap = new HashMap<>();
        //入度为0的点,才能进这个队列
		Queue<Node> zeroInQueue = new LinkedList<>();
		for (Node node : graph.nodes.values()) {
			inMap.put(node, node.in);
			if (node.in == 0) {
				zeroInQueue.add(node);
			}
		}
        //拓扑排序结果,依次加入result
		List<Node> result = new ArrayList<>();
		while (!zeroInQueue.isEmpty()) {
			Node cur = zeroInQueue.poll();
			result.add(cur);
			for (Node next : cur.nexts) {
				inMap.put(next, inMap.get(next) - 1);
				if (inMap.get(next) == 0) {
					zeroInQueue.add(next);
				}
			}
		}
		return result;
	}
}

kruskal算法

适用范围:要求无向图

package class06;

import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.PriorityQueue;
import java.util.Set;

//undirected graph only
public class Code04_Kruskal {

	// Union-Find Set
	public static class UnionFind {
		private HashMap<Node, Node> fatherMap;
		private HashMap<Node, Integer> rankMap;

		public UnionFind() {
			fatherMap = new HashMap<Node, Node>();
			rankMap = new HashMap<Node, Integer>();
		}

		private Node findFather(Node n) {
			Node father = fatherMap.get(n);
			if (father != n) {
				father = findFather(father);
			}
			fatherMap.put(n, father);
			return father;
		}

		public void makeSets(Collection<Node> nodes) {
			fatherMap.clear();
			rankMap.clear();
			for (Node node : nodes) {
				fatherMap.put(node, node);
				rankMap.put(node, 1);
			}
		}

		public boolean isSameSet(Node a, Node b) {
			return findFather(a) == findFather(b);
		}

		public void union(Node a, Node b) {
			if (a == null || b == null) {
				return;
			}
			Node aFather = findFather(a);
			Node bFather = findFather(b);
			if (aFather != bFather) {
				int aFrank = rankMap.get(aFather);
				int bFrank = rankMap.get(bFather);
				if (aFrank <= bFrank) {
					fatherMap.put(aFather, bFather);
					rankMap.put(bFather, aFrank + bFrank);
				} else {
					fatherMap.put(bFather, aFather);
					rankMap.put(aFather, aFrank + bFrank);
				}
			}
		}
	}

	public static class EdgeComparator implements Comparator<Edge> {

		@Override
		public int compare(Edge o1, Edge o2) {
			return o1.weight - o2.weight;
		}

	}

	public static Set<Edge> kruskalMST(Graph graph) {
		UnionFind unionFind = new UnionFind();
		unionFind.makeSets(graph.nodes.values());
		PriorityQueue<Edge> priorityQueue = new PriorityQueue<>(new EdgeComparator());
		for (Edge edge : graph.edges) {//M条边
			priorityQueue.add(edge);//O(logM)
		}
		Set<Edge> result = new HashSet<>();
		while (!priorityQueue.isEmpty()) {//M条边
			Edge edge = priorityQueue.poll();//O(logM)
			if (!unionFind.isSameSet(edge.from, edge.to)) {//O(1)
				result.add(edge);
				unionFind.union(edge.from, edge.to);
			}
		}
		return result;
	}
}

prim算法

适用范围:要求无向图

package class06;

import java.util.Comparator;
import java.util.HashSet;
import java.util.PriorityQueue;
import java.util.Set;

// undirected graph only
public class Code05_Prim {

	public static class EdgeComparator implements Comparator<Edge> {

		@Override
		public int compare(Edge o1, Edge o2) {
			return o1.weight - o2.weight;
		}

	}

	public static Set<Edge> primMST(Graph graph) {
        //解锁的边进入小根堆
		PriorityQueue<Edge> priorityQueue = new PriorityQueue<>(new EdgeComparator());
		HashSet<Node> set = new HashSet<>();
		Set<Edge> result = new HashSet<>();//依次挑选的边在result里
		for (Node node : graph.nodes.values()) {//随便挑选了一个点
            //node是开始点
			if (!set.contains(node)) {
				set.add(node);
				for (Edge edge : node.edges) {//由一个点,解锁所有相连的边
					priorityQueue.add(edge);
				}
				while (!priorityQueue.isEmpty()) {
					Edge edge = priorityQueue.poll();//弹出解锁的边中,最小的边
					Node toNode = edge.to;//可能的一个新的点
					if (!set.contains(toNode)) {//不含有的时候,
						set.add(toNode);
						result.add(edge);
						for (Edge nextEdge : toNode.edges) {
							priorityQueue.add(nextEdge);
						}
					}
				}
			}
		}
		return result;
	}

	// 请保证graph是连通图
	// graph[i][j]表示点i到点j的距离,如果是系统最大值代表无路
	// 返回值是最小连通图的路径之和
	public static int prim(int[][] graph) {
		int size = graph.length;
		int[] distances = new int[size];
		boolean[] visit = new boolean[size];
		visit[0] = true;
		for (int i = 0; i < size; i++) {
			distances[i] = graph[0][i];
		}
		int sum = 0;
		for (int i = 1; i < size; i++) {
			int minPath = Integer.MAX_VALUE;
			int minIndex = -1;
			for (int j = 0; j < size; j++) {
				if (!visit[j] && distances[j] < minPath) {
					minPath = distances[j];
					minIndex = j;
				}
			}
			if (minIndex == -1) {
				return sum;
			}
			visit[minIndex] = true;
			sum += minPath;
			for (int j = 0; j < size; j++) {
				if (!visit[j] && distances[j] > graph[minIndex][j]) {
					distances[j] = graph[minIndex][j];
				}
			}
		}
		return sum;
	}

	public static void main(String[] args) {
		System.out.println("hello world!");
	}

}

Dijkstra算法

适用范围:没有权值为负数的边

package class06;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map.Entry;

// no negative weight
public class Code06_Dijkstra {

	public static HashMap<Node, Integer> dijkstra1(Node head) {
        //从head出发到所有结点的最小距离
        //key:从head出发到达key
        //value:从head出发到key的最小距离
        //如果在表中,没有T的记录,含义是从head出发到T这个点的距离为正无穷
		HashMap<Node, Integer> distanceMap = new HashMap<>();
		distanceMap.put(head, 0);
        //已经求过距离的节点,存在selectedNodes中,以后再也不碰
		HashSet<Node> selectedNodes = new HashSet<>();

		Node minNode = getMinDistanceAndUnselectedNode(distanceMap, selectedNodes);
		while (minNode != null) {
			int distance = distanceMap.get(minNode);
			for (Edge edge : minNode.edges) {
				Node toNode = edge.to;
				if (!distanceMap.containsKey(toNode)) {
					distanceMap.put(toNode, distance + edge.weight);
				}
				distanceMap.put(edge.to, Math.min(distanceMap.get(toNode), distance + edge.weight));
			}
			selectedNodes.add(minNode);
			minNode = getMinDistanceAndUnselectedNode(distanceMap, selectedNodes);
		}
		return distanceMap;
	}

	public static Node getMinDistanceAndUnselectedNode(HashMap<Node, Integer> distanceMap, 
			HashSet<Node> touchedNodes) {
		Node minNode = null;
		int minDistance = Integer.MAX_VALUE;
		for (Entry<Node, Integer> entry : distanceMap.entrySet()) {
			Node node = entry.getKey();
			int distance = entry.getValue();
			if (!touchedNodes.contains(node) && distance < minDistance) {
				minNode = node;
				minDistance = distance;
			}
		}
		return minNode;
	}

	public static class NodeRecord {
		public Node node;
		public int distance;

		public NodeRecord(Node node, int distance) {
			this.node = node;
			this.distance = distance;
		}
	}

	public static class NodeHeap {
		private Node[] nodes;
		private HashMap<Node, Integer> heapIndexMap;
		private HashMap<Node, Integer> distanceMap;
		private int size;

		public NodeHeap(int size) {
			nodes = new Node[size];
			heapIndexMap = new HashMap<>();
			distanceMap = new HashMap<>();
			this.size = 0;
		}

		public boolean isEmpty() {
			return size == 0;
		}

		public void addOrUpdateOrIgnore(Node node, int distance) {
			if (inHeap(node)) {
				distanceMap.put(node, Math.min(distanceMap.get(node), distance));
				insertHeapify(node, heapIndexMap.get(node));
			}
			if (!isEntered(node)) {
				nodes[size] = node;
				heapIndexMap.put(node, size);
				distanceMap.put(node, distance);
				insertHeapify(node, size++);
			}
		}

		public NodeRecord pop() {
			NodeRecord nodeRecord = new NodeRecord(nodes[0], distanceMap.get(nodes[0]));
			swap(0, size - 1);
			heapIndexMap.put(nodes[size - 1], -1);
			distanceMap.remove(nodes[size - 1]);
			nodes[size - 1] = null;
			heapify(0, --size);
			return nodeRecord;
		}

		private void insertHeapify(Node node, int index) {
			while (distanceMap.get(nodes[index]) < distanceMap.get(nodes[(index - 1) / 2])) {
				swap(index, (index - 1) / 2);
				index = (index - 1) / 2;
			}
		}

		private void heapify(int index, int size) {
			int left = index * 2 + 1;
			while (left < size) {
				int smallest = left + 1 < size && distanceMap.get(nodes[left + 1]) < distanceMap.get(nodes[left])
						? left + 1 : left;
				smallest = distanceMap.get(nodes[smallest]) < distanceMap.get(nodes[index]) ? smallest : index;
				if (smallest == index) {
					break;
				}
				swap(smallest, index);
				index = smallest;
				left = index * 2 + 1;
			}
		}

		private boolean isEntered(Node node) {
			return heapIndexMap.containsKey(node);
		}

		private boolean inHeap(Node node) {
			return isEntered(node) && heapIndexMap.get(node) != -1;
		}

		private void swap(int index1, int index2) {
			heapIndexMap.put(nodes[index1], index2);
			heapIndexMap.put(nodes[index2], index1);
			Node tmp = nodes[index1];
			nodes[index1] = nodes[index2];
			nodes[index2] = tmp;
		}
	}

	public static HashMap<Node, Integer> dijkstra2(Node head, int size) {
		NodeHeap nodeHeap = new NodeHeap(size);
		nodeHeap.addOrUpdateOrIgnore(head, 0);
		HashMap<Node, Integer> result = new HashMap<>();
		while (!nodeHeap.isEmpty()) {
			NodeRecord record = nodeHeap.pop();
			Node cur = record.node;
			int distance = record.distance;
			for (Edge edge : cur.edges) {
				nodeHeap.addOrUpdateOrIgnore(edge.to, edge.weight + distance);
			}
			result.put(cur, distance);
		}
		return result;
	}

}

y(Node node, int index) {
while (distanceMap.get(nodes[index]) < distanceMap.get(nodes[(index - 1) / 2])) {
swap(index, (index - 1) / 2);
index = (index - 1) / 2;
}
}

	private void heapify(int index, int size) {
		int left = index * 2 + 1;
		while (left < size) {
			int smallest = left + 1 < size && distanceMap.get(nodes[left + 1]) < distanceMap.get(nodes[left])
					? left + 1 : left;
			smallest = distanceMap.get(nodes[smallest]) < distanceMap.get(nodes[index]) ? smallest : index;
			if (smallest == index) {
				break;
			}
			swap(smallest, index);
			index = smallest;
			left = index * 2 + 1;
		}
	}

	private boolean isEntered(Node node) {
		return heapIndexMap.containsKey(node);
	}

	private boolean inHeap(Node node) {
		return isEntered(node) && heapIndexMap.get(node) != -1;
	}

	private void swap(int index1, int index2) {
		heapIndexMap.put(nodes[index1], index2);
		heapIndexMap.put(nodes[index2], index1);
		Node tmp = nodes[index1];
		nodes[index1] = nodes[index2];
		nodes[index2] = tmp;
	}
}

public static HashMap<Node, Integer> dijkstra2(Node head, int size) {
	NodeHeap nodeHeap = new NodeHeap(size);
	nodeHeap.addOrUpdateOrIgnore(head, 0);
	HashMap<Node, Integer> result = new HashMap<>();
	while (!nodeHeap.isEmpty()) {
		NodeRecord record = nodeHeap.pop();
		Node cur = record.node;
		int distance = record.distance;
		for (Edge edge : cur.edges) {
			nodeHeap.addOrUpdateOrIgnore(edge.to, edge.weight + distance);
		}
		result.put(cur, distance);
	}
	return result;
}

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值