input:
["like","god","internal","me","internet","interval","incension","intension","face","intrusion"]
output:
["l2e","god","internal","me","i6t","interval","inc5n","inte4n","f2e","intr4n"]
规则:
1.头尾字符必须保留, 缩写的长度 和原来字符长度相等,则保留原字符串(字符串长度<=3时, 则没有缩写, 比如 god)
2. 缩写不能有冲突:例如 internal, interval
可以缩写成 i6l, in5l, int4l, inte3l, inter2l
以及 intern1l, interv1l , 但这个和原字符长度相等,也就没必要缩写了,所以保留原字符串
3. incension-- inc5n, intrusion , intension 变成 intr4n 和 inte4n 就不会冲突
方法一: 贪心法 复杂度 n^2
先把所有字符串变成最大程度的缩写:
l2e god i6l me i6t i6l i7n i7n f2e i7n
然后从index =0 开始 往 index+1 到 len 开始检查冲突。设计一个 pre[0-len-1] 来记录每个字符串前面字符串的个数, 理论上都应该初始化为1, 为了方便起见 在操作下标时, pre[i]+1 就OK了
从index =0 开始 选定一个 index, 然后while(true) 循环, 里面用一个set 记录冲突的index +j, 注意记录完 如果有冲突 需要把 index 本身也加入 set, 然后 pre[i]++ 去尝试一个更长的 缩写,直到冲突结束。
1 class Solution { 2 public List<String> wordsAbbreviation(List<String> dict) { 3 4 int len = dict.size(); 5 int[] pre = new int[len]; 6 String[] ans = new String[len]; 7 for(int i=0; i<len; i++){ 8 ans[i] = get_abbr(dict.get(i), pre[i]); 9 // System.out.print(ans[i]+" "); 10 } 11 12 for(int i=0; i<len-1; i++){ 13 while(true){ 14 Set<Integer> set = new HashSet<>(); 15 for(int j= i+1; j<len; j++){ 16 if(ans[i].equals(ans[j])){ 17 set.add(j); 18 } 19 } 20 if(set.isEmpty()) break; 21 set.add(i); 22 for(int index: set){ 23 ans[index] = get_abbr(dict.get(index),++pre[index]); 24 System.out.print(ans[index]+" "); 25 } 26 System.out.println(); 27 } 28 } 29 return Arrays.asList(ans); 30 } 31 32 String get_abbr(String word, int k){ 33 int len = word.length(); 34 if(len-k<=3) return word; 35 // use good as example, g2d, 2 = n-(k+1)-1 = 4-1-1=2 36 return word.substring(0,k+1) + (len-k-2) + word.substring(len-1,len); 37 } 38 }
这是set 里 internal 和 interval 迭代的过程
in5l in5l
int4l int4l
inte3l inte3l
inter2l inter2l
internal interval
这是set里incension intrusion , intension 贪心法迭代的过程
in6n in6n in6n
inc5n int5n int5n
inte4n intr4n
方法二:group +least common prefix 分组+最小公共前缀 复杂度 nlogn
冲突的字符串一定满足 如下 特点: 第一个和最后一个字母一样,并且长度一样,例如
intrusion , intension ---> i7n
算法如下:
1. 求出每个字符串最短缩写,把相同的分组放在同一个 group里
2. 把每个分组排序,排序的目的为便于求出相邻之间最短公共前缀
为何要排序,如果不排序就得比较任意两个,复杂度为(n^2) ,排序后只用比较相邻两个,复杂度为 nlong+n
然而现实是 排序的时间复杂度 竟然比 不排序直接比较的复杂度高.
2.1 不排序直接暴力比较: 需要二重循环比较,得到 最长公共子串后取较大的那个
for(int i=0; i<group.size() -1; i++){ for(int j= i+1; j<group.size(); j++){ int p = longestCommonPrefix(group.get(i).word, group.get(j).word); pre[i] = Math.max(pre[i], p); pre[j] = Math.max(p,pre[j]); // System.out.println(pre[i] +" "+pre[j]); }
2.2 先排序后 只用比较一轮的原理: 具有最长公共子川的两个string 排序后一定相邻。
Collections.sort(group,(a,b)->(a.word.compareTo(b.word))); int[] pre = new int[group.size()]; for(int i=1; i<group.size(); i++){ pre[i] = longestCommonPrefix(group.get(i).word,group.get(i-1).word); pre[i-1] = Math.max(pre[i-1],pre[i]); }
3. 根据前缀长度重新去算不冲突的前缀
先排序再比较的版本:
1 class Solution { 2 public List<String> wordsAbbreviation(List<String> dict) { 3 4 int len = dict.size(); 5 6 String[] ans = new String[len]; 7 Map<String, List<IndexWord>> groups = new HashMap<>(); 8 9 for(int i=0; i<len; i++){ //先把string 分组放入 map 10 String abbr = get_abbr(dict.get(i), 0); 11 if(!groups.containsKey(abbr)){ 12 groups.put(abbr,new ArrayList<>()); 13 } 14 groups.get(abbr).add(new IndexWord(dict.get(i),i)); 15 } 16 17 for(List<IndexWord> group: groups.values()){ 18 // Collections.sort(group, (a, b) -> a.word.compareTo(b.word)); //按照字母排序 19 Collections.sort(group,(a,b)->(a.word.compareTo(b.word))); 20 int[] pre = new int[group.size()]; 21 for(int i=1; i<group.size(); i++){ 22 pre[i] = longestCommonPrefix(group.get(i).word,group.get(i-1).word); 23 pre[i-1] = Math.max(pre[i-1],pre[i]); 24 } 25 for(int i=0; i<group.size(); i++){ 26 ans[group.get(i).index] = get_abbr(group.get(i).word,pre[i]); 27 } 28 } 29 30 return Arrays.asList(ans); 31 } 32 33 int longestCommonPrefix(String word1, String word2){ 34 int index =0; 35 while(index<word1.length() && index<word2.length() && word1.charAt(index) == word2.charAt(index)){ 36 index ++; 37 } 38 return index; 39 } 40 41 String get_abbr(String word, int k){ 42 int len = word.length(); 43 if(len-k<=3) return word; 44 // use good as example, g2d, 2 = n-(k+1)-1 = 4-1-1=2 45 return word.substring(0,k+1) + (len-k-2) + word.substring(len-1,len); 46 } 47 } 48 49 class IndexWord{ 50 int index; 51 String word; 52 IndexWord(String word, int index){ 53 this.word = word; 54 this.index = index; 55 } 56 }
不用排序,直接暴力比较的版本:
1 1 for(List<IndexWord> group: groups.values()){ 2 2 3 3 int[] pre = new int[group.size()]; 4 4 for(int i=0; i<group.size() -1; i++){ 5 5 for(int j= i+1; j<group.size(); j++){ 6 6 int p = longestCommonPrefix(group.get(i).word, group.get(j).word); 7 7 pre[i] = Math.max(pre[i], p); 8 8 pre[j] = Math.max(p,pre[j]); 9 9 // System.out.println(pre[i] +" "+pre[j]); 10 10 } 11 11 } 12 12 13 13
方法三: Tier Tree