BASIC-19 / Tsinsen 1043 完美的代价(java)

问题描述
  回文串,是一种特殊的字符串,它从左往右读和从右往左读是一样的。小龙龙认为回文串才是完美的。现在给你一个串,它不一定是回文的,请你计算最少的交换次数使得该串变成一个完美的回文串。
  交换的定义是:交换两个相邻的字符
  例如mamad
  第一次交换 ad : mamda
  第二次交换 md : madma
  第三次交换 ma : madam (回文!完美!)
输入格式
  第一行是一个整数N,表示接下来的字符串的长度(N <= 8000)
  第二行是一个字符串,长度为N.只包含小写字母
输出格式
  如果可能,输出最少的交换次数。
  否则输出Impossible
样例输入
5
mamad
样例输出
3

题目分析:

求把一个字符串变成回文串的最小交换次数,有难度,需要仔细分析移动过程,分析出算法。

算法分析:

贪心算法。为什么会想到用贪心算法呢,慢慢分析思路,如何交换才能得到回文串并且要保证次数最少。如果先将两端进行对称,再从两端不断向中间推进,再推进的过程不再影响到两端的字符,而是只与当前的状态有关,这样每次都是局部最优,属于贪心算法。必然也是次数最少的方法。

具体如何实现呢,我们从左向右地去遍历数组,然后再从右向左地查找对称的字符。

如果从两端不断向中间推进的过程里,左右两端的指针i,j重合了,说明这是单独的字符,要进行计数,比如本题中d是单独字符。这里分析一下,如果总长为偶数,则必然是Impossible,很好理解,一个偶数串里有一个单独的字符,是不可能成为回文串的。比较难理解的是总长为奇数的情况,如果出现了单独字符,我们可以先跳过它,但是如果出现了第二个单独字符,则Impossible了,奇数串里存在两个单独字符,只交换相邻字符,也不能够得到回文串了。分析得出了两种Impossible的情况之后,我们考虑怎么去处理刚刚跳过的单独字符,其实也不用考虑,移动到最中心去就好了。

上面分析的是左右两端的指针i,j重合的情况。如果两端的指针i,j重合没有重合,就很好办了,说明找到了相同字符,比如本题第一位是m,就从右向左地查找到了第一个m的位置。要做的就是把m移动到最后去。这样我们就把左边第一位和右第一位进行了对称。然后把右端向中间推进一位。

下面循环整个过程就可以,比如把m移动好之后,就从右向左地继续找和左边第二位的a一样的字符,如果找到了,就把它的位置换到倒数第二位,不断对称,也符合最初所说的贪心算法,也就是无后效性和局部最优的两个特性。

算法设计:

import java.util.*;
public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        char[] a = new char[n];
        a = sc.next().toCharArray();
        sc.close();
        
        int j = n - 1;
        int single = 0;
        int sum = 0;
        int k, i;
        for (i = 0; i < j; i++) {   //从左向右遍历
            k = j;
            while (a[i] != a[k]) {
                k--; //从右向左查找
            }
            if (k == i) { //从两端不断向中间推进,左右两端的指针i,j重合
                single++; //单独的字母
                if (n % 2 == 0 || single > 1) {
                    //总长为偶数则Impossible
                    //总长为奇数,先跳过这个单独字母,出现第二个单独字母则Impossible
                    System.out.println("Impossible");
                    return; 
                } else {
                    sum += n / 2 - i;  //单独的字母移动到中心
                }
            } else { //从两端不断向中间推进,左右两端的指针i,j未重合
                for (int m = k; m < j; m++) { //交换到最后一个位置去
                    a[m] = a[m + 1];
                }
                sum += j - k;
                j--; //右端向中间推进
            }
        }
        System.out.println(sum);
    }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值