刷题--完美的代价

问题描述
  回文串,是一种特殊的字符串,它从左往右读和从右往左读是一样的。
小龙龙认为回文串才是完美的。现在给你一个串,它不一定是回文的,
请你计算最少的交换次数使得该串变成一个完美的回文串。
  交换的定义是:交换两个相邻的字符
  例如mamad
  第一次交换 ad : mamda
  第二次交换 md : madma
  第三次交换 ma : madam (回文!完美!)


输入格式
  第一行是一个整数N,表示接下来的字符串的长度(N <= 8000)
  第二行是一个字符串,长度为N.只包含小写字母


输出格式
  如果可能,输出最少的交换次数。
  否则输出Impossible


样例输入
5
mamad


样例输出
3

首先,什么情况下的字符串可以构成回文序列?

 1.如果字符串的长度为偶数,那么不能有出现次数为奇数的元素;

2.如果字符串长度为奇数,那么至多只能有一个出现次数为奇数的元素。

解决思路:

首先定义i指向字符串开头,j指向字符串的最后一个元素,进入循环,先让i不动,j从后往前指,寻找与i指向的元素相同的元素。因为j时刻改变,所以需要k来指每次进行配对的字符串的最后一个元素,循环时让j=k即可。

此时有两种情况

1. i != j:说明找到了,此时将j所指元素后移到k所指位置(即与i对称的位置),j与k之间的元素前移为所找到的元素腾出位置;

然后,i指向后移一个,k指向前移一个,再次进行循环,已经配对好的不用管。

2.i = j:说明没有找到,即i所指元素在此时需要进行配对的序列中只出现了一次(但不代表在整个字符串序列中只出现了一次,但在整个序列来说,一定是出现了奇数次)。

此时需要判断字符串长度的奇偶性,若为偶,直接打印"Impossible";若为奇,需要判断是第几次出现这样的情况,若不是第一次出现,直接打印"Impossible"。

如果是第一次出现,这个出现奇数次的元素,一定是要放在中间的,但是现在不能直接将它放在中间(如果放在中间,后面再继续配对,可能会将其再次移走),所以此时只需要跳过这个元素,让i指向下一个元素,而k不变,此时再次配对,与i的元素配对的元素就会放在i对称位置的后一个,此时虽然不对称,但最后只需要将奇数次的元素移到中间,奇数次元素后面到中间位置的元素依次前移,这样就可以对称了。而奇数次元素移到中间位置所需要的次数即为下标之差,即len/2 - 1。此时记录下这个次数就好。

最后跳出循环i与k相等,指向的元素也必然相等,不用移动,所以便可以结束,输出次数。
 

代码如下:

#include <stdio.h>
#include <stdlib.h>

int main()
{
    int len = 0; // 字符串长度
    char ch[8005];

    scanf("%d", &len);
    getchar();
    gets(ch);

    int i = 0;       // 从前面往后指
    int j = 0;       // 从后面往前指
    int k = len - 1; // 由于j每次需要减减值会改变,而j每次开始循环需要指向新的下标,所以用k来记录j需要的起始值
    int x = 0;
    int sum = 0;
    int flag = 0;

    for (i = 0; i < k; i++)
    {
        for (j = k; j >= i; j--)
        {

            if (i == j) // 说明在此时需要进行操作的字符串中,ch[i]仅出现一次,即没有与之匹配的元素
            {
                if (len % 2 == 0)
                {
                    printf("Impossible");
                    return 0;
                }
                else if (flag == 1)
                {
                    printf("Impossible");
                    return 0;
                }
                sum += len / 2 - i; // 将其移到中间所需步数,不能立刻放在中间,最后再进行,但只需要求步数,所以最后也不必真的移动
                flag = 1;
                break;
            }

            if (ch[i] == ch[j]) // 找到第一个和i所指元素相等的元素
            {
                // 此时需要将后面的元素前移
                for (x = j; x < k; x++)
                {
                    ch[x] = ch[x + 1];
                    sum++;
                }

                ch[k] = ch[i]; // 放在对应位置
                k--;
                break;
            }
        }
    }

    printf("%d", sum);

    return 0;
}

这个只记录了次数,如果我们想看看进行回文操作之后的序列,可以在最后进行移动。

代码如下:

​​​​​​​​​​​​​​​​​​​​​

#include <stdio.h>
#include <stdlib.h>

int main()
{
    int len = 0; // 字符串长度
    char ch[8005];

    scanf("%d", &len);
    getchar();
    gets(ch);

    int i = 0;       // 从前面往后指
    int j = 0;       // 从后面往前指
    int k = len - 1; // 由于j每次需要减减值会改变,而j每次开始循环需要指向新的下标,所以用k来记录j需要的起始值
    int x = 0;
    int sum = 0;
    int flag = 0;
    int record = 0;
    char temp;

    for (i = 0; i <= k; i++)
    {
        for (j = k; j >= i; j--)
        {

            if (i == j) // 说明在此时需要进行操作的字符串中,ch[i]仅出现一次,即没有与之匹配的元素
            {
                if (len % 2 == 0)
                {
                    printf("Impossible");
                    return 0;
                }
                else if (flag == 1)
                {
                    printf("Impossible");
                    return 0;
                }
                sum += len / 2 - i; // 将其移到中间所需步数,不能立刻放在中间,最后再进行,但只需要求步数,所以最后也不必真的移动
                record = i;
                flag = 1;
                break;
            }

            if (ch[i] == ch[j]) // 找到第一个和i所指元素相等的元素
            {
                // 此时需要将后面的元素前移
                for (x = j; x < k; x++)
                {
                    ch[x] = ch[x + 1];
                    sum++;
                }

                ch[k] = ch[i]; // 放在对应位置
                k--;
                break;
            }
        }
    }

    temp = ch[record];
    for(x = record; x < len/2; x++)
    {
        ch[x] = ch[x+1];
    }ch[x] = temp;

    printf("%d\n", sum);
    printf("%s",ch);

    return 0;
}

可以看到此时样例的输出:

 

写的时候犯的错误:

先进行了判断元素是否相等再判断i == j,这种写法是错误的,如果当i真的等于j的时候,这时应该当作“没有找到”的情况处理,而循环进入,先进行ch[i] == ch[j]的判断,此时i=j,这个条件必定满足,此时就会当作“找到了”处理,此时若k大于j,就会进行交换,就会出错。

若有错误或者不足,还请多多指教,批评指正🥰

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值