当时考蓝桥杯(java)的时候,也没怎么学过算法,一看这道题涉及到了图论,直接就放弃了。现在回过头来学了下,写个解析。
题目
小蓝想制作一个吊坠,他手上有 n 个长度为 m 的首尾相连的环形字符串{s1, s2, · · · , sn} ,他想用 n − 1 条边将这 n 个字符串连接起来做成吊坠,要求所有的字符串连完后形成一个整体。连接两个字符串 si, sj 的边的边权为这两个字符串的最长公共子串的长度(可以按环形旋转改变起始位置,但不能翻转),小蓝希望连完后的这 n − 1 条边的边权和最大,这样的吊坠他觉得最好看,请计算最大的边权和是多少。
分析
这道题涉及到两个问题
这很显然是一个涉及到图论的问题,可以采用与最小生成树类似的算法
对于两个节点,它们之间的边权的计算
边权计算
采用动态规划来计算
1 2 3 4 5 6 7 8 9 10 11 12 13 14 static int similarity (String a, String b) { a=a+a; int [][] dp=new int [a.length()+1 ][b.length()+1 ]; for (int i=1 ;i<=a.length();i++){ for (int j=1 ;j<=b.length();j++){ if (a.charAt(i-1 )==b.charAt(j-1 )){ dp[i][j]=dp[i-1 ][j-1 ]+1 ; }else { dp[i][j]=Math.max(dp[i-1 ][j],dp[i][j-1 ]); } } } return dp[a.length()][b.length()]; }
构建边权数据结构
这个比较简单,就不解释了
1 2 3 4 5 6 7 8 9 class Edge { int index1,index2,value; public Edge (int index1,int index2,int value) { this .index2=index2; this .index1=index1; this .value=value; } }
最大生成树
与最小生成树类似,先计算所有的边权
1 2 3 4 5 6 7 ArrayList<Edge> list=new ArrayList <>(); for (int i=0 ;i<words_count-1 ;i++){ for (int j=i+1 ;j<words_count;j++){ list.add(new Edge (i,j,similarity(words[i],words[j]))); } } list.sort((a,b)->-Integer.compare(a.value,b.value));
从大到小排序,逐个尝试添加,如果不会形成环,就继续。
使用int数组root
来保存每个节点的的根节点,为了保证有序性,默认将index比较小的那一个作为根;若root[index]==index
,则表明该节点就是根节点
1 2 3 4 5 6 7 8 9 10 11 12 int [] root=new int [words_count];for (int i=0 ;i<words_count;i++){ root[i]=i; } int total=0 ;for (var i:list){ if (root[i.index1]!=root[i.index2]){ root[i.index2]=getRoot(i.index1,root); total+=i.value; } } System.out.println(total);
其中getRoot()
函数返回这个节点对应的根节点,以下标较小的作为根
1 2 3 static int getRoot (int index,int [] roots) { return index==roots[index]?index:getRoot(roots[index],roots); }
但其实仔细分析,不难发现以上代码每一步都能保证“root数组每个位置对应的都是根节点”,因此,getRoot
函数可以简化如下
1 2 3 static int getRoot (int index,int [] roots) { return roots[index]; }
总代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 import java.util.ArrayList;import java.util.SortedSet;import java.util.TreeSet;public class Main { static int words_count; public static void main (String[] args) { String[] words=new String []{ "aabb" ,"abba" ,"acca" ,"abcd" }; words_count=words.length; ArrayList<Edge> list=new ArrayList <>(); for (int i=0 ;i<words_count-1 ;i++){ for (int j=i+1 ;j<words_count;j++){ list.add(new Edge (i,j,similarity(words[i],words[j]))); } } list.sort((a,b)->-Integer.compare(a.value,b.value)); int [] root=new int [words_count]; for (int i=0 ;i<words_count;i++){ root[i]=i; } int total=0 ; for (var i:list){ if (root[i.index1]!=root[i.index2]){ root[i.index2]=root[index]; total+=i.value; } } System.out.println(total); } static int similarity (String a, String b) { a=a+a; int [][] dp=new int [a.length()+1 ][b.length()+1 ]; for (int i=1 ;i<=a.length();i++){ for (int j=1 ;j<=b.length();j++){ if (a.charAt(i-1 )==b.charAt(j-1 )){ dp[i][j]=dp[i-1 ][j-1 ]+1 ; }else { dp[i][j]=Math.max(dp[i-1 ][j],dp[i][j-1 ]); } } } return dp[a.length()][b.length()]; } } class Edge { int index1,index2,value; public Edge (int index1,int index2,int value) { this .index2=index2; this .index1=index1; this .value=value; } }
参考资料