试题 算法训练 小明爬山(最长公共子序列 Java)

问题描述
  你有个同学叫小明,他早听闻祖国河山秀丽,于是有一个爬山的计划,并列了一张所有山的高度表,而又因“人往高处走”的说法,所以他希望爬的每一座山都比前一座要高,并且不能改变山的高度表上的顺序,爬的山数还要最多,请贵系的你帮他解决这个问题。
输入格式   
输入第一行为num,代表山的个数   
输入第二行有num个整数,代表每座山的高度
输出格式
  输出只有一个数,为符合要求的最大爬山数
样例输入
10
1 3 5 7 9 2 4 6 8 10
样例输出
6
样例输入
10
1 3 2 7 5 4 5 6 10 11
样例输出
7
数据规模和约定   
对于100%的数据,num <= 100000,高度<=10000。
题目链接:小明爬山

思路:
  可以很容易看出是求最长上升子序列的题,根据其思想去解决

求解:

  1. 方法一:使用动态规划的思想
    a:状态的表示: d p [ i ] dp[i] dp[i] 表示以第 i 个数结尾的最长上升子序列的长度
    b:状态转移:如果第 i 个数大于以第1~ i-1的某个数结尾的数 j,那么 d p [ i ] dp[i] dp[i] 就可以由 d p [ j ] dp[j] dp[j]得到,值为 d p [ j ] + 1 dp[j]+1 dp[j]+1

代码:

import java.util.Scanner;
public class Main {
	static int[] nums = new int[1000001];
	static int[] dp = new int[1000001];
	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		int n = sc.nextInt();
		for(int i = 1; i <= n; i++)
			nums[i] = sc.nextInt();
		dp[1] = 1;	//初始化
		int max = 1; //最长长度
		for (int i = 2; i <= n; i++) {
			dp[i] = 1;  //每一位数都可以由自身构成长度为1的子序列
			for (int j = 1; j < i; j++) {//更新长度
				if(nums[i] > nums[j])
					dp[i] = Math.max(dp[i], dp[j] + 1);
			}
			max = Math.max(max, dp[i]);
		}
		System.out.println(max);
	}
}

复杂度分析:
  两个for循环会达到 O ( n 2 ) O(n^2) O(n2),数据量大会超时,所以只能通过部分测试点。
在这里插入图片描述

  1. 方法二:利用序列自身特征去巧算,此方法需要一个辅助数组统计最长上升子序列的长度。
    a:数组 tmp[]临时存储山的高度,len统计tmp[]内的数据个数,high[]为原始序列。
    b:初始化: t m p [ 1 ] = h i g h [ 1 ] , l e n = 1 tmp[1] = high[1], len = 1 tmp[1]=high[1]len=1
    c:操作步骤:逐个处理high[]中的数字:当处理 h i g h [ k ] high[k] high[k] 时:
     ① h i g h [ k ] high[k] high[k] t m p [ l e n ] tmp[len] tmp[len] 大,就将其加到tmp[]末尾,len + 1。
     ② h i g h [ k ] high[k] high[k] t m p [ l e n ] tmp[len] tmp[len] 小,替换tmp[]中第一个大于等于它的数字

下面举个例
  high[ ] = {4,8,9,5,6,7}
在这里插入图片描述
这里说明一下为什么要替换第一个大于等于它的数字 ?
  因为后面可能有很多数都比 t m p [ l e n ] tmp[len] tmp[len] 小,而用这些数组成子序列可能会有更好的结果,我们用比 t m p [ l e n ] tmp[len] tmp[len] 小的 h i g h [ i ] high[i] high[i] 替换第一个大于等于它的数既并不会影响 l e n len len 的最终长度,也保证了后面小于 t m p [ l e n ] tmp[len] tmp[len] 的数也能去尝试构成结果序列的一部分。
  就像上面表格的第三行 4 , 8 , 9 4,8,9 489 来说,若小于 9 的 5 不替换大于等于它的第一个数,就得不到最终的结果序列 4 , 5 , 6 , 7 4,5,6,7 4567

代码:

import java.util.Scanner;

public class Main {
	static int[] high = new int[100001];	//存放山高度的数组
	static int[] tmp = new int[100001];		//辅助统计最长递增子序列的数组
	static int n;	//山的数量
	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		n = sc.nextInt();
		for (int i = 1; i <= n; i++)
			high[i] = sc.nextInt();
		System.out.println(getLIS());
	}
	public static int getLIS() {	//获取最长长度
		int len = 1;
		tmp[1] = high[1];	//初始化
		for (int i = 2; i <= n; i++) {
			if(high[i] > tmp[len])	//符合递增条件,直接放入
				tmp[++len] = high[i];	
			else {	//不是递增的就替换第一个不小于它的数
				int j = findIndex(i, len);	//查找第一个不小于它的数
				tmp[j] = high[i];	//替换
			}
		}
		return len;
	}
	public static int findIndex(int k, int len) {//利用二分查找,降低复杂度
		int l = 1, r = len;
		int num = high[k], res = r;
		while(l < r) {
			int mid = (l + r) >> 1;
			if(tmp[mid] >= num) {//大于或等于就改变res和右区间
				res = mid;
				r = mid;
			}
			else 	//小于就改变左区间
				l = mid + 1;
		}
		return res;
	}
}

复杂度分析:for循环中二分,复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)。成功AC。
在这里插入图片描述

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Easenyang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值