特殊消消看(game)——一道链表好题

前言

这道题是lzw神犇自己出的一道原创好题,一开始我想到了正确解法,就是细节上没有处理好,初测只有10分,WA了9个点。

题目描述

n个珠子排成一排,每个珠子有一个颜色,用小写字母a…z表示。对于某个珠子,如果存在一个和它相邻并且颜色不同的珠子,那么称它是特殊的。每回合会消去所有特殊的珠子。问多少回合后不能再消。

输入格式

第一行n。第二行一个a…z的字符串,表示珠子的颜色。

输出格式

一行,答案。

输入输出样例

样例一:
game.in
4
aabb

game.out
2
样例二:
game.in
6
aabcaa

game.out
1

样例解释

对于样例一的两个回合:aabb->ab->NULL
对于样例二的一个回合:aabcaa->aa

数据规模与约定

50%的数据n<=10^3
100%的数据n<=10^6

题解

这道题O(n^2)的暴力很好想,就是模拟去删点,这里就不赘述了。正解是用链表做的。我们可以把所有点分成一个个块,块内所有点的颜色相同,相邻的块颜色不同。因为要快速删块,所以考虑使用链表。我们可以用node类型储存每个块的颜色和点数,并构建一个链表。每次把头和尾的块点数-1,中间的点数-2。做的时候可能有些块会被减光,就要把它前面没被减光的块和它后面没有减光的块连一条链,如果颜色相同则需要合并。至于复杂度,每个点都要被删一次,所以是O(n)的。
好久没写过链表了,有好多细节处理得还不大好,所以初测只有10分。附上我10分代码。

#include<iostream>
#include<cstdio>
using namespace std;
struct node
{
    char data;
    int count;
    node *next;
};
int n,ans;
string s;
int main()
{
    freopen("game.in","r",stdin);
    freopen("game.out","w",stdout);
    cin>>n>>s;
    node *h,*p,*q;
    h=new node;
    h->next=NULL;
    h->data=s[0];
    int i=1;
    while (s[i]==s[0]&&i<s.size()) i++;
    h->count=i;
    p=h;
    while (i<s.size())
    {
        q=new node;
        q->data=s[i];
        q->next=NULL;
        int x=i;
        while (s[i]==q->data&&i<s.size()) i++;
        q->count=i-x;
        p->next=q;
        p=q;
    }
    while (h!=NULL&&h->next!=NULL)
    {
        h->count--;
        p=h->next;
        q=h;
        while (p->next!=NULL)
        {
            p->count-=2;
            if (p->count<=0)
            {
                if (p->next->data==q->data)
                {
                    q->count+=max(0,p->next->count-2);
                    q->next=p->next->next;
                }
                else
                {
                    q->next=p->next;
                    p=q;
                }
            }
            q=p;
            p=p->next;
        }
        if (p!=h)
        {
            p->count--;
            if (p->count<=0)
            {
                q->next=NULL;
            }
        }
        if (h->count==0)
        {
            h=h->next;
        }
        ans++;
//      p=h;
//      while (p->next!=NULL)
//      {
//          cout<<p->data<<' '<<p->count<<'\n';
//          p=p->next;
//      }
//      cout<<p->data<<' '<<p->count<<'\n';
    }
    cout<<ans;
}

看出来哪里错了吗?我一开始很自信自己是对的,直到我看到分数。当一个块被减光时,我是直接把它前一个块和后一个块连起来,却没有保证前后的块都没被减光,于是块就会比实际要多,然后就错了。我还是对着数据分析了半天才发现的。
不说了,还是欣赏一下我的AC代码吧。

#include<iostream>
#include<cstdio>
using namespace std;
struct node
{
    char data;//data表示这一块的字母是什么 
    int count;//count表示这一块有几个连续的这样的数字 
    node *next;//指向下一个块 
};
int n,ans;
string s;
int main()
{
    freopen("game.in","r",stdin);
    freopen("game.out","w",stdout);
    cin>>n>>s;//其实n并没有什么用 
    node *h,*p,*q;
    h=new node;
    h->next=NULL;
    h->data=s[0];
    int i=1;
    while (s[i]==s[0]&&i<s.size()) i++;//先把链表头处理掉 
    h->count=i;
    p=h;
    while (i<s.size())
    {
        q=new node;
        q->data=s[i];
        q->next=NULL;
        int x=i;
        while (s[i]==q->data&&i<s.size()) i++;
        q->count=i-x;
        p->next=q;
        p=q;
    }//以上是建立链表的过程 
    while (h!=NULL&&h->next!=NULL)//当链表中什么都没有了或者只剩下一块的时候就做完了 
    {
        h->count--;//最左边的那一块只需要-1 
        p=h->next;
        q=h;
        while (p->next!=NULL)//遍历整个链表 
        {
            p->count-=2;//中间的块要-2 
            if (p->count<=0) //如果这一块减光了,就不操作(这样就自动被删除了)
            {
                p=p->next;
                continue;
            }
            if (q->data!=p->data)//如果这一块没被减光,就把上一个没被减光的next指向这一块 
            {
                q->next=p;//跳过中间一些被减光的块 
                q=p;
            }
            else q->count+=p->count;//如果这两块的data一样,就可以合并 
            p=p->next;
        }//头部和中间都处理完了,接下来要处理尾部 
        if (q->data==p->data)//尾部的合并 
        {
            q->count+=p->count-1;//做完这一步,相当于尾部(p)没了,而q变成了新的尾部,-1不能漏掉 
            q->next=NULL;
        }
        else q->next=p;//跳过中间一些被减光的块 
        if (p!=h)//如果尾和头不是同一个块就-1 
        {
            p->count--;
            if (p->count<=0)//尾部没了 
            {
                q->next=NULL;
            }
        }
        if (h->count<=0)//头部没了的操作放后面会方便一点,起码能保证后面要么都没了,要么就是没被减光的块 
        {
            h=h->next;
        }
        ans++;
//以下是遍历整个链表的操作,可以把每一块输出来 
//      p=h;
//      while (p!=NULL&&p->next!=NULL)
//      {
//          cout<<p->data<<' '<<p->count<<"  ";
//          p=p->next;
//      }
//      if (p!=NULL) cout<<p->data<<' '<<p->count<<'\n';
    }
    cout<<ans;
}

总结

像这种细节很多的题,思路一定要清晰,考虑多种情况,自己要多测几组数据,并把中间过程多输出一点,这样才能发现一些错误。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值