最长递增子序列

前段时间闲着没事做就上网看了看一些编程比赛题,我不是大牛,当然从简单的看起,做的时候腾讯的一道“豆豆”的题让我很感兴趣,题目是这样的:
有只企鹅叫豆豆,总是被别的企鹅欺负。豆豆在长期的隐忍之后,掌握了所有企鹅的高度和攻击力强度,还得到了一把黄金剑。在拥有了黄金剑以后,豆豆终于可以展开绝
地大反击。但这把黄金剑的用法却很奇怪。
首先,豆豆第一次可以选择任何一只企鹅开始挑战。豆豆这一次必胜。
再次,当豆豆已经挑战过某一只企鹅后,再下一次的挑战对象只能是比上一名对手高,且比上一名对手攻击力强的企鹅。这样豆豆必胜。否则黄金剑会觉得打的没意思而故
意发脾气输掉。豆豆还会被大家集体暴打。
面对着这把脾气很大的黄金剑,豆豆想请你帮助他计算一下,他最多可以连续击败多少只企鹅?
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),可能还有更佳的方法,敬请拍砖!!
最后也许你会说我为什么没有给出一个求出最长递增子序列的算法,对!我的程序并不能求出这样的子序列,仅仅只能算出它的长度,不过只需要稍加修改我的程序就可以写出这样一个算法,有兴趣的朋友可以在下面回帖附上自己的代码,非常希望能和各位大牛交流,呵呵!
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值