删数问题

 

问题描述

从键盘输入一个高精度的正整数n(<=240位),去掉其中任意S个数字后,剩下的数字按照从左到右的顺序重新组成一个新的数字,寻找一种方案,使得剩余的数字最小

例如,n=178543,S=4 最优解13

递归

假设刚开始的这一个正整数n,表示为\(a_1\)...\(a_n\),而最优解表示为\(b_1\)...\(b_l\)其中l=n-S。其实我们可以看到,如果\(b_i\)确定下来了,\(b_i\)=\(a_j\),那么\(b_{i+1}\)应该从\(a_{j+1}\)到\(a_{n-(l-i)}\)中选择最小的,n-(l-i)是因为必须保证后面存在这么多位数字的情况下这一位最小。
\(b_i\)=\(min\) \(a_i,{start<=i<=n-num}\)num表示后面所剩的位数,本来应该是n-num+1,但是这里是<=,所以是n-num

Java代码实现

将n表示为数组的形式

        private static int num=2;//初始值是S,表示这个数后面还有几位
	public static void getSum(int[]a,int start) {//start表示a中可以开始的位置
		if(num==0)//这是最后一个数,递归出口
			return;
		int min=a[start];
		int index=start;
		for(int i=start;i<=a.length-num;i++) {
			if(min==0)//此处首位也可以是0
				break;
			else if(min>a[i]) {
				min=a[i];
				index=i;
			}
		}
		if(index==a.length-num)//最后的都不变
		{
			for(int i=index;i<a.length;i++) {
				System.out.print(a[i]);
			}
			return;
		}
		System.out.print(min);
		num--;//这一位找到了
		getSum(a,index+1);//进行从index+1到最后的子问题
	}

将n表示为String,用ASCII码

    private static int num=2;//初始值是S,表示这个数后面还有几位
    public static void getSum(String str,int start) {
		if(num==0)//这是最后一个数,递归出口
			return;
		int index=start;
		for(int i=start;i<=str.length()-num;i++) {
			if(str.charAt(index)==48)//0
				break;
			else if(str.charAt(index)>str.charAt(i))
				index=i;
		}
		if(index==str.length()-num) {
			System.out.println(str.substring(index));
			return;
		}
		System.out.print(str.charAt(index));
		num--;//这一位找到了
		getSum(str,index+1);//进行从index+1到最后的子问题
	}

DP

其实可以想象到,这里做了许多重复比较。所以也可以用填表法(DP)。a[i][j]表示从i到第j的最小值,那么就是找S次最小值就可以了。

Java实现代码

public static void getSum(int[]a,int S) {
		int[][]dp=new int[a.length][a.length];
		int[][] path=new int[a.length][a.length];//记录在a数组中的位置,求最优解时用这个下标+1
		//初始值
		for(int i=0;i<a.length;i++) {
			dp[i][i]=a[i];
			path[i][i]=i;
		}
		//填表
		for(int r=1;r<a.length;r++)
		for(int i=0;i<a.length-r;i++) {
			int j=i+r;
			if(dp[i][j-1]<dp[i+1][j]) {
				dp[i][j]=dp[i][j-1];
				path[i][j]=path[i][j-1];//左边
			}else {
				dp[i][j]=dp[i+1][j];
				path[i][j]=path[i+1][j];//右边
			}
		}
		int start=0;
		for(int i=S;i>0;i--) {
			System.out.print(dp[start][a.length-i]);
				start=path[start][a.length-i]+1;
		}
	}

可以按照同递归的方法将对数组的操作变为对字符串的,以便应对更多位数的n。
其实DP进行了许多无用计算,算法仍然可以优化。明天再更。

贪心策略

我们先设想一下,如果一堆数字只需要删除一个。那么应该删除从左往右第一个极大值,因为如果数字是升序排列的,只需要删除最后一个(即最大的)。当数字不是升序的,假设a1..an,ai为从左向右第一个极大值(即ai+1<ai),如果删除ai所得为a1...ai+1...an,如果不删除ai删除k>i,则a1...ai...ak-1ak+1...an,很明显这个数>a1...ai+1...an。如果删除k<i则a1...ak-1ak+1...ai...an则这个数也>a1...ai+1...an:
\(\left\{ \begin{aligned} a1...ai+1...an,{删除a_i}\\ {\left\{ \begin{aligned}a1...ai...ak-1ak+1...an,删除a_k,k>i\\ a1...ak-1ak+1...ai...an,删除a_k,k<i\end{aligned}\right.} \end{aligned} \right. \)
k!=i时两种都比a1...ai+1...an大,所以删除\(a_i\)是最优解。

这是删除一个的时候,当删除S个时,就是重复上述过程S次

Java实现代码

public static void getSum2(int[]a,int S) {
		//从左到右找到第一位极大值
		boolean[] flag=new boolean[a.length];
		for(int i=0;i<a.length-1;i++) {
			if(S==0)
				break;
			if(a[i]>a[i+1])
				{
				flag[i]=true;
				S--;
				}
		}
		for(int j=0;j<a.length-S;j++) {
			if(!flag[j])
				System.out.print(a[j]);
		}
	}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值