题目很长,总结下来就是找出最多只包含两个数的子数组,竟然是子数组问题,第一时间想到滑动窗口,固定左窗口,遍历右窗口,在加入第三个数时,维护左窗口,左窗口维护时,需要注意
1 0 1 2 2 2 1 2这种,在遍历到2时,应该维护到0后面,而不是1后面,在我写的程序中用set保存子数组的数字,很好理解,但是并不是优秀的写法,复杂度比较高
class Solution {
public int totalFruit(int[] fruits) {
//找到数组中只包含两个数的最长子数组
Set<Integer> set=new HashSet<>();//保存篮子里的水果
Map<Integer,Integer> map=new HashMap<>();//保存每个数最后出现的位置
int i=0;//左窗口
int max=0;//最长的数组
for(int j=0;j<fruits.length;){//右窗口
if(set.size()==2&&!set.contains(fruits[j])){//如果已经有了两种水果并且遍历到了新的水果
int fru=0;//需要移出去的水果
int index=Integer.MAX_VALUE;//需要移出去水果的下标
for(int a:set){
if(map.get(a)<index){//哪个水果最后出现的位置在前面就把哪个水果移出去
fru=a;
index=map.get(a);
}
}
set.remove(fru);//移出要移出的水果
i=index+1;//维护左窗口
}
else{
map.put(fruits[j],j);//更新水果最后出现的位置
set.add(fruits[j]);//添加水果
max=Math.max(max,j-i+1);//维护最大值
j++;//维护右窗口
}
}
return max;
}
}
这里再附上官方的写法,也是滑动数组,复杂度也比较高,但是这种写法的map和我写法的map不一样,这里的map是保存水果的数量,再种类超过2以后,左侧窗口维护,可以理解成一个一个扔出去,知道把一个种类的水果全扔去了,也很好理解
class Solution {
public int totalFruit(int[] fruits) {
int n = fruits.length;//数组长度
Map<Integer, Integer> cnt = new HashMap<Integer, Integer>();//保存水果的个数
int left = 0, ans = 0;
for (int right = 0; right < n; ++right) {
cnt.put(fruits[right], cnt.getOrDefault(fruits[right], 0) + 1);//记录水果个数
while (cnt.size() > 2) {//已经存在了超过两种水果
cnt.put(fruits[left], cnt.get(fruits[left]) - 1);//左侧窗口不断维护
if (cnt.get(fruits[left]) == 0) {//知道维护到一种完全出去了
cnt.remove(fruits[left]);
}
++left;
}
ans = Math.max(ans, right - left + 1);//维护最大值
}
return ans;
}
}
再写一个欠下的LCIS(最长公共递增子序列),参考LCS和LIS,这个综合应用比较难理解,但是对LIS和LCS很熟练地话,用点比较蠢得方法还是能写,dp的定义和最长公共子序列相似
dp[i][j]:a的前i个字符和b的前j个字符最长的公共递增子序列
首先
a[i-1]!=b[j-1]时,dp[i][j]=dp[i-1][j],这个比较好理解,因为a[i-1]!=b[j-1],所以他们一定不是公共的,所以就是dp就等于不取当前字符的长度
当a[i-1]==b[j-1],就比较难理解,我这里用了三层循环,为了好理解,当他们相等时,就要找到以他们为结尾的最长递增序列,这里就和最长递增子序列很相似
public class LCIS {
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
int N= sc.nextInt();
int M=sc.nextInt();
int[] a=new int[N];
int[] b=new int[M];
for(int i=0;i<N;i++){
a[i]=sc.nextInt();
}
for (int j=0;j<M;j++){
b[j]=sc.nextInt();
}
int ans=0;//保存结果
int[][] dp=new int[N+1][M+1];//dp:a的前i个字符和b的前j个字符最长的LCIS
for(int i=1;i<=N;i++){
for(int j=1;j<=M;j++){
if(a[i-1]==b[j-1]){//如果相等
int maxv=1;
for (int k=1;k<j;k++){//找到最长的递增序列
if(a[i-1]>b[k-1])//if(arr[j]<arr[i]) dp[i]=Math.max(dp[i],dp[j]+1 );和这个相似,不过说是两组数,但是因为两数相等,实际是差不多的
maxv=Math.max(maxv,dp[i-1][k]+1);
dp[i][j]=Math.max(dp[i][j],maxv);//维护最大值
}
}
else//如果a[i-1]不等于b[j-1]
dp[i][j]=dp[i-1][j];//那么就和a的i-1到b的j的dp是一样的,因为i-1不等于j-1,是不能公共的
ans=Math.max(ans,dp[i][j]);//维护最大值
}
}
System.out.println(ans);
}
}
两层循环就是把找递增的一步在第二层循环里操作,有些细节理解的不是很好
public class LCIS_最长公共上升子序列 {
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
int N= sc.nextInt();
int M=sc.nextInt();
int[] a=new int[N];
int[] b=new int[M];
for(int i=0;i<N;i++){
a[i]=sc.nextInt();
}
for (int j=0;j<M;j++){
b[j]=sc.nextInt();
}
int ans=0;
int[][] dp=new int[N+1][M+1];//dp:a的前i个字符和b的前j个字符最长的LCIS
for(int i=1;i<=N;i++){
int val=0;//记录以[i-1]结尾的最长公共递增长度
for (int j=1;j<=M;j++){
if(a[i-1]==b[j-1]){
dp[i][j]=val+1;
ans=Math.max(ans,dp[i][j]);
}
else {
dp[i][j]=dp[i-1][j];
if(a[i-1]>b[j-1]){
val=Math.max(val,dp[i-1][j]);
}
}
}
}
System.out.println(ans);
}
}