LeetCode #854 K-Similar Strings

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/SquirrelYuyu/article/details/83743703

(Week 7 算法博客)

题目

K-Similar Strings

Strings A and B are K-similar (for some non-negative integer K) if we can swap the positions of two letters in A exactly K times so that the resulting string equals B.

Given two anagrams A and B, return the smallest K for which A and B are K-similar.

Example 1:

Input: A = "ab", B = "ba"
Output: 1

Example 2:

Input: A = "abc", B = "bca"
Output: 2

Example 3:

Input: A = "abac", B = "baca"
Output: 2

Example 4:

Input: A = "aabc", B = "abca"
Output: 2

Note:

  1. 1 <= A.length == B.length <= 20
  2. A and B contain only lowercase letters from the set {‘a’, ‘b’, ‘c’, ‘d’, ‘e’, ‘f’}

分析

根据 K-Similar String 的产生过程,可以设想,每个 a[i]->b[i] 都是一条边(除了 a[i] == b[i] 的情况),所有的 a[i]->b[i] 构成一个图。a[i] 不断交换到 b[j] 的过程产生一个环。

例如,由"aabc"和"abca",“aabbccddee"和"cdacbeebad”,可以构造如下的图:

对一个环,交换的次数 = 这个环的节点数 - 1。

那么是不是可以通过把环分离出来,寻找答案呢?

其实很难。因为有一个例子:

A="aeabcdefbcdf"
B="bcdfbeaaefcd"

由此构造的图,可以有如下两组不同的环分解结果:

在这里插入图片描述

我改了许多次代码后,因为这个例子而再次失败了,一看,我觉得……扑街啊。

如果要把里面包含的各种环都分离出来,是很复杂的。那么,考虑不将字符看作节点,而是将整个字符串看作一个节点,一次简单的交换操作构成一条边,通过BFS来找到终点的最短路径。

以字符串为节点,进行BFS

设A为源字符串,B为目的字符串,这样定义一个节点字符串S的子节点:

找到B的第一个不和S匹配的字符 c ,位于 i ,从 i 开始再找S的第一个 c 所在的位置 j ,交换 S[i]S[j] 获得子节点。

C++代码如下:

class Solution {
public:
    int kSimilarity(string A, string B) {
		queue<string> q;
		map<string, int> distance;
		q.push(A);
		distance[A] = 0;
		while (!findNext(q, distance, B));
		return distance[B];
	}

	bool findNext(queue<string> &q, map<string, int> &distance, const string &B) {
		string A = q.front();
		q.pop();
		int i;
		for (i = 0; i < A.length(); i++) {
			if (A[i] != B[i]) break;
		}
		if (i == A.length()) return true;
		for (int j = i + 1; j < A.length(); j++) {
			if (B[i] == A[j]) {
				string C = A;
				C[j] = A[i];
				C[i] = A[j];
				if (distance.find(C) == distance.end()) {
					distance[C] = distance[A] + 1;
					q.push(C);
					if (C == B) {
						return true;
					}
				}
			}
		}
		return false;
	}
};

用时 124 m s 124ms

暴力动态规划

根据网站所给的解答,还可以采用暴力动态规划来做这道题。

在进行规划之前,将字符看作节点,源字符串和目的字符串之间的转移关系是初始图,用一个长度为6*6的数组来表示这个图。

因为这个图中的交换次数等于 ( C L i 1 ) ∑(CL_i-1) ,其中 C L i CL_i 表示图中某个环 C i C_i 的长度,

所以,这个图中的交换次数也等于 ( N u m b e r    o f    n o n s e l f    e d g e s ) ( N u m b e r    o f    c y c l e s ) (Number \ \ of \ \ nonself \ \ edges)−(Number \ \ of \ \ cycles)

如果要使交换次数最小,就令环数 N u m b e r    o f    c y c l e s Number\ \ of\ \ cycles 最大。

然后,可以得出这样的关系: n u m C y c l e s ( G ) = 1 + n u m C y c l e s ( G C ) numCycles(G)=1+numCycles(G-C) ,其中 G C G-C 指从图中去除某个环 C C 后的新图。

接下来,暴力进行动态规划:求出所有环 C i C_i ,有 n u m C y c l e s ( G ) = 1 + m a x { n u m C y c l e s ( G C 1 ) , n u m C y c l e s ( G C 2 ) , n u m C y c l e s ( G C n ) } numCycles(G)=1+max\{numCycles(G-C_1),numCycles(G-C_2), \cdots numCycles(G-C_n)\} ,其中,参与运算的 C k C_k 应该是能够被 G G 减去的环。

G C k G-C_k 继续进行如上的过程,这样递归下去,得到最终答案。

相应的Java代码及注释如下:

class Solution {
    String[] alphabet = new String[]{"a", "b", "c", "d", "e", "f"};	// 字母表
    Map<String, Integer> memo;	// key为由表示图的数组转换而来的字符串,value指这个图中最多有多少个环
    // memo缓存初始图,以及初始图和空图之间所有可能的中间图

    public int kSimilarity(String A, String B) {
        if (A.equals(B)) return 0;
        int N = A.length();
        memo = new HashMap();
        int ans = 0;	

		// count是初始图,表示源字符串和目的字符串之间的转移关系,用6*6数组表示
        int[] count = new int[alphabet.length * alphabet.length];
        for (int i = 0; i < N; ++i)
            if (A.charAt(i) != B.charAt(i)) {
                count[alphabet.length * (A.charAt(i) - 'a') + (B.charAt(i) - 'a')]++;
                ans++;
            }
		// ans表示非自向边的数目

		// possibles存储所有由环转换而来的图数组
        List<int[]> possibles = new ArrayList();
        // 枚举大小为size的环
        for (int size = 2; size <= alphabet.length; ++size)
            search: for (String cycle: permutations(alphabet, 0, size)) {
                // 检查环内的字符是否升序排列的,是则合法,否则检查下一个环
                for (int i = 1; i < size; ++i)
                    if (cycle.charAt(i) < cycle.charAt(0))
                        continue search;

				// cycle合法,将cycle转换成图,加入possibles
                int[] row = new int[count.length];
                for (int i = 0; i < size; ++i) {
                    int u = cycle.charAt(i) - 'a';
                    int v = cycle.charAt((i+1) % size) - 'a';
                    row[alphabet.length * u + v]++;
                }
                possibles.add(row);
            }

        int[] ZERO = new int[count.length];
        memo.put(Arrays.toString(ZERO), 0);
        return ans - numCycles(possibles, count);
    }

	// count是某个图,它可能为初始图,也可能是某个由G-C得到的中间图
    public int numCycles(List<int[]> possibles, int[] count) {
        String countS = Arrays.toString(count);
        if (memo.containsKey(countS)) return memo.get(countS);

		// 如果possibles中的环图row能被count减去,则得到count2
		// 然后求max{ans, 1+numCycles(possibles, count2)}
		// ans指目前已求得的count图中的最大环数
        int ans = Integer.MIN_VALUE;
        search: for (int[] row: possibles) {
            int[] count2 = count.clone();
            for (int i = 0; i < row.length; ++i) {
                if (count2[i] >= row[i])
                    count2[i] -= row[i];
                else
                    continue search;
            }
            ans = Math.max(ans, 1 + numCycles(possibles, count2));
        }
		// 向memo中加入(count图,它对应的最大环数)
        memo.put(countS, ans);
        return ans;
    }

	// 枚举出所有的长度为size、不包含used代指的字符、有序的环
	// 比如,used值为0b1,代指字符'a',size=1,则返回结果为["b","c","d","e","f"]
	// used值为0b11,代指字符'a'和'b',size=1,则返回结果为["c","d","e","f"]
	// used值为0b1111,代指字符'a''b''c''d',size=2,则返回结果为["ef"]
	// used值为0,则返回的就是所有长度为size的有序字符串/环
    public List<String> permutations(String[] alphabet, int used, int size) {
        List<String> ans = new ArrayList();
        if (size == 0) {
            ans.add(new String(""));
            return ans;
        }

        for (int b = 0; b < alphabet.length; ++b)
            if (((used >> b) & 1) == 0)
                for (String rest: permutations(alphabet, used | (1 << b), size - 1))
                    ans.add(alphabet[b] + rest);
        return ans;
    }
}

猜你喜欢

转载自blog.csdn.net/SquirrelYuyu/article/details/83743703
今日推荐