求最长单调子序列java,单调减子序列(java实现)

题目:从一个由N个整数排列组成的整数序列中,自左向右不连续的选出一组整数,可以组成一个单调减小的子序列(如从{68 69 54 64 68 64 70 67 78 62 98 87}中我们可以选取出{69 68 64 62}这个子序列;当然,这里还有很多其他符合条件的子序列)。给定整数序列的长度和整数序列中依次的值,请你求出这个整数序列中“最长的单调减小的子序列的长度”以及“不同但长度都是最长得单调减小的子序列的数量”。

输入第1行为一个整数N,表示输入的整数序列的长度(1≤N≤50000)。输入第2行包括由空格分隔的N个整数(每个整数都在32位长整型范围内)。

输出包括一行,为两个数字,分别为针对给定的整数序列求出的“最长的单调减小的子序列的长度”以及“值不同但长度都是最长得单调减小的子序列的数量”

样例输入

12

68 69 54 64 68 64 70 67 78 62 98 87

样例输出

4 2

对于这个题,一共有两个小部分的问题要解决。前一个问题是最长不上升子序列,属于LIS问题,使用动态规划解决,后一个问题属于去重问题。

对于LIS问题,声明dp[i] 以第i个元素为结尾的子序列的最长的长度。

对第i个元素,与前i-1个元素进行比较:

dp[i] = 1; //当末尾只要一个元素时 长度为1

如果 arr[i] < arr[j]:

如果dp[i] < dp[j] + 1

此时dp[i]的值会被更新为dp[j] + 1

其他情况不做处理

对于去重问题:

“值不同但长度都是最长得单调减小的子序列的数量” 这里说的是:

比如输入:

6

2 1 2 1 2 1

输出应为 2 1

2 1 2 1 这两个是值相同的,所以应该当做一个

使用size[i] 数组去记录第i元素为结尾时,值不同但长度都是最长得单调减小的子序列的数量

每次在dp更新一遍以后,进行size的更新。

去掉相同值的情况,如果只去关注最后结尾时:

因为每次遍历都会更新状态,也就是说如果有相同值的时候 后者会把前者的情况 都会过一遍,所以只要每次更新时保证只取相同值的最后一个出现的元素位置的size[j]即可,也就是最右边的那个。

对于i元素所构成的最长子序列的前一个元素可能有很多不同值,所以要记录这些值,并只取最右边的。

最后size 和 dp都已经生成了最终数组

然后对整个数组进行遍历, 找出最大序列 且值不同的序列的数量

方法同找单个i位置元素的值不同但长度都是最长得单调减小的子序列的数量 一致

其他说明:

数据较大 使用java中的BigInteger

遍历找值不同但长度都是最长得单调减小的子序列的数量时 使用倒序查找

代码:

Scanner read = new Scanner(System.in);

int n = read.nextInt();

long[] arr = new long[n];

long[] dp = new long[n];

BigInteger[] size = new BigInteger[n];

for(int i = 0; i < n; ++i){

arr[i] = read.nextLong();

}

long max = 0;

for(int i = 0; i < n; ++i){

dp[i] = 1;

size[i] = new BigInteger("0");

for(int j = 0; j < i; ++j){

if(arr[j] > arr[i]){

if(dp[j] + 1 > dp[i]){

dp[i] = dp[j] + 1;

}

}

}

if(dp[i] > max){

//更新 最长长度

max = dp[i];

}

// 确定以arr[i]结尾的 子序列中 值不同但长度都是最长得单调减小的子序列的数量

if(dp[i] > 1){//如果 不是只有一个数字的时候

Set sl = new HashSet<>();

for(int j = i - 1; j >= 0; --j){

//从右向左查询 只查询第一次遇到的并且是最大长度的 size[i]

// 没有记录路径 通过 arr[j] > arr[i] && dp[j] == dp[i] - 1 来确定是否是前一个转移

// 遇到相同结尾的情况,更右边的已经包含了左边的情况

if(arr[j] > arr[i] && dp[j] == dp[i] - 1 && !sl.contains(arr[j])){

sl.add(arr[j]);//去重

size[i] = size[i].add(size[j]);

}

}

}else{

//只有一个数字是 数量为1

size[i] = new BigInteger("1");

}

}

BigInteger maxBigI = new BigInteger("0");

Set set = new HashSet<>();

//遍历整个序列 找出最大长度 且值不同的序列的数量

for(int i = n - 1; i >= 0; --i){

if(dp[i] == max && !set.contains(arr[i])){

set.add(arr[i]);

maxBigI = maxBigI.add(size[i]);

}

}

System.out.println(max + " " + maxBigI.toString());

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值