LeetCode-按顺序刷题备注50-100

55.跳跃游戏

  • 代码演示:
class Solution {
    public boolean canJump(int[] nums) {
        //记录可以跳到最远的位置
        int maxIndex = 0;
        for(int i = 0 ; i < nums.length ;i++){
            //不断更新最远位置。
            //若所能到达最远位置的数值为0,且最远位置未到达末尾,则返回false
            if(i <= maxIndex){
                maxIndex = Math.max(maxIndex,i+nums[i]);
            }
            //若最远位置大于数组长度,则返回true。
            if(maxIndex >= nums.length -1){
                return true;
            }
        }
        return false;
    }
}

56.合并区间

方法1:

  • 代码演示:
class Solution {
    public int[][] merge(int[][] intervals) {

        //这道题最主要的就是先要排序,如果不进行排序,数组区间的起始位置无序,则判断相对复杂。
        //因此将数组按照开始区间从小到大排序,降低代码复杂度
        Arrays.sort(intervals,new Comparator<>(){
            public int compare(int[] interval1,int[] interval2){
                return interval1[0] - interval2[0];
            }
        });

        /*
        start:区间开始位置;end:区间结束位置
        i:当前数组的开始位置,index:寻找重叠区间的结束位置
        */
        List<List<Integer>> res = new ArrayList<>();
        for(int i = 0 ; i < intervals.length;){
            int end = intervals[i][1];
            int index = i+1;
            //循环来寻找是否有重叠区间
            while(index < intervals.length && intervals[index][0] <= end){
                if(intervals[index][1] > end){
                    end = intervals[index][1];
                }
                index++;
            }
            //重叠区间寻找结束,更新起始位置,与结束位置组成区间压入结果集。
            int start = intervals[i][0];
            res.add(Arrays.asList(start,end));
            //更新数组的开始位置
            i = index;
        }


        int len = res.size();
        int[][] outRes = new int[len][2];
        for(int i = 0 ; i < len ;i++){
            outRes[i][0] = res.get(i).get(0);
            outRes[i][1] = res.get(i).get(1);
        }
        return outRes;
    }
}

方法2:

  • 代码演示:
class Solution {

    public int[][] merge(int[][] intervals) {
        int len = intervals.length;
        //让遇见按照起点位置从小到大排列。
        //排序后,相邻两个位置的区间,只有两种情况:交集,包含
        Arrays.sort(intervals,new Comparator<>(){
            public int compare(int[] i1,int[] i2){
                return i1[0] - i2[0];
            }
        });

        List<List<Integer>> res = new ArrayList<>();
        int start = intervals[0][0];
        int end = intervals[0][1];
        for(int i = 1 ; i < len ; i++){
            //若包含,如[1,4],[2,3]直接跳出
            if(intervals[i][0] >= start && intervals[i][1] <= end){
                continue;
            }
            //若交集,则更新右区间
            if(intervals[i][0] <= end){
                end = intervals[i][1];
            }else{
                //否则,将之前的区间压入结果集,更新此时的左右区间
                res.add(Arrays.asList(start,end));
                start = intervals[i][0];
                end = intervals[i][1];
            }
        }
        //最后一个区间因为没有比较区间,无法压入,需要特殊处理
        res.add(Arrays.asList(start,end));

        int[][] merge = new int[res.size()][2];
        for(int i = 0 ; i < res.size();i++){
            merge[i][0] = res.get(i).get(0);
            merge[i][1] = res.get(i).get(1);
        }
        return merge;
    }
}

57.插入区间

  • 思路:56题与此类似。
    在这里插入图片描述

  • 题解:https://leetcode-cn.com/problems/insert-interval/solution/bi-xu-miao-dong-li-kou-qu-jian-ti-mu-zhong-die-qu-/

  • 代码演示:

class Solution {
    public int[][] insert(int[][] intervals, int[] newInterval) {
        int len = intervals.length;
        int[][] res = new int[len+1][2];

        int i = 0; //数组索引
        int index = 0; //结果集索引

        //将左边相离的区间压入结果集
        while(i < len && intervals[i][1] < newInterval[0]){
            res[index++] = intervals[i++];
        }
        
        //判断当前区间是否与新区间重叠,若重叠则进行合并。直至找到当前区间在新区间右边且相离。
        while( i < len && intervals[i][0] <= newInterval[1]){
            newInterval[0] = Math.min(newInterval[0],intervals[i][0]);
            newInterval[1] = Math.max(newInterval[1],intervals[i][1]);
            i++;
        }
        //将新区间压入结果集
        res[index++] = newInterval;
        
        //将右边相离的区间压入结果集
        while(i < len){
            res[index++] = intervals[i++];
        }
        //返回结果集的长度数组。原始长度>=结果集长度,因此直接返回原始长度数组会有多余的区间
        return Arrays.copyOf(res,index);
    }
}

61.旋转链表

  • 代码演示:
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode rotateRight(ListNode head, int k) {
        if(k == 0 || head == null){
            return head;
        }
        int len = 0;
        ListNode pre = new ListNode(0);
        pre.next = head;
        //求出链表长度且定位旋转后的头结点
        while(pre.next != null){
            len++;
            pre = pre.next; //最终pre指向末尾节点
        }

        ListNode tail = new ListNode(0);
        tail = pre;
        pre.next = head;

        //结束后,pre指向旋转后链表头结点的前一个节点
        //向右移动k个位置,若假设链表成环,则头结点为逆时针旋转的k个位置的节点,因此k = len - k % len
        k = len - k % len;
        if( k == 0){
            return head;
        }
        while(k > 0){
            pre = pre.next;
            k--;
        }

        //链表拼接
        ListNode sentry = new ListNode(0);
        tail.next = head;
        sentry.next = pre.next;
        pre.next = null;
        return sentry.next;
    }
}

66.加一

  • 代码演示:
class Solution {
    public int[] plusOne(int[] digits) {
        int len = digits.length;
        for(int i = len - 1; i >= 0; i--) {
            digits[i]++;
            digits[i] %= 10;
            //若不为0,说明没有进位,直接返回
            if(digits[i]!=0)
                return digits;
        }
        //循环结束后都没有返回,说明digits为[9,...,9]则直接生成新数组,首位赋值为1
        digits = new int[len + 1];
        digits[0] = 1;
        return digits;
    }
}

69.x的平方根

方法1:二分查找

  • 代码演示:
class Solution {
    public int mySqrt(int x) {

        if(x == 1){
            return 1;
        }

        int left = 0;
        int right = x;
        int res = 0;
        while(left < right){
            //因为希望找到k2<=x的最大k值,因此我们使用二分法区间分为[left,mid],[mid+1,right]
            //每次保留mid<x/mid的mid值。可以每次保证res<=所求值,二分最终即是所求值
            int mid = left + (right - left) / 2;
            //避免mid*mid整型溢出,因此用除法来判断。
            if(mid < x / mid){
                res = mid;
                left = mid + 1;
            }else if (mid > x /mid){
                right = mid;
            }else{
                return mid;
            }
        }
        return res;
    }
}

71.简化路径

  • 栈与队列题解博客中也有相应解释。重复记录,只是为了记录每一次的思路。
  • 代码演示:
class Solution {
    public String simplifyPath(String path) {

        String[] pathSplit = path.split("/");
        Deque<String> stack = new LinkedList<>();
        /*
        将字符串分割后数组中包括:"",".","..","字符串"。
        分别对上述情况进行分别处理。
        */
        for(String str : pathSplit){
            if(str.equals("") || str.equals(".")){
                continue;
            }else if(str.equals("..")){
                if(!stack.isEmpty()){
                    stack.pop();
                }
            }else{
                stack.push(str);
            }
        }
        //若栈为空,直接返回根路径
        if(stack.isEmpty()){
            return "/";
        }
        //否则依次出栈进行路径的拼接
        StringBuilder res = new StringBuilder();
        while(!stack.isEmpty()){
            String s = "/" + stack.pop();
            res.insert(0,s);
        }
        return res.toString();
    }
}

5736.单线程

方法1:排序+优先队列

  • 思路:
    • 创建一个新的二维数组,在保留题目提供的tasks数据的基础上,再把下标存进去;
      把tasks按任务起始时间排序;
    • 建小顶堆,堆顶元素是时长最小的任务,时长相同的按下标排序;
    • 初始化当前时间now = 0,遍历tasks中的任务,当堆空或者now>=enqueneTime(即任务开始时间)时,将任务加入堆中,之后再弹出堆顶元素,根据弹出的新任务需要执行的时间来更新当前时刻now
    • 重复4直到所有任务都被遍历
    • 依次弹出堆中所有元素
  • 代码演示:
class Solution {
    class Task{
        int id;
        int enqueneTime;
        int processingTime;
        public Task(int id,int enqueneTime,int processingTime){
            this.id = id;
            this.enqueneTime = enqueneTime;
            this.processingTime = processingTime;
        }
    }

    public int[] getOrder(int[][] tasks) {
        
        List<Task> taskList = new ArrayList<>();
        for(int i = 0 ; i < tasks.length ;i++){
            taskList.add(new Task(i,tasks[i][0],tasks[i][1]));
        }
        
        //将任务按照进入时间进行排序
        Collections.sort(taskList,new Comparator<>(){
            public int compare(Task t1,Task t2){
                return t1.enqueneTime - t2.enqueneTime;
            }
        });
      
        //优先级队列:存储的为当前待运行任务。根据题目所定义的排序来进行待运行任务排序
        PriorityQueue<Task> queue = new PriorityQueue(new Comparator<Task>(){
            public int compare(Task t1,Task t2){
                if(t1.processingTime == t2.processingTime){
                    return t1.id - t2.id;
                }else{
                    return t1.processingTime - t2.processingTime;
                }
            }
        });
        
        /*
        nowTime:cpu当前时间
        i:任务遍历索引
        resIndex:结果数组索引
        res:结果数组
        */
        int nowTime = 0; 
        int i = 0;
        int resIndex = 0;
        int len = tasks.length;
        int[] res = new int[len];
        while(i < len){
            /*
            当前任务的进入时间小于cpu当前时间,进入待运行队列。
            若阻塞队列为空,则说明cpu当前空闲。若任务还没执行完,将新任务加入待运行队列,
            while判断中即描述的为上述情况
            */
            while(i < len && (taskList.get(i).enqueneTime <= nowTime || queue.isEmpty())){
                //若cpu为空,因此nowTime应更新为下一次的enqeueTime
                //若cpu不为空,因此nowTime不变
                //因为,当运行的任务enqueTime肯定小于nowTime,因此下步比较主要是为了更新当队列为空的情况。
                nowTime = Math.max(taskList.get(i).enqueneTime,nowTime);
                queue.offer(taskList.get(i));
                i++;
            }

            //从待运行队列中取出一个待运行任务,进行运行。记录索引值,更新cpu时间。
            Task t = queue.poll();
            res[resIndex++] = t.id;
            nowTime += t.processingTime;
        }

        //当任务都已经遍历结束,但是队列中还有可能存有待运行任务。逐一运行直至队列为空
        while(!queue.isEmpty()){
            res[resIndex++] = queue.poll().id;
        }

        return res;
    }
}

74.搜索二维矩阵

方法1:建立二维坐标系

  • 根据题意已知,二维数组从左往右递增,从上往下递增,所以得出以下结论:
    • 某列的某个数字,该数之上的数字,都比其小;
    • 某行的某个数字,该数右侧的数字,都比其大;
  • 所以,解题流程如下所示:
    • 以二维数组左下角为原点,建立直角坐标轴。
    • 若当前数字大于了查找数,查找往上移一位。
    • 若当前数字小于了查找数,查找往右移一位。
  • 代码演示:
class Solution {
    public boolean searchMatrix(int[][] matrix, int target) {
        int row = matrix.length;
        int col = matrix[0].length;
        
        int i = row - 1;
        int j = 0;
        while(i >= 0 && j < col){
            if(matrix[i][j] == target){
                return true; 
            }else if (matrix[i][j] > target){
                i--;
            }else{
                j++;
            }
        }
        return false;
    }
}

方法2:二分查找

  • 思路:
    • 由于每行的第一个元素大于前一行的最后一个元素,且每行元素是升序的,所以每行的第一个元素大于前一行的第一个元素,因此矩阵第一列的元素是升序的。
    • 可以对矩阵的第一列的元素二分查找,找到最后一个不大于目标值的元素,然后在该元素所在行中二分查找目标值是否存在。
  • 代码演示:
class Solution {
    public boolean searchMatrix(int[][] matrix, int target) {
        int row = matrix.length;
        int col = matrix[0].length;

        /*
        先找到符合条件的行值,若为找到行值,直接返回false
        再找到对应的列值,若未找到返回false,找到返回true
        */
        int first = findFrist(matrix,target);
        if(first == -1){
            return false;
        }    

        return findSecond(matrix,target,first);
    }

    public int findFrist(int[][] matrix, int target){
        int left = 0;
        int right = matrix.length;
        int res = -1;
        while(left < right){
            int mid = left + (right - left) / 2;
            if(matrix[mid][0] < target){
                res = mid;
                left = mid+1;
            }else if (matrix[mid][0] > target){
                right = mid;
            }else{
                return mid;
            }
        }
        return res;
    }

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

75.颜色分裂

方法1:两次循环

  • 代码演示
class Solution {
    public void sortColors(int[] nums) {
        int head = 0;
        int len = nums.length;
        //将0移直最前面
        for(int i = 0 ; i < len ;i++){
            if(nums[i] == 0 ){
                swap(nums,head,i);
                head++;
            }
        }
        //将1移直0的后面
        //经过上述两次循环,2肯定在1的后面,因此整体呈现0,1,2的顺序
         for(int i = head ; i < len ;i++){
            if(nums[i] == 1 ){
                swap(nums,head,i);
                head++;
            }
        }
    }

    public void swap(int[] nums, int left, int right){
        int temp = nums[left];
        nums[left] = nums[right];
        nums[right] = temp;
    }
}

方法2:

  • 题解:https://leetcode-cn.com/problems/sort-colors/solution/kuai-su-pai-xu-partition-guo-cheng-she-ji-xun-huan/
  • 思路:通过循环不变量来进行区间划分
    • 有在子区间 [0, zero) 的元素都等于 0;
    • 所有在子区间 [zero, i) 的元素都等于 1;
    • 所有在子区间 [two, len - 1] 的元素都等于 2。
  • 代码演示:
class Solution {
    public void sortColors(int[] nums) {
        if(nums.length < 2){
            return;
        }


        /*
        将整个数字通过定义的索引分为三等分,[0,zero),[zero,i),[two,len-1]。分别表示结果集0,1,2的区间
        保证初始区间都为空,因此设zero=0,two=nums.length,i=0
        */
        int zero = 0;
        int i = 0;
        int two = nums.length;

        while(i < two){
            if(nums[i] == 0){
                swap(nums,zero,i);
                zero++;
                i++;
            }else if (nums[i] == 1){
                i++;
            }else{
                two--;
                swap(nums,i,two);
            }
        }
    }

    public void swap(int[] nums, int left, int right){
        int temp = nums[left];
        nums[left] = nums[right];
        nums[right] = temp;
    }
}

77.组合

方法1:回溯

  • 代码演示:
class Solution {
    List<List<Integer>> res = new ArrayList<>();
    Deque<Integer> combina = new LinkedList<>();
    public List<List<Integer>> combine(int n, int k) {
        dfs(n,k,1);
        return res;
    }

    public void dfs(int n ,int k,int index){
        if(combina.size() == k){
            res.add(new ArrayList<>(combina));
            return;
        }

        /*
        假设n=4,k=2,从4开始搜索就没有意义;假设n=5,k=3,从4开始搜索就没有意义(前提:1-3中未选择数)
        因此可以进行剪枝,搜索区间的上界+未搜索个数 - 1 = n,因此 搜索区间上界 = n - 未搜索个数 + 1
        */
        for(int i = index ; i <= n - (k - combina.size()) + 1 ;i++){
            combina.addLast(i);
            dfs(n,k,i+1);
            combina.removeLast();
        }
    }
}

78.子集

  • 递归树:
    在这里插入图片描述

  • 代码演示:

class Solution {
    List<List<Integer>> res = new ArrayList<>();
    Deque<Integer> combina = new LinkedList<>();
    public List<List<Integer>> subsets(int[] nums) {
        dfs(nums,0);
        return res;
    }
	
	//因为要求所属的全部子集,因此每次进入一个新节点就添加即可
    public void dfs(int nums[],int begin){
        res.add(new ArrayList<>(combina));
    
        for(int i = begin ; i < nums.length;i++){
            combina.addLast(nums[i]);
            dfs(nums,i+1);
            combina.removeLast();
        }
    }
}

79.单词搜索

方法1:递归+回溯

  • 思路:注意返回条件
  • 代码演示
class Solution {
    boolean[][] used;
    char[] wordChar;
    int[][] DIRECTIONS = new int[][]{{1,0},{0,1},{-1,0},{0,-1}};
    int row;
    int col;
    public boolean exist(char[][] board, String word) {
        this.row = board.length;
        this.col = board[0].length;
        this.wordChar = word.toCharArray();
        this.used = new boolean[row][col];
        for(int i = 0 ; i < row; i++){
            for(int j = 0 ; j < col ;j++){
                if(dfs(board,i,j,wordChar.length,0)){
                    return true;  
                }
            }
        }
        return false;
    }


    public boolean dfs(char[][] board,int x, int y, int wordLen,int curIndex){
        /*
        递归终止条件:
            1.若当前字符与word中字符不相等,则直接返回false,不向下递归
            2.若当前字符与word中字符相等:若递归到最后一个字符,则直接返回true;否则继续递归
        */
        if(board[x][y] == wordChar[curIndex]){
            if(curIndex == wordLen - 1){
                return true;
            }
        }else{
            return false;
        }

        for(int[] direction : DIRECTIONS){
            used[x][y] = true;
            int newX = x + direction[0];
            int newY = y + direction[1];
            if(judge(newX,newY) && used[newX][newY] == false){
                //若找到相等的字符串,直接返回true。
                if(dfs(board,newX,newY,wordLen,curIndex+1)){
                    return true;
                }                
            }
            used[x][y] = false;
        }
        //将四个方向遍历完还未找到匹配的字符串,则返回false。
        //说明从当前起点,找不到匹配的字符串
        return false;
    }

    public boolean judge(int x ,int y){
        return x >= 0 && x < row && y >= 0 && y < col;
    }
}

80.删除有序数组中的重复项 II

方法1:快慢指针

  • 代码演示
class Solution {
    public int removeDuplicates(int[] nums) {
        /*
        sum:当前数值出现的次数
        index:慢指针
        sum:快指针
        */
        int sum = 1;
        int index = 1;
        for(int i = 1 ; i < nums.length ;i++){
            //更新元素出现的次数
            if(nums[i] == nums[i-1]){
                sum++;
            }else{
                sum = 1;
            }

            //若出现次数<=2,则说明重复元素未到最大值,慢指针移动,否则。慢指针停止移动。
            if(sum <= 2){
                nums[index++] = nums[i];
            }
        }
        return index;
    }
}

方法2:快慢指针2:

class Solution {
    public int removeDuplicates(int[] nums) {
        int len = nums.length;
        if(len <= 2){
            return len;
        }
        //low:新数组的尾部索引,count:新数组尾部索引出现的次数
        int count = 1;
        int low = 0;
        for(int i = 1; i < len ;i++){
            //若遍历位置与新数组尾部索引相等,则判断count;
            if(nums[i] == nums[low]){
                //count==2,说明最多出现两次。直接continue,查看下一位置
                if(count == 2){
                    continue;
                }
                count++;
            //不相等则更新count;
            }else{
                count = 1;
            }
            //填充新数组
            low++;
            nums[low] = nums[i];
        }
        return low+1;
    }
}

82.删除链表中的重复元素II

方法1:

class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        if(head == null){
            return head;
        }

        ListNode sentry = new ListNode(0);
        
        //pre:指向未重复节点的最后一个节点
        sentry.next = head;
        ListNode pre = sentry;
        
        while(pre.next != null && pre.next.next != null){
            //当存在重复时,while循环找出所有的重复,直至找到第一个不重复元素,链接。这样就去除了一组重复元素。
            //否则直接更新pre
            if(pre.next.val == pre.next.next.val){
                ListNode cur = pre.next.next;
                while(cur.next != null && cur.val == cur.next.val){
                    cur = cur.next;
                }
                pre.next = cur.next;
            }else{
                pre = pre.next;
            } 
        }           
        return sentry.next;
    }
}

方法2:

  • 与方法1思想一致,只是写法有所区别
class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        if(head == null){
            return head;
        }
        ListNode sentry = new ListNode(0);
        sentry.next = head;
        //pre:重复元素的上一个元素,
        ListNode pre = sentry;
        //cur:当前判断元素
        ListNode cur = head;
        while(cur != null && cur.next != null){
            //如果有重复
            if(cur.val == cur.next.val){
                //找到重复的末尾
                while(cur != null && cur.next != null && cur.val == cur.next.val){
                    cur = cur.next;
                }
                //pre.next指向重复元素的下一个元素,即将重复元素删除
                pre.next = cur.next;
                //cur重复元素的下一个元素
                cur = pre.next;
            }else{
                //否则,cur与pre依次后移一位
                pre = cur;
                cur = cur.next;
            }
        }
        return sentry.next;
    }
}

84.柱状图的最大矩形

  • 题解:
    • https://leetcode-cn.com/problems/largest-rectangle-in-histogram/solution/bao-li-jie-fa-zhan-by-liweiwei1419/
    • https://leetcode-cn.com/problems/largest-rectangle-in-histogram/solution/dong-hua-yan-shi-dan-diao-zhan-84zhu-zhu-03w3/

方法1:暴力

  • 思路:枚举以每个柱形为高度的最大矩形的面积。也就是固定高度,寻找最大的宽度
  • 代码演示:
class Solution {
    public int largestRectangleArea(int[] heights) {
        int len = heights.length;
        int max = 0;
        for(int i = 0 ; i < len ;i++){
            //以当前高为基础,找宽
            int curHeight = heights[i];
            
            //以当前高度为基础,分别往左和往右寻找最后一个大于或者等于当前高度的索引
            //这样,在当前位置可以组成的矩形面积就为:当前高度 * right-left + 1
            int left = i;
            while(left >= 1 && heights[left - 1] >= curHeight){
                left--;
            }       

            int right = i;
            while(right < len -1 && heights[right + 1] >= curHeight){
                right++;
            }     

            int width = right - left + 1;
            max = Math.max(max,width * curHeight);
        }
        return max;
    }
}

方法2:单调栈

  • 思路:
    • 在方法1中,对当前索引判断是否可以向左右扩张,找到可以扩展最大宽度,然后计算面积.因此,可以尝试使用单调栈维护一个单调不减的栈,栈顶元素肯定不能向左扩张,但是除过栈顶之外的栈中元素可以向右扩张。
    • 当碰到一个新元素时,若新元素小于栈顶元素,说明当前栈顶元素不能向右扩张,因此直接弹出栈顶元素,且计算当前宽度与面积。
    • 若前一个栈顶元素弹出后,当前元素还小于栈顶元素。此时,因为当前元素并没有进行更新,宽度会增大,高度为当期栈顶元素高度,还是可以计算出当前栈顶元素可扩张的最大面积。
  • 代码演示:
class Solution {
    public int largestRectangleArea(int[] heights) {
        int len  = heights.length;
        if(len == 0){
            return 0;
        }

        //给原数组加左右边界。
        //左边界:保证在后续的处理中栈非空。右边界:保证在后续的处理中,栈中的元素能够处理完(除过第一个0)。
        int[] newHeight = new int[len + 2];
        for(int i = 0 ;i < len;i++){
            newHeight[i+1] = heights[i];
        }

        //栈中元素为单调不减
        Deque<Integer> stack = new LinkedList<>();
        int res = 0;

        for(int i = 0 ; i < len + 2 ;i++){
            //若碰到一个小于当前栈顶元素,说明面积不能继续向右扩张
            //则弹出栈顶元素,计算当前面积
            while(!stack.isEmpty() && newHeight[i] < newHeight[stack.peekLast()]){
                int curHeight = newHeight[stack.pollLast()];
                //处理重复元素。虽然栈顶元素一致, 也就是高度一致,但是宽度在逐渐缩小。面积肯定会更小
                while(newHeight[stack.peekLast()] == curHeight){
                    stack.pollLast();
                }

                //计算宽度,比较面积取最大值
                int left = stack.peekLast();
                int right = i;
                int width = right - left - 1;
                res = Math.max(res,curHeight * width); 
            }
            stack.addLast(i);
        }
        return res;
    }
}

91.解码方法

方法1:DP

在这里插入图片描述

  • 代码演示:
class Solution {
    public int numDecodings(String s) {
        char[] schar = s.toCharArray();
        if(schar[0] == '0'){
            return 0;
        }
        int len = schar.length;
        //dp[i]表示nums[0..i-1]的解码总数。在进行状态转移时,可以考虑最后一次解码使用了 s 中的哪些字符
        int[] dp = new int[len+1];
        //初始值,nums[0...0]解码总数为0
        dp[0] = 1;
        for(int i = 1; i <= len ;i++){
            //最后一次解码使用了1个字符,因此f[i] = f[i-1]。
            if(schar[i-1] != '0'){
                dp[i] = dp[i-1];
            }

            if(i <= 1){
                continue;
            }
            //最后一次解码使用了两个字符,因此f[i] += f[i-2]
            int num = (schar[i-2] - '0') * 10 + (schar[i-1] - '0');
            if(num >= 10 && num <= 26){
                dp[i] += dp[i-2];
            }
        }
        return dp[len];
    }
}

方法2:递归

  • 递归树:
    在这里插入图片描述

  • 代码演示

class Solution {
    char[] schar;
    int len;
    int res = 0;
    Map<Integer,Integer> map = new HashMap<>();

    public int numDecodings(String s) {
        this.schar = s.toCharArray();
        if(schar[0] == '0'){
            return 0;
        }
        this.len = s.length();
        return dfs(0);
    }

    public int dfs(int index){
        if(index >= len){
            return 1;
        }

        //递归为子问题,因此每次需要判断当前起始区间起始值是否为‘0’
        //特殊情况,如果当前值schar[index] == 0,直接返回0。
        if(schar[index] == '0'){
            return 0;
        }

        if(map.containsKey(index)){
            return map.get(index);
        }
        //取一位
        int res = dfs(index+1);
        if(index < len - 1){
            //取两位
            int num = (schar[index] - '0') * 10 + (schar[index + 1] - '0');
            if(num >= 10 && num <= 26){
                res += dfs(index + 2);
            }
        }
        map.put(index,res);
        return res;
    }
}

93.复原IP地址

方法1:回溯+剪枝。

  • 代码演示:
class Solution {
    List<String> res = new ArrayList<>();
    Deque<String> path = new LinkedList<>();
    public List<String> restoreIpAddresses(String s) {
        dfs(s,0,4);
        return res;
    }


    public void dfs(String s, int begin, int noSplitTime){
        //若直接判断noSplitTime == 0,压入,未考虑是否遍历到字符串的末尾。
        //因此,只有遍历到末尾,且未分割为0时,就说明当前分割效果成立。
       if(begin == s.length()){
            if(noSplitTime == 0){
                res.add(String.join(".",path));
            }
            return;
       }
       
		//循环条件,每次只需要取连续的三个字符进行遍历判断即可。
        for(int i = begin ; i < begin + 3;i++){
            //确保当前i在字符串长度范围内,可分割
            if(i >= s.length()){
                break;
            }

            /*
            255.255.255.255
            剩余的字串长度大于需要拼接的最大长度,则直接continue。
            假设当前i为1,选择了2,剩余未分割次数为3。发现s.length()- 1 = 11 > 9,则最终字符串肯定分配不完,continue;
            */
            if(noSplitTime * 3 < s.length()- i){
                continue;
            }

            String ss = s.substring(begin,i+1);
            if(judgeIpAdress(ss)){
                path.addLast(ss);
                dfs(s,i+1,noSplitTime-1);
                path.removeLast();
            }
        }
    }

    public boolean judgeIpAdress(String subS){
        if(subS.length() > 1 && subS.charAt(0) == '0'){
            return false;
        }
        int num = 0;
        for(int i = 0 ; i < subS.length() ;i++){
            num = num * 10 + (subS.charAt(i) - '0');
        }
        if(num > 255){
            return false;
        }
        return true;
    }
}

92.反转链表Ⅱ

方法1:头插法

  • 代码演示
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode reverseBetween(ListNode head, int left, int right) {
        if(head.next == null){
            return head;
        }

        ListNode sentry = new ListNode(0);
        sentry.next = head;
        int index = right - left;
        ListNode pre = sentry;
        ListNode tail = head;
        //找到待转换节点的头节点与待转换节点序列的前一个节点pre
        while(left > 1){
            pre = tail;
            tail = tail.next;
            left--;
        }
        //头插法,将每一个待旋转的节点头插值pre节点之后,共插入n次
        while(index > 0){
            ListNode temp = tail.next;
            tail.next = temp.next;
            temp.next = pre.next;
            pre.next = temp;
            index--;
        }
        return sentry.next;
    }
}

95.不同的二叉搜索树Ⅱ

方法1:递归

  • 题解:https://leetcode-cn.com/problems/unique-binary-search-trees-ii/solution/cong-gou-jian-dan-ke-shu-dao-gou-jian-suo-you-shu-/
  • 重点提示:不同关心递归的中间是如何进行的,只需要关心递归的入口与出口,以及逻辑处理代码。
  • 代码演示:
lass Solution {
    public List<TreeNode> generateTrees(int n) {
        if(n < 1)
            return new ArrayList<>();
        return helper(1, n);
    }

    public List<TreeNode> helper(int start, int end){
        List<TreeNode> list = new ArrayList<>();

        if(start > end){
            // 如果当前子树为空,不加null行吗?
            list.add(null);
            return list;
        }

        for(int i = start; i <= end; i++){
            // 想想为什么这行不能放在这里,而放在下面?
            // TreeNode root = new TreeNode(i);
            List<TreeNode> left = helper(start, i-1);  
            List<TreeNode> right = helper(i+1, end); 

            // 固定左孩子,遍历右孩子
            for(TreeNode l : left){
                for(TreeNode r : right){
                    TreeNode root = new TreeNode(i);
                    root.left = l;
                    root.right = r;
                    list.add(root);
                }
            }
        }
        return list;
    }
}

96.不同的二叉搜索树

  • 题解:https://leetcode-cn.com/problems/unique-binary-search-trees/solution/bu-tong-de-er-cha-sou-suo-shu-cong-yuan-shi-de-di-/

方法1:递归(超时)

  • 超时原因:许多重复计算
  • 代码演示:
class Solution {
    public int numTrees(int n) {
        int totalTree = 0;

        if(n == 1 || n == 0){
            return 1;
        }

        for(int i = 1 ; i <= n ;i++){
            int left = numTrees(i-1);
            int right = numTrees(n-i);
            totalTree += left * right;
        }
        return totalTree;
    } 
}

方法2:递归+记忆化

  • 代码演示:
class Solution {
    int[] memo;
    public int numTrees(int n) {
        this.memo = new int[n+1];
        return helper(n);
    } 

    /*
    123.与345可以组成的二叉搜索树个数相同。因此,不用在意具体数值,只需要留意当前组成二叉搜索树的数字个数。
    */
    public int helper(int n){
        if(n == 1 || n == 0){
            return 1;
        }

        //若当前个数被计算过,直接返回
        if(memo[n] > 0){
            return memo[n];
        }

        //分别计算以1-n为根节点的二叉搜索树的个数,后相加。
        for(int i = 1 ; i <= n ;i++){
            int left = helper(i-1);
            int right = helper(n-i);
            memo[n] += left * right;
        }
        return memo[n];
    }
}

方法3:动态规划

  • 思路:
    在这里插入图片描述

  • 代码演示:

class Solution {
    public int numTrees(int n) {
        //dp[i]:表示i个节点所能构成的搜索树的个数
        int[] dp = new int[n+1];
        //初始值:当0个节点时,空树也为一个二叉搜索树
        dp[0] = 1;
        for(int i = 1 ; i <= n;i++){
            for(int j = 0 ; j < i ;j++){
                dp[i] += dp[j] * dp[i - j - 1];
            }
        }
        return dp[n];
    }
}

98.验证二叉搜索树

方法1:递归,区间

  • 代码演示:
class Solution {
    public boolean isValidBST(TreeNode root) {
        return helper(root,null,null);
    }
    
    /*
    如果该二叉树的左子树不为空,则左子树上所有节点的值均小于它的根节点的值;
    若它的右子树不空,则右子树上所有节点的值均大于它的根节点的值;
    它的左右子树也为二叉搜索树。
    lower,upper。上下边界
    */
    public boolean helper(TreeNode root,Integer lower,Integer upper){
        if(root == null){
           return true;
        }

        if(lower != null &&  root.val <= lower ){
            return false;
        }

        if(upper != null &&  root.val >= upper){
            return false;
        }

        return helper(root.left,lower,root.val) && helper(root.right,root.val,upper);
    }
}

方法2:递归+中序遍历

  • 代码演示:
 //递归+中序遍历
class Solution {
    long pre = Long.MIN_VALUE;
    public boolean isValidBST(TreeNode root) {
        if(root == null){
            return true;
        }

        //判断左子树
        if(!isValidBST(root.left)){
            return false;
        }

        //判断当前节点是否小于等于中序遍历的前一个节点,若小于等于,则不满足二叉搜索树。
        //二叉搜索树的中序遍历是有序的
        if(root.val <= pre){
            return false;
        }
        pre = root.val;

        //访问右子树
        return isValidBST(root.right);
    }
}

方法3:中序遍历+非递归

  • 代码演示
class Solution {
    Deque<TreeNode> stack = new LinkedList<>();
    long pre = Long.MIN_VALUE;
    public boolean isValidBST(TreeNode root) {
        if(root == null){
            return true;
        }

        while(root != null || !stack.isEmpty()){
            while(root != null){
                stack.push(root);
                root = root.left;
            }
            TreeNode cur = stack.pop();
            
            //比较且更新前序节点。
            if(cur.val <= pre){
                return false;
            }
            pre = cur.val;
            root = cur.right;
        }
        return true;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值