动态规划(2)------线性动规

线性动规是动态规划的分支之一,其常见的问题有:最长上升子序列,合唱队形,挖地雷等。下文将对这些经典问题做出相应的解答分析。

最长上升子序列

给定一个无序的整数数组,找到其中最长上升子序列的长度。

示例:
输入: 10,9,2,5,3,7,101,18
输出: 4
解释: 最长的上升子序列是 2,3,7,101,它的长度是 4。

分析解答:
由题意我们可知,“最长上升子序列”,顾名思义,序列的最后一个数一定是最大的。那我们不妨采用暴力破解的方式,依次让数组的每个值作为序列的最后一个数。在这里存放数据的数组我们用date表述,用数组B表示以date[i]结尾的最长子序列的长度。当我们要求以date[i]结尾的最长子序列时,必定要遍历下标 i 以前的每一个元素,不妨我们假设 下标i以前的数据date[j]的值小于date[i],那么以date[i]结尾的序列长度就等于以date[j]结尾的序列长度加1.要求最大序列长度,则要比较以上文方式求出来的序列长度,用状态转移方程可表示为 B[i] = max{B[j] + 1} = max{ B[j] } + 1

import java.util.Arrays;
import java.util.Scanner;

/*
最长上升子序列
*/
public class LongestAscendingSubsequence {

	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		int n = sc.nextInt();
		
		//如果待测序列的长度为0
		if(n == 0) {
			System.out.println(0);
			return;
		}
		
		int[] date = new int[n+1];  //1 - n用来存放星信息
		int[] B = new int[n+1];     //用来记录以i结尾的最长上升子序列
		
		for(int i=1;i<=n;i++) {
			date[i] = sc.nextInt();
		}
		sc.close();
		
		for(int i=1;i<=n;i++) {
			int flag = 0;
			//以第一个元素结尾的最长子序列为1
			if(i == 1) {
				B[i] = 1;
			}
			for(int j=1;j<i;j++) {
				if(date[j]<date[i]) {
					flag  = 1;
					B[i] = Math.max(B[i],B[j]+1);
				} 
			}
			if(flag == 0) {
				B[i] = 1;
			}
		}
		Arrays.parallelSort(B);
		System.out.println(B[n]);
	}
}

合唱队形

N位同学站成一排,音乐老师要请其中的(N-K)位同学出列,使得剩下的K位同学排成合唱队形。合唱队形是指这样的一种队形:设K位同学从左到右依次编号为1,2…,K,他们的身高分别为T1,T2,…,TK 则他们的身高满足T1<…Ti+1>…>TK(1≤i≤K)。你的任务是,已知所有N位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。

输入格式
输入的第一行是一个整数N,表示同学的总数。
第二行有n个整数,用空格分隔,第i个整数Ti是第i位同学的身高(厘米)。

输出格式
输出包括一行,这一行只包含一个整数,就是最少需要几位同学出列。

数据范围
2≤N≤100
130≤Ti≤230

输入样例:
8
186 186 150 200 160 130 197 220

输出样例:
4

分析解答:
分析题目,合唱队形实际上是中间高两边矮的一种队形。见下图
在这里插入图片描述结合我们刚刚解决的"最长上升子序列"问题,"合唱队形"实际上是以date[i]结尾同时又以date[i]开头的最长子序列问题,我们只需分别找出这俩个子序列的最长长度,便可以求出最小的离队人数。

import java.util.Scanner;

/*
合唱队形
*/
public class ChorusFormation {
	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		int n = sc.nextInt();      //表示同学的个数
		int[] date = new int[n];
		
		for(int i=0;i<n;i++) {
			date[i] = sc.nextInt();
		}
		sc.close();
		
		//将date[]数组反向
		int[] dateReverse = new int[n];
		for(int i=n-1;i>=0;i--) {
			dateReverse[n-(i+1)] = date[i];
		}
		
		int[] leftB = new int[n];     //以date[i]为尾的最长最长子序列
		int[] rightB = new int[n];    //以date[i]为头的最长最长子序列
		
		f(date,leftB);             //从左往右遍历,得到以date[i]结尾的最长子序列
		f(dateReverse,rightB);     //从右往左遍历,得到以date[i]开头的最长子序列
		
		int min=n-(leftB[0]+rightB[n-1]-1);
		for(int i=0;i<n;i++) {
			int temp = n-(leftB[i]+rightB[n-i-1]-1);
			if(temp<min) {
				min = temp;
			}
		}
				
		System.out.println(min);
		
	}
	
	public static void f(int[] dateArray,int[] B) {
		int n = dateArray.length;
		
		for(int i=0;i<n;i++) {
			if(i == 0) {
				B[i] = 1;
			}
			int flag=0;
			for(int j=0;j<i;j++) {
				if(dateArray[j]<dateArray[i]) {
					flag = 1;
					B[i] = Math.max(B[i], B[j]+1);
				}
			}
			if(flag == 0) {
				B[i] = 1;
			}
		}
	}
	
}

以上即是对线性动规问题的简单分析。

发布了10 篇原创文章 · 获赞 2 · 访问量 1141

猜你喜欢

转载自blog.csdn.net/qq_44872260/article/details/105620565