自用

算法基础阶段

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(N
logN),额外空间复杂度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;
                }
            }
        }
    }
posted @ 2022-09-25 10:46  Alex-jzw  阅读(94)  评论(0)    收藏  举报