leetcode刷题笔记


请添加图片描述

ArrayList和LinkedList
  1. ArrayList和LinkedList,一个是动态数组的数据结构,一个是链表的数据结构,它们都是对List接口的实现。前者是数组队列;后者是双向链表结构,可以作为堆栈、队列、双端队列
  2. 随机访问操作时,ArrayList O(1)比LinkedList O(n)的效率更高,LinkedList时线性的数据结构
  3. 在进行增加和删除操作时,LinkedList比ArrayList效率高,ArrayList是数组,有可能会对操作点之后所有数据的下标的索引造成影响,需要进行数据的移动
  4. 从利用效率上看,ArrayList自由性较低,在list列表的结尾预留一定容量空间,LinkedList的每一个元素需要消耗空间
  5. LinkedList不支持高效的随机元素访问
    当操作在一列数据的后面添加数据,随机的访问其中的元素是,使用ArrayList更好;
    在一列数据的前面或中间添加或删除数据,按照顺序访问其中元素时,应该使用LinkedList
    深入理解List的toArray()方法和toArray(T[] a)方法
    这两个方法都是将列表List中的元素转导出为数组,不同的是,toArray()方法导出的是Object类型数组,而toArray[T[] a]方法导出的是指定类型的数组。
StringBuilder和StringBuffer

String的值是不可变的,导致每次对String的操作都会生成新的String对象,效率低下,而且浪费有限的内存空间。

StringBuilder和StringBuffer类的对象能够被多次的修改,并且不产生新的未使用对象

StringBuilder类在Java5中被提出,和StringBuffer之间的最大不同在于StringBuilder的方法不是线程安全的(不能同步访问)

StringBuilder比StringBuffer有速度优势,所以多数情况下使用StringBuilder类。在应用程序要求线程安全的情况下,必须使用StringBuffer类

String可以空赋值,其他两种不行

操作少量数据可以使用String

常见的数据结构

数组,栈,链表,队列,树,图,堆,散列表等

数组:在内存中连续存储多个元素的结构,在内存中的分配也是连续的,数组中的元素通过数组的下标进行访问。数组的查询比较方便,但是添加和删除操作比较慢,需要移动其他的元素,固定大小

栈:先进先出,入栈,出栈,栈通常用于递归功能的实现比如斐波那契数列

队列:先进先出,在多线程阻塞队列管理中比较适用

链表:物理存储单元上非连续的、非顺序的存储结果,逻辑顺序通过链表的指针地址实现,包含数据域和指针域。链表可以任意加减元素,添加和删除操作比较快。但是因为包含大量的指针域,占用的空间比较大。查找元素耗时,适合数据量小,频繁增加,删除操作

二叉树:添加,删除元素都很快,在查找方面也有很多的算法优化,二叉树既有链表的好处,也有数组的好处。处理大批量的动态数据。二叉树有很多扩展的数据结构,包括平衡二叉树,红黑树,B+树等。Mysql的数据库索引结构使用的是B+树,HashMap的底层源码中使用红黑树

散列表:哈希表,key value进行访问的数据结构,通过key和value来映射到集合中的一个位置,很快就可以找到集合中对应的元素。Java中有些集合类就是借鉴的哈希原理进行构造。比如HashMap和HashTable等,hash表对于集合的查找元素是非常方便的,因为哈希表是基于数组的,在添加和删除元素比较慢。进一步就是数组结合链表,hashMap使用数组+链表+红黑树

堆:完全二叉树,大根堆和小根堆,一般用来做数组中的排序,称为堆排序

图:节点的又穷集合和边的结合,无向图和有向图。在存储数据上有比较复杂和高效的算法,分别有邻接举证、邻接表、十字链表、邻接多重表、边集数组等存储结构

排序
快排、
private static void QuickSort1(int[] nums, int left, int right) {
        int f = nums[left];
        int i = left, j = right;
        if (i >= j)
            return;
        while (i < j) {
            while (f <= nums[j] && i < j) {
                j--;
            }
            nums[i] = nums[j];
            while (f >= nums[i] && i < j) {
                i++;
            }
            nums[j] = nums[i];
        }
        nums[i] = f;
        QuickSort1(nums, left, i - 1);
        QuickSort1(nums, i + 1, right);
    }

时间复杂度:在拆分时,极端情况:基准数左边的元素个数都为0,而右边n-1个。这个时候,就需要拆分n次,每次拆分整理的时间复杂度为n,最坏的时间复杂度为n2。C(n)=(n-1)+(n-2)+…+1=n(n-1)/2。最坏情况下,快速排序的时间复杂度为n2

最好的情况下就是每次拆分都能够从书序的中间拆分,拆分logn次,时间复杂度为nlogn

在随机取基准数的时候,数据是可能会发生变化的,快速排序不是稳定的

数组中的第K个最大元素
// 给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。
//
// 请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
//
//
//
// 示例 1:
//
//
// 输入: [3,2,1,5,6,4] 和 k = 2
// 2 1 3 5 6 4
// 输出: 5
//
//
// 示例 2:
//
//
// 输入: [3,2,3,1,2,4,5,5,6] 和 k = 4
// 输出: 4
//
//
//
// 提示:
//
//
// 1 <= k <= nums.length <= 10⁴
// -10⁴ <= nums[i] <= 10⁴
//
// Related Topics 数组 分治 快速选择 排序 堆(优先队列) 👍 1460 👎 0

package leetcode.editor.cn;

// Java:数组中的第K个最大元素
public class KthLargestElementInAnArray {
    public static void main(String[] args) {
        Solution solution = new KthLargestElementInAnArray().new Solution();
        // TO TEST
        System.out.println(solution.findKthLargest(new int[] {3, 2, 3, 1, 2, 4, 5, 5, 6}, 4));
    }

    // leetcode submit region begin(Prohibit modification and deletion)
    class Solution {
        // 快速排序
        public int findKthLargest(int[] nums, int k) {
            int left = 0, right = nums.length - 1;
            while (left <= right) {
                int f = sort(nums, left, right);
                if (f == k - 1) {
                    return nums[f];
                }
                if (f > k - 1) {
                    right = f - 1;
                } else {
                    left = f + 1;
                }
            }

            return -1;
        }

        public int sort(int[] nums, int left, int right) {
            int key = nums[right];
            int i = left, j = right;
            while (i < j) {

                while (i < j && key <= nums[i]) {
                    i++;
                }
                nums[j] = nums[i];
                while (i < j && key >=  nums[j]) {
                    j--;
                }
                nums[i] = nums[j];
            }
            nums[i] = key;
            return i;
        }
    }
    // leetcode submit region end(Prohibit modification and deletion)

}
前 K 个高频元素
// 给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。
//
//
//
// 示例 1:
//
//
// 输入: nums = [1,1,1,2,2,3], k = 2
// 输出: [1,2]
//
//
// 示例 2:
//
//
// 输入: nums = [1], k = 1
// 输出: [1]
//
//
//
// 提示:
//
//
// 1 <= nums.length <= 10⁵
// k 的取值范围是 [1, 数组中不相同的元素的个数]
// 题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的
//
//
//
//
// 进阶:你所设计算法的时间复杂度 必须 优于 O(n log n) ,其中 n 是数组大小。
// Related Topics 数组 哈希表 分治 桶排序 计数 快速选择 排序 堆(优先队列) 👍 990 👎 0

package leetcode.editor.cn;

import java.util.*;

// Java:前 K 个高频元素
public class TopKFrequentElements {
    public static void main(String[] args) {
        Solution solution = new TopKFrequentElements().new Solution();
        // TO TEST
        System.out.println(Arrays.toString(solution.topKFrequent(new int[] {-1, 1, 4, -4, 3, 5, 4, -2, 3, -1}, 3)));
    }

    // leetcode submit region begin(Prohibit modification and deletion)
    class Solution {
        /**
         * 使用HashMap存储元素和出现次数,重写排序比较方法
         */
        public int[] topKFrequent1(int[] nums, int k) {
            HashMap<Integer, Integer> map = new HashMap<>();
            for (int num : nums) {
                map.put(num, map.getOrDefault(num, 0) + 1);
            }
            ArrayList<Map.Entry<Integer, Integer>> entries = new ArrayList<>(map.entrySet());
            entries.sort(new Comparator<Map.Entry<Integer, Integer>>() {
                @Override
                public int compare(Map.Entry<Integer, Integer> o1, Map.Entry<Integer, Integer> o2) {
                    return o2.getValue().compareTo(o1.getValue());
                }
            });
            int[] res = new int[k];
            Map.Entry<Integer, Integer> entry;
            for (int i = 0; i < k; i++) {
                res[i] = entries.get(i).getKey();
            }
            return res;

        }
        /*
         * 堆
         * 1.如果堆的元素个数小于k,直接插入堆中
         * 2.如果堆的个数等于k,检查堆顶与当前出现次数大小,堆顶更大就舍弃当前值,堆顶更小就替换
         */

        /*
         * 使用hashmap存储元素出现的次数,使用快速排序求前k个最大的,时间复杂度为n
         */
        public int[] topKFrequent(int[] nums, int k) {
            HashMap<Integer, Integer> map = new HashMap<>();
            for (int num : nums) {
                map.put(num, map.getOrDefault(num, 0) + 1);
            }
            ArrayList<Map.Entry<Integer, Integer>> entries = new ArrayList<>(map.entrySet());
            List<int[]> list = new LinkedList<int[]>();
            for (Map.Entry<Integer, Integer> entry : entries) {
                list.add(new int[] {entry.getKey(), entry.getValue()});
            }
            // 快速排序,只需要前k个
            int left = 0, right = list.size() - 1;
            while (left <= right) {
                int tmp = QSort(list, left, right);
                if (tmp == k - 1) {
                    break;
                } else if (tmp > k - 1) {
                    right = tmp - 1;
                } else {
                    left = tmp + 1;
                }
            }
            int[] res = new int[k];
            for (int i = 0; i < k; i++) {
                res[i] = list.get(i)[0];
            }
            return res;

        }

        private int QSort(List<int[]> list, int left, int right) {
            int i = left, j = right;
            int key = right;
            // 防止覆盖
            int[] num = {list.get(key)[0], list.get(key)[1]};
            int k = list.get(key)[1];
            while (i < j) {
                while (i < j && k <= list.get(i)[1]) {
                    i++;
                }
                list.set(j, new int[] {list.get(i)[0], list.get(i)[1]});
                while (i < j && k >= list.get(j)[1]) {
                    j--;
                }
                list.set(i, new int[] {list.get(j)[0], list.get(j)[1]});

            }
            list.set(i, num);
            return i;
        }

    }
    // leetcode submit region end(Prohibit modification and deletion)

}
冒泡排序
/**
     * 冒泡排序
     * 1.从数组头开始,比较相邻的元素,如果第一个元素比第二个大,就交换他们
     * 2.对每一对相邻元素做相同的工作
     * 3.重复
     */
    public static void BubbleSort(int[] nums){
        if(nums.length == 0) return;
        for (int i = 0; i < nums.length-1; i++) {
            for (int j = 0; j < nums.length-i-1; j++) {
                if(nums[j]>nums[j+1]){
                    int tmp = nums[j];
                    nums[j] = nums[j+1];
                    nums[j+1] = tmp;
                }

            }
        }
    }

时间复杂度为n^2

该算法在比较两个相同的数时没必要进行交换,是稳定的算法

选择排序
/**
     * 选择排序
     * 1.找到数组中最大或者最小的元素
     * 2.将它和数组的第一个元素交换位置,如果第一个元素就是最小的元素,它就和自己交换
     * 3.在剩下的元素中找到最大或者最小的元素,将它和数组的第二个元素交换位置,往复
     */
    public static void ChooseSort(int[] nums){
        if(nums.length == 0) return;
        for (int i = 0; i < nums.length; i++) {
            int min = i;
            for (int j = i+1; j < nums.length; j++) {
                //找到后面最小的元素
                if(nums[j]<nums[min]){
                    min = j;
                }
            }
            //交换
            int tmp = nums[min];
            nums[min] = nums[i];
            nums[i] = tmp;
        }
    }

时间复杂度 n^2

如果选择的元素比当前的元素小,剩下的元素中有和指定的元素相同的元素,破坏了稳定性。数组5,8,5,2,9,我们知道第一遍选择第1个元素5会和2交换,那么原序列中两个5的相对前后顺序就被破坏了

插入排序
/**
     * 插入排序
     * 1.对于未排序的元素,在已排序序列中从左往右扫描,找到相应位置并插入
     * 2.为了给要插入的元素腾出空间,需要将插入位置之后的已排序元素都向后移动一位
     * @param
     */
    public static void InsertSort(int[] nums){
        if(nums.length == 0) return;
        //待排序的数据,该数据之前的已被排序
        int current;
        for (int i = 0; i < nums.length-1; i++) {
            //已被排序数据的做引
            int index = i;
            current = nums[index+1];
            //将当前元素后移一位
            while(index >= 0 && current < nums[index]){
                nums[index+1] = nums[index];
                index--;
            }
            //插入元素
            nums[index + 1] = current;

        }
    }

时间复杂度:最坏的情况下为n^2 最好的情况下为n

比较的时候发现,两个数相等的话,不会进行移动,前后两个数都是相等的,该算法是稳定的算法

希尔排序
/**
 * 希尔排序
 * 基于插入排序的快速的排序算法
 * 简单插入排序对于大规模乱序数组很慢
 * 希尔排序为了加速简单的改进了插入排序,也称为缩小增量排序
 * 希尔排序时吧待排序数组按一定的数量分组,对每组使用直接插入排序算法排序,然后缩小数量继续分组排序,随着数量的减少,每组包含的元素越来越多,当数量减至1是,整个数组恰被分为一组,排序便完成了,这个不断缩小的数量构成了一个增量序列
 * 4,5,3,7,6,3,2,1
 * 4 3 2 1 6 5 3 7
 * 2 1 3 3 4 5 6 7
 * 1 2 3 3 4 5 6 7
 * @param
 */
public static void ShellSort(int[] nums){
    int len = nums.length;
    if(len<2){
        return;
    }
    //当前待排序数据,该数据之前的已被排序
    int current;
    //增量
    int gap = len / 2;
    while(gap > 0){
        for (int i = gap; i < len; i++) {
            current = nums[i];
            //前面有序序列的索引
            int index = i - gap;
            while(index >= 0 && current < nums[index]){
                nums[index + gap] = nums[index];
                //有序序列的下一个
                index -= gap;
            }
            //插入
            nums[index + gap] = current;
        }
        //index相除取整
        gap = gap / 2;
    }
}

时间复杂度:和增量序列有关{n/2,(n/2)/2,…,1}

稳定性:插入排序时稳定的,但是在不同的插入排序中,相同元素可能在各自的插入排序中移动,5,2,2,1,第一次排序第一个元素5会和第三个元素2交换,第二个元素2会和第四个元素1交换,原序列中两个2的相对前后顺序就被破坏

不稳定

最小的k个数

堆 快速排序

//输入整数数组 arr ,找出其中最小的 k 个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。 
//
// 
//
// 示例 1: 
//
// 输入:arr = [3,2,1], k = 2
//输出:[1,2] 或者 [2,1]
// 
//
// 示例 2: 
//
// 输入:arr = [0,1,2,1], k = 1
//输出:[0] 
//
// 
//
// 限制: 
//
// 
// 0 <= k <= arr.length <= 10000 
// 0 <= arr[i] <= 10000 
// 
// Related Topics 堆 分治算法 
// 👍 242 👎 0

package leetcode.editor.cn;

import java.util.Arrays;
import java.util.PriorityQueue;
import java.util.Queue;

//Java:最小的k个数
public class ZuiXiaoDeKgeShuLcof{
    public static void main(String[] args) {
        Solution solution = new ZuiXiaoDeKgeShuLcof().new Solution();
        // TO TEST
        int[] arr={0,0,2,3,2,1,1,2,0,4};
//        Sort(arr,0,arr.length-1);
        arr=solution.getLeastNumbers1(arr,5);
//        solution.Sort(arr,0,arr.length-1);
        for (int i = 0; i < arr.length; i++) {
            System.out.print(arr[i]+" ");
        }
    }

    //leetcode submit region begin(Prohibit modification and deletion)

    class Solution {
        /**
         * 排序后进行输出,快排
         * @param arr
         * @param k
         * @return
         */
        public int[] getLeastNumbers1(int[] arr, int k) {

            return QuikSearch(arr,k,0,arr.length-1);
        }
        public int[] QuikSearch(int[] arr, int k,int left,int right){
            //每快排一次,找到排序后下标为i的元素
            //k<j下次只需要切分left~j-1的元素
            //k>j 遍历数组j+1~right的元素
            //相等,返回数组
            int j=QuikSort(arr,left,right);
            if(k==j){
                return Arrays.copyOf(arr,k);
            }
            return k>j?QuikSearch(arr,k,j+1,right):QuikSearch(arr,k,left,j-1);
        }

        /**
         * 使用最后一个数作为基准值
         * @param arr
         * @param left
         * @param right
         */
        public int QuikSort(int[] arr,int left, int right){
            if(left>=right){
                return left;
            }
            int i=left,j=right;
            //元素基准值
            int key=arr[right];
            while(i<j){
                while(arr[j]>=key&&i<j){
                    j--;
                }
                //基准位置放j位置的值
                if(i<j) arr[i++]=arr[j];
                while(arr[i]<key&&i<j){
                    i++;
                }
                //j位置放置i的值
                if(i<j) arr[j--]=arr[i];


            }
            //i位置放置基准值
            arr[i]=key;
            return i;
        }
        /**
         * 使用快排会超时
         * @param arr
         * @param left
         * @param right
         */
        public void Sort(int[] arr, int left, int right) {
            int i=left,j=right;
            //当左边大于右边元素时
            if(i>=j){
                return;
            }
            //元素基准值
            int key=arr[left];
            while(i<j){
                while(arr[j]>key&&i<j){
                    j--;
                }
                //基准位置放j位置的值
                arr[i++]=arr[j];
                while(arr[i]<key&&i<j){
                    i++;
                }
                //j位置放置i的值
                arr[j--]=arr[i];
            }
            //i位置放置基准值
            arr[i]=key;
            //左边元素进行递归
            Sort(arr,left,i-1);
            //右边元素进行递归
            Sort(arr,j+1,right);
        }
        // n 1

        /**
         *大根堆(前K小)/小根堆(前K大)
         * 用一个容量为K的大根堆,每次poll出最大的数
         * 1.若目前堆的大小为k,将当前数字放入堆中
         * 2.否则判断当前元素与大根堆对丁元素的大小关系,如果当前元素比大根堆堆顶大,直接跳过
         * 反之,如果当前数字比大根堆堆顶小,先poll掉堆顶,再将该数字放入堆中
         * @param arr
         * @param k
         * @return
         */
        public int[] getLeastNumbers(int[] arr, int k)
        {
            if(k==0||arr.length==0){
                return new int[0];
            }
            //默认是小根堆,大根堆需要重写比较器
            Queue<Integer> pq=new PriorityQueue<>((v1,v2)->v2-v1);
            for (int num:arr) {
                if(pq.size()<k){
                    pq.offer(num);
                }else if(num<pq.peek()){
                    pq.poll();
                    pq.offer(num);
                }
            }
            //返回堆中的元素
            int[] res=new int[pq.size()];
            int idx=0;
            for (int num:pq) {
                res[idx++]=num;
            }
            return res;
        }
//        n nlogn

    }
//leetcode submit region end(Prohibit modification and deletion)

}
归并排序
/**
     * 归并排序
     * 分而治之,按照设定的与之进行分解成多个计算,然后将各个计算结果进行汇总
     * 若将两个有序集合合并成一个有序表,称为2-路归并
     *
     * @param
     */
    public static int[] MergeSort(int[] nums){
        if(nums.length < 2) return nums;
        int mid = nums.length / 2;
        //分成两组
        int[] left = Arrays.copyOfRange(nums,0,mid);
        int[] right = Arrays.copyOfRange(nums,mid,nums.length);
        //递归拆分
        return merge(MergeSort(left),MergeSort(right));
    }
    public static int[] merge(int[] left,int[] right){
        int[] result = new int[left.length+right.length];
        //i表示左边数组的索引,j表示右边
        for (int index = 0,i = 0,j = 0; index < result.length; index++) {
            if(i >= left.length){
                //说明左侧的数据已经全部取完,取右边的数据
                result[index] = right[j++];
            }else if(j >= right.length){
                //说明右侧的数据已经全部取完,取左边的数据
                result[index] = left[i++];
            }else if(left[i] > right[j]){
                //左边大于右边,取右边的
                int a = right[j++];
                result[index] = a;
            }else{
                //右边大于左边,取左边的
                result[index] = left[i++];
            }
        }
        return result;
    }

时间复杂度:总时间=分解时间+子序列排序时间+合并时间,分解时间是一个常数,可以忽略不计 nlogn

稳定性:在合并的时候,如果相等,选择前面的元素到辅助数组,所以归并排序时稳定的

堆排序
/**
 * 堆排序,这里的堆是一种特殊的二叉树,通常叫做二叉堆
 * 是完全二叉树,堆中某个节点的值总是不大于或不小于其父节点的值
 * 二叉堆:
 *  大根堆:每一个根节点都大于等于它的左右孩子节点,叫最大堆
 *  小根堆:每一个根节点都小于等于它的左右孩子节点,叫最小堆
 *  
 * @param args
 */

时间复杂度nlogn

不稳定

计数排序

非比较排序

桶排序

计数排序可以看成每个桶值存储相同元素,桶排序每个桶存储一定范围的元素,通过函数的某种映射关系,将待排序数组中的元素映射到各个对应的桶中,对每个桶中的元素进行排序,最后将非空桶中的元素逐个放入原序列中

  1. 找出数组中的最大值max和最小值min,可以确定数组所在范围min-max

  2. 根据数据范围确定桶的数量

    1. 若桶的数量太少,桶排序失效
    2. 若桶的数量太多,有的桶可能没有,没有数据造成数据浪费

    桶的数量由自己确定,尽量让元素平均分布到每一个桶中 (最大值-最小值)/每个桶所能放置多少个不同数值+1

  3. 确定桶的区间,一般是按照(最大值-最小值)/桶的数量划分,左闭右开

基数排序

按照从右往左的顺序,依次将每一位都当做一次关键字,然后按照该关键字对数组排序,同时每一轮排序都基于上轮排序后的结果,当我们将所有的位排序后,整个数组达到有序状态。基数排序不是基于比较的算法

  • 从高位开始进行排序
  • 从低位开始进行排序
基数排序 桶排序 计数排序

都是稳定的

  • 基数排序:根据每一位的关键字来分配桶
  • 桶排序:存储一定范围的值
  • 基数排序:每个桶只存储一个类型值,但是数量不限
颜色分类
// 给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。
//
// 此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。
//
//
//
//
//
//
// 示例 1:
//
//
// 输入:nums = [2,0,2,1,1,0]
// 输出:[0,0,1,1,2,2]
//
//
// 示例 2:
//
//
// 输入:nums = [2,0,1]
// 输出:[0,1,2]
//
//
// 示例 3:
//
//
// 输入:nums = [0]
// 输出:[0]
//
//
// 示例 4:
//
//
// 输入:nums = [1]
// 输出:[1]
//
//
//
//
// 提示:
//
//
// n == nums.length
// 1 <= n <= 300
// nums[i] 为 0、1 或 2
//
//
//
//
// 进阶:
//
//
// 你可以不使用代码库中的排序函数来解决这道题吗?
// 你能想出一个仅使用常数空间的一趟扫描算法吗?
//
// Related Topics 数组 双指针 排序 👍 1132 👎 0

package leetcode.editor.cn;

// Java:颜色分类
public class SortColors {
    public static void main(String[] args) {
        Solution solution = new SortColors().new Solution();
        // TO TEST
        int[] nums = new int[] {2, 0, 2, 1, 1, 0};
        solution.sortColors(nums);
        for (int num : nums) {
            System.out.print(num + " ");
        }
    }

    // leetcode submit region begin(Prohibit modification and deletion)
    class Solution {
        // 使用常数计算0 1 2的个数,然后对数组进行赋值
        public void sortColors(int[] nums) {
            int ZeroCount = 0, OneCount = 0, TwoCount = 0;
            for (int num : nums) {
                if (num == 0) {
                    ZeroCount++;
                } else if (num == 1) {
                    OneCount++;
                } else if (num == 2) {
                    TwoCount++;
                }
            }
            for (int i = 0; i < nums.length; i++) {
                if (i < ZeroCount) {
                    nums[i] = 0;
                } else if (i < ZeroCount + OneCount) {
                    nums[i] = 1;
                } else if (i < ZeroCount + OneCount + TwoCount) {
                    nums[i] = 2;
                }
            }
        }
    }
    // leetcode submit region end(Prohibit modification and deletion)

}
二叉树
二叉树的深度
class Solution {
    /**
     * 递归方法,dfs
     * 左边和右边更深的
     * @param root
     * @return
     */
    public int maxDepth1(TreeNode root) {
        if(root==null) return 0;
        int leftDepth=0,rightDepth=0;
        if(root.left!=null){
            leftDepth=maxDepth1(root.left);
        }
        if(root.right!=null){
            rightDepth=maxDepth1(root.right);
        }
        return leftDepth>rightDepth?leftDepth+1:rightDepth+1;
    } 

    /**
     * bfs
     * 一层一层进行拓展
     * @param root
     * @return
     */
    public int maxDepth(TreeNode root){
        if(root==null) return 0;
        LinkedList<TreeNode> que=new LinkedList<>();
        que.offer(root);
        int ans=0;
        while(!que.isEmpty()){
            int size=que.size();
            while(size>0){
                TreeNode tmp=que.poll();
                if(tmp.left!=null){
                    que.offer(tmp.left);
                }
                if(tmp.right!=null){
                    que.offer(tmp.right);
                }
                size--;
            }
            ans++;
        }
        return ans;
    }
}
二叉搜索树
//Java:二叉搜索树的后序遍历序列
public class ErChaSouSuoShuDeHouXuBianLiXuLieLcof{
    public static void main(String[] args) {
        Solution solution = new ErChaSouSuoShuDeHouXuBianLiXuLieLcof().new Solution();
        // TO TEST
    }
    //leetcode submit region begin(Prohibit modification and deletion)
class Solution {
        /**
         * 后序遍历:左右中
         * 二叉搜索树:左 < 中,右 > 中
         * 递归分治
         *  终止条件:i>=j,此子树节点数量<=1
         *  递推:
         *      1.划分左右子树,遍历数组元素,找到第一个大于根节点的节点[i,m-1][m,j-1]
         *      2.判断是否是二叉搜索树
         *          左子树区间所有节点小于根节点
         *          右子树区间所有节点大于根节点
         *          如果满足这两个条件可判断是否是二叉搜索树
         *       返回值,判断此树是否正确,此树的左子树是否正确,此树的右子树是否正确
         *
         * @param postorder
         * @return
         */
    public boolean verifyPostorder(int[] postorder) {
        return recur(postorder,0,postorder.length-1);
    }

    public boolean recur(int[] postorder, int i, int j) {
        if(i>=j) return true;
        int p=i;
//        for (int k = 0; k < j; k++) {
//            if(postorder[k]>postorder[j]){
//                m=k;
//            }
//        }
        while(postorder[p]<postorder[j]) p++;
        int m=p;
        while(postorder[p]>postorder[j]) p++;
        return p==j&&recur(postorder,i,m-1)&&recur(postorder,m,j-1);
    }
    }
二叉树的镜像
/**
     * BFS层次遍历
     * 栈或者队列都是可以的
     * 传进队列的顺序为右左
     * 再构造二叉树
     * @param root
     * @return
     */
    public TreeNode mirrorTree(TreeNode root) {
        if(root == null) return null;
        LinkedList<TreeNode> que=new LinkedList<>();
        que.offer(root);
        while(!que.isEmpty()){
            TreeNode node=que.poll();
            if(node.right!=null){
                que.offer(node.right);
            }
            if(node.left!=null){
                que.offer(node.left);
            }
            TreeNode tmp=node.right;
            node.right=node.left;
            node.left=tmp;
        }
        return root;
    }
    // n n
    /**
     * 递归
     * 递归的交换左右节点,在root==null时退出
     * @param root
     * @return
     */
    public TreeNode mirrorTree1(TreeNode root){
        if(root==null) return null;
        TreeNode node=mirrorTree(root.left);
        root.left=mirrorTree(root.right);
        root.right=node;
        return root;
    }
对称二叉树
    /**
     * 递归方法,判断是否是对称的二叉树
     * dfs:深度优先遍历
     * left.left=right.right
     * left.right=right.left
     * 跳出循环:left=null,right==null
     * 特殊情况:root==null
     * @param root
     * @return
     */
    public boolean isSymmetric1(TreeNode root) {
//        没有考虑特殊情况
        if(root==null) return true;
        return isS(root.left,root.right);
    }
    public boolean isS(TreeNode left,TreeNode right){
//        三种情况
        if(left==null&&right==null){
            return true;
        }else if(left==null||right==null||left.val!=right.val){
            return false;
        }else{
            return isS(left.left,right.right)&&isS(left.right,right.left);
        }
    }
    // n遍历n个节点 n和树的深度有关,最坏情况下树变成一个链表结构



    /**
     *广度优先遍历
     * 定义两个队列
     * 首先从队列中取出两个节点进行比较
     * 将left的left节点和right的right节点放入队列
     * 将left的right节点和right的left节点放入队列
     * @param root
     * @return
     */
    public boolean isSymmetric(TreeNode root){
        if(root==null) return true;
        if(root.left==null&&root.right==null){
            return true;}
        LinkedList<TreeNode> que=new LinkedList<TreeNode>();
        que.offer(root.left);
        que.offer(root.right);
        while(!que.isEmpty()){
            TreeNode left=que.poll();
            TreeNode right=que.poll();
            if(left==null&&right==null) continue;
            if(left==null||right==null||left.val!=right.val){
                return false;
            }

            que.add(left.left);
            que.add(right.right);

            que.add(left.right);
            que.add(right.left);

        }
        return true;
    }
    //n n
}
二叉搜索树的公共祖先
class Solution {
    /**
     * BFS
     * 一个节点与另一个节点都为同一个节点的公共节点
     * 一个节点为一个节点的父节点
     * 一个节点与另一个节点的最近父节点不相同                                                    ,需要网上进行比较
     * 左右中
     * (0 (3 5 *4) *2) (7 9 *8) *6
     * 0 *2 3 *4 5 *6 7 *8 9
     * *6 *2 0 *4 3 5 *8 7 9
     * 找出两个节点的祖先
     * 迭代
     * p,q比root值小,在root左侧
     * 比root值大,在root右侧
     * 一个大一个小,返回root
     * @param root
     * @param p
     * @param q
     * @return
     */
    public TreeNode lowestCommonAncestor1(TreeNode root, TreeNode p, TreeNode q) {
        if(root==null) return null;
        LinkedList<TreeNode> que=new LinkedList<>();
        que.offer(root);
        while(!que.isEmpty()){
            TreeNode tmp=que.poll();
            if(tmp.val>p.val&&tmp.val>q.val){
                que.offer(tmp.left);
            }
            else if(tmp.val<p.val&&tmp.val<q.val){
                que.offer(tmp.right);
            }else{
                return tmp;
            }
        }
        return null;
    }
//    n 1
/**
 * dfs
 * p,q在root的右子树中递归root.right
 * p,q在root的左子树中递归root.left
 * 返回root
 * @param root
 * @param p
 * @param q
 * @return
 */
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q){
    if(root.val<p.val&&root.val<q.val){
        //递归并返回
        return lowestCommonAncestor(root.right,p,q);
    }
    if(root.val>p.val&&root.val>q.val){
        return lowestCommonAncestor(root.left,p,q);
    }
    return root;
}
//    n n
}```
二叉树的最近公共祖先
/**
     * 找到p,q结点的祖先,比较得到最近的祖先
     * 通过递归对二叉树进行后序遍历,当遇到结点p或q时返回。
     * 从底至顶回溯,当结点p,q在结点root的异侧时,结点root为最近公共祖先,向上返回root
     * 返回值:
     * 1.当left和right同时为空,root的左右子树中都不包含p,q,返回null
     * 2.当left和right同时不为空,p,q分列在root的异侧,因此root为最近公共祖先,返回root
     * 3.当left为空,right不为空;p,q都不在root的左子树中,直接返回right,
     *  1.p,q其中一个在root的右子树中,此时right指向p
     *  2.p,q两节点都在root的右子树中,此时的right指向最近公共祖先结点
     * 4.当left不为空,right为空,与情况3同理
     * @param root
     * @param p
     * @param q
     * @return
     */
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if(root==null||root==p||root==q) return root;//树为空时直接返回null,p和q中有等于root的,它们的最近公共祖先即为root
        TreeNode left=lowestCommonAncestor(root.left,p,q);//递归左子节点,返回值记为 leftleft ;
        TreeNode right = lowestCommonAncestor(root.right,p,q);//递归右子节点,返回值记为 rightleft ;
        if(left==null&&right==null) return null;//当left和right同时为空,root的左右子树中都不包含p,q,返回null
        if(left==null) return right;//left为空,right不为空;p,q都不在root的左子树中,直接返回right,
        if(right==null) return left;//right为空,left不为空;p,q都不在root的右子树中,直接返回left,
        return root;// 当left和right同时不为空,p,q分列在root的异侧,因此root为最近公共祖先,返回root


    }
从上到下打印二叉树
/**
     * bfs
     * @param root
     * @return
     */
    public int[] levelOrder(TreeNode root) {
        if(root==null) return new int[0];
        LinkedList<TreeNode> que=new LinkedList<>();
        //add操作,访问元素,LinkedList或ArrayList都可以使用
        ArrayList<Integer> list=new ArrayList<>();
        que.add(root);
        while(!que.isEmpty()){
            TreeNode tmp=que.poll();

            list.add(tmp.val);
            if(tmp.left!=null){
                que.add(tmp.left);
            }
            if(tmp.right!=null){
                que.add(tmp.right);
            }
        }
        int[] num=new int[list.size()];
        for (int i = 0; i < list.size(); i++) {
            num[i]= list.get(i);
        }
        return num;
    }
层次遍历
/**
     * 二叉树的层次遍历
     * 一个队列用来做正常遍历
     * 一个链表用来存储二叉树每一层的节点
     * 每一层遍历完之后,将该层的链表加入双层链表中
     * 核心要点:每一层遍历时,循环的开始为链表的长度,避免因为链表做了改变之后长度变化
     * @param root
     * @return
     */
    public List<List<Integer>> levelOrder(TreeNode root) {
        Queue<TreeNode> que=new LinkedList<>();
        //add操作,访问元素,LinkedList或ArrayList都可以使用
        List<List<Integer>> list=new ArrayList<>();
        if(root != null) que.add(root);
        while(!que.isEmpty()){
            //定义一个每一层节点的列表
            List<Integer> tmp=new ArrayList<>();
            //避免队列大小的改变
            for (int i = que.size(); i > 0 ; i--) {
                TreeNode node=que.poll();
                tmp.add(node.val);
                if(node.left != null) que.add(node.left);
                if(node.right != null) que.add(node.right);
            }
            list.add(tmp);
        }
        return list;
    }
}
层次遍历进阶版
/**
     * 第一行按照从左到右的顺序打印,第二层按照从右到左的顺序打印,第三行再按照从左到右的顺序打印
     * 不对:每一层遍历,加入队列时改变顺序,或者遍历时改变顺序
     * 利用双端队列的两端都可以添加元素的特性,设打印列表(双端队列)tmp,规定:奇数层添加至tmp尾部,偶数层添加至tmp头部
     * @param root
     * @return
     */
    public List<List<Integer>> levelOrder(TreeNode root) {
        Queue<TreeNode> que=new LinkedList<>();
        //add操作,访问元素,LinkedList或ArrayList都可以使用
        List<List<Integer>> list=new ArrayList<>();
        if(root != null) que.add(root);
        while(!que.isEmpty()){
            //定义一个每一层节点的列表
            LinkedList<Integer> tmp=new LinkedList<>();
            //避免队列大小的改变
            for (int i = que.size(); i > 0 ; i--) {
                TreeNode node=que.poll();
                //二叉树为偶数层或奇数层
                if(list.size()%2 == 0) tmp.addLast(node.val);
                else tmp.addFirst(node.val);
                if(node.left != null) que.add(node.left);
                if(node.right != null) que.add(node.right);
                }
            list.add(tmp);
        }
        return list;
    }
按之字形打印二叉树
// 按之字形顺序打印二叉树
// 定义两个队列,一个队列存储所有,另一个存储每一层的节点
public static ArrayList<ArrayList<Integer>> Print(TreeNode pRoot) {
    ArrayList<ArrayList<Integer>> arrayLists = new ArrayList<>();
    if (pRoot == null) {
        return arrayLists;
    }
    Queue<TreeNode> tmp = new LinkedList<>();
    tmp.offer(pRoot);
    while (!tmp.isEmpty()) {
        ArrayList<Integer> list = new ArrayList<>();
        for (int i = tmp.size(); i > 0; i--) {
            TreeNode node = tmp.poll();
            if (node.left != null) {
                tmp.add(node.left);
            }
            if (node.right != null) {
                tmp.add(node.right);
            }
            if (arrayLists.size() % 2 == 0) {
                // 偶数层,从左到右
                list.add(list.size(), node.val);
            } else {
                // 奇数层,从右到左
                list.add(0, node.val);
            }
        }
        if (!list.isEmpty()) {
            arrayLists.add(list);
        }
    }
    return arrayLists;
}
二叉树的前序遍历
//给你二叉树的根节点 root ,返回它节点值的 前序 遍历。 
//
// 
//
// 示例 1:
//
// 
//输入:root = [1,null,2,3]
//输出:[1,2,3]
// 
//
// 示例 2: 
//
// 
//输入:root = []
//输出:[]
// 
//
// 示例 3: 
//
// 
//输入:root = [1]
//输出:[1]
// 
//
// 示例 4: 
//
// 
//输入:root = [1,2]
//输出:[1,2]
// 
//
// 示例 5: 
//
// 
//输入:root = [1,null,2]
//输出:[1,2]
// 
//
// 
//
// 提示: 
//
// 
// 树中节点数目在范围 [0, 100] 内 
// -100 <= Node.val <= 100 
// 
//
// 
//
// 进阶:递归算法很简单,你可以通过迭代算法完成吗? 
// Related Topics 栈 树 深度优先搜索 二叉树 
// 👍 621 👎 0

package leetcode.editor.cn;

import com.sun.source.tree.Tree;

import java.util.ArrayList;
import java.util.Deque;
import java.util.LinkedList;
import java.util.List;

//Java:二叉树的前序遍历
public class BinaryTreePreorderTraversal{
    public static void main(String[] args) {
        Solution solution = new BinaryTreePreorderTraversal().new Solution();
        // TO TEST
    }
        //leetcode submit region begin(Prohibit modification and deletion)
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
//    递归算法,迭代算法
    public List<Integer> preorderTraversal1(TreeNode root) {
        List<Integer> res = new ArrayList<Integer>();
        pre(root,res);
        return res;
    }
    public void pre(TreeNode root,List<Integer> res){
        if(root == null){
            return;
        }
        res.add(root.val);
        pre(root.left,res);
        pre(root.right,res);
    }
    //在递归的时候隐式的维护了一个栈,我们在迭代的时候需要显式的将这个栈模拟出来
    public List<Integer> preorderTraversal(TreeNode root){
        List<Integer> res = new ArrayList<Integer>();
        if(root == null){
            return res;
        }
        Deque<TreeNode> stack = new LinkedList<TreeNode>();
        TreeNode node = root;
        while(!stack.isEmpty() || node != null){
            while(node != null){
                res.add(node.val);
                stack.push(node);
                node = node.left;
            }
            node = stack.pop();
            node = node.right;
        }
        return res;
    }
}
//leetcode submit region end(Prohibit modification and deletion)

}
树的子结构 中等 dfs
//输入两棵二叉树A和B,判断B是不是A的子结构。(约定空树不是任意一个树的子结构) 
//
// B是A的子结构, 即 A中有出现和B相同的结构和节点值。 
//
// 例如: 
//给定的树 A: 
//
// 3 
// / \ 
// 4 5 
// / \ 
// 1 2 
//给定的树 B: 
//
// 4 
// / 
// 1 
//返回 true,因为 B 与 A 的一个子树拥有相同的结构和节点值。 
//
// 示例 1: 
//
// 输入:A = [1,2,3], B = [3,1]
//输出:false
// 
//
// 示例 2: 
//
// 输入:A = [3,4,5,1,2], B = [4,1]
//输出:true 
//
// 限制: 
//
// 0 <= 节点个数 <= 10000 
// Related Topics 树 深度优先搜索 二叉树 
// 👍 361 👎 0

package leetcode.editor.cn;

import java.util.LinkedList;

//Java:树的子结构
public class ShuDeZiJieGouLcof{
    public static void main(String[] args) {
        Solution solution = new ShuDeZiJieGouLcof().new Solution();
        // TO TEST
    }
        //leetcode submit region begin(Prohibit modification and deletion)
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    /**
     * B是A的子结构, 即 A中有出现和B相同的结构和节点值。
     * // 3
     * // / \
     * // 4 5
     * // / \
     * // 1 2
     * //给定的树 B:
     * //
     * // 4
     * // /
     * // 1
     * BFS
     * @param A
     * @param B
     * @return
     */
    public boolean isSubStructure1(TreeNode A, TreeNode B) {
        if(B == null) return false;
        else if(A == null) return false;
        LinkedList<TreeNode> list = new LinkedList<>();
        list.add(A);
        while(!list.isEmpty()){
            TreeNode tmp = list.poll();
            if(tmp.val==B.val){
                if(isEqualAB(tmp,B)){
                    return true;
                }
            }
            if(tmp.left!=null){
                list.add(tmp.left);
            }
            if(tmp.right != null){
                list.add(tmp.right);
            }
        }
        return false;
    }

    private boolean isEqualAB(TreeNode A, TreeNode B) {
        LinkedList<TreeNode> list = new LinkedList<>();
        LinkedList<TreeNode> listB = new LinkedList<>();
        list.add(A);
        listB.add(B);
        while(!list.isEmpty()){
            TreeNode tmpA = list.poll();
            TreeNode tmpB = listB.poll();
            if(tmpA.val!=tmpB.val){
                return false;
            }
            if(tmpA.left!=null){
                if(tmpB.left!=null){
                    list.add(tmpA.left);
                    listB.add(tmpB.left);
                }else{
                    return false;
                }

            }
            if(tmpA.right != null){
                if(tmpB.right != null){
                    list.add(tmpA.right);
                    listB.add(tmpB.right);
                }else{
                    return false;
                }

            }
        }
        return true;
    }

    /**
     * 先序遍历树A的每个节点
     * 判断树A中是否包含树B recur(A,B)
     * recur(A,B)
     *  终止条件:
     *      当节点B为空,树B已经匹配完成,返回true
     *      当节点A为空,说明已经越过树A节点,匹配失败,返回false
     *      当节点A和B的值不同,匹配失败,返回false
     *  返回值:
     *      1.判断A和B的左子节点是否相等 recur(A.left,B.left)
     *      2.判断A和B的右子节点是否相等,recur(A.right,B.right)
     * isSubStructure(A,B)
     *  1.当A或者B为空的时候 返回false
     *  2.如果B是A的子结构,满足以下三种情况之一
     *      1.以节点A为根节点的子树包含树B recur(A,B)
     *      2.树B是树A左子树的子结构 isSubStructure(A.left,B)
     *      3.树B是树A有子树的子结构 isSubStructure(A.right,B)
     * @param A
     * @param B
     * @return
     */
    public boolean isSubStructure(TreeNode A, TreeNode B){
        if((A==null)||B==null) {
            return false;
        }
        return recur(A,B)||isSubStructure(A.left,B)||isSubStructure(A.right,B);
    }

    private boolean recur(TreeNode A, TreeNode B) {
        if(B==null) return true;
        if(A==null||A.val!=B.val) return false;
        return recur(A.left,B.left)&&recur(A.right,B.right);
    }

}
//leetcode submit region end(Prohibit modification and deletion)

}
二叉树中和为某一值的路径 dfs中等
//给你二叉树的根节点 root 和一个整数目标和 targetSum ,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。 
//
// 叶子节点 是指没有子节点的节点。 
//
//http://123.207.85.44:9001/acoms/commonFlow/queryTaskByUserId
//
// 示例 1: 
//
// 
//
// 
//输入:root = [5,4,8,11,null,13,4,7,2,null,null,5,1], targetSum = 22
//输出:[[5,4,11,2],[5,8,4,5]]
// 
//
// 示例 2: 
//
// 
//
// 
//输入:root = [1,2,3], targetSum = 5
//输出:[]
// 
//
// 示例 3: 
//
// 
//输入:root = [1,2], targetSum = 0
//输出:[]
// 
//
// 
//
// 提示: 
//
// 
// 树中节点总数在范围 [0, 5000] 内 
// -1000 <= Node.val <= 1000 
// -1000 <= targetSum <= 1000 
// 
//
// 注意:本题与主站 113 题相同:https://leetcode-cn.com/problems/path-sum-ii/ 
// Related Topics 树 深度优先搜索 回溯 二叉树 👍 262 👎 0

package leetcode.editor.cn;

import java.util.LinkedList;
import java.util.List;

//Java:二叉树中和为某一值的路径
public class ErChaShuZhongHeWeiMouYiZhiDeLuJingLcof{
    public static void main(String[] args) {
        Solution solution = new ErChaShuZhongHeWeiMouYiZhiDeLuJingLcof().new Solution();
        // TO TEST
    }
        //leetcode submit region begin(Prohibit modification and deletion)
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    /**
     * @description: 深度优先搜索,回溯,先序遍历+路径记录
     * 记录从根节点到当前节点的路径,当各节点值的和等于目标值sum时,将路径加入结果列表
     *
     * @param: root
 * @param: target
     * @return: java.util.List<java.util.List<java.lang.Integer>>
     * @author:
     * @create: 2021-11-10
     */
    // 初始化:结果列表res,路径列表path
    // 返回值:返回res
    List<List<Integer>> list = new LinkedList<List<Integer>>();
    public List<List<Integer>> pathSum(TreeNode root, int target) {
        LinkedList<Integer> path = new LinkedList<>();
        recur(root,path,target);
        return list;
    }

    public void recur(TreeNode root,LinkedList<Integer> path,int target){
        if(root == null){
            return;
        }
        // 1.路径更新
        path.add(root.val);
        // 2.目标值更新
        target -= root.val;
        // 3.路径记录
        //  当root为叶节点且路径和等于目标值
        if(root.left == null && root.right == null && target == 0){
            // 将path对象加入了list,后续path改变时list中的path对象也会改变
//            list.add(path);
            // 复制一个path并且加入到list
            list.add(new LinkedList<>(path));
            // 有回溯操作,不需要将path置为空
//            while(!path.isEmpty()){
//                path.remove();
//            }
        }
        // 4.先序遍历,递归左右子节点
        recur(root.left,path,target);
        recur(root.right,path,target);
        // 5.路径恢复,向上回溯前,需要将当前节点从路径path中删除
        path.removeLast();

    }



}
//leetcode submit region end(Prohibit modification and deletion)

}
二叉搜索树与双向链表 dfs 中等
//输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的循环双向链表。要求不能创建任何新的节点,只能调整树中节点指针的指向。 
//
// 
//
// 为了让您更好地理解问题,以下面的二叉搜索树为例: 
//
// 
//
// 
//
// 
//
// 我们希望将这个二叉搜索树转化为双向循环链表。链表中的每个节点都有一个前驱和后继指针。对于双向循环链表,第一个节点的前驱是最后一个节点,最后一个节点的后继是
//第一个节点。 
//
// 下图展示了上面的二叉搜索树转化成的链表。“head” 表示指向链表中有最小元素的节点。 
//
// 
//
// 
//
// 
//
// 特别地,我们希望可以就地完成转换操作。当转化完成以后,树中节点的左指针需要指向前驱,树中节点的右指针需要指向后继。还需要返回链表中的第一个节点的指针。 
//
// 
//
// 注意:本题与主站 426 题相同:https://leetcode-cn.com/problems/convert-binary-search-tree-
//to-sorted-doubly-linked-list/ 
//
// 注意:此题对比原题有改动。 
// Related Topics 栈 树 深度优先搜索 二叉搜索树 链表 二叉树 双向链表 👍 348 👎 0

package leetcode.editor.cn;
class Node {
    public int val;
    public Node left;
    public Node right;

    public Node() {}

    public Node(int _val) {
        val = _val;
    }

    public Node(int _val,Node _left,Node _right) {
        val = _val;
        left = _left;
        right = _right;
    }
};
//Java:二叉搜索树与双向链表
public class ErChaSouSuoShuYuShuangXiangLianBiaoLcof{
    public static void main(String[] args) {
        Solution solution = new ErChaSouSuoShuYuShuangXiangLianBiaoLcof().new Solution();
        // TO TEST
    }
        //leetcode submit region begin(Prohibit modification and deletion)

// Definition for a Node.


class Solution {
    /**
     * @description: 中序遍历,左中右,当转化完成后,树中节点的左指针需要指向前驱,树中节点的右指针需要指向后继,还需要返回链表中的第一个节点的指针
     * 将二叉搜索树转换成一个排序的循环双向链表
     * 1.排序链表:节点从小到大排序,使用中序遍历
     * 2.双向链表:在构建相邻节点的引用关系时,设置前驱节点pre和当前节点cur,构建pre.right = cur,cur.left = pre
     * 3.循环链表:设链表头节点head和尾结点tail,head.left=tail和tail.right = head
     * 使用中序遍历访问树的各节点cur,在访问每个节点时构建cur和前驱节点pre的引用指向,中序遍历完成后,最后构建头节点和尾结点的引用指向
     * @param: root
     * @return: leetcode.editor.cn.Node
     * @author:
     * @create: 2021-11-10
     */
    Node pre,head;
    public Node treeToDoublyList(Node root) {
        // 1.若节点root为空,直接返回
        if(root == null) {
            return null;
        }
        // 2.初始化:空节点pre
        // 3.转化为双向链表:调用dfs(root)
        dfs(root);
        // 4.构建循环链表:中序遍历完成后,head指向头节点,pre指向尾结点,因此修改head和pre的双向节点引用
        head.left = pre;
        pre.right = head;
        // 5.返回值:返回链表的头节点head
        return head;
    }

    /**
     * @description: 1.终止条件:当节点cur为空,代表越过叶节点,直接返回
     * 2.递归左子树,dfs(cur.left)
     * 3.构建链表:
     *  1.当pre为空时:代表正在访问链表头节点,记为head
     *  2.当pre不为空时:修改双向节点引用,pre.right = cur cur.left = pre
     *  3.保存cur:更新pre = cur,即节点cur时后继节点的pre
     * 4.递归右子树dfs(cur.right)
     * @param: root
     * @return: void
     * @author:
     * @create: 2021-11-11
     */

    public void dfs(Node cur) {
        if(cur == null){
            return;
        }
        dfs(cur.left);
        // 反转链表
        if(pre == null){
            head = cur;
        }else{
            pre.right = cur;
        }
        cur.left = pre;
        pre = cur;
        dfs(cur.right);
    }
}
//leetcode submit region end(Prohibit modification and deletion)

}
不同的二叉搜索树
// 给你一个整数 n ,求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。
//
//
//
// 示例 1:
//
//
// 输入:n = 3
// 输出:5
//
//
// 示例 2:
//
//
// 输入:n = 1
// 输出:1
//
//
//
//
// 提示:
//
//
// 1 <= n <= 19
//
// Related Topics 树 二叉搜索树 数学 动态规划 二叉树 👍 1502 👎 0

package leetcode.editor.cn;

// Java:不同的二叉搜索树
public class UniqueBinarySearchTrees {
    public static void main(String[] args) {
        Solution solution = new UniqueBinarySearchTrees().new Solution();
        // TO TEST
        /*int[] nums = {2, 3, 1, 4, 5};
        solution.sort(nums, 0, nums.length - 1);
        System.out.println(Arrays.toString(nums));*/
        System.out.println(solution.numTrees(3));
    }

    // leetcode submit region begin(Prohibit modification and deletion)
    class Solution {
        /*
         * 动态规划 
         * 遍历每个数字i作为树根,将(1,i-1)序列作为左子树,将(i+1,n)作为右子树,递归构建左子树和右子树
         * G(n):长度为n的序列能构成的不同二叉搜索树的个数.G(0)=G(1)=1
         * F(i,n):以i为根,序列长度为n的不同二叉搜索树个数=G(i-1,n-i).F(3,7)=G(2)*G(4)
         * 
         */
        public int numTrees(int n) {
            int[] G = new int[n + 1];
            G[0] = 1;
            G[1] = 1;
            for (int i = 2; i <= n; i++) {
                for (int j = 1; j <= i; j++) {
                    G[i] += G[j - 1] * G[i - j];
                }
            }
            return G[n];
        }

        public void sort(int[] nums, int i, int j) {
            int key = nums[i];
            int left = i, right = j;
            if (left >= right) {
                return;
            }
            while (left < right) {
                while (left < right && key < nums[right]) {
                    right--;
                }
                if (right > 0) {
                    nums[left] = nums[right];
                }
                while (left < right && key > nums[left]) {
                    left++;
                }
                if (left > 0) {
                    nums[right] = nums[left];
                }
            }
            nums[left] = key;
            sort(nums, i, left - 1);
            sort(nums, left + 1, j);
        }
    }
    // leetcode submit region end(Prohibit modification and deletion)

}
验证二叉搜索树
// 给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。
//
// 有效 二叉搜索树定义如下:
//
//
// 节点的左子树只包含 小于 当前节点的数。
// 节点的右子树只包含 大于 当前节点的数。
// 所有左子树和右子树自身必须也是二叉搜索树。
//
//
//
//
// 示例 1:
//
//
// 输入:root = [2,1,3]
// 输出:true
//
//
// 示例 2:
//
//
// 输入:root = [5,1,4,null,null,3,6]
// 输出:false
// 解释:根节点的值是 5 ,但是右子节点的值是 4 。
//
//
//
//
// 提示:
//
//
// 树中节点数目范围在[1, 10⁴] 内
// -2³¹ <= Node.val <= 2³¹ - 1
//
// Related Topics 树 深度优先搜索 二叉搜索树 二叉树 👍 1387 👎 0

package leetcode.editor.cn;

// Java:验证二叉搜索树
public class ValidateBinarySearchTree {
    public static void main(String[] args) {
        Solution solution = new ValidateBinarySearchTree().new Solution();
        // TO TEST
        TreeNode root = new TreeNode(2);
        TreeNode rootLeft = new TreeNode(1);
        TreeNode rootRight = new TreeNode(3);
        root.left = rootLeft;
        root.right = rootRight;
        System.out.println(solution.isValidBST(root));
    }

    // leetcode submit region begin(Prohibit modification and deletion)
    /**
     * Definition for a binary tree node. public class TreeNode { int val; TreeNode left; TreeNode right; TreeNode() {}
     * TreeNode(int val) { this.val = val; } TreeNode(int val, TreeNode left, TreeNode right) { this.val = val;
     * this.left = left; this.right = right; } }
     */
    class Solution {
        // 节点的左子树只包含 小于 当前节点的数。
        // 节点的右子树只包含 大于 当前节点的数。
        // 所有左子树和右子树自身必须也是二叉搜索树。
        public boolean isValidBST(TreeNode root) {
            return isValidBST(root, Long.MIN_VALUE, Long.MAX_VALUE);
        }

        public boolean isValidBST(TreeNode root, long lower, long higher) {
            if (root == null) {
                return true;
            }
            if (root.val <= lower || root.val >= higher) {
                return false;
            }
            return isValidBST(root.left, lower, root.val) && isValidBST(root.right, root.val, higher);
        }
    }
    // leetcode submit region end(Prohibit modification and deletion)

}
从前序与中序遍历序列构造二叉树 递归 哈希表
// 给定一棵树的前序遍历 preorder 与中序遍历 inorder。请构造二叉树并返回其根节点。
//
//
//
// 示例 1:
//
//
// Input: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
// Output: [3,9,20,null,null,15,7]
//
//
// 示例 2:
//
//
// Input: preorder = [-1], inorder = [-1]
// Output: [-1]
//
//
//
//
// 提示:
//
//
// 1 <= preorder.length <= 3000
// inorder.length == preorder.length
// -3000 <= preorder[i], inorder[i] <= 3000
// preorder 和 inorder 均无重复元素
// inorder 均出现在 preorder
// preorder 保证为二叉树的前序遍历序列
// inorder 保证为二叉树的中序遍历序列
//
// Related Topics 树 数组 哈希表 分治 二叉树 👍 1385 👎 0

package leetcode.editor.cn;

import java.util.HashMap;

// Java:从前序与中序遍历序列构造二叉树
public class ConstructBinaryTreeFromPreorderAndInorderTraversal {
    public static void main(String[] args) {
        Solution solution = new ConstructBinaryTreeFromPreorderAndInorderTraversal().new Solution();
        // TO TEST
        solution.buildTree(new int[] {3, 9, 20, 15, 7}, new int[] {9, 3, 15, 20, 7});
    }

    // leetcode submit region begin(Prohibit modification and deletion)
    /**
     * Definition for a binary tree node. public class TreeNode { int val; TreeNode left; TreeNode right; TreeNode() {}
     * TreeNode(int val) { this.val = val; } TreeNode(int val, TreeNode left, TreeNode right) { this.val = val;
     * this.left = left; this.right = right; } }
     */
    class Solution {
        // Input: preorder = [3,9,20,15,7], 中左右
        // inorder = [9,3,15,20,7] 左中右
        // Output: [3,9,20,null,null,15,7]
        // 构造二叉树,返回根节点,哈希表,分治
        // 将中序遍历每个元素的位置存入哈希表中,通过前序遍历的元素的值
        // 就能获取到中序遍历中该元素的位置,从而划分左右的元素,构建做二叉树和右二叉树
        HashMap<Integer, Integer> map = new HashMap<>();

        public TreeNode buildTree(int[] preorder, int[] inorder) {
            for (int i = 0; i < inorder.length; i++) {
                map.put(inorder[i], i);
            }
            return helper(preorder, 0, preorder.length, inorder, 0, inorder.length);
        }

        private TreeNode helper(int[] preorder, int p_start, int p_end, int[] inorder, int i_start, int i_end) {
            if (p_start == p_end) {
                return null;
            }
            int e = preorder[p_start];
            TreeNode root = new TreeNode(e);
            int index = map.get(e);
            int leftLength = index - i_start;
            root.left = helper(preorder, p_start + 1, p_start + leftLength + 1, inorder, i_start, index);
            root.right = helper(preorder, p_start + leftLength + 1, p_end, inorder, index + 1, i_end);
            return root;
        }

    }
    // leetcode submit region end(Prohibit modification and deletion)

}
二叉树展开为链表 dfs
// 给你二叉树的根结点 root ,请你将它展开为一个单链表:
//
//
// 展开后的单链表应该同样使用 TreeNode ,其中 right 子指针指向链表中下一个结点,而左子指针始终为 null 。
// 展开后的单链表应该与二叉树 先序遍历 顺序相同。
//
//
//
//
// 示例 1:
//
//
// 输入:root = [1,2,5,3,4,null,6]
// 输出:[1,null,2,null,3,null,4,null,5,null,6]
//
//
// 示例 2:
//
//
// 输入:root = []
// 输出:[]
//
//
// 示例 3:
//
//
// 输入:root = [0]
// 输出:[0]
//
//
//
//
// 提示:
//
//
// 树中结点数在范围 [0, 2000] 内
// -100 <= Node.val <= 100
//
//
//
//
// 进阶:你可以使用原地算法(O(1) 额外空间)展开这棵树吗?
// Related Topics 栈 树 深度优先搜索 链表 二叉树 👍 1042 👎 0

package leetcode.editor.cn;

// Java:二叉树展开为链表
public class FlattenBinaryTreeToLinkedList {
    public static void main(String[] args) {
        Solution solution = new FlattenBinaryTreeToLinkedList().new Solution();
        // TO TEST
        TreeNode root = new TreeNode(1);
        TreeNode rootLeft = new TreeNode(2);
        TreeNode rootLeftLeft = new TreeNode(3);
        TreeNode rootLeftRight = new TreeNode(4);
        TreeNode rootRight = new TreeNode(5);
        TreeNode rootRightRight = new TreeNode(6);
        root.left = rootLeft;
        rootLeft.left = rootLeftLeft;
        rootLeft.right = rootLeftRight;
        root.right = rootRight;
        rootRight.right = rootRightRight;
        solution.flatten(root);
        while (root != null) {
            System.out.println(root.val);
            root = root.right;
        }
    }

    // leetcode submit region begin(Prohibit modification and deletion)
    /**
     * Definition for a binary tree node. public class TreeNode { int val; TreeNode left; TreeNode right; TreeNode() {}
     * TreeNode(int val) { this.val = val; } TreeNode(int val, TreeNode left, TreeNode right) { this.val = val;
     * this.left = left; this.right = right; } }
     */
    class Solution {

        // 栈 树 深度优先搜索 链表 二叉树
        // 展开后的单链表应该同样使用 TreeNode ,其中 right 子指针指向链表中下一个结点,而左子指针始终为 null 。
        // 展开后的单链表应该与二叉树 先序遍历 顺序相同。中左右
        // 类似后序遍历,利用递归 右左中
        // 保存
        TreeNode pre = null;

        public void flatten(TreeNode root) {
            if (root == null) {
                return;
            }
            flatten(root.right);
            flatten(root.left);
            root.left = null;
            root.right = pre;
            pre = root;

        }
    }
    // leetcode submit region end(Prohibit modification and deletion)

}
二分查找
统计一个数字在排序数组中出现的次数
//统计一个数字在排序数组中出现的次数。 
//
// 
//
// 示例 1: 
//
// 输入: nums = [5,7,7,8,8,10], target = 8
//输出: 2 
//
// 示例 2: 
//
// 输入: nums = [5,7,7,8,8,10], target = 6
//输出: 0 
//
// 
//
// 限制: 
//
// 0 <= 数组长度 <= 50000 
//
// 
//
// 注意:本题与主站 34 题相同(仅返回值不同):https://leetcode-cn.com/problems/find-first-and-last-
//position-of-element-in-sorted-array/ 
// Related Topics 数组 二分查找 
// 👍 139 👎 0

package leetcode.editor.cn;
//Java:在排序数组中查找数字 I
public class ZaiPaiXuShuZuZhongChaZhaoShuZiLcof{
    public static void main(String[] args) {
        Solution solution = new ZaiPaiXuShuZuZhongChaZhaoShuZiLcof().new Solution();
        // TO TEST
        int[] nums={5,7,7,8,8,10};
        System.out.println(solution.search(nums,8));
    }
    //leetcode submit region begin(Prohibit modification and deletion)
class Solution {
        /**
         * 二分查找
         * 比中间小就在左边查找,比中间大就在右边查找
         * @param nums
         * @param target
         * @return
         */
    public int search1(int[] nums, int target) {
        int count=0;
        for (int i = 0; i < nums.length; i++) {
            if(nums[i]==target){
                count++;
            }
        }
        return count;
//        二分查找
//        int left=0,right=nums.length;
//        int mid=(left+right)/2;
//        while(nums[mid]!=target){
//            if(nums[mid]>target){
//                right=mid--;
//            }
//            if(nums[mid]<target){
//                left=mid++;
//            }
//            mid=(left+right)/2;
//        }
//        return mid;
    }

        /**
         * 使用二分法分别找到左边界和右边界right-left-1
         */
        public int search(int[] nums, int target){
            //搜索右边界right
            int i=0,j=nums.length-1;
            while(i<=j){
                int m=(i+j)/2;
                if(nums[m]<=target){
                    i=m+1;
                }else{
                    j=m-1;
                }
            }
            int right=i;
            //若数组没有target,提前返回
            if(j>=0&&nums[j]!=target) return 0;
            //搜索左边界right
            i=0;
            j=nums.length-1;
            while(i<=j){
                int m=(i+j)/2;
                if(nums[m]<target) i=m+1;
                else j=m-1;
            }
            int left=j;
            return right-left-1;
        }
        //优化,将二分查找封装,分别二分查找target和target-1的右边界,将两结果相减并返回
    }
//leetcode submit region end(Prohibit modification and deletion)

}
在排序数组中查找元素的第一个和最后一个位置 中等
// 给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。
//
// 如果数组中不存在目标值 target,返回 [-1, -1]。
//
// 进阶:
//
//
// 你可以设计并实现时间复杂度为 O(log n) 的算法解决此问题吗?
//
//
//
//
// 示例 1:
//
//
// 输入:nums = [5,7,7,8,8,10], target = 8
// 输出:[3,4]
//
// 示例 2:
//
//
// 输入:nums = [5,7,7,8,8,10], target = 6
// 输出:[-1,-1]
//
// 示例 3:
//
//
// 输入:nums = [], target = 0
// 输出:[-1,-1]
//
//
//
// 提示:
//
//
// 0 <= nums.length <= 10⁵
// -10⁹ <= nums[i] <= 10⁹
// nums 是一个非递减数组
// -10⁹ <= target <= 10⁹
//
// Related Topics 数组 二分查找 👍 1402 👎 0

package leetcode.editor.cn;

// Java:在排序数组中查找元素的第一个和最后一个位置
public class FindFirstAndLastPositionOfElementInSortedArray {
    public static void main(String[] args) {
        Solution solution = new FindFirstAndLastPositionOfElementInSortedArray().new Solution();
        // TO TEST
        int[] res = solution.searchRange(new int[] {1}, 0);
        for (int re : res) {
            System.out.print(re + " ");
        }
    }

    // leetcode submit region begin(Prohibit modification and deletion)
    class Solution {
        // 二分查找 O(log n)
        // 在排序数组中查找元素的第一个和最后一个位置
        // 分别查找到左边界和右边界
        public int[] searchRange(int[] nums, int target) {
            int[] res = new int[2];
            int left = 0, right = nums.length - 1;
            // 右边界
            right = binarySearch(left, right, nums, target);
            // 如果数组没有target,直接返回
            if (nums.length == 0 || right == -1 || nums[right] != target) {
                res[0] = -1;
                res[1] = -1;
                return res;
            }
            res[1] = right;
            left = 0;
            right = nums.length - 1;
            // 左边界
            right = binarySearch(left, right, nums, target - 1);
            res[0] = right + 1;
            return res;
        }

        public int binarySearch(int left, int right, int[] nums, int target) {
            while (left <= right) {
                int mid = (left + right) / 2;
                if (nums[mid] <= target) {
                    left = mid + 1;
                } else {
                    right = mid - 1;
                }
            }
            return right;
        }
    }
    // leetcode submit region end(Prohibit modification and deletion)

}
搜索插入位置
//给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。 
//
// 请必须使用时间复杂度为 O(log n) 的算法。 
//
// 
//
// 示例 1: 
//
// 
//输入: nums = [1,3,5,6], target = 5
//输出: 2
// 
//
// 示例 2: 
//
// 
//输入: nums = [1,3,5,6], target = 2
//输出: 1
// 
//
// 示例 3: 
//
// 
//输入: nums = [1,3,5,6], target = 7
//输出: 4
// 
//
// 示例 4: 
//
// 
//输入: nums = [1,3,5,6], target = 0
//输出: 0
// 
//
// 示例 5: 
//
// 
//输入: nums = [1], target = 0
//输出: 0
// 
//
// 
//
// 提示: 
//
// 
// 1 <= nums.length <= 104 
// -104 <= nums[i] <= 104 
// nums 为无重复元素的升序排列数组 
// -104 <= target <= 104 
// 
// Related Topics 数组 二分查找 
// 👍 1005 👎 0

package leetcode.editor.cn;
//Java:搜索插入位置
public class SearchInsertPosition{
    public static void main(String[] args) {
        Solution solution = new SearchInsertPosition().new Solution();
        // TO TEST
    }
        //leetcode submit region begin(Prohibit modification and deletion)
class Solution {
    public int searchInsert(int[] nums, int target) {
//        给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
        //使用二分查找
        int left=0,right=nums.length-1;
        int mid;
        while(left<=right){
            mid=(right+left)/2;
            if(nums[mid]==target){
                return mid;
            }
            if(nums[mid]>target) right=mid-1;
            else left=mid+1;
        }
        return left;
    }
}
//leetcode submit region end(Prohibit modification and deletion)

}
搜索旋转排序数组 二分查找 中等
// 整数数组 nums 按升序排列,数组中的值 互不相同 。
//
// 在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行了 旋转,使数组变为 [nums[k], nums[
// k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2
// ,4,5,6,7] 在下标 3 处经旋转后可能变为 [4,5,6,7,0,1,2] 。
//
// 给你 旋转后 的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的下标,否则返回 -1 。
//
//
//
// 示例 1:
//
//
// 输入:nums = [4,5,6,7,0,1,2], target = 0
// 输出:4
//
//
// 示例 2:
//
//
// 输入:nums = [4,5,6,7,0,1,2], target = 3
// 输出:-1
//
// 示例 3:
//
//
// 输入:nums = [1], target = 0
// 输出:-1
//
//
//
//
// 提示:
//
//
// 1 <= nums.length <= 5000
// -10^4 <= nums[i] <= 10^4
// nums 中的每个值都 独一无二
// 题目数据保证 nums 在预先未知的某个下标上进行了旋转
// -10^4 <= target <= 10^4
//
//
//
//
// 进阶:你可以设计一个时间复杂度为 O(log n) 的解决方案吗?
// Related Topics 数组 二分查找 👍 1781 👎 0

package leetcode.editor.cn;

// Java:搜索旋转排序数组
public class SearchInRotatedSortedArray {
    public static void main(String[] args) {
        Solution solution = new SearchInRotatedSortedArray().new Solution();
        // TO TEST
        int[] nums = new int[] {4, 5, 6, 7, 0, 1, 2};
        System.out.println(solution.search(nums, 3));
    }

    // leetcode submit region begin(Prohibit modification and deletion)
    class Solution {
        //
        // [nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]]
        // 输入:nums = [4,5,6,7,0,1,2], target = 0
        // 输出:4
        // 如果[l,mid-1]是有序数组,target的大小在nums[l]-nums[mid]中,应该将搜索范围缩小至l,mid-1,否则在mid+1,r中查找
        // 如果[mid+1,r]是有序数组,target的大小在nums[mid+1]-nums[r]中,应该将搜索范围缩小至mid+1,r,否则在l,mid-1中查找
        public int search(int[] nums, int target) {
            if (nums.length == 0) {
                return -1;
            }
            if (nums.length == 1) {
                return target == nums[0] ? 0 : -1;
            }
            int len = nums.length - 1;
            int l = 0, r = len;
            while (l >= 0 && l <= r) {
                int mid = (l + r) / 2;
                if (target == nums[mid]) {
                    return mid;
                }
                if (nums[l] <= nums[mid]) {
                    if (target >= nums[l] && target <= nums[mid]) {
                        r = mid - 1;
                    } else {
                        l = mid + 1;
                    }
                } else {
                    if (target >= nums[mid + 1] && target <= nums[r]) {
                        l = mid + 1;
                    } else {
                        r = mid - 1;
                    }
                }
            }
            return -1;
        }
    }
    // leetcode submit region end(Prohibit modification and deletion)

}
最长上升子序列(三) 二分查找 动态规划
/*最长上升子序列(三) 给定数组 arr ,设长度为 n ,输出 arr 的最长上升子序列。(如果有多个答案,请输出其中 按数值(注:区别于按单个字符的ASCII码值)进行比较的 字典序最小的那个)
输入:
        [1,2,8,6,4]
复制
返回值:
        [1,2,4]
复制
说明:
其最长递增子序列有3个,(1,2,8)、(1,2,6)、(1,2,4)其中第三个 按数值进行比较的字典序 最小,故答案为(1,2,4)*/
// 1 2 3 3 3
// 动态规划
// 维护一个单调栈,然后进行比较
// 使用二分法进行优化
/*
 * 状态定义:dp[i]代表nums以nums[i]结尾的最长子序列长度
 * 转移方程:dp[i] = max(dp[i],dp[j]+1) for j in [0,i)   
 * 初始状态:dp[i]所有元素都置为1.此时长度都为1
 * 返回值:返回dp列表的最长子序列
 */
/*
 * 状态定义:维护一个列表tails,每个元素的值代表长度为k+1的子序列尾部元素的值
 * 转移方程: res为tails的长度,代表当前的最长上升子序列长度,每轮遍历nums[k]时,通过二分法遍历[0,res)列表区间,找出nums[k]的大小分界点
 *  1.区间中存在tails[i]>nums[k]:将第一个满足tails[i]>nums[k]执行tails[i] = nums[k],更小的nums[k]后面更可能接一个比它大的数字
 *  2.区间中不存在tails[i]>nums[k]:nums[k]可以接在所有长度的子序列的后面,接在最长的后面
 * 初始状态:tails列表所有的值为0
 * 返回tails列表
 */
public static int[] LIS(int[] arr) {
    // write code here
    int[] tails = new int[arr.length];
    int[] res = new int[arr.length];
    int len = 0;
    Arrays.fill(tails, 0);
    for (int i = 0; i < arr.length; i++) {
        int mid = binarySearch(tails, arr[i], len);
        tails[mid] = arr[i];
        if (len == mid) {
            len++;
            res = Arrays.copyOfRange(tails, 0, len);
        }
    }
    return res;
}

// 二分查找
private static int binarySearch(int[] tails, int k, int len) {
    int left = 0, right = len;
    while (left < right) {
        int mid = (left + right) / 2;
        if (tails[mid] < k) {
            left = mid + 1;
        } else {
            right = mid;
        }
    }
    return left;
}
搜索二维矩阵 II
// 编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性:
//
//
// 每行的元素从左到右升序排列。
// 每列的元素从上到下升序排列。
//
//
//
//
// 示例 1:
//
//
// 输入:matrix = [[1,4,7,11,15],[2,5,8,12,19],[3,6,9,16,22],[10,13,14,17,24],[18,21
// ,23,26,30]], target = 5
// 输出:true
//
//
// 示例 2:
//
//
// 输入:matrix = [[1,4,7,11,15],[2,5,8,12,19],[3,6,9,16,22],[10,13,14,17,24],[18,21
// ,23,26,30]], target = 20
// 输出:false
//
//
//
//
// 提示:
//
//
// m == matrix.length
// n == matrix[i].length
// 1 <= n, m <= 300
// -10⁹ <= matrix[i][j] <= 10⁹
// 每行的所有元素从左到右升序排列
// 每列的所有元素从上到下升序排列
// -10⁹ <= target <= 10⁹
//
// Related Topics 数组 二分查找 分治 矩阵 👍 915 👎 0

package leetcode.editor.cn;

// Java:搜索二维矩阵 II
public class SearchA2dMatrixIi {
    public static void main(String[] args) {
        Solution solution = new SearchA2dMatrixIi().new Solution();
        // TO TEST
        System.out
            .println(solution.searchMatrix(new int[][] {{1, 4, 7, 11, 15}, {2, 5, 8, 12, 19}, {3, 6, 9, 16, 22}}, 5));
    }

    // leetcode submit region begin(Prohibit modification and deletion)
    class Solution {
        // 编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target
        // 每行的元素从左到右升序排列。
        // 每列的元素从上到下升序排列。
        // 输入:matrix = [[1,4,7,11,15],[2,5,8,12,19],[3,6,9,16,22],[10,13,14,17,24],[18,21
        // ,23,26,30]], target = 5
        // 输出:true
        /*
         * 二分查找 分治 矩阵 
         * // * 1 4 7 11 15 
         * // * 2 5 8 12 19 
         * // * 3 6 9 16 22 
         * // * 10 13 14 17 24 
         * // * 18 21 23 26 30
         */
        public boolean searchMatrix1(int[][] matrix, int target) {
            boolean flag;
            for (int i = 0; i < matrix.length; i++) {
                // 提前结束:当第一个元素大于target或者最后一个元素小于target
                if (matrix[i][0] > target || matrix[i][matrix[0].length - 1] < target) {
                    continue;
                }
                if (binarySearch(matrix[i], target)) {
                    return true;
                }
            }
            return false;
        }

        public boolean binarySearch(int[] nums, int target) {
            int left = 0, right = nums.length - 1;
            while (left <= right) {
                int mid = (left + right) / 2;
                if (nums[mid] == target) {
                    return true;
                }
                if (nums[mid] < target) {
                    left = mid + 1;
                } else {
                    right = mid - 1;
                }
            }
            return false;
        }

        /*
         * 从右上角的位置触发开始遍历,每次向左的数字会变小,向下的数字会变大
         * 如果target的值大于当前值,向下走
         * 如果target的值小于当前值,就向左走
         */
        public boolean searchMatrix(int[][] matrix, int target) {
            int i = 0, j = matrix[0].length - 1;
            int cur;
            while (true) {
                if (i >= matrix.length || j < 0) {
                    return false;
                }
                cur = matrix[i][j];
                if (cur == target) {
                    return true;
                }
                if (cur < target) {
                    i++;
                } else {
                    j--;
                }
            }
        }
    }
    // leetcode submit region end(Prohibit modification and deletion)

}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值