最长递增子序列

前段时间闲着没事做就上网看了看一些编程比赛题,我不是大牛,当然从简单的看起,做的时候腾讯的一道“豆豆”的题让我很感兴趣,题目是这样的:
有只企鹅叫豆豆,总是被别的企鹅欺负。豆豆在长期的隐忍之后,掌握了所有企鹅的高度和攻击力强度,还得到了一把黄金剑。在拥有了黄金剑以后,豆豆终于可以展开绝
地大反击。但这把黄金剑的用法却很奇怪。
首先,豆豆第一次可以选择任何一只企鹅开始挑战。豆豆这一次必胜。
再次,当豆豆已经挑战过某一只企鹅后,再下一次的挑战对象只能是比上一名对手高,且比上一名对手攻击力强的企鹅。这样豆豆必胜。否则黄金剑会觉得打的没意思而故
意发脾气输掉。豆豆还会被大家集体暴打。
面对着这把脾气很大的黄金剑,豆豆想请你帮助他计算一下,他最多可以连续击败多少只企鹅?
Input
第一行:一个数据n,代表企鹅群里除了豆豆一共有n(1 ≤ n ≤ 1000)只企鹅。
第2至第n+1行:每行2个数字。第i+1行的第一个数字为企鹅i的高度。第i+1行的第二个数字为企鹅i的攻击力。0 ≤ 高度,攻击力 ≤ 1,000,000。
Output
一个数。代表豆豆最多可以连续击败的企鹅数。
Sample Input
Sample Input #1
3
1 3
3 2
2 4
Sample Output #1
2
Sample Input #2
5
10 1
9 2
7 3
6 4
5 5
Sample Output #2
1
初看这题就想到了DP,以前大牛们不是说过:一切皆为DP吗!呵呵,我思索了下,题目铺垫很长,但输入、输出却很明确,两个限制性条件:身高,攻击力。
我最先先想到的是逐个遍历每只企鹅,然后依次比较能攻击的数目,取最大的那个。事实上我仔细一想,这样是非常麻烦,两个条件的要求使得比较没有看起来
那么容易,这时我想如果能减少一个条件这个问题就非常好解决了,比如不考虑身高,这么一想就明确了,先把身高排序,这样就只用比较攻击力了,而且由于
身高排好序了,攻击力的比较只需要和身高比自己高的企鹅进行比较,这么讲太抽象,我将排好序的企鹅列表如下:
企鹅总数为:5
5 5
6 4
7 3
9 2
10 1
下面只需要比较企鹅的攻击力,如从5 5这只企鹅开始,依次用5和4,3,2,1,比较找到比5大的个数,记录进最后的个数max,然后用6 4这只企鹅,依次用4和3,2,1,比较找到比4大的个数,和max比较,如果比max大就赋值给max。遍历一遍后就能找出最大能攻击的企鹅个数,于是我再纸上写下了以下的代码:
#include "stdio.h"   
#define MAX 1000   
struct node   
{   
             int h;   
             int a;   
}nodeas[MAX];   
void main()   
{   
             int n, i, j, k, p, q;   
             int temp = 0;   
             int max = 1;   
             scanf("%d",&n);   
             for (i = 0; i < n; i++)   
             {   
                            scanf("%d%d",&nodeas[i].h,&nodeas[i].a);            
             }   
             for (j = 0; j < n; j++)   
             {   
                            for (k = j; k < n; k++)   
                            {   
                                         if (nodeas[k].h < nodeas[j].h)   
                                         {   
                                                        nodeas[k].h += nodeas[j].h;   
                                                        nodeas[j].h        = nodeas[k].h - nodeas[j].h;   
                                                        nodeas[k].h -= nodeas[k].h - nodeas[j].h;   
                                                        nodeas[k].a += nodeas[j].a;   
                                                        nodeas[j].a        = nodeas[k].a - nodeas[j].a;   
                                                        nodeas[k].a -= nodeas[k].a - nodeas[j].a;   
                                         }            
                            }            
             }   
             for (p = 0; p < n; p++)   
             {   
                            for (q = p; q < n; q++)   
                            {   
                                         if (nodeas[p].a < nodeas[q].a)   
                                         {   
                                                        temp++;   
                                         }   
                            }   
                            if (temp > max)   
                            {   
                                         max = temp;       
                            }   
                            temp = 0;   
             }   
             printf("%d\n",max);   
}
但我的组长看到我写的代码后马上指出了我的错误所在,这种方法是相当于假定一个节点为子序列头结点后,遍历后面的节点,之后比它大,就纳入子序列中,但是这样做是有问题的;想想这样一个序列:1 9 2 3 4 5;当选择了1为头结点之后,要是你选择9为头结点,后面的节点就都不能选了。而实际上不选择9,你可以得到长度为5的最长递增序列!而你的程序是一下子把9 2 3 4 5都拿到了子序列中,应该是有问题的,还需要考虑一下。
于是我又仔细想了想上面的程序确实没有考虑全面,正如组长所说的那样,对于类似192345这样的例子就会出错,那么这个最长递增子序列怎么求呢,我微微想了下,可以把我想要求的最小子序列存起来,依次将递增的数值存入数组,先将第一个数字存入数组,然后遍历源序列,遇到比子序列中最后一个数字大的数就加入子序列,如果比最后一个小,就再从头遍历子序列找到刚好比它大的数,将它替换这个数之前的数,这样只需遍历一遍原序列就能找出最长递增子数列的长度,在排好序后,修改所写代码如下:
node_list[q] = nodeas[0].a;             
for (p = 0; p < n; p++)             
{             
 if (node_list[q] < nodeas[p].a)      
 {        
  q++;                
  node_list[q] = nodeas[p].a;      
 }      
 else if (node_list[q] > nodeas[p].a)      
 {        
  for (r = 0; r < q; r++)        
  {                  
   if (node_list[r] > nodeas[p].a)          
   {                       
    node_list[r] = nodeas[p].a;          
   }        
  }      
 }             
}             
printf("%d\n",q+1);
这样实现成功之后,我又仔细看了下这段程序,觉得他还不够优美,细细算了下时间复杂度,达到了O(N2),品味了一下,觉得在遍历子序列时还可以优化,比如用二分查找法,于是我进一步修改了代码,使用二分查找来实现替换子序列中的数,代码修改部分如下:
else if (node_list[q] > nodeas[p].a)      
{        
 low = 0;        
 high = q;        
 while (low <= high)        
 {                  
  mid = (int)((low + high) / 2);          
  if (node_list[mid] > nodeas[p].a)          
  {            
   high = mid - 1;          
  }          
  else if (node_list[mid] < nodeas[p].a)          
  {            
   low = mid + 1;          
  }          
  else          
  {            
   break;          
  }        
 }        
 node_list[mid] = nodeas[p].a;      
}
这样一来在遍历源序列的时间复杂度就降到了O(NlogN),可能还有更佳的方法,敬请拍砖!!
最后也许你会说我为什么没有给出一个求出最长递增子序列的算法,对!我的程序并不能求出这样的子序列,仅仅只能算出它的长度,不过只需要稍加修改我的程序就可以写出这样一个算法,有兴趣的朋友可以在下面回帖附上自己的代码,非常希望能和各位大牛交流,呵呵!

你可能感兴趣的:(职场,休闲,最长递增子序列,递增子序列)