1.題目:刪數問題
給定n位正整數a,去掉其中任意k≤n 個數字後,剩下的數字按原次序排列組成一個新的正整數。對於給定的n位正整數a和正整數 k,設計一個演算法找出剩下數字組成的新數最小的刪數方案。如果數字最前面有0不輸出。
輸入格式:第 1 行是1 個正整數 a。第 2 行是正整數k。
輸出格式:輸出最小數。
輸入樣例: 輸出樣例:
2.貪心選擇性質
2.1 思路
題目要求的是刪除k個數字之後的最小數,假設輸入序列的個數為n,本題可以轉化為:在輸入序列中取n-k個數構成最小數。我們每次取的構成最小數的數值就是當前序列的最小值,由於取出的數要按原次序排列,所以本題要考慮以下兩種情況:
①選擇當前序列的最小值後,最小值後面序列的個數小於我們還需要的數。這種情況我們就要捨棄掉第一小的數,選擇第二小的數判斷是否符合條件,若不符合繼續去第三小的數,以此類推,直到找到符合條件的數,break退出。
②選擇當前序列的最小值後,最小值後面序列的個數大於或等於我們還需要的數。這種情況當然就是我們需要的啦。
考慮以上兩種情況之後,還要注意,第一位數值若為0,不能輸出。
2.2 程式碼
#include <iostream>
#include <string.h>
#include <algorithm>
using namespace std;
char a[1000];
struct Num{
int arr;//數值
int brr;//下標
};//弄個結構體是為了快速排序的時候可以返回下標
Num num[1000];
Num num2[1000];//因為快速排序之後num會變成快排之後的序列,用num2保留原本序列
int k;
bool cmp(Num x,Num y){
if(x.arr==y.arr){
return x.brr<y.brr;
}
return x.arr<y.arr;
}
//快速排序
int vv(Num aa[],int start,int end,int min){
sort(aa+start,aa+end,cmp);
return aa[min].brr;
} //弄個min是為了能夠找到第二小、第三小...的數
int main() {
cin>>a;
cin>>k;
int n=strlen(a);//求出a的長度
bool cc=false;//一開始還沒輸出,初始值設為false
for(int i=0;i<n;i++){
num[i].arr=a[i]-48;
num[i].brr=i;
}//a是個字元陣列,這裡把它轉成整型陣列
for(int i=0;i<n;i++){
num2[i].arr = num[i].arr;
num2[i].brr = num[i].brr;
}//複製
int w=n-k;//需要找幾次
for(int i=0;i<w;i++){
int min = 0;
int result = vv(num,0,n,min);
//如果找到的數符合要求
if(n-1-result >= w-i-1){
//為0,但不是第一個
if(num2[result].arr==0&&cc==true){
cout << num2[result].arr;
}else{ //不為0
if((num2[result].arr)!=0){
cout << num2[result].arr;
cc=true;//記得設為已經有輸出了
}
}
n=n-result-1;//取得符合條件得數後,序列就變成該數後面的序列了
for(int ii=0;ii<n;ii++){
num[ii].arr = num2[ii+result+1].arr;
num[ii].brr = ii;
num2[ii].arr = num[ii].arr;
num2[ii].brr = num[ii].brr;
}
}else{//不符合要求,迴圈找到符合要求的,類似上面
for(min=1;min<n;min++){
int resultt = vv(num,0,n,min);
if(n-1-resultt >= w-i-1){
if(num2[resultt].arr==0&&cc==true){
cout<<num2[resultt].arr;
}else{
if((num2[resultt].arr)!=0){
cout<<num2[resultt].arr;
cc=true;
}
}
n=n-resultt-1;
for(int iii=0;iii<n;iii++){
num[iii].arr = num2[iii+resultt+1].arr;
num[iii].brr = iii;
num2[iii].arr = num[iii].arr;
num2[iii].brr = num[iii].brr;
}
break;
}
}
}
}
return 0;
}
3.時間複雜度分析
時間複雜度應該是O(n*(n-k))
理由:本題要找n-k次,找到合適的數值後都要遍歷複製陣列,平均時間複雜度為n/2,綜合起來時間複雜度為:O(n*(n-k))——取決於k的大小,若k很小的話,約等於O(n^2)
4.對貪心演算法的理解
在對問題求解時,總是做出在當前看來是最好的選擇。也就是說,不從整體最優上加以考慮,演算法得到的是在某種意義上的“區域性最優解”。
因此貪心演算法不是對所有問題都能得到整體最優解,關鍵是貪心策略的選擇。
5.疑難雜症
還有一種輸入方法,為什麼已經用long long int,足夠長了還不讓我過PINTIA,所有輸入樣例都過了,就是PINTIA不讓我過。
不過用long long int的方式定義a,輸入後再轉成陣列會導致倒序,程式碼寫起來有點複雜。以下就是long long int的方式定義a的程式碼:
#include <iostream>
#include <algorithm>
using namespace std;
long long int a;
long long int k;
struct Num{
int arr;//數值
int brr;//下標
};
Num num [1000];
Num num2 [1000];
bool cmp(Num x,Num y){
if(x.arr==y.arr){
return x.brr<y.brr;
}
return x.arr<y.arr;
}
//快速排序
int vv(Num aa[],int start,int end,int min){
sort(aa+start,aa+end,cmp);
return aa[min].brr;
}
int main() {
cin>>a;
cin>>k;
int n=0;
bool cc=false;
while(a!=0){
num[n].arr=a%10;
a=a/10;
n=n+1;
}//求得共有n個數
for(int i=n-1;i>=0;i--){
num[i].brr=n-1-i;
}
for(int i=0;i<n;i++){
num2[i].arr = num[i].arr;
num2[i].brr = num[i].brr;
}
//總共要找幾次
int w=n-k;
for(int i=0;i<w;i++){
int min=0;
int result = vv(num,0,n,min);
//如果找到的數符合要求
if(n-1-result >= w-i-1){
//為0,但不是第一個
if((num2[n-1-result].arr)==0&&cc==true){
cout << num2[n-1-result].arr;
}else{
if((num2[n-1-result].arr)!=0){
cout << num2[n-1-result].arr;
cc=true;
}
}
for(int i=0;i<n-1-result;i++){
num[i].arr = num2[i].arr;
num[i].brr = n-result-2-i;
}
n=n-result-1;
}else{//不符合要求
for(min=1;min<n;min++){
int resultt = vv(num,0,n,min);
if(n-1-resultt >= w-i-1){
if((num2[n-1-resultt].arr)==0&&cc==true){
cout<<num2[n-1-resultt].arr;
}else{
if((num2[n-1-resultt].arr)!=0){
cout<<num2[n-1-resultt].arr;
cc=true;
}
}
for(int i=0;i<n-1-resultt;i++){
num[i].arr = num2[i].arr;
num[i].brr = n-resultt-2-i;
}
n=n-resultt-1;
break;
}
}
}
}
return 0;
}