问题描述
从键盘输入一个高精度的正整数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
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
dp[i][i]=a[i];
path[i][i]=i;
}
//填表
for(int r=1;r
for(int i=0;i
int j=i+r;
if(dp[i][j-1]
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+1i,则a1...ai...ak-1ak+1...an,很明显这个数>a1...ai+1...an。如果删除ka1...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
\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
if(S==0)
break;
if(a[i]>a[i+1])
{
flag[i]=true;
S--;
}
}
for(int j=0;j
if(!flag[j])
System.out.print(a[j]);
}
}