有向圖的最大環數


leetcode 854

問題描述

給定兩個等長字符串A和B,它們所含的字符個數及種類完全一樣,問最少需要對A執行多少次交換字符才能使得A變成B?

分析

因為這個問題數據規模很小,只包含6種字符、A和B的長度都不超過20,所以暴力+適當剪枝的思路就能夠通過。

首先對於A[i]B[i]的部分,完全不需要做任何處理;
其次,對於A[i]!=B[i]的部分,顯然需要找A[j]來跟A[i]進行交換,A[j]滿足A[j]
B[i]。在這個過程中,如果A[i]==B[j],那自然是“意外之喜”,“一箭雙雕”,“一石二鳥”。可以很自信的想:如果能夠一箭雙雕,必然是最優策略。但是,如果沒有“一箭雙雕”,那就只能逐個嘗試尋找最優的 j 了。假設就選擇了j,交換完后得到新的字符串A',可以遞歸調用求solve(A’,B)。
在這個遞歸過程中,因為B是不變的,這個函數只要A確定,返回值就定下來了。所以可以用備忘錄方法(記憶化搜索)來加速遞歸。

站在更宏觀的角度考慮這個問題,把每個A字符串當做結點,每一次swap操作會形成新的結點並添加一條邊,以上遞歸的過程相當於深度優先搜索。如果改寫成廣度優先搜索,運行效率必定能夠提高。

站在更宏觀的角度考慮這個問題,這是一個很艱難的圖論問題。問題等價於尋找有向圖的邊的一個覆蓋,使得每一個子集都是環,要使環數最大。這個問題似乎是個NP問題。
但是貪心的方式足以通過此題。
貪心法則如下:

  • 選擇每個頂點的最小環構成一個最小環集合,對此集合執行去重操作。
  • 如果環集合中存在結點數為1的環,必然選擇之。
  • 如果環集合中存在結點數為2的環,必然選擇之。
  • 否則,執行以下步驟。
  • 對於這個環集合,統計圖中邊的使用次數。
  • 對每個環,求它邊的平均使用次數作為這個環的value。
  • 優先消去value最小的環

這個問題等價於:
給定一個可以包含重復元素的數組,最少需要執行多少次swap操作,才能使數組變得有序。

C++遞歸寫法

class Solution {
public:
    unordered_map<string,int> mp;
    int kSimilarity(string A, string B) {
        if(A<B) return kSimilarity(B,A);
        if(mp[A+B]) return mp[A+B];
        int i=0;
        while(i<A.size() && A[i]==B[i]) i++;
        if(i==A.size()) return 0;
        int j=i+1;
        vector<int> pos;
        while(j<A.size()){
            if(A[j]==B[i]){
                if(A[i]==B[j]){
                    pos.clear();
                    pos.push_back(j);
                    break;
                }
                pos.push_back(j);
            };
            j++;
        }
        int res=INT_MAX;
        for(int p:pos){
            swap(A[i],A[p]);
            res=min(res,kSimilarity(A.substr(i+1),B.substr(i+1)));
            swap(A[i],A[p]);
        }
        return mp[A+B]=res+1;
    }
};

Java非遞歸寫法

class Solution {
    public int kSimilarity(String A, String B) {
        if (A.equals(B)) return 0;
        Set<String> vis= new HashSet<>();
        Queue<String> q= new LinkedList<>();
        q.add(A);
        vis.add(A);
        int res=0;
        while(!q.isEmpty()){
            res++;
            for (int sz=q.size(); sz>0; sz--){
                String s= q.poll();
                int i=0;
                while (s.charAt(i)==B.charAt(i)) i++;
                for (int j=i+1; j<s.length(); j++){
                    if (s.charAt(j)==B.charAt(j) || s.charAt(i)!=B.charAt(j) ) continue;
                    String temp= swap(s, i, j);
                    if (temp.equals(B)) return res;
                    if (vis.add(temp)) q.add(temp);
                }
            }
        }
        return res;
    }
    public String swap(String s, int i, int j){
        char[] ca=s.toCharArray();
        char temp=ca[i];
        ca[i]=ca[j];
        ca[j]=temp;
        return new String(ca);
    }
}

Java貪心法

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.util.*;
import java.util.stream.Collectors;

class Solution {

/**
 * 構圖,構完圖之后,兩個字符串就可以丟掉了
 */
int[][] buildGraph(char[] a, char[] b) {
    TreeMap<Character, Integer> ma = new TreeMap<>();
    for (char i : a) {
        if (!ma.containsKey(i)) {
            ma.put(i, ma.size());
        }
    }
    for (char j : b) {
        if (!ma.containsKey(j)) {
            ma.put(j, ma.size());
        }
    }
    int g[][] = new int[ma.size()][ma.size()];
    for (int i = 0; i < a.length; i++) {
        int from = ma.get(b[i]), to = ma.get(a[i]);
        g[from][to] += 1;
    }
    return g;
}

/**
 * 計算結點node的出度
 */
int outEdge(int node, int[][] g) {
    return Arrays.stream(g[node]).sum();
}

int[][] copyGraph(int[][] g) {
    int[][] a = new int[g.length][g.length];
    for (int i = 0; i < g.length; i++) {
        for (int j = 0; j < g.length; j++) {
            a[i][j] = g[i][j];
        }
    }
    return a;
}

/**
 * 尋找node結點所在的最小環
 */
List<List<Integer>> findMinRingOf(int node, int[][] g) {
    g = copyGraph(g);
    List<List<Integer>> rings = new LinkedList<>();
    while (outEdge(node, g) > 0) {
        int[] prev = new int[g.length];//記錄最小環的路徑
        Arrays.fill(prev, -1);
        Queue<Integer> q = new LinkedList<>();
        q.add(node);
        out:
        while (!q.isEmpty()) {
            Integer i = q.poll();
            for (int j = 0; j < g[i].length; j++) {
                if (g[i][j] > 0) {
                    if (prev[j] != -1) continue;//已經訪問過了就不再訪問了
                    prev[j] = i;
                    q.add(j);//准備擴展j結點
                    if (j == node) {//找到了
                        break out;
                    }
                }
            }
        }
        ArrayList<Integer> a = new ArrayList<>(g.length);
        a.add(node);
        int now = node;
        while (true) {
            int next = prev[now];
            if (next == node) break;
            a.add(next);
            now = next;
        }
        //翻轉數組
        for (int i = 0; i < a.size() >> 1; i++) {
            int temp = a.get(i);
            a.set(i, a.get(a.size() - 1 - i));
            a.set(a.size() - 1 - i, temp);
        }
        if (rings.isEmpty() || rings.get(0).size() == a.size()) {
            rings.add(a);
        } else {
            break;
        }
        removeRing(a, g);
    }
    return rings;
}

/**
 * 用完一個環之后,把環刪除
 */
void removeRing(List<Integer> ring, int[][] g) {
    for (int i = 0; i < ring.size(); i++) {
        g[ring.get(i)][(ring.get((i + 1) % ring.size()))]--;
    }
}

/**
 * 貪心尋找圖中最優環
 */
List<Integer> findMinRing(int[][] g) {
    List<List<Integer>> rings = new LinkedList<>();//全部環構成的集合
    for (int i = 0; i < g.length; i++) {
        if (outEdge(i, g) > 0) {
            List<List<Integer>> r = findMinRingOf(i, g);
            rings.addAll(r);
        }
    }
    //去重
    Set<String> had = new TreeSet<>();
    LinkedList<List<Integer>> uniqRings = new LinkedList<>();
    for (List<Integer> ring : rings) {
        String k = ring.stream().sorted().map(x -> x + "").collect(Collectors.joining(","));
        if (!had.contains(k)) {
            had.add(k);
            uniqRings.add(ring);
        }
    }
    rings = uniqRings;
    //統計每條邊的使用次數
    double[][] use = new double[g.length][g.length];
    for (List<Integer> ring : rings) {
        for (int j = 0; j < ring.size(); j++) {
            use[ring.get(j)][ring.get((j + 1) % ring.size())]++;
        }
    }
    rings.sort(Comparator.comparing(x -> {
        if (x.size() == 1) return -1.0;//優先級最高
        if (x.size() == 2) return 0.0;//優先級次高
        double s = 0;
        for (int i = 0; i < x.size(); i++) {
            s += use[x.get(i)][x.get((i + 1) % x.size())];
        }
        s /= x.size();
        return s;
    }));
    if (rings.size() == 0) return null;
    return rings.get(0);
}

public int kSimilarity(String A, String B) {
    char[] a = A.toCharArray(), b = B.toCharArray();
    int[][] g = buildGraph(a, b);
    int N = a.length;
    while (true) {
        List<Integer> ring = findMinRing(g);
        if (ring == null) break;
        N--;
        removeRing(ring, g);
    }
    return N;
}

public static void main(String[] args) {
    try {
        Scanner cin = new Scanner(new FileInputStream("in.txt"));
        System.out.println(new Solution().kSimilarity(cin.next(), cin.next()));
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    }

}
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM