[b]变位词[/b]
一种把某个词或句子的字母的位置(顺序)加以改换所形成的新词,英文叫做anagram,词典把这个词翻译成“变位词”。
最近参加了一个面试,其中一道上机题目就是有关[b]变位词[/b]的。
题目描述大致如下:
1.给出一个两个字符串互为变位词的相似度算法。当他们为变位词的时候输出1.0;当他们长度不同且没有相同字母时输出0;其他情况给出一个规则输出一个0到1之间的浮点数。
2.有一个文件其中有18万个单词,输出变位词集合长度超过8的变位词集合到一个文件中。
这道题目的考点有四个:
1.变位词算法的设计
2.变位词相似度算法的设计
3.Java文件流读写
4.java容器的使用情况
由于在前一道题目上时间分配不合理,我没有充足的时间完成代码:相似度算法、文件流输出没有实现,变位词集合查找算法设计的也有缺陷,单元测试根本没写。
虽然从面试的角度看我这个问题解决的很失败,但是生活还要继续。在本文中,我再次完成这道题目记录我这次失败的机试。
按照我的直觉,相似度算法是最麻烦的,因为它没有一个精确的定义。因此,最后再解决它。
[b]第一步:变位词算法的设计[/b]
理解变位词:从变位词概念上讲,词语是忽略大小写的;两个词语的字母及字母的数量是相同的;相同的单词当然是变位词。
这样的话,变位词判断至少有两种方法:
1.对两个词语按字母顺序排序,再比较其是否相同,相同则为变位词。
2.把两个词语按字母-字母个数分解到两个键值对(map)中,再比较两个map。
下面给出变位词排序算法的Java实现:
package com.zas.anagram;
/**
* 变位词算法设计
* @author zas
*
*/
public class Anagram {
/**
* @param args
*/
public static void main(String[] args) {
System.out.println(Anagram.isAnagram(null, null));
System.out.println(Anagram.isAnagram("", ""));
System.out.println(Anagram.isAnagram("", null));
System.out.println(Anagram.isAnagram(null, ""));
System.out.println(Anagram.isAnagram(null, "cba"));
System.out.println(Anagram.isAnagram("cba", null));
System.out.println(Anagram.isAnagram("abc", "cba"));
System.out.println(Anagram.isAnagram("abc", "cbaa"));
System.out.println(Anagram.isAnagram("abc", "cbc"));
}
/**
* 判断两个单词是否互为变位词
* @param string
* @param string2
* @return true/false
*/
public static boolean isAnagram(String wordA, String wordB) {
//异常情况处理
if(null == wordA && null == wordB){
return true;
}
if(false == handleNull(wordA, wordB)){
return false;
}
return isAnagramBySort(wordA, wordB);
}
/**
* 处理异常情况 返回 true表示要继续处理 false表示不为变位词
* @param wordA
* @param wordB
* @return true/false
*/
private static boolean handleNull(String wordA, String wordB) {
//一个为空,另一个不为空不是变位词
if(null == wordA && null != wordB){
return false;
}
if(null == wordB && null != wordA){
return false;
}
//长度不同不为变位词
if(wordA.length() != wordB.length()){
return false;
}
return true;
}
/**
* 通过排序后比较其是否相同判断是否为变位词
* @param wordA
* @param wordB
* @return true/false
*/
private static boolean isAnagramBySort(String wordA, String wordB) {
//获取两个单词的小写复本
wordA = wordA.toLowerCase();
wordB = wordB.toLowerCase();
//对两个单词按字母大小顺序排序
wordA = sort(wordA);
wordB = sort(wordB);
if(wordA.equals(wordB)){
return true;
}
return false;
}
/**
* 按字母顺序排序字符串
* @param wordA
* @return
*/
private static String sort(String word) {
char[] charArray = word.toCharArray();
//排序基本为小数据量的,因此采用冒泡、选择、插入中的一种,这里选择选择排序
for (int i = 0; i < charArray.length; i++) {
//内层循环找到未排序的最小字母
int selectedIndex = i;
for (int j = 0; j < charArray.length; j++) {
if(charArray[selectedIndex] > charArray[j]){
selectedIndex = j;
}
}
if(selectedIndex != i){
char tempForSwap = charArray[selectedIndex];
charArray[selectedIndex] = charArray[i];
charArray[i] = tempForSwap;
}
}
return String.valueOf(charArray);
}
}
通过map来实现变位词判断:
package com.zas.anagram;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
/**
* 变位词算法设计
* @author zas
*
*/
public class Anagram {
/**
* @param args
*/
public static void main(String[] args) {
System.out.println(Anagram.isAnagram(null, null));
System.out.println(Anagram.isAnagram("", ""));
System.out.println(Anagram.isAnagram("", null));
System.out.println(Anagram.isAnagram(null, ""));
System.out.println(Anagram.isAnagram(null, "cba"));
System.out.println(Anagram.isAnagram("cba", null));
System.out.println(Anagram.isAnagram("abc", "cba"));
System.out.println(Anagram.isAnagram("abc", "cbaa"));
System.out.println(Anagram.isAnagram("abc", "cbc"));
}
/**
* 判断两个单词是否互为变位词
* @param string
* @param string2
* @return true/false
*/
public static boolean isAnagram(String wordA, String wordB) {
//异常情况处理
if(null == wordA && null == wordB){
return true;
}
if(false == handleNull(wordA, wordB)){
return false;
}
//return isAnagramBySort(wordA, wordB);
return isAnagramByMap(wordA, wordB);
}
/**
* 处理异常情况 返回 true表示要继续处理 false表示不为变位词
* @param wordA
* @param wordB
* @return true/false
*/
private static boolean handleNull(String wordA, String wordB) {
//一个为空,另一个不为空不是变位词
if(null == wordA && null != wordB){
return false;
}
if(null == wordB && null != wordA){
return false;
}
//长度不同不为变位词
if(wordA.length() != wordB.length()){
return false;
}
return true;
}
/**
* 通过排序后比较其是否相同判断是否为变位词
* @param wordA
* @param wordB
* @return true/false
*/
private static boolean isAnagramBySort(String wordA, String wordB) {
//获取两个单词的小写复本
wordA = wordA.toLowerCase();
wordB = wordB.toLowerCase();
//对两个单词按字母大小顺序排序
wordA = sort(wordA);
wordB = sort(wordB);
if(wordA.equals(wordB)){
return true;
}
return false;
}
/**
* 按字母顺序排序字符串
* @param wordA
* @return
*/
private static String sort(String word) {
char[] charArray = word.toCharArray();
//排序基本为小数据量的,因此采用冒泡、选择、插入中的一种,这里选择选择排序
for (int i = 0; i < charArray.length; i++) {
//内层循环找到未排序的最小字母
int selectedIndex = i;
for (int j = 0; j < charArray.length; j++) {
if(charArray[selectedIndex] > charArray[j]){
selectedIndex = j;
}
}
if(selectedIndex != i){
char tempForSwap = charArray[selectedIndex];
charArray[selectedIndex] = charArray[i];
charArray[i] = tempForSwap;
}
}
return String.valueOf(charArray);
}
/**
* 通过 字母-字母个数 键值对来判断变位词
* @param wordA
* @param wordB
* @return true false;
*/
private static boolean isAnagramByMap(String wordA, String wordB) {
Map mapForWordA = getWordMap(wordA);
Map mapForWordB = getWordMap(wordB);
//字母的个数不同肯定不是变位词
if(mapForWordA.size() != mapForWordB.size()){
return false;
}
//迭代mapForWordA的字母 并在mapForWordB中获得对应的字母个数 若不同则不是变位词
Set key = mapForWordA.keySet();
for (Iterator it = key.iterator(); it.hasNext();) {
Character c = (Character) it.next();
Integer charCountA = mapForWordA.get(c);
Integer charCountB = mapForWordB.get(c);
if(charCountA != charCountB){
return false;
}
}
return true;
}
/**
* 获得一个字符串的字母-字母个数键值对
* @param wordA
* @return
*/
private static Map getWordMap(String word) {
Map map = new HashMap();
char[] charArray = word.toCharArray();
for (int i = 0; i < charArray.length; i++) {
Character c = charArray[i];
Integer charCount = map.get(c);
if(null == charCount){
charCount = 1;
}else{
charCount = charCount + 1;
}
map.put(c, charCount);
}
return map;
}
}
从一个词典列表文件中读取单词到List, 再把这个单词List分解成变位词集合列表,根据条件查找变位词集合列表子集,输出变位词列表到文件的Java实现如下:
package com.zas.anagram;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* 变位词算法设计
* @author zas
*
*/
public class Anagram {
/**
* @param args
*/
public static void main(String[] args) {
System.out.println(Anagram.isAnagram(null, null));
System.out.println(Anagram.isAnagram("", ""));
System.out.println(Anagram.isAnagram("", null));
System.out.println(Anagram.isAnagram(null, ""));
System.out.println(Anagram.isAnagram(null, "cba"));
System.out.println(Anagram.isAnagram("cba", null));
System.out.println(Anagram.isAnagram("abc", "cba"));
System.out.println(Anagram.isAnagram("abc", "cbaa"));
System.out.println(Anagram.isAnagram("abc", "cbc"));
}
/**
* 判断两个单词是否互为变位词
* @param string
* @param string2
* @return true/false
*/
public static boolean isAnagram(String wordA, String wordB) {
//异常情况处理
if(null == wordA && null == wordB){
return true;
}
if(false == handleNull(wordA, wordB)){
return false;
}
//return isAnagramBySort(wordA, wordB);
return isAnagramByMap(wordA, wordB);
}
/**
* 处理异常情况 返回 true表示要继续处理 false表示不为变位词
* @param wordA
* @param wordB
* @return true/false
*/
private static boolean handleNull(String wordA, String wordB) {
//一个为空,另一个不为空不是变位词
if(null == wordA && null != wordB){
return false;
}
if(null == wordB && null != wordA){
return false;
}
//长度不同不为变位词
if(wordA.length() != wordB.length()){
return false;
}
return true;
}
/**
* 通过排序后比较其是否相同判断是否为变位词
* @param wordA
* @param wordB
* @return true/false
*/
private static boolean isAnagramBySort(String wordA, String wordB) {
//获取两个单词的小写复本
wordA = wordA.toLowerCase();
wordB = wordB.toLowerCase();
//对两个单词按字母大小顺序排序
wordA = sort(wordA);
wordB = sort(wordB);
if(wordA.equals(wordB)){
return true;
}
return false;
}
/**
* 按字母顺序排序字符串
* @param wordA
* @return
*/
private static String sort(String word) {
char[] charArray = word.toCharArray();
//排序基本为小数据量的,因此采用冒泡、选择、插入中的一种,这里选择选择排序
for (int i = 0; i < charArray.length; i++) {
//内层循环找到未排序的最小字母
int selectedIndex = i;
for (int j = 0; j < charArray.length; j++) {
if(charArray[selectedIndex] > charArray[j]){
selectedIndex = j;
}
}
if(selectedIndex != i){
char tempForSwap = charArray[selectedIndex];
charArray[selectedIndex] = charArray[i];
charArray[i] = tempForSwap;
}
}
return String.valueOf(charArray);
}
/**
* 通过 字母-字母个数 键值对来判断变位词
* @param wordA
* @param wordB
* @return true false;
*/
private static boolean isAnagramByMap(String wordA, String wordB) {
Map mapForWordA = getWordMap(wordA);
Map mapForWordB = getWordMap(wordB);
//字母的个数不同肯定不是变位词
if(mapForWordA.size() != mapForWordB.size()){
return false;
}
//迭代mapForWordA的字母 并在mapForWordB中获得对应的字母个数 若不同则不是变位词
Set key = mapForWordA.keySet();
for (Iterator it = key.iterator(); it.hasNext();) {
Character c = (Character) it.next();
Integer charCountA = mapForWordA.get(c);
Integer charCountB = mapForWordB.get(c);
if(charCountA != charCountB){
return false;
}
}
return true;
}
/**
* 获得一个字符串的字母-字母个数键值对
* @param wordA
* @return
*/
private static Map getWordMap(String word) {
Map map = new HashMap();
char[] charArray = word.toCharArray();
for (int i = 0; i < charArray.length; i++) {
Character c = charArray[i];
Integer charCount = map.get(c);
if(null == charCount){
charCount = 1;
}else{
charCount = charCount + 1;
}
map.put(c, charCount);
}
return map;
}
/**
* 从文件中获取词典列表
* @param path
* @return List
*/
private static List getWordsListFromFile(String path) {
List wordList = new ArrayList();
File file = new File(path);
FileReader fr = null;
BufferedReader br = null;
try{
fr = new FileReader(file);
br = new BufferedReader(fr);
String s;
while((s = br.readLine()) != null){
//去首尾空白
s = s.trim();
wordList.add(s);
}
}catch(FileNotFoundException e){
e.printStackTrace();
}catch (Exception e) {
e.printStackTrace();
}finally{
if(br != null){
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(fr != null){
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return wordList;
}
/**
* 获取所有变位词集合列表
* @param wordList
* @return
*/
private static Map> getAnagramCollectionMap(List wordList) {
Map> anagramCollectionMap = new HashMap>();
while(wordList.size() > 0){
String word = wordList.remove(0);
//将单词存入变位词集合map中
//这里有两种算法,一种是把单词排序之后放入map这样就不需要遍历map
//另一种是遍历map的key判断它是否和该单词互为变位词
//这里采用第一种
String sortedWord = sort(new String(word).toLowerCase());
List list = anagramCollectionMap.get(sortedWord);
if(list == null){
list = new ArrayList();
}
list.add(word);
anagramCollectionMap.put(sortedWord, list);
}
return anagramCollectionMap;
}
/**
* 根据某种条件从map集中获取符合条件的列表 可以考虑实现一个说明模式
* 为了演示简便,给出获取特定大小变位词集合的实现
* @param anagramCollectionMap
* @return
*/
private static Map> getAnagramCollectionMapByCondition(Map> anagramCollectionMap, int size) {
Map> resultMap = new HashMap>();
Set key = anagramCollectionMap.keySet();
for (Iterator it = key.iterator(); it.hasNext();) {
String str = (String) it.next();
List list= anagramCollectionMap.get(str);
if(list.size() == size){
resultMap.put(str, list);
}
}
return resultMap;
}
/**
* 向文件中输出变位词集合列表
* @param path
* @return
*/
private static void writeWordsListToFile(String path, Map> anagramCollectionMap) {
File file = new File(path);
FileWriter fw = null;
BufferedWriter bw = null;
try{
fw = new FileWriter(file);
bw = new BufferedWriter(fw);
Set key = anagramCollectionMap.keySet();
for (Iterator it = key.iterator(); it.hasNext();) {
String str = (String) it.next();
List list= anagramCollectionMap.get(str);
bw.write(str + list.toString());
bw.newLine();
}
}catch(FileNotFoundException e){
e.printStackTrace();
}catch (Exception e) {
e.printStackTrace();
}finally{
if(bw != null){
try {
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(fw != null){
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
这次的实现比去面试时思路清晰多了!
变位词相似度算法暂时还没思路,后面再写篇文章补上吧!