JAVA 算法 (中)

发布于:2024-11-28 ⋅ 阅读:(252) ⋅ 点赞:(0)

在这里插入图片描述

part 3

多数元素

在这里插入图片描述

	public int majorityElement(int [] nums){
        //对接消耗的数字
        int currentNum = nums[0];
        //对接消耗的数字个数
        int count =1;
        for (int i = 0; i < nums.length; i++) {
            if (count==0) {
                //对接消耗的数字设置为当前元素
                currentNum=nums[i];
                count=1;
            }else {
                if (nums[i]==currentNum){
                    //对接消耗的数字个数增加
                    count++;
                }else {
                    //数字不等,兑子,对拼消耗的数字个数减一
                    count--;
                }
            }
        }
        return currentNum;
    }

盛最多水的容器

不属于热点面试题
在这里插入图片描述

	public int maxArea(int [] height){
        int i =0;
        int j = height.length-1;
        int maxWater = 0;
        while (i<j){
            //height[i] 和 height[j],谁小就移动谁
            maxWater = height[i]<height[j]?
            Math.max(maxWater,(j-i)*height[i++]):
            Math.max(maxWater,(j-i)*height[j--]);
        }
        return maxWater;
    }

三数之和

在这里插入图片描述

 	/*暴力求解:排序+三重循环*/
    public List<List<Integer>> threeSum3Loop(int[] nums) {
        List<List<Integer>> results = new LinkedList<>();
        if(nums.length < 3) return results; //长度小于3,就不存在三数之和了
        //排序
        Arrays.sort(nums);
        //如果第一个数就大于0,那就没必要再查找下去了
        if(nums[0] > 0) return results;
        for(int i = 0; i < nums.length - 2; i++){
            int x = nums[i];
            if(x > 0) break;
            //去重,假如给的用例是[0, 0, 0, 0],当i第一次遇到0时,为首次遇见没有关系,可如果这次i的循环结束了
            //i+1进入第二次循环,那么这时候还是遇到0,为连续的第二次遇见,这就造成了前后重复,必须要跳过
            if(i > 0 && nums[i - 1] == nums[i]) continue;
            for(int j = i + 1; j < nums.length - 1; j++){
                int y = nums[j];
                //去重原理同i
                if(j > i + 1 && nums[j - 1] == nums[j]) continue;
                for(int k = j + 1; k < nums.length; k++){
                    int z = nums[k];
                    if(k > j + 1 && nums[k - 1] == nums[k]) continue;  //去重原理同j
                    if(x + y + z == 0){
                        List<Integer> list = new LinkedList<Integer>();
                        list.add(x);
                        list.add(y);
                        list.add(z);
                        results.add(list);
                    }
                }
            }
        }
        return results;
    }

    /*排序 + 两重循环 + HashMap*/
    public List<List<Integer>> threeSumWithMap(int[] nums) {
        List<List<Integer>> results = new LinkedList<>();
        if(nums.length < 3) return results; //长度小于3,就不存在三数之和了
        //排序
        Arrays.sort(nums);
        //如果第一个数就大于0,那就没必要再查找下去了
        if(nums[0] > 0) return results;
        //用一个HashMap将所有的元素加进去,这里的keys是nums[i],value是i
        //如果出现重复的key,那么新出现的value将会覆盖前面的一个value,
        // 用这个map来替代第三重循环
        HashMap<Integer, Integer> map = new HashMap<>();
        for(int i = 0; i < nums.length; i++){
            map.put(nums[i], i);
        }
        for(int i = 0; i < nums.length - 2; i++){
            int x = nums[i];
            //如果第一个数就大于0,那就没必要再查找下去了
            if(x > 0) break;
            //去重
            if(i > 0 && nums[i - 1] == nums[i]) continue;
            for(int j = i + 1; j < nums.length - 1; j++){
                int y = nums[j];
                if(x + y > 0) break;
                //去重
                if(j > i + 1 && nums[j - 1] == nums[j]) continue;
                //z为组合需要的那个值
                int z = 0 - x - y;
                //如果map集合里面有这个值,最重要的是要判断它的value(序号)是不是在i和j的后面
                //因为之前的第三重for循环的k是从j + 1开始的,所以它一定会大于j
                if(map.containsKey(z) && map.get(z) > j){
                    List<Integer> list = new LinkedList<Integer>();
                    list.add(x);
                    list.add(y);
                    list.add(z);
                    results.add(list);
                }
            }
        }
        return results;
    }

    /*排序 + 双指针解法*/
    public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> results = new LinkedList<>();
        if(nums.length < 3) return results; //长度小于3,就不存在三数之和了
        Arrays.sort(nums);
        int n = nums.length;
        for (int i = 0; i < n; i++) {
            //如果第一个数就大于0,那就没必要再查找下去了
            if (nums[i] > 0) break;
            int head = i + 1, tail = n - 1;
            //head指针和tail指针相对而行,但必须保证head在tail的左边
            while (head < tail) {
                //三数之和有三种情况:大于0,小于0,等于0
                int sum = nums[i] + nums[head] + nums[tail];
                if (sum < 0) head++; //三数之和小于0,左边head的那个数太小了,要把它向右移
                else if (sum > 0) tail--;//三数之和大于0,tail坐标的那个数太大了,要把它向左移
                else {
                    //三数之和等于0,添加到返回列表里面,并且同时将左指针右移,右指针左移,探索下一组适合的数据
                    List<Integer> list = new LinkedList<>();
                    list.add(nums[i]);
                    list.add(nums[head]);
                    list.add(nums[tail]);
                    results.add(list);
                    //去重
                    while (head+1 <= tail && nums[head] == nums[head+1]) head++;
                    while (head+1 <= tail && nums[tail] == nums[tail-1]) tail--;
                    head++;
                    tail--;
                }
            }
            //去重
            while (i+1 < n && nums[i+1] == nums[i]) i++;
        }
        return results;
    }

下一个排列

在这里插入图片描述

/**
 * @description :(LeetCode-31) 下一个排列
 * 实现获取 下一个排列 的函数,
 * 算法需要将给定数字序列重新排列成字典序中下一个更大的排列
 * (即,组合出下一个更大的整数)。
 * 如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。
 * 必须 原地 修改,只允许使用额外常数空间。
 */
public class NextPermutation_31 {

    public void nextPermutation(int[] nums) {
        int i = nums.length - 2;
        /*找到数组中的“小数”,退出循环就说明nums[i] < nums[i + 1]*/
        while (i >= 0 && nums[i] >= nums[i + 1]) {
            i--;
        }
        if (i >= 0) {
            int j = nums.length - 1;
            /*找到数组中的尽可能小的“大数,退出循环就说明nums[i] < nums[j]”*/
            while (j >= 0 && nums[i] >= nums[j]) {
                j--;
            }
            /*交换“小数”和“大数”的位置*/
            swap(nums, i, j);
        }
        /*将“大数”后面的所有数重置为升序*/
        reverse(nums, i + 1);
    }

    public void swap(int[] nums, int i, int j) {
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }

    public void reverse(int[] nums, int start) {
        int left = start, right = nums.length - 1;
        while (left < right) {
            swap(nums, left, right);
            left++;
            right--;
        }
    }

    public static void main(String[] args) {
        int[] nums1  = {5,3,1,6,2,4};
        int[] nums2  = {1,2,3,4,5,6};
        new NextPermutation_31().nextPermutation(nums1);
    }

}

旋转图像

在这里插入图片描述

	public void rotate(int[][] matrix) {
        if(matrix.length == 0 || matrix.length != matrix[0].length) {
            return;
        }
        int maxIndex = matrix.length - 1;
        /*先沿右上 - 左下的对角线镜像翻转*/
        for (int x = 0; x <= maxIndex; ++x){
            for (int y = 0; y <= maxIndex - x; ++y){
                int temp = matrix[x][y];
                matrix[x][y] = matrix[maxIndex - y][maxIndex - x];
                matrix[maxIndex - y][maxIndex - x] = temp;
            }
        }
        /*再沿水平中线上下翻转*/
        for (int i = 0; i <= (maxIndex >> 1); ++i){
            for (int j = 0; j <= maxIndex; ++j){
                int temp = matrix[i][j];
                matrix[i][j] = matrix[maxIndex - i][j];
                matrix[maxIndex - i][j] = temp;
            }
        }
    }

螺旋矩阵

在这里插入图片描述

	 public List<Integer> spiralOrder(int[][] matrix) {
        List<Integer> list = new ArrayList<Integer>();
        if(matrix == null || matrix.length == 0)
            return list;
        int rowLength = matrix.length;
        int colLength = matrix[0].length;
        int plies = 0;/*层数指示器*/

        /*获得矩阵的层数*/
        int count = (Math.min(rowLength, colLength)+1)/2;
        /*从外部向内部遍历,逐层打印数据*/
        while(plies < count) {
            /*每层矩阵的起始元素都是matrix[plies][plies]*/
            /*从左至右,到matrix[plies][colLength-1-plies]时停止,准备转向*/
            for (int j = plies; j < colLength-plies; j++) {
                list.add(matrix[plies][j]);
            }
            /*从上往下,本列的第一个元素已经在上面的“从左至右”中处理了,
            * 所以行从应该从plies+1开始,列(colLength-1-plies)维持不变,
            * 起始元素就是matrix[plies+1][colLength-1-plies],
            * 到matrix[rowLength-1-plies][colLength-1-plies]时停止,准备转向*/
            for (int j = plies+1; j < rowLength-plies; j++) {
                list.add(matrix[j][(colLength-1)-plies]);
            }
            /*从右往左,本行的第一个元素已经在上面的“从上往下”中处理了,
            * 所以列应该从(colLength-1-plies)-1开始,行维持不变,
            * 起始元素就是matrix[rowLength-1-plies][(colLength-1-plies)-1],
            * 到matrix[rowLength-1-plies][plies]时停止,准备转向*/
            /*rowLength-1-plies == plies,表示本层只有单行,不必再处理*/
            for (int j = (colLength-1)-(plies+1); j >= plies && (rowLength-1-plies != plies); j--) {
                list.add(matrix[(rowLength-1)-plies][j]);
            }
            /*从下往上,本列的第一个元素已经在上面的“从右往左”中处理了,
             * 所以行从应该从(rowLength-1-plies)-1开始,列(plies)维持不变,
             * 起始元素就是matrix[(rowLength-1-plies)-1][plies],
             * 到matrix[plies+1][plies]时停止*/
            /*colLength-1-plies != plies,表示本层只有单列,不必再处理*/
            for (int j = (rowLength-1)-(plies+1); j >= plies+1 && (colLength-1-plies != plies); j--) {
                list.add(matrix[j][plies]);
            }
            plies++; /*层数累加*/
        }
        return list;
    }

跳跃游戏

在这里插入图片描述

	public boolean canJump(int[] nums) {
        if(nums.length < 2) return true;
        /*从倒数第二个元素开始倒序往前找*/
        for(int curr = nums.length-2; curr>=0;curr--){
            if(nums[curr] == 0){
                /*如果存在当前元素=0的情况,需要找到可以跳过这个0的数组元素值*/
                int neededJumps = 1;
                while(neededJumps > nums[curr]){
                    /*要记得,每倒序往前走一个元素,需要跳过的数值也要加1*/
                    neededJumps++;
                    curr--;
                    if(curr < 0) return false;
                }
            }
        }
        return true;
    }

    /*基于动态规划的实现*/
    public boolean canJumpWithDP(int[] nums) {
        int n=nums.length;
        int maxStep=nums[0];
        for(int i=1;i<n;i++){
            if(maxStep==0)  return false;
            maxStep=Math.max(maxStep-1,nums[i]);
        }
        return true;
    }

数组中的第K个最大元素

在这里插入图片描述
这道题是面试的高频考题,同时也是基础排序算法的应用。
仔细分析题目,需要找的是数组排序后的第 k 个最大的元素,所以最简单的办法,首先把数组排序后,返回第 k 个最大的元素就行了。

	 //基于JDK中sort方法实现
	 /*数组排序后,直接返回第 k 个元素*/
    public int findKthLargest(int[] nums, int k) {
        final int N = nums.length;
        Arrays.sort(nums);
        return nums[N - k];
    }
	 /*基于快速排序的分区实现*/
    public int findKthLargest(int[] nums, int k) {
        if (nums.length == 0) {
            return 0;
        }
        int len = nums.length;
        int left = 0;
        int right = len - 1;

        // 转换一下,第 k 大元素的下标是 len - k
        int target = len - k;

        while (true) {
            int index = partition(nums, left, right);
            if (index == target) {
                return nums[index];
            } else if (index < target) {
                left = index + 1;
            } else {
                right = index - 1;
            }
        }
    }

    /**
     * 快速排序分区方法
     */
    public int partition(int[] array, int start, int end) {
        /*只有一个元素时,无需再分区*/
        if(start == end) return start;
        /*随机选取一个基准数*/
        int pivot = (int) (start + Math.random() * (end - start + 1));
        /*zoneIndex是分区指示器,初始值为分区头元素下标减一*/
        int zoneIndex = start - 1;
        /*将基准数和分区尾元素交换位置*/
        swap(array, pivot, end);
        for (int i = start; i <= end; i++){
            /*当前元素小于等于基准数*/
            if (array[i] <= array[end]) {
                /*首先分区指示器累加*/
                zoneIndex++;
                /*当前元素在分区指示器的右边时,交换当前元素和分区指示器元素*/
                if (i > zoneIndex)
                    swap(array, i, zoneIndex);
            }
        }
        return zoneIndex;
    }

    /**
     * 交换数组内两个元素
     */
    public void swap(int[] array, int i, int j) {
        int temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }
	//堆排序方式
    /* 直接利用JDK中的优先队列来实现堆排序,进而实现题目的要求
     * JDK中的优先队列默认是最小堆,最大堆需要自行实现Comparator*/
    public int findKthLargestWithPQ(int[] nums, int k) {
        /*使用一个含有 k 个元素的最小堆*/
        PriorityQueue<Integer> pq = new PriorityQueue<>(k);
        for (int i = 0; i < k; i++) {
            pq.add(nums[i]);
        }
        for (int i = k; i < nums.length; i++) {
            /*获取但并不弹出栈顶元素*/
            /*当前遍历的元素比堆顶元素大,当前元素替换栈顶元素*/
            if (nums[i] > pq.peek()) {
                pq.poll();
                pq.offer(nums[i]);
            }
        }
        return pq.peek();
    }

    /*利用最大堆实现本题要求*/
    public int findKthLargest(int[] nums, int k) {
        int len = nums.length;
        /*1.构建一个最大堆*/
        buildMaxHeap(nums,len);
        for (int i = nums.length - 1; i >= nums.length - k + 1; --i) {
            swap(nums, 0, i);
            --len;
            adjustHeap(nums, 0,len);
        }
        return nums[0];
    }

    /* 建立最大堆*/
    public void buildMaxHeap(int[] array, int heapSize) {
        /*从最后一个非叶子节点开始向上构造最大堆*/
        for (int i = (heapSize/2-1); i >= 0; i--) {
            adjustHeap(array, i,heapSize);
        }
    }

    /* 调整使之成为最大堆*/
    public void adjustHeap(int[] array, int i, int heapSize) {
        int maxIndex = i;
        int left = 2*i+1;
        int right = 2*(i+1);
        /*如果有左子树,且左子树大于父节点,则将最大指针指向左子树*/
        if (left < heapSize && array[left] > array[maxIndex])
            maxIndex = left;
        /*如果有右子树,且右子树大于父节点且大于左子树,则将最大指针指向右子树*/
        if (right < heapSize && array[right] > array[maxIndex]&&array[right]>array[left])
            maxIndex = right;
        /*如果父节点不是最大值,则将父节点与最大值交换,并且递归调整与父节点交换的位置。*/
        if (maxIndex != i) {
            swap(array, maxIndex, i);
            adjustHeap(array, maxIndex,heapSize);
        }
    }

    /* 交换数组内两个元素*/
    public static void swap(int[] array, int i, int j) {
        int temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }

前 K 个高频元素

在这里插入图片描述

 /*LeetCode官方解法
    直接利用JDK中的优先队列来实现堆排序,进而实现题目的要求
     * 这里需要自行实现Comparator*/
    public int[] topKFrequentWithPQ(int[] nums, int k) {
        Map<Integer, Integer> map = new HashMap<>();
        for(int n: nums){
            map.put(n, map.getOrDefault(n,0)+1);
        }

        /*每个放进优先队列的数组int[]只有两个元素,
        第一个元素代表数组的值,第二个元素代表了该值出现的次数**/
        PriorityQueue<int[]> minHeap = new PriorityQueue<>(k,(a,b)->(a[1]-b[1]));
        for(Map.Entry<Integer,Integer> entry: map.entrySet()){
            int count = entry.getValue();
            if (minHeap.size() == k) {
                if (minHeap.peek()[1] < count) {
                    minHeap.poll();
                    minHeap.offer(new int[]{entry.getKey(), count});
                }
            }else{
                minHeap.offer(new int[]{entry.getKey(), count});
            }
        }

        int[] ret = new int[k];
        for (int i = 0; i < k; ++i) {
            ret[i] = minHeap.poll()[0];
        }
        return ret;
    }

寻找两个正序数组的中位数

在这里插入图片描述

	public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        int m = nums1.length, n = nums2.length;
        /*将数组元素个数总数为奇数和偶数情况统一处理
        * 奇数个数: left==right
        * 偶数个数: right==left+1
        * 比如 m=4,n=6,则left = 5,right = 6
        * m=4,n=5,则left = 5,right = 5 */
        int left = (m + n + 1) / 2;
        int right = (m + n + 2) / 2;
        return (getkth(nums1, 0, nums2, 0, left)
                + getkth(nums1, 0, nums2, 0, right)) / 2.0;
    }

    public double getkth(int[] nums1, int nums1Start, int[] nums2, int nums2Start, int k) {
        /*有任何一个数组空了,直接返回另外一个非空数组的中位数结果*/
        if (nums1Start > nums1.length - 1)
            return nums2[nums2Start + k - 1];
        if (nums2Start > nums2.length - 1)
            return nums1[nums1Start + k - 1];
        /*找第1小的数字,所以只需判断两个数组中第一个数字哪个小就可以了*/
        if (k == 1)
            return Math.min(nums1[nums1Start], nums2[nums2Start]);
        /*获得两个数组中第k/2个元素的值*/
        int num1Mid = Integer.MAX_VALUE, num2Mid = Integer.MAX_VALUE;
        if (nums1Start + k/2 - 1 < nums1.length)
            num1Mid = nums1[nums1Start + k/2 - 1];
        if (nums2Start + k/2 - 1 < nums2.length)
            num2Mid = nums2[nums2Start + k/2 - 1];

        /*比较两值的大小,然后以递归的形式继续更小数组的处理*/
        if (num1Mid < num2Mid)
            /*num1Mid更小,排除num1[]中小于等于num1Mid的部分*/
            return getkth(nums1, nums1Start + k/2,
                    nums2, nums2Start,k - k/2);
        else
            /*num2Mid更小,排除num2[]中小于等于num2Mid的部分*/
            return getkth(nums1, nums1Start, nums2,
                    nums2Start + k/2, k - k/2);
    }

链表类

两数相加

在这里插入图片描述

	public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        int carry = 0;
        ListNode p, dummy = new ListNode(0);
        p = dummy;
        while (l1 != null || l2 != null || carry != 0) {
            if (l1 != null) {
                carry += l1.val;
                l1 = l1.next;
            }
            if (l2 != null) {
                carry += l2.val;
                l2 = l2.next;
            }
            p.next = new ListNode(carry%10);
            carry /= 10;
            p = p.next;
        }
        return dummy.next;
    }

删除链表的倒数第 N 个结点

在这里插入图片描述

	 public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode start = new ListNode(0);
        ListNode slow = start, fast = start;
        slow.next = head;

        /*先移动快指针n步*/
        for(int i=1; i<=n+1; i++)   {
            fast = fast.next;
        }
        /*同时移动快慢指针*/
        while(fast != null) {
            slow = slow.next;
            fast = fast.next;
        }
        /*去除不要的第N个结点*/
        slow.next = slow.next.next;
        return start.next;
    }

反转链表 II

在这里插入图片描述

	public ListNode reverseBetween(ListNode head, int left, int right) {
        ListNode dummy = new ListNode(0);
        dummy.next = head;
        ListNode preNode = dummy;
        /*将preNode移动left-1步
        *preNode的next就是反转的开始 */
        for(int i = 0; i<left-1; i++) preNode = preNode.next;

        /*要反转的链表结点就是preNode的下个结点*/
        ListNode startNode = preNode.next;
        /*同时记录要反转的下一个结点*/
        ListNode nextNode = startNode.next;

        for(int i=0; i<right-left; i++) {
            startNode.next = nextNode.next;
            nextNode.next = preNode.next;
            preNode.next = nextNode;
            nextNode = startNode.next;
        }
        return dummy.next;
    }

重排链表

在这里插入图片描述

/**
 * @description :(LeetCode-143) 重排链表
 * 给定一个单链表 L 的头节点 head ,单链表 L 表示为:
 *  L0 → L1 → … → Ln-1 → Ln
 * 请将其重新排列后变为:
 * L0 → Ln → L1 → Ln-1 → L2 → Ln-2 → …
 * 不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
 */
	public void reorderList(ListNode head) {
        if(head==null||head.next==null) return;

        /*寻找链表的中间结点,和876不同
        这里如果结点数是偶数个,
        返回的是中间结点的第一个结点*/
        ListNode slow=head;
        ListNode fast=head;
        while(fast.next!=null&&fast.next.next!=null){
            slow=slow.next;
            fast=fast.next.next;
        }

        /*反转后半部分链表*/
        ListNode preMiddle=slow;
        ListNode preCurrent=slow.next;
        while(preCurrent.next!=null){
            ListNode current=preCurrent.next;
            preCurrent.next=current.next;
            current.next=preMiddle.next;
            preMiddle.next=current;
        }

        /*“梅花间隔”的形式将反转后的链表插入到前半部分链表*/
        slow=head;
        fast=preMiddle.next;
        while(slow!=preMiddle){
            preMiddle.next=fast.next;
            fast.next=slow.next;
            slow.next=fast;
            slow=fast.next;
            fast=preMiddle.next;
        }
    }

LRU 缓存机制

在这里插入图片描述

/**
 * @description :(LeetCode-146) LRU 缓存机制
 * 运用你所掌握的数据结构,设计和实现一个  LRU (最近最少使用) 缓存机制 。
 * 实现 LRUCache 类:
 * LRUCache(int capacity) 以正整数作为容量 capacity 初始化 LRU 缓存
 * int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。
 * void put(int key, int value) 如果关键字已经存在,则变更其数据值;
 * 如果关键字不存在,则插入该组「关键字-值」。当缓存容量达到上限时,
 * 它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。
 */
public class LRUCache {

    /*双向链表的结点定义,凡是使用过的结点都会移动到链表的头部*/
    class LinkedNode {
        int key;
        int value;
        LinkedNode pre;
        LinkedNode next;
    }

    /*头插法插入结点*/
    private void addNode(LinkedNode node) {
        node.pre = head;
        node.next = head.next;
        head.next.pre = node;
        head.next = node;
    }

    /*将结点从链表摘除*/
    private void removeNode(LinkedNode node){
        LinkedNode pre = node.pre;
        LinkedNode post = node.next;
        pre.next = post;
        post.pre = pre;
    }

    /*将结点移动到头部*/
    private void moveToHead(LinkedNode node){
        this.removeNode(node);
        this.addNode(node);
    }

    /*移除尾部结点*/
    private LinkedNode removeTail(){
        LinkedNode res = tail.pre;
        this.removeNode(res);
        return res;
    }

    private HashMap<Integer, LinkedNode> cache = new HashMap<Integer, LinkedNode>();
    /*链表的头尾指针结点,不存放实际业务数据*/
    private LinkedNode head, tail;

    public LRUCache(int capacity) {
        head = new LinkedNode();
        /*用head结点的key存放容量
        * value存放当前链表中结点的实际数量*/
        head.key = capacity;
        head.value = 0;
        head.pre = null;
        tail = new LinkedNode();
        tail.next = null;
        head.next = tail;
        tail.pre = head;
    }

    public int get(int key) {
        LinkedNode node = cache.get(key);
        if(node == null){
            return -1;
        }
        /*结点被使用了,移动到头部*/
        this.moveToHead(node);
        return node.value;
    }

    public void put(int key, int value) {
        LinkedNode node = cache.get(key);
        if(node == null){
            LinkedNode newNode = new LinkedNode();
            newNode.key = key;
            newNode.value = value;
            this.cache.put(key, newNode);
            this.addNode(newNode);
            ++head.value;
            if(head.value > head.key){
                /*超出了容量限制,移除尾部结点*/
                LinkedNode tail = this.removeTail();
                this.cache.remove(tail.key);
                --head.value;
            }
        }else{
            /*更新结点数据,并移动到头部*/
            node.value = value;
            this.moveToHead(node);
        }
    }
}

合并K个升序链表

在这里插入图片描述

	public ListNode mergeKLists(ListNode[] lists) {
        return merge(lists, 0, lists.length - 1);
    }

    public ListNode merge(ListNode[] lists, int left, int right) {
        if (left == right) {
            return lists[left];
        }
        if (left > right) {
            return null;
        }
        int mid = (left + right) >> 1;
        return mergeTwoLists(merge(lists, left, mid), merge(lists, mid + 1, right));
    }

    /*循环+双指针解决两个链表的合并
    * 代码来自(LeetCode-21)合并两个有序链表
    * MergeTwoSortLists_21.java*/
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        if(l1 == null) return l2;
        if(l2 == null) return l1;
        ListNode resultNode  = new ListNode(0);
        ListNode p = resultNode ;
        while(l1 != null && l2 != null){
            if(l1.val < l2.val){
                p.next = l1;
                l1 = l1.next;
            }else{
                p.next = l2;
                l2 = l2.next;
            }
            p = p.next;
        }
        if(l1 != null)
            p.next = l1;
        if(l2 != null)
            p.next = l2;
        return resultNode.next;
    }

    /*使用堆排序的思想来实现,利用了JDK中的优先队列*/
    public ListNode mergeKListsWithPQ(ListNode[] lists) {
        if (lists==null||lists.length==0) return null;

        PriorityQueue<ListNode> queue= new PriorityQueue<ListNode>(lists.length,new Comparator<ListNode>(){
            @Override
            public int compare(ListNode o1,ListNode o2){
                if (o1.val<o2.val)
                    return -1;
                else if (o1.val==o2.val)
                    return 0;
                else
                    return 1;
            }
        });

        /*dummy用以返回结果链表
        *tail用以获得堆顶元素并合并链表*/
        ListNode dummy = new ListNode(0);
        ListNode tail=dummy;

        /*首先将所有链表的首节点放入队列,进行初始化*/
        for (ListNode node:lists)
            if (node!=null)
                queue.add(node);

        while (!queue.isEmpty()){
            /*将堆顶的元素放入结果链表*/
            tail.next=queue.poll();
            tail=tail.next;
            /*将堆顶的元素的下一个结点放入队列*/
            if (tail.next!=null)
                queue.add(tail.next);
        }
        return dummy.next;
    }

K 个一组翻转链表

在这里插入图片描述

/**
 * @description :(LeetCode-25) K 个一组翻转链表
 * 给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。
 * k 是一个正整数,它的值小于或等于链表的长度。
 * 如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。
 */
public class ReverseNodesInKGroup_25 {

    public ListNode reverseKGroup(ListNode head, int k) {
        ListNode begin;
        if (head==null || head.next ==null || k==1)
            return head;
        ListNode dummy = new ListNode(-1);
        dummy.next = head;
        begin = dummy;
        int i=0;
        while (head != null){
            i++;
            if (i%k == 0){
                begin = reverse(begin, head.next);
                head = begin.next;
            } else {
                head = head.next;
            }
        }
        return dummy.next;
    }

    /*反转链表结点的方法*/
    public ListNode reverse(ListNode preSubList, ListNode subListNext){
        ListNode startNode = preSubList.next;
        ListNode nextNode = startNode.next;
        ListNode inPreNode = preSubList;
        while(startNode.next != subListNext){
            startNode.next = nextNode.next;
            nextNode.next = inPreNode.next;
            inPreNode.next = nextNode;
            nextNode = startNode.next;
        }
        return startNode;
    }
}

part4

栈与队列

每日温度

在这里插入图片描述

	/**
	 * @description :(LeetCode-739) 每日温度
	 * 请根据每日气温列表 temperatures ,请计算在每一天需要等几天才会有更高的温度。
	 * 如果气温在这之后都不会升高,请在该位置用 0 来代替。
	 */

	/*标准栈的解法*/
    public int[] dailyTemperatures(int[] temperatures) {
        Deque<Integer> stack = new LinkedList<Integer>();;
        int[] ret = new int[temperatures.length];
        for(int i = 0; i < temperatures.length; i++) {
            while(!stack.isEmpty() && temperatures[i] > temperatures[stack.peek()]) {
                int idx = stack.pop();
                ret[idx] = i - idx;
            }
            stack.push(i);
        }
        return ret;
    }

    /*用倒序遍历数组来求解*/
    public int[] dailyTemperaturesUseArray(int[] temperatures) {
        int length = temperatures.length;
        int[] ret = new int[length];

        /*从右向左遍历,数组最后一个元素无需处理*/
        for (int i = length - 2; i >= 0; i--) {
            /*backIdx表示从当前元素开始往后寻找获得需要的结果,
            backIdx=backIdx+ret[backIdx]是为了利用已经有的结果进行跳跃*/
            for (int backIdx = i + 1; backIdx < length; backIdx=backIdx+ret[backIdx]) {
                if (temperatures[backIdx] > temperatures[i]) {
                    ret[i] = backIdx - i;
                    break;
                } else if (ret[backIdx] == 0) {/*遇到0表示后面不会有更大的值,那当然当前值就应该也为0*/
                    ret[i] = 0;
                    break;
                }
            }
        }
        return ret;
    }

柱状图中最大的矩形

在这里插入图片描述

	/**
	 * @description :(LeetCode-84) 柱状图中最大的矩形
	 * 给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
	 * 求在该柱状图中,能够勾勒出来的矩形的最大面积。
	 */
	 /*基于单调栈的实现*/
    public int largestRectangleArea(int[] heights) {
        int len = heights.length;
        if (len == 0) {
            return 0;
        }
        if (len == 1) {
            return heights[0];
        }

        int result = 0;
        Deque<Integer> stack = new LinkedList<Integer>();
        /*遍历数组*/
        for (int i = 0; i < len; i++) {
            while (!stack.isEmpty() && heights[i] < heights[stack.peekLast()]) {
                /*当前需要计算面积的元素的下标*/
                int curIndex = stack.pollLast();
                /*获得当前元素的值,也就是矩形的高*/
                int curHeight = heights[curIndex];
                /*计算矩形的宽*/
                int curWidth;
                if (stack.isEmpty()) {
                    /*栈为空,表示目前遍历过所有元素都比当前的i要大,i是最小的一个,
                    宽度就可以直接取i的值*/
                    curWidth = i;
                } else {
                    curWidth = i - stack.peekLast() - 1;
                }
                result = Math.max(result, curHeight * curWidth);
            }
            stack.addLast(i);
        }

        /*处理目前还在栈中的元素*/
        while (!stack.isEmpty()) {
            int curHeight = heights[stack.pollLast()];
            int curWidth;
            if (stack.isEmpty()) {
                curWidth = len;
            } else {
                curWidth = len - stack.peekLast() - 1;
            }
            result = Math.max(result, curHeight * curWidth);
        }
        return result;
    }

    /*基于单调栈+哨兵的实现*/
    public int largestRectangleAreaWithSentinel(int[] heights) {
        int len = heights.length;
        if (len == 0) {
            return 0;
        }
        if (len == 1) {
            return heights[0];
        }
        int result = 0;

        int[] heightsWithSentinel = new int[len + 2];
        /*头哨兵,不会大于输入数组里任何一个元素,它肯定不会出栈,因此栈一定不会为空*/
        heightsWithSentinel[0] = 0;
        System.arraycopy(heights, 0, heightsWithSentinel, 1, len);
        /*尾哨兵,它不会大于输入数组里任何一个元素,
        它会让所有输入数组里的元素出栈(头边的哨兵元素除外)*/
        heightsWithSentinel[len + 1] = 0;
        len += 2;
        heights = heightsWithSentinel;

        Deque<Integer> stack = new LinkedList<Integer>();
        /*先放入头哨兵,在循环里就不用做非空判断*/
        stack.addLast(0);

        for (int i = 1; i < len; i++) {
            while (heights[i] < heights[stack.peekLast()]) {
                int curHeight = heights[stack.pollLast()];
                int curWidth = i - stack.peekLast() - 1;
                result = Math.max(result, curHeight * curWidth);
            }
            stack.addLast(i);
        }
        return result;
    }

    /*基于数组的实现*/
    public int largestRectangleAreaWithArray(int[] heights) {
        if (heights == null || heights.length == 0) {
            return 0;
        }
        /*存放元素右侧和左侧仅次于当前元素高度(或相同高度)的邻居元素下标的数组*/
        int[] leftLower = new int[heights.length];
        int[] rightLower = new int[heights.length];
        /*初始化*/
        rightLower[heights.length - 1] = heights.length;
        leftLower[0] = -1;

        /*正序遍历,对当前元素寻找左侧第一个小于当前元素高度(或相同高度)的邻居元素*/
        for (int i = 1; i < heights.length; i++) {
            /*从当前元素左侧第一个邻居元素开始,往左侧寻找*/
            int neighbourLeft  = i - 1;
            /*不断的往左寻找,一直到数组头或者左侧第一个小于等于当前元素的邻居元素为止*/
            while (neighbourLeft >= 0 && heights[neighbourLeft] >= heights[i]) {
                /*在我们的遍历过程中,当前元素的所有左侧元素我们已经处理过了,
                那就是说我们可以重用先前计算的结果快速跳转,比如数组
                {3,6,5,7,4,8,1,0},
                假如当前元素是下标为4的元素4,很明显,前面的3,6,5,7都已经处理过,
                leftLower=
                [-1, 0, 0, 2, 0, 0, 0, 0],
                7会被5卡住,5和6都会被3卡住,4比7、5、6都小,
                可以快速到达5或6被卡住的地方判断4会不会也被卡住,
                neighbourLeft的变化将是 (4-1)3-->2=leftLower[3]-->0=leftLower[2]
                而heights[0]<heights[4],退出循环,最终leftLower[4]=0*/
                neighbourLeft  = leftLower[neighbourLeft];
            }
            leftLower[i] = neighbourLeft ;
        }

        /*逆序遍历,对当前元素寻找右侧第一个小于当前元素高度(或相同高度)的邻居元素
        * 思想和上面的正序遍历一样,请自行分析*/
        for (int i = heights.length - 2; i >= 0; i--) {
            int neighbourRight = i + 1;
            while (neighbourRight < heights.length && heights[neighbourRight] >= heights[i]) {
                neighbourRight = rightLower[neighbourRight];
            }
            rightLower[i] = neighbourRight;
        }

        /*已经找到了所有元素对应的邻居元素,寻找最大的那个*/
        int maxArea = 0;
        for (int i = 0; i < heights.length; i++) {
            maxArea = Math.max(maxArea, heights[i] * (rightLower[i] - leftLower[i] - 1));
        }
        return maxArea;
    }

基本计算器

在这里插入图片描述

	/**
	 * @description :(LeetCode-224) 基本计算器
	 * 给你一个字符串表达式 s ,请你实现一个基本计算器来计算并返回它的值。
	 * 示例 1:
	 * 输入:s = "1 + 1"
	 * 输出:2
	 */
	 public int calculate(String s) {
        /*存放当前的计算结果*/
        int result = 0;
        /*存放当前待计算数字*/
        int num = 0;
        /*符号,加号(+1)或者减号(-1)*/
        int sign = 1;
        Deque<Integer> stack = new LinkedList<Integer>();

        char[] chars = s.toCharArray();
        int len = chars.length;

        for (int i = 0; i < len; i++) {
            char c = chars[i];
            /*如果当前字符为' ',无需处理*/
            if (c == ' ') {
                continue;
            }
            /*如果当前字符是一个数字,则用num进行记录,当然需要考虑到数字不止一位
            所以 num = num * 10 + c - '0'*/
            if (c >= '0' && c <= '9') {
                num = num * 10 + c - '0';
                /*判断当前数字是否已经取完,只要后续字符在'0'到'9'之间,说明当前数字还未结束*/
                if (i < len - 1 && '0' <= chars[i + 1] && chars[i + 1] <= '9') {
                    continue;
                }
            /*如果当前字符为计算符号*/
            } else if (c == '+' || c == '-') {
                num = 0;
                sign = c == '+' ? 1 : -1;
            /*如果当前符号为'(',需要将前面的计算结果和符号入栈*/
            } else if (c == '(') {
                stack.push(result);
                stack.push(sign);
                result = 0;
                sign = 1;
            /*如果当前符号为')',括号里的表达式已经计算完成了,
            可以和括号外的结果进行合并计算了*/
            } else if (c == ')') {
                /*计算符号出栈*/
                sign = stack.pop();
                /*将num替换为括号中的计算结果*/
                num = result;
                /*将result替换为括号前边的计算结果*/
                result = stack.pop();
            }
            /*每循环一次,得到一个新的结果*/
            result += sign * num;
        }
        return result;
    }

验证二叉搜索树

在这里插入图片描述

/**
 * @description :(LeetCode-98) 验证二叉搜索树
 * 给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。
 * 有效 二叉搜索树定义如下:
 * 节点的左子树只包含 小于 当前节点的数。
 * 节点的右子树只包含 大于 当前节点的数。
 * 所有左子树和右子树自身必须也是二叉搜索树。
 */

    /*基于二叉搜索树定义/性质实现*/
    public boolean isValidBST(TreeNode root) {
        return isValidBST(root, Long.MIN_VALUE, Long.MAX_VALUE);
    }

    public boolean isValidBST(TreeNode root, long minVal, long maxVal) {
        /*要么根节点本身为null,要么递归到了叶子节点,所以同时也是递归终止条件*/
        if (root == null) return true;
        /*节点不满足二叉搜索树定义,直接返回false*/
        if (root.val >= maxVal || root.val <= minVal) return false;
        /*对当前节点下的左右子树递归去判断,变化左子树的最大范围和右子树的最小范围*/
        return isValidBST(root.left, minVal, root.val) && isValidBST(root.right, root.val, maxVal);
    }

    /*基于中序遍历的栈实现*/
    public boolean isValidBSTInOrderWithStack(TreeNode root) {
        if (root == null) return true;
        Deque<TreeNode> stack = new LinkedList<TreeNode>();
        /*用来存储前一个节点的值*/
        TreeNode pre = null;
        while (root != null || !stack.isEmpty()) {
            while (root != null) {
                stack.push(root);
                root = root.left;
            }
            root = stack.pop();
            /*如果当前节点的值不大于前一个节点的值,不符合二叉搜索树定义*/
            if(pre != null && root.val <= pre.val) return false;
            pre = root;
            root = root.right;
        }
        return true;
    }

    TreeNode pre=null;
    /*基于中序遍历的递归实现*/
    public boolean isValidBSTInOrder(TreeNode root) {
        if(root==null) return true;
        if(!isValidBST(root.left))
            return false;
        /*这里用于判断是不是找到最左边的节点了,如果是就不用比较*/
        if(pre==null)
            pre=root;
        /*如果不是就比较这个节点和前一个节点的值*/
        else if(root.val<=pre.val)
            return false;
        pre=root;
        return isValidBST(root.right);
    }

二叉树的层序遍历

在这里插入图片描述

/**
 * @description :(LeetCode-102) 二叉树的层序遍历
 * 给你一个二叉树,请你返回其按 层序遍历 得到的节点值。
 * (即逐层地,从左到右访问所有节点)。
 */
    /*基于队列的实现*/
    public List<List<Integer>> levelOrderWithQueue(TreeNode root) {
        Queue<TreeNode> queue = new LinkedList<TreeNode>();
        List<List<Integer>> result = new LinkedList<List<Integer>>();

        if(root == null) return result;

        /*放入根结点*/
        queue.offer(root);
        while(!queue.isEmpty()){
            /*获得每层的节点数*/
            int levelNodeNum = queue.size();
            /*每层的结果集*/
            List<Integer> subList = new LinkedList<Integer>();
            /*处理本层的节点,包括输出节点值和将节点的左右子树入队*/
            for(int i=0; i<levelNodeNum; i++) {
                if(queue.peek().left != null) queue.offer(queue.peek().left);
                if(queue.peek().right != null) queue.offer(queue.peek().right);
                subList.add(queue.poll().val);
            }
            result.add(subList);
        }
        return result;
    }

    /*基于递归的实现*/
    public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> result = new LinkedList<>();
        levelPreVisit(result, root, 0);
        return result;
    }

    public void levelPreVisit(List<List<Integer>> result, TreeNode root, int height) {
        if (root == null) return;
        /*新的层需要增加新的子List*/
        if (height >= result.size()) {
            result.add(new LinkedList<Integer>());
        }
        /*对当前结点的处理需要注意所在的层*/
        result.get(height).add(root.val);
        levelPreVisit(result, root.left, height+1);
        levelPreVisit(result, root.right, height+1);
    }

二叉树的锯齿形层序遍历

在这里插入图片描述

/**
 * @description :(LeetCode-103) 二叉树的锯齿形层序遍历
 * 给定一个二叉树,返回其节点值的锯齿形层序遍历。
 * (即先从左往右,再从右往左进行下一层遍历,以此类推,层与层之间交替进行)。
 */
    public List<List<Integer>> zigzagLevelOrder(TreeNode root)
    {
        List<List<Integer>> sol = new ArrayList<>();
        travel(root, sol, 0);
        return sol;
    }

    private void travel(TreeNode root, List<List<Integer>> result, int height){
        if(root == null) return;

        /*新的层需要增加新的子List*/
        if (height >= result.size()) {
            result.add(new LinkedList<Integer>());
        }

        List<Integer> subList  = result.get(height);
        /*因为要锯齿形层序遍历,所以对层高需要进行判断
        * 如果是从左到右,按普通的尾部追加节点即可
        * 如果是从右到左,使用头插法增加节点
        * */
        if(height % 2 == 0)
            subList.add(root.val);
        else
            subList.add(0, root.val);

        travel(root.left, result, height + 1);
        travel(root.right, result, height + 1);
    }

从前序与中序遍历序列构造二叉树

在这里插入图片描述

/**
 * @description :(LeetCode-105) 从前序与中序遍历序列构造二叉树
 * 给定一棵树的前序遍历 preorder 与中序遍历inorder。请构造二叉树并返回其根节点。
 */
    /*基于递归的实现*/
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        return buildTree(0, 0, inorder.length - 1, preorder, inorder);
//        Map<Integer, Integer> inMap = new HashMap<Integer, Integer>();
//
//        for(int i = 0; i < inorder.length; i++) {
//            inMap.put(inorder[i], i);
//        }
//        return buildTreeWithMap(0, 0, inorder.length - 1, preorder, inorder,inMap);
    }

    /**
     * @param preStart 前序数组的第一个元素,也就是根结点
     * @param inStart 中序数组的开始元素
     * @param inEnd 中序数组的结束元素
     * @return
     */
    public TreeNode buildTree(int preStart, int inStart, int inEnd, int[] preorder, int[] inorder) {
        if (preStart > preorder.length - 1 || inStart > inEnd) {
            return null;
        }
        /*构建二叉树的根结点*/
        TreeNode root = new TreeNode(preorder[preStart]);
        /*存放根结点在中序数组的位置*/
        int inIndex = 0;
        /*在中序数组中寻找根结点所在位置*/
        for (int i = inStart; i <= inEnd; i++) {
            if (inorder[i] == root.val) {
                inIndex = i;
            }
        }

        /*递归的处理左右子树*/
        /*左子树的根结点就是前序数组中preStart的后一个元素,
        范围则是中序数组的inStart到inIndex - 1之间*/
        root.left = buildTree(preStart + 1, inStart, inIndex - 1, preorder, inorder);
        /*右子树的根结点所在位置需要跳过整个左子树节点数目,也就是inIndex - inStart,
        所以根结点的位置就是preStart + inIndex - inStart再加1
        范围则是中序数组的inIndex + 1到n之间*/
        root.right = buildTree(preStart + inIndex - inStart + 1, inIndex + 1, inEnd, preorder, inorder);
        return root;
    }

    /*使用Map加速查找过程*/
    public TreeNode buildTreeWithMap(int preStart, int inStart, int inEnd, int[] preorder, int[] inorder,
                                     Map<Integer, Integer> inMap) {
        if (preStart > preorder.length - 1 || inStart > inEnd) {
            return null;
        }
        /*构建二叉树的根结点*/
        TreeNode root = new TreeNode(preorder[preStart]);
        /*存放根结点在中序数组的位置*/
        int inIndex = inMap.get(root.val);

        root.left = buildTreeWithMap(preStart + 1, inStart, inIndex - 1,
                preorder, inorder,inMap);
        root.right = buildTreeWithMap(preStart + inIndex - inStart + 1, inIndex + 1, inEnd,
                preorder, inorder,inMap);
        return root;
    }

二叉树展开为链表

在这里插入图片描述

/**
 * @description :(LeetCode-114) 二叉树展开为链表
 * 给你二叉树的根结点 root ,请你将它展开为一个单链表:
 * 展开后的单链表应该同样使用 TreeNode ,
 * 其中 right 子指针指向链表中下一个结点,而左子指针始终为 null 。
 * 展开后的单链表应该与二叉树先序遍历顺序相同。
 */
    /*递归实现,但是空间复杂度为O(n)*/
    public void flatten(TreeNode root) {
        if (root == null) return;
        /*将二叉树展开为链表之后会破坏二叉树的结构,需要事先存储左右子树*/
        TreeNode leftSave = root.left;
        TreeNode rightSave = root.right;
        /*将根结点的左子树置为null*/
        root.left = null;
        /*递归处理左右子树*/
        flatten(leftSave);
        flatten(rightSave);
        /*将左子树的节点加入链表,替换根结点的右子树*/
        root.right = leftSave;
        TreeNode cur = root;
        /*将右子树的节点加入链表*/
        while (cur.right != null) cur = cur.right;
        cur.right = rightSave;
    }

    /*循环实现,空间复杂度为O(1)*/
    public void flattenWithLoop(TreeNode root) {
        while (root != null) {
            if (root.left == null) {
                root = root.right;
            } else {
                /*寻找左子树最右边的节点*/
                TreeNode rightmost = root.left;
                while (rightmost.right != null) {
                    rightmost = rightmost.right;
                }
                /*将原来的右子树挂到左子树的最右边节点的右指针上*/
                rightmost.right = root.right;
                /*将左子树挂到root节点的右指针上*/
                root.right = root.left;
                root.left = null;
                /*此时root节点的左子树已经为null,
                向下寻找,看看下面的节点上是否还存在着左子树,重复上述过程*/
                root = root.right;
            }
        }
    }

二叉树的右视图

在这里插入图片描述

/**
 * @author :Mark老师
 * @description :(LeetCode-199) 二叉树的右视图
 * 给定一个二叉树的 根节点 root,想象自己站在它的右侧
 * 按照从顶部到底部的顺序,返回从右侧所能看到的节点值。
 */
    /*递归实现*/
    public List<Integer> rightSideView(TreeNode root) {
        List<Integer> result = new ArrayList<Integer>();
        rightView(root, result, 0);
        return result;
    }

    public void rightView(TreeNode root, List<Integer> result, int depth){
        if(root == null){
            return;
        }
        /*对于右子树来说,遇到一个右子树的节点,就加入结果集
        对左子树来说,只有结果集不存在右子树的节点,才会加入结果集*/
        if(depth == result.size()){
            result.add(root.val);
        }
        /*往下一层寻找,优先寻找右子树*/
        rightView(root.right, result, depth + 1);
        rightView(root.left, result, depth + 1);
    }

    /*利用层序遍历逆序和队列实现*/
    public List<Integer> rightSideViewWithQueue(TreeNode root) {
        List<Integer> result = new ArrayList();
        Queue<TreeNode> queue = new LinkedList();
        if (root == null) return result;

        queue.offer(root);
        while (!queue.isEmpty()) {
            /*获得每层的节点数*/
            int levelNodeNum = queue.size();
            /*处理本层的节点*/
            for (int i=0; i<levelNodeNum; i++) {
                TreeNode cur = queue.poll();
                if (i == 0) result.add(cur.val);
                /*和层序遍历不同,应该是右子树先入队*/
                if (cur.right != null) queue.offer(cur.right);
                if (cur.left != null) queue.offer(cur.left);
            }
        }
        return result;
    }

实现 Trie (前缀树)

在这里插入图片描述

/**
 * @description :(LeetCode-208) 实现 Trie (前缀树)
 * Trie(发音类似 "try")或者说 前缀树 是一种树形数据结构,
 * 用于高效地存储和检索字符串数据集中的键。
 * 这一数据结构有相当多的应用情景,例如自动补完和拼写检查。
 * 请你实现 Trie 类
 */
    /*树节点的定义*/
    static class TrieNode {
        TrieNode[] children;
        boolean isEnd;
        TrieNode() {
            /*word和prefix仅由小写英文字母组成,只有26种可能*/
            children = new TrieNode[26];
            isEnd = false;
        }
    }

    static class Trie {
        /*Trie的根节点*/
        TrieNode root;

        public Trie() {
            root = new TrieNode();
        }

        public void insert(String word) {
            insertChar(word, 0, root);
        }

        public boolean search(String word) {
            return searchChar(false,word,0, root);
        }

        public boolean startsWith(String prefix) {
            return searchChar(true,prefix,0, root);
        }

        /*用递归实现插入字符串*/
        void insertChar(String word, int index, TrieNode currNode) {
            /*字符串中的字符已经全部插入完成,增加结束标识*/
            if (index == word.length()) {
                currNode.isEnd = true;
                return;
            }
            /*计算字符在数组中的下标*/
            int charIndex = word.charAt(index) - 'a';
            if (currNode.children[charIndex] == null) {
                currNode.children[charIndex] = new TrieNode();
            }
            insertChar(word, index + 1, currNode.children[charIndex]);
        }

        /*用递归实现查找字符串,
        题目要求实现的search和startsWith本质上是一回事,所以共用一个查找方法
        用参数isPrefix区分*/
        boolean searchChar(boolean isPrefix,String word, int index, TrieNode currNode) {
            /*整个字符串遍历完成,要返回true,要么是前缀查找,要么达到了子树的最后*/
            if (index == word.length()) {
                return isPrefix || currNode.isEnd;
            }
            int charIndex = word.charAt(index) - 'a';
            if (currNode.children[charIndex] == null) {
                return false;
            }
            return searchChar(isPrefix,word, index + 1, currNode.children[charIndex]);
        }
    }
}

二叉树的最近公共祖先

在这里插入图片描述

/**
 * @description :(LeetCode-236) 二叉树的最近公共祖先
 * 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
 * 百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,
 * 最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先
 * 且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
 */
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        /*找到p或者q所在节点,终止递归;或者找到叶子节点也没找到p或者q*/
        if(root == null || root == p || root == q)  return root;
        /*返回时left和right的返回结果是p或者q或者为null,
        或者p和q的最近公共祖先node*/
        TreeNode left = lowestCommonAncestor(root.left, p, q);
        TreeNode right = lowestCommonAncestor(root.right, p, q);
        /*left和right不为空,说明p和q就在当前root节点下,
        root节点就是我们需要的最近公共祖先*/
        if(left != null && right != null)   return root;
        /*left和right有一个为空,返回不为空的那个
        * left和right的都为空,返回哪个都可以,这里统一返回right*/
        return left != null ? left : right;
    }

路径总和 III

在这里插入图片描述

/**
 * @author :Mark老师
 * @description :(LeetCode-437) 路径总和 III
 * 给定一个二叉树的根节点 root ,和一个整数 targetSum ,
 * 求该二叉树里节点值之和等于 targetSum 的 路径 的数目。
 * 路径 不需要从根节点开始,也不需要在叶子节点结束,
 * 但是路径方向必须是向下的(只能从父节点到子节点)。
 */
    public int pathSum(TreeNode root, int sum) {
        HashMap<Integer, Integer> prefixSum = new HashMap();
        /*Map的key就是前缀和,value记录是前缀和出现的次数
        初始化,任何节点本身也可以形成一个路径。
        如果某个节点的值就为target,
        那么它本身就是一个解。前缀和为0正好可以与它形成这个解。
        给所有节点一个可单独形成合法路径的机会。*/
        prefixSum.put(0,1);
        /*从根结点开始遍历*/
        return traversal(root, 0, sum, prefixSum);
    }

    public int traversal(TreeNode root, int currSum, int target,
                         HashMap<Integer, Integer> prefixSum) {
        if (root == null) {
            return 0;
        }
        /*已有的前缀和加上当前节点的值形成的当前节点前缀和*/
        currSum = currSum+ root.val;
        /*差值 = 当前节点的前缀和-targetSum
        在前面保存的前缀和map中寻找有没有等于差值的记录
        result用来记录当前节点下满足条件的路径数*/
        int result = prefixSum.getOrDefault(currSum - target, 0);
        /*将当前节点的前缀和放入Map,有就说明现在多了一条满足题目要求的路径,+1,
        没有,这个前缀和初始的value为1*/
        prefixSum.put(currSum, prefixSum.getOrDefault(currSum, 0) + 1);
        /*向下层节点递归遍历寻找满足条件的节点,左右子树的都要加上*/
        result = result + traversal(root.left, currSum, target, prefixSum)
                + traversal(root.right, currSum, target, prefixSum);
        /*一个节点的前缀和信息更新到map里时,它应当只对其子节点们有效,所以
        往上层返回的时候要减一*/
        prefixSum.put(currSum, prefixSum.get(currSum) - 1);
        return result;
    }

删除二叉搜索树中的节点

在这里插入图片描述

/**
 * @description :(LeetCode-450) 删除二叉搜索树中的节点
 * 给定一个二叉搜索树的根节点 root 和一个值 key,
 * 删除二叉搜索树中的 key 对应的节点,
 * 并保证二叉搜索树的性质不变。
 * 返回二叉搜索树(有可能被更新)的根节点的引用。
 */
    public TreeNode deleteNode(TreeNode root, int key) {
        if(root==null) return null;

        /*如果目标节点小于当前节点值,则去左子树中删除*/
        if(key<root.val){
            root.left = deleteNode(root.left,key);
        }
        /*如果目标节点大于当前节点值,则去右子树中删除*/
        else if(key>root.val){
            root.right = deleteNode(root.right,key);
        }
        /*找到了当前节点*/
        else{
            /*当前节点的左子树为空,返回当前节点的右子树后,
            上级节点指向当前节点的指针会被这个返回值所代替
            因为叶子节点的左右子树均为null,通过这种方式,
            叶子节点自然而然会被删除*/
            if(root.left==null){
                return root.right;
            }
            /*当前节点的右子树为空,返回当前节点的左子树后,
            上级节点指向当前节点的指针会被这个返回值所代替*/
            else if(root.right==null){
                return root.left;
            }
            else{/*当前节点的左右子树均不为空*/
                /*寻找当前右子树的最左节点*/
                TreeNode rightMin = root.right;
                while(rightMin.left!=null){
                    rightMin = rightMin.left;
                }
                /*当前节点左子树成为其右子树的最左节点的左子树*/
                rightMin.left = root.left;
                /*当前节点的的右子顶替其位置,节点被删除*/
                root = root.right;
            }
        }
        return root;
    }

把二叉搜索树转换为累加树

在这里插入图片描述

/**
 * @description :(LeetCode-538) 把二叉搜索树转换为累加树
 * 给出二叉 搜索 树的根节点,该树的节点值各不相同,
 * 请你将其转换为累加树(Greater Sum Tree),
 * 使每个节点 node 的新值等于原树中大于或等于 node.val 的值之和。
 */
    int sum = 0;

    public TreeNode convertBST(TreeNode root) {
        accessTree(root);
        return root;
    }

    public void accessTree(TreeNode root) {
        if (root == null) return;
        accessTree(root.right);
        root.val += sum;
        sum = root.val;
        accessTree(root.left);
    }

二叉树的直径

在这里插入图片描述

/**
 * @author :Mark老师
 * @description :(LeetCode-543) 二叉树的直径
 * 给定一棵二叉树,你需要计算它的直径长度。
 * 一棵二叉树的直径长度是任意两个结点路径长度中的最大值。
 * 这条路径可能穿过也可能不穿过根结点。
 */
    int max = 0;

    public int diameterOfBinaryTree(TreeNode root) {
        maxDepth(root);
        return max;
    }

    private int maxDepth(TreeNode root) {
        if (root == null) return 0;

        /*递归计算当前节点左子树计算深度*/
        int left = maxDepth(root.left);
        /*递归计算当前节点右子树计算深度*/
        int right = maxDepth(root.right);

        /*取已有深度最大值以及当前节点左右子树深度和两者的最大值*/
        max = Math.max(max, left + right);

        /*返回以当前节点为根的树的深度*/
        return Math.max(left, right) + 1;
    }

二叉树中的最大路径和

在这里插入图片描述

/**
 * @description :(LeetCode-124) 二叉树中的最大路径和
 * 路径 被定义为一条从树中任意节点出发,沿父节点-子节点连接,达到任意节点的序列。
 * 同一个节点在一条路径序列中 至多出现一次 。该路径 至少包含一个节点,且不一定经过根节点。
 * 路径和 是路径中各节点值的总和。
 * 给你一个二叉树的根节点 root ,返回其 最大路径和 。
 */
    int maxValue = Integer.MIN_VALUE;

    public int maxPathSum(TreeNode root) {
        maxPathDown(root);
        return maxValue;
    }

    private int maxPathDown(TreeNode node) {
        if (node == null) return 0;
        /*递归计算当前节点通往左子树节点的节点值之和,节点值之和<0,抛弃*/
        int left = Math.max(0, maxPathDown(node.left));
        /*递归计算当前节通往右子树节点的节点值之和,节点值之和<0,抛弃*/
        int right = Math.max(0, maxPathDown(node.right));

        /*取已有最大值以及“当前节点值和左右子树节点值之和的和”两者的最大值*/
        maxValue = Math.max(maxValue, left + right + node.val);

        /*返回以当前节点为根的树的节点值之和*/
        return Math.max(left, right) + node.val;
    }

part 5

搜索旋转排序数组

在这里插入图片描述

/**
 * @description :(LeetCode- 33) 搜索旋转排序数组
 * 整数数组 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 。
 */

    /*二分查找*/
    public int search(int[] nums, int target) {
        int start = 0, end = nums.length - 1;
        while (start <= end) {
            int mid = (start + end)/2;
            int numMid = nums[mid];
            if(numMid == target) return  mid;
            /*numMid和target都大于nums[0]或者都小于nums[0],说明nums[mid]和target在同一段
            *nums[mid]和target不在同一段,将numMid变为正无穷∞或者负无穷-∞*/
            if (!((numMid < nums[0]) == (target < nums[0]))) {
                numMid = target < nums[0] ? Integer.MIN_VALUE : Integer.MAX_VALUE;
            }
            /*mid所指示的数num比target小,移动start,否则移动end*/
            if (numMid < target)
                start = mid + 1;
            else
                end = mid - 1;
        }
        return -1;
    }

    /*基于数组一半有序的原理*/
    public int search2(int[] nums, int target) {
        int start = 0;
        int end = nums.length - 1;
        while (start <= end) {
            int mid = (start + end) / 2;
            int num = nums[mid];
            if (target == num) {
                return mid;
            }
            /*左半段是有序的*/
            if (nums[start] <= num) {
                /*target 在这段里*/
                if (target >= nums[start] && target < num) {
                    end = mid - 1;
                } else {
                    start = mid + 1;
                }
            /*右半段是有序的*/
            } else {
                if (target > num && target <= nums[end]) {
                    start = mid + 1;
                } else {
                    end = mid - 1;
                }
            }
        }
        return -1;
    }

在排序数组中查找元素的第一个和最后一个位置

在这里插入图片描述

/**
 * @description :(LeetCode- 34) 在排序数组中查找元素的第一个和最后一个位置
 * 给定一个按照升序排列的整数数组 nums,和一个目标值 target。
 * 找出给定目标值在数组中的开始位置和结束位置。
 * 如果数组中不存在目标值 target,返回 [-1, -1]。
 * 进阶: * 你可以设计并实现时间复杂度为 O(log n) 的算法解决此问题吗?
 */
    public int[] searchRange(int[] nums, int target) {
        int[] result = new int[2];
        result[0] = findFirst(nums, target);
        /*如果第一个没找到,最后一个也不必找了*/
        if(result[0] == -1) return new int[]{-1,-1};
        /*找到第一个,寻找最后一个不需要从数组第0个元素开始寻找*/
        result[1] = findLast(result[0],nums, target);
        return result;
    }

    private int findFirst(int[] nums, int target){
        int idx = -1;
        int start = 0;
        int end = nums.length - 1;
        while(start <= end){
            int mid = (start + end) / 2;
            /*等于的情况下也不能停止寻找,一直要找到start > end为止,
            才是第一个target所在位置*/
            if(nums[mid] >= target){
                end = mid - 1;
            }else{
                start = mid + 1;
            }
            if(nums[mid] == target) idx = mid;
        }
        return idx;
    }

    private int findLast(int first,int[] nums, int target){
        int idx = -1;
        int start = first;
        int end = nums.length - 1;
        while(start <= end){
            int mid = (start + end) / 2;
            if(nums[mid] <= target){
                start = mid + 1;
            }else{
                end = mid - 1;
            }
            if(nums[mid] == target) idx = mid;
        }
        return idx;
    }

寻找旋转排序数组中的最小值

在这里插入图片描述

/**
 * @description :(LeetCode- 153) 寻找旋转排序数组中的最小值
 * 已知一个长度为 n 的数组,预先按照升序排列,经由 1 到 n 次 旋转 后,得到输入数组。
 *  * 给你一个元素值 互不相同 的数组 nums ,它原来是一个升序排列的数组,
 *  并按上述情形进行了多次旋转。请你找出并返回数组中的 最小元素 。
 */
    public int findMin(int[] nums) {
        if (nums == null || nums.length == 0) {
            return 0;
        }
        if (nums.length == 1) {
            return nums[0];
        }
        int start = 0, end = nums.length - 1;
        while (start <= end) {
            /*如果start和end只相差一个元素,直接比较两者大小*/
            if (start + 1 == end) {
                return Math.min(nums[start], nums[end]);
            }
            int mid = (start + end) / 2;
            if(nums[start]<=nums[mid] && nums[mid]<=nums[end]){
                return nums[start];
                /*最小值在 left半区*/
            }else if(nums[start]>=nums[mid] && nums[mid]<nums[end]){
                end = mid ;
            }
            /*最小值在 right半区*/
            else{
                start = mid ;
            }
        }
        return -1;
    }

搜索二维矩阵 II

在这里插入图片描述

	/**
	 * @description :(LeetCode- 240) 搜索二维矩阵 II
	 * 编写一个高效的算法来搜索 m * n 矩阵 matrix 中的一个目标值 target 。
	 * 该矩阵具有以下特性:
	 * 每行的元素从左到右升序排列。
	 * 每列的元素从上到下升序排列。
	 */
    public boolean searchMatrix(int[][] matrix, int target) {
        if(matrix == null) {
            return false;
        }
        /*矩阵行和列下标的最大值*/
        int cols = matrix[0].length-1;
        int rows = matrix.length-1;
        /*目标值大于矩阵元素的最大值*/
        if(target > matrix[rows][cols]) return false;

        int currentCol = cols;
        int currentRow = 0;
        /*循环中的变化情况为,要么当前行递增,要么当前列递减*/
        while(currentCol >= 0 && currentRow <= rows) {
            if(target == matrix[currentRow][currentCol]) {
                return true;
            } else if(target < matrix[currentRow][currentCol]) {
                currentCol--;
            } else if(target > matrix[currentRow][currentCol]) {
                currentRow++;
            }
        }
        return false;
    }

无重复字符的最长子串

在这里插入图片描述

/**
 * @description :(LeetCode- 3) 无重复字符的最长子串
 * 给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。
 */
 /*LeetCode官方解法,使用HashSet判断是否有重复的字符*/
    public int lengthOfLongestSubstringWithHash(String s) {
        /*哈希集合,记录每个字符是否出现过*/
        Set<Character> occ = new HashSet<Character>();
        int n = s.length();
        /*右指针,初始值为 -1,相当于我们在字符串的左边界的左侧,还没有开始移动*/
        int rk = -1, ans = 0;
        /*遍历字符串,i代表左指针*/
        for (int i = 0; i < n; ++i) {
            /*i = 0,hash集合中还没有字符,自然就不需要移除*/
            if (i != 0) {
                /*左指针向右移动一格,移除一个字符*/
                occ.remove(s.charAt(i - 1));
            }
            /*i = 0时,rk + 1=0,从字符串第0个字符开始*/
            while (rk + 1 < n && !occ.contains(s.charAt(rk + 1))) {
                /*不断地移动右指针*/
                occ.add(s.charAt(rk + 1));
                ++rk;
            }
            /*第 i 到 rk 个字符是一个极长的无重复字符子串*/
            ans = Math.max(ans, rk - i + 1);
        }
        return ans;
    }

    /*使用HashMap优化*/
    public int lengthOfLongestSubstringWithMap(String s) {
        /*map用来记录每个字符的最近一次的索引*/
        HashMap<Character, Integer> map = new HashMap<>();
        int max = 0, start = 0;
        /*end负责遍历整个字符串*/
        for (int end = 0; end < s.length(); end++) {
            char ch = s.charAt(end);
            if (map.containsKey(ch)){
                /*如果map中已经包含当前字符,说明出现了重复,
                将start位置跳转到当前字符上一次出现位置+1,比如:"abcabcdbb"
                0 1 2 3 4 5 6 7 8
                a b c a b c d b b
                      ^       ^
                    start    end
                end=7,start=3,这个时候start可以直接跳至 5*/
                start = Math.max(map.get(ch)+1,start);
            }
            max = Math.max(max,end - start + 1);
            map.put(ch,end);
        }
        return max;
    }

最长回文子串

在这里插入图片描述

/**
 * @description :(LeetCode-5) 最长回文子串
 * 给你一个字符串 s,找到 s 中最长的回文子串。
 */
    public String longestPalindrome(String s) {
        int maxStart = 0, maxEnd = 0;
        for (int i = 0; i < s.length(); i++) {
            /*以当前字符为中心,进行左右扩展*/
            int charCenterLen = expandAroundCenter(s, i, i);
            /*以当前字符后的空白为中心,进行左右扩展*/
            int BlackCenterLen = expandAroundCenter(s, i, i + 1);
            int len = Math.max(charCenterLen, BlackCenterLen);
            /*更新最长的回文子串的长度*/
            if (len > maxEnd - maxStart) {
                maxStart = i - (len - 1) / 2;
                maxEnd = i + len / 2;
            }
        }
        return s.substring(maxStart, maxEnd + 1);
    }

    private int expandAroundCenter(String s, int left, int right) {
        /*从中心,进行左右扩展判断字符是否相等,每次步进一个字符*/
        while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)) {
            left--;
            right++;
        }
        return right - left - 1;
    }

字符串转换整数 (atoi)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

/**
 * @description :(LeetCode-8)  字符串转换整数 (atoi)
 * 请你来实现一个 myAtoi(string s) 函数,
 * 使其能将字符串转换成一个 32 位有符号整数(类似 C/C++ 中的 atoi 函数)。
 */
    public int myAtoi(String s) {
        int len = s.length();
        /*因为要逐个字符处理,先转换为字符数组*/
        char[] charArray = s.toCharArray();
        int idx = 0;

        /*1、检查是否空格*/
        while (idx < len && charArray[idx] == ' ') {
            idx++;
        }

        /*2、idx和字符串的长度相等,说明字符串全部是空格*/
        if (idx == len) {
            return 0;
        }

        /*3、此时idx指向为第一个非空格字符,idx前面字符均可忽略
        如果出现符号字符,用sign记录正负*/
        int sign = 1;
        char firstChar = charArray[idx];
        if (firstChar == '+') {
            idx++;
        } else if (firstChar == '-') {
            idx++;
            sign = -1;
        }

        /*4、读取后续出现的数字字符并进行转换*/
        int result = 0;
        while (idx < len) {
            char currentChar = charArray[idx];
            /*出现了'0'~'9'之外的字符终止循环,比如示例4:“words and 987”,会直接返回0*/
            if (currentChar > '9' || currentChar < '0') {
                break;
            }

            /*只能存储 32 位大小的有符号整数,
            如果在已有数字的基础上*10再加上当前数字,很有可能溢出,
            需要提前判断乘以 10 以后是否溢出,
            result>Integer.MAX_VALUE/10用以判断result本身乘以 10是否溢出
            (result == Integer.MAX_VALUE / 10 && (currentChar - '0') > Integer.MAX_VALUE % 10)
            用以判断result本身乘以10后的个位数部分,是否可以容纳下当前数字,当然直接写数字7也可以,
            比如:(result == Integer.MAX_VALUE / 10 && (currentChar - '0') > 7
            */
            if (result > Integer.MAX_VALUE / 10
                    || (result == Integer.MAX_VALUE / 10 && (currentChar - '0') > Integer.MAX_VALUE % 10)) {
                return Integer.MAX_VALUE;
            }
            /*和上面代码的意义一致
            if中后面的条件也可以改成(result == Integer.MIN_VALUE / 10 && (currentChar - '0') > 8
            */
            if (result < Integer.MIN_VALUE / 10
                    || (result == Integer.MIN_VALUE / 10 && (currentChar - '0') > -(Integer.MIN_VALUE % 10))) {
                return Integer.MIN_VALUE;
            }

            /*转换,并更新result,准备处理下一个字符*/
            result = result * 10 + sign * (currentChar - '0');
            idx++;
        }
        return result;
    }

翻转字符串里的单词

在这里插入图片描述

/**
 * @description :(LeetCode- 151) 翻转字符串里的单词
 * 给你一个字符串 s ,逐个翻转字符串中的所有 单词 。
 */
    /*使用JDK中的api*/
    public String reverseWords(String s) {
        String[] words = s.trim().split(" +");
        Collections.reverse(Arrays.asList(words));
        return String.join(" ", words);
    }

    /*LeetCode的官方解法,使用了栈的思路来实现*/
    public String reverseWordsWithStack(String s) {
        int left = 0, right = s.length() - 1;
        // 去掉字符串开头的空白字符
        while (left <= right && s.charAt(left) == ' ') {
            ++left;
        }
        // 去掉字符串末尾的空白字符
        while (left <= right && s.charAt(right) == ' ') {
            --right;
        }

        Deque<String> d = new ArrayDeque<String>();
        StringBuilder word = new StringBuilder();

        while (left <= right) {
            char c = s.charAt(left);
            if ((word.length() != 0) && (c == ' ')) {
                // 将单词 push 到队列的头部
                d.offerFirst(word.toString());
                word.setLength(0);
            } else if (c != ' ') {
                word.append(c);
            }
            ++left;
        }
        d.offerFirst(word.toString());

        return String.join(" ", d);
    }

    /*倒着遍历原始字符串s*/
     public String reverseWordsWithBack(String s) {
         char[] charArray = s.toCharArray();
         /*left用以排除字符串s开始的空格,最终定位到s中第一个字母*/
         int left = 0;
         /*right用以排除字符串s后面的空格,最终定位到s中最后一个字母
         * 而且right会在s中倒着遍历字符串的单词*/
         int right = charArray.length-1;
         StringBuilder sb = new StringBuilder();
         /*排除字符串s前后空格*/
         while(charArray[left]==' ') left++;
         while(charArray[right] == ' ') right--;

         /*开始倒序遍历字符串*/
         while(left <= right){
             /*index用以倒序遍历字符串中的字母*/
             int index = right;
             while(index >= left && charArray[index] != ' ' )
                 index--;
             /*遇见单词前的空格,取出单词,单词的范围就是index+1~right*/
             for(int i = index+1 ; i <= right ; i++ )
                 sb.append( charArray[i] );
             if(index > left)
                 sb.append(' ');
             /*单词之间可能有多个空格,排除*/
             while( index >= left && charArray[index]==' ' )
                 index--;
             /*遇见了新单词,重复开始处理*/
             right = index;
         }
         return sb.toString();
     }

比较版本号

在这里插入图片描述
在这里插入图片描述

/**
 * @description :(LeetCode- 165) 比较版本号
 * 给你两个版本号 version1 和 version2 ,请你比较它们。
 * 版本号由一个或多个修订号组成,各修订号由一个 '.' 连接。
 * 每个修订号由 多位数字 组成,可能包含 前导零 。
 * 每个版本号至少包含一个字符。修订号从左到右编号,下标从 0 开始,最左边的修订号下标为 0 ,
 * 下一个修订号下标为 1 ,以此类推。例如,2.5.33 和 0.1 都是有效的版本号。
 */
    /*使用双指针来实现*/
    public int compareVersion(String version1, String version2) {
        int i = 0, j = 0;
        int v1Length = version1.length();
        int v2Length = version2.length();
        while(i < v1Length || j < v2Length)
        {
            int num1 = 0, num2 = 0;
            /*遍历字符串,将每个小数点'.'分隔开的版本号解析成数字
            * 每解析出一位,将已有的数字*10再加上新解析出的数字*/
            while(i < v1Length && version1.charAt(i) != '.')
                num1 = num1 * 10 + version1.charAt(i++) - '0';
            while(j < v2Length && version2.charAt(j) != '.')
                num2 = num2 * 10 + version2.charAt(j++) - '0';
            if(num1 > num2) return 1;
            else if( num1 < num2) return -1;
            i++;
            j++;
        }
        return 0;
    }

    /*使用JDK的API来实现*/
    public int compareVersionWithAPI(String version1, String version2) {
        String[] levels1 = version1.split("\\.");
        String[] levels2 = version2.split("\\.");

        int length = Math.max(levels1.length, levels2.length);
        for (int i=0; i<length; i++) {
            Integer v1 = i < levels1.length ? Integer.parseInt(levels1[i]) : 0;
            Integer v2 = i < levels2.length ? Integer.parseInt(levels2[i]) : 0;
            int compare = v1.compareTo(v2);
            if (compare != 0) {
                return compare;
            }
        }
        return 0;
    }

最小覆盖子串

在这里插入图片描述

/**
 * @description :(LeetCode-76) 最小覆盖子串
 * 给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。
 * 如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 "" 。
 */
    public String minWindow(String s, String t) {
        if (s == null || s.length() == 0 || t == null || t.length() == 0){
            return "";
        }
        /*s 和 t 由英文字母组成,所以128个够用了*/
        int[] tChars = new int[128];
        /*用字符串t的字符初始化字典tChars,记录每个字符出现的次数*/
        for (int i = 0; i < t.length(); i++) {
            tChars[t.charAt(i)]++;
        }
        /*left是窗口当前左边界,right是窗口当前右边界*/
        int left = 0, right = 0;
        /*size记录满足条件的窗口大小,count是需求的字符个数*/
        int size = Integer.MAX_VALUE, count = t.length();
        /*start是最小覆盖串开始的index*/
        int start = 0;
        /*遍历所有字符*/
        while (right < s.length()) {
            char c = s.charAt(right);
            if (tChars[c] > 0) {/*需要当前字符c*/
                count--;
            }
            /*当前字符在tChars中的次数减一,这个次数可以为负数
            * 比如当前字符在s中存在,在t中不存在
            * 或者当前窗口范围内,在s中出现的次数大于t中出现的次数*/
            tChars[c]--;
            /*窗口中已经包含所有字符*/
            if (count == 0) {
                /*左边界向右递增,滑动窗口开始缩减,字典tChars要进行复原
                * 缩减的停止处就是窗口内的字符集合刚好包含了t的全部字符,类似于示例 1中窗口包含了"BANC"
                * 如果字典tChars的某个字符个数小于0,说明要么当前字符在s中存在,在t中不存在
                * 要么当前窗口范围内,在s中出现的次数大于t中出现的次数*/
                while (left < right && tChars[s.charAt(left)] < 0) {
                    tChars[s.charAt(left)]++;
                    left++;
                }
                /*更新size的最小值*/
                if (right - left + 1 < size) {
                    size = right - left + 1;
                    start = left;/*记录下最小值时候的开始位置,最后返回覆盖串时候会用到*/
                }
                /*left向右移动后窗口肯定不能满足了重新开始循环*/
                tChars[s.charAt(left)]++;
                left++;
                count++;
            }
            right++;
        }
        return size == Integer.MAX_VALUE ? "" : s.substring(start, start + size);
    }

网站公告

今日签到

点亮在社区的每一天
去签到