目录
- [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,1 和 3,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,记count=1;
- 节点9,不为空,消耗一个槽位,但又新加两个槽位,count=count-1+2=2;
- 节点3,不为空,消耗一个槽位,但又新加两个槽位,count=count-1+2=3;
- 节点4,count=count-1+2=4;
- 节点#,消耗一个槽位,count=cout-1=3;
- 节点#,消耗一个槽位,count=cout-1=2;
- 节点1,count=count+1=3;
- 节点#,count=count-1=2;
- 节点#,count=count-1=1;
- 节点2,count=count+1=2;
- 节点#,count=count-1=1;
- 节点6,count=count+1=2;
- 节点#,count=count-1=1;
- 节点#,count=count-1=0;
- 至此,所有节点遍历完毕,槽位为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)