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

Plutoaaa發表於2018-02-12

問題描述
  迴文串,是一種特殊的字串,它從左往右讀和從右往左讀是一樣的。小龍龍認為迴文串才是完美的。現在給你一個串,它不一定是迴文的,請你計算最少的交換次數使得該串變成一個完美的迴文串。
  交換的定義是:交換兩個相鄰的字元
  例如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);
    }
}

相關文章