LeetCode刷题 day37

目录

  • [1. 最多 K 个重复元素的最长子数组](#1. 最多 K 个重复元素的最长子数组)
  • [2. 验证二叉树的前序序列化](#2. 验证二叉树的前序序列化)
  • [3. 递增的三元子序列](#3. 递增的三元子序列)

1. 最多 K 个重复元素的最长子数组

给你一个整数数组nums和一个整数k

一个元素x在数组中的频率 指的是它在数组中的出现次数。

如果一个数组中所有元素的频率都小于等于k,那么我们称这个数组是好 数组。

请你返回nums中 最长好 子数组的长度。

子数组 指的是一个数组中一段连续非空的元素序列。

示例 1:

输入:nums = 1,2,3,1,2,3,1,2, k = 2

输出:6

解释:最长好子数组是 1,2,3,1,2,3 ,值 1 ,2 和 3 在子数组中的频率都没有超过 k = 2 。2,3,1,2,3,13,1,2,3,1,2 也是好子数组。

最长好子数组的长度为 6 。

示例 2:

输入:nums = 1,2,1,2,1,2,1,2, k = 1

输出:2

解释:最长好子数组是 1,2 ,值 1 和 2 在子数组中的频率都没有超过 k = 1 。2,1 也是好子数组。

最长好子数组的长度为 2 。

示例 3:

输入:nums = 5,5,5,5,5,5,5, k = 4

输出:4

解释:最长好子数组是 5,5,5,5 ,值 5 在子数组中的频率没有超过 k = 4 。

最长好子数组的长度为 4 。

提示:

1 < = n u m s . l e n g t h < = 10 5 1 <= nums.length <= 10^5 1<=nums.length<=105

1 < = n u m s i < = 10 9 1 <= numsi <= 10^9 1<=numsi<=109

1 < = k < = n u m s . l e n g t h 1 <= k <= nums.length 1<=k<=nums.length

思路

双指针+哈希表

哈希表统计每个数字的频率,双指针指向合法的区间

java 复制代码
class Solution {
    public int maxSubarrayLength(int[] nums, int k) {
        int n = nums.length;
        Map<Integer,Integer> map = new HashMap<>(n);
        int maxLen = 0;
        for(int i=0,j=0;j<n;){
            int curr = nums[j];
            int count = map.getOrDefault(curr,0);
            if(count==k){//区间不合法,左侧指针右移
                map.put(nums[i],map.get(nums[i])-1);
                i++;
            }else{
                map.put(curr,count+1);
                j++;
                maxLen = Math.max(j-i,maxLen);
            }
        }
        return maxLen;
    }
}

时间复杂度: O ( n ) O(n) O(n) 近似 O ( n ) O(n) O(n) 哈希表的取值操作平均为 O ( 1 ) O(1) O(1)

空间复杂度: O ( n ) O(n) O(n)

2. 验证二叉树的前序序列化

序列化二叉树的一种方法是使用 前序遍历 。当我们遇到一个非空节点时,我们可以记录下这个节点的值。如果它是一个空节点,我们可以使用一个标记值记录,例如#

例如,上面的二叉树可以被序列化为字符串 "9,3,4,#,#,1,#,#,2,#,6,#,#",其中#代表一个空节点。

给定一串以逗号分隔的序列,验证它是否是正确的二叉树的前序序列化。编写一个在不重构树的条件下的可行算法。

保证 每个以逗号分隔的字符或为一个整数或为一个表示null指针的'#'

你可以认为输入格式总是有效的

例如它永远不会包含两个连续的逗号,比如"1,,3"

注意:不允许重建树。

示例 1:

输入: preorder = "9,3,4,#,#,1,#,#,2,#,6,#,#"

输出: true

示例 2:

输入: preorder = "1,#"

输出: false

示例 3:

输入: preorder = "9,#,#,1"

输出: false

提示:

1 < = p r e o r d e r . l e n g t h < = 10 4 1 <= preorder.length <= 10^4 1<=preorder.length<=104

preorder 由以逗号 "," 分隔的 0,100 范围内的整数和 "#" 组成

思路

每个节点一个槽位,而每个节点又有子节点,当前节点不为空时,左右子节点需要两个槽位,当前节点为空时,左右子节点不需要槽位。

因此,若当前节点不为空,会占用一个槽位,但同时又会产生两个新的槽位供子节点使用,若当前节点为空,会占用一个槽位

前序遍历的过程中,产生的槽位不会多余节点数量,因此槽位只可能少,不可能多,故只要遍历一遍序列,若发现槽位不够,则必然不是前序序列

"9,3,4,#,#,1,#,#,2,#,6,#,#"为例,演示算法过程

  1. 起始时,空槽位个数为1,记count=1;
  2. 节点9,不为空,消耗一个槽位,但又新加两个槽位,count=count-1+2=2;
  3. 节点3,不为空,消耗一个槽位,但又新加两个槽位,count=count-1+2=3;
  4. 节点4,count=count-1+2=4;
  5. 节点#,消耗一个槽位,count=cout-1=3;
  6. 节点#,消耗一个槽位,count=cout-1=2;
  7. 节点1,count=count+1=3;
  8. 节点#,count=count-1=2;
  9. 节点#,count=count-1=1;
  10. 节点2,count=count+1=2;
  11. 节点#,count=count-1=1;
  12. 节点6,count=count+1=2;
  13. 节点#,count=count-1=1;
  14. 节点#,count=count-1=0;
  15. 至此,所有节点遍历完毕,槽位为0,说明前序序列合法
java 复制代码
class Solution {
    public boolean isValidSerialization(String preorder) {
        //槽位初始数量为1
        int count = 1;
        char[] cp = preorder.toCharArray();
        int n = cp.length;
        for(int i=0;i<n;){
            if(count==0){
                return false;
            }
            if(cp[i]==','){
                i++;
            }else if(cp[i]=='#'){
                count--;
                i++;
            }else{
                while(i<n&&cp[i]!=','){
                    i++;
                }
                count++;
            }
        }
        return count==0;
    }
}

时间复杂度: O ( n ) O(n) O(n)

空间复杂度: O ( 1 ) O(1) O(1)

3. 递增的三元子序列

给你一个整数数组nums,判断这个数组中是否存在长度为3的递增子序列。

如果存在这样的三元组下标(i, j, k)且满足i < j < k,使得nums[i] < nums[j] < nums[k],返回true ;否则,返回false

示例 1:

输入:nums = 1,2,3,4,5

输出:true

解释:任何 i < j < k 的三元组都满足题意

示例 2:

输入:nums = 5,4,3,2,1

输出:false

解释:不存在满足题意的三元组

示例 3:

输入:nums = 2,1,5,0,4,6

输出:true

解释:其中一个满足题意的三元组是 (1, 4, 5),因为 (nums1 =1) < (nums40 4)<(nums5 = 6)

提示:

1 < = n u m s . l e n g t h < = 5 ∗ 10 5 1 <= nums.length <= 5 * 10^5 1<=nums.length<=5∗105

− 2 31 < = n u m s i < = 2 31 − 1 -2^{31} <= numsi <= 2^{31} - 1 −231<=numsi<=231−1

思路

先给出时间复杂度为 O ( n 2 ) O(n^2) O(n2)的动态规划做法。用数组dp来记录以当前位置为最后元素的最长子序列长度,当遍历到numsi时,

d p i = max ⁡ ( d p j , d p i ) ( j < i 且 n u m s j < n u m s i ) dpi = \max\left( dpj,dpi \right) \quad (j < i \ \text{且} \ numsj < numsi) dpi=max(dpj,dpi)(j<i 且 numsj<numsi)

java 复制代码
class Solution {
    public boolean increasingTriplet(int[] nums) {
        int n = nums.length;
        int[] dp = new int[n];
        for(int i=0;i<n;i++){
            dp[i] = 1;
            for(int j=i-1;j>=0;j--){
                if(nums[i]>nums[j]){
                    dp[i] = Math.max(dp[i],dp[j]+1);
                    if(dp[i]>=3){
                        return true;
                    }
                }
            }
        }
        return false;
    }
}

该算法通过77/86个测试用例

改进思路

有没有 O ( n ) O(n) O(n)的算法,有!

到当前位置前一个位置为止,如果知道长度为1和长度为2的子序列最后一个元素最小为多少,那么就能判断当前位置是否存在长度为3的子序列。(细品)

用两个变量count0和count1记录到当前位置前一个位置为止,长度为1的最小子序列,和长度为2的最小子序列。初始时count[0]=Integer.MAX_VALUE,count[1]=Integer.MAX_VALUE

nums[i]<=count[0],说明nums[0,...,i]这个子数组中,长度为1的最小子序列为nums[i],且nums[i]无法形成长度为2的子序列,因为它现在是nums[0,..,i]中的最小元素

count[0]<nums[i]<=count[1],说明以count[0]为第一个元素,nums[i]为第二个元素的长度为2的子序列要比当前的长度为2的最小子序列还要小,因此更新count[1] = nums[i],同样地,此时nums[i]无法在nums[0,..i]中形成长度为3的子序列,因为最小的长度为2的子序列已经比nums[i]大了,此时以numsi为第2个元素更可能找到长度为3的子序列

count[1]<nums[i],说明找到了一个长度为3的子序列,以count[0],count[1],nums[i]可以组成长度为3的子序列

java 复制代码
class Solution {
    public boolean increasingTriplet(int[] nums) {
        int n = nums.length;
        //用集合来表示
        //count[0]表示子序列长度为1的最后一个元素下标
        //count[1]表示子序列长度为2的最后一个元素值
        int[] count = new int[2];
        count[0] = Integer.MAX_VALUE;
        count[1] = Integer.MAX_VALUE;
        for(int i=0;i<n;i++){
            //当前元素小于count[0]表示,可以以它为起始元素
            if(nums[i]<=count[0]){
                count[0] = nums[i];
            }else if(nums[i]<=count[1]){
                count[1] = nums[i];
            }else{
                return true;
            }
        }
        return false;
    }
}

时间复杂度: O ( n ) O(n) O(n)

空间复杂度: O ( 1 ) O(1) O(1)

相关推荐
huaweichenai1 小时前
使用maven搭建sprint-boot
java·maven·sprint
zerozrc01 小时前
Java 方法调用的底层原理
java·后端
Smilecoc1 小时前
方向导数与梯度
算法
星火10241 小时前
【LangChain4j系列01】LangChain4j 快速入门与核心概念
java·人工智能
瑞码空间1 小时前
01背包:动态规划的Hello World
c++·算法·0/1背包问题
SemiTris1 小时前
从Controller到Tomcat底层请求链路全解析
java·tomcat
月光有害1 小时前
理解 Spring 依赖注入:从构造器注入到集合与条件 Bean
java·后端·spring
码农进化录1 小时前
Java 程序员的 AI 进化论 | 用 AI 生成 Spring Boot 脚手架,省下两小时重复劳动
java·spring boot·openai
名字还没想好☜2 小时前
Java 用 MethodHandle 替代反射:调用性能实测、invokeExact 的坑与缓存
java·开发语言·缓存·反射·methodhandle