剑指Offer(java版):第一个只出现一次的字符

题目:在字符串中找出第一个只出现一次的字符。如输入"abaccdeff",则输出'b'.

看到这样的题目,我们最直观的想法就是从头开始扫描这个字符串中的字 符。当访问某个字符时拿这个字符和后面的每个字符相比较,如果在后面没有发现重复的字符,则该字符就是只出现一次的字符。如果字符串有n个字符,每个字符 可能与后面的O(n)个字符想比较,因此这种思路的时间复杂度为O(n2),面试官不会满意这种思路,它会提示我们继续想更快的思路。

由于题目与字符出现的次数有关,我们是不是可疑统计每个字符在该字符 串中出现的次数,要达到这个目的,我们需要一个数据容器来存放每个字符出现的次数。在这个容器中可以根据字符来查找它出现的次数,也就是说这个容器的作用 就是把一个字符映射称一个数字。在常用的数据容器中,哈希表正是这个用途。

为了解决这个问题,我们可以定义哈希表的键值(key)是字符,而值 (Value)是该字符出现的次数。同时我们还需要从头开始扫描字符串两次。第一次扫描字符串时,每扫描到一个字符就在哈希表中的对应项中把次数加1.接 下来第二次扫描时,每扫描到一个字符就能从哈希表中得到该字符出现的次数。这样第一个只出现一次的字符就是符合要求的输出。

用Java代码实现我们的思路:

package cglib;

import java.util.LinkedHashMap;

public class jiekou {

    public Character firstNotRepeating(String str){  
        if(str == null)  
            return null;  
        char[] strChar = str.toCharArray();  //将字符串转换成数组
        LinkedHashMap<Character,Integer> hash = new LinkedHashMap<Character,Integer>();  
        for(char item:strChar){  
            if(hash.containsKey(item))  
                hash.put(item, hash.get(item)+1);  
            else  
                hash.put(item, 1);  
        }  
        for(char key:hash.keySet())  
        {  
            if(hash.get(key)== 1)  
                return key;  
        }  
        return null;  
    }  
    public static void main(String[] args){  
        String str = "abaccdebff";  
        jiekou test = new jiekou();  
        System.out.println(test.firstNotRepeating(str));  
    }  
     }

 

输出d

 

拓展1:

在前面的例子中,我们之所以可以把哈希表的大小设为256,是因为字符(char)是8个bit的类型,总共只有256个字符。但实际上字符不只是256个,比如中文就有几千个汉字。如果题目要求考虑汉字,前面的算法是不是有问题?如果有,可以怎么解决。

 

public class jiekou {

     /**
     * @param args
     */  
    public static void main(String[] args) {  
        // TODO 自动生成的方法存根  
        String testString="ccaaddddb北京bb11大学??//";  
        getFirstMaxOccurrenceChar(testString);  
      
 
 
    }  
    /*查找第一次出现单独字符的主函数*/  
    private static void getFirstMaxOccurrenceChar(String temString) {  
        char[] temp=temString.toCharArray();  
        MyHashTable myHashTable=new MyHashTable();  
        for (char c : temp) {  
            MyData myData=new MyData();  
            myData.setCharData(c);  
            myHashTable.insert(myData);  
        }  
        MyData[] result=MyHashTable.getHashMap();  
        boolean flag=false;  
        for (int i = 0; i < result.length; i++) {  
            MyData myData = result[i];  
            /*只要hash表中该数据不为null且计数为1则输出并跳出循环*/  
            if (myData!=null&&myData.getCount()==1) {  
                System.out.println("第一次出现单字符为:"+myData.getCharData());  
                flag=true;  
                break;  
            }  
        }  
        if (flag==false) {  
            System.out.println("不存在单字符!");  
        }  
    }  
 
}  
/*设计hash表,包含一个长度为Oxffff的数组和insert函数*/  
class MyHashTable{  
    private static MyData[] hashMap=new MyData[0xffff];  
    /*如果第一次插入,则将计数设置为1,否则计数+1*/  
    public void insert(MyData myData){  
        if (hashMap[myData.getCharData()]==null) {  
            myData.setCount(1);  
        }else {  
            myData.setCount(hashMap[myData.getCharData()].getCount()+1);  
        }  
        hashMap[myData.getCharData()]=myData;  
          
    }  
    public static MyData[] getHashMap() {  
        return hashMap;  
    }  
      
}  
/*设计hash表中的类型,即一个字符和它的计数*/  
class MyData{  
    private char charData;  
    private int count;  
    public char getCharData() {  
        return charData;  
    }  
    public void setCharData(char charData) {  
        this.charData = charData;  
    }  
    public int getCount() {  
        return count;  
    }  
    public void setCount(int count) {  
        this.count = count;  
    }  
     }   


输出

第一次出现单字符为:京

 

拓展2:

定义一个函数,输入两个字符串,从第一个字符串中删除在第二个字符串中出现过的所有字符。例如第一个字符串"we are students",第二个字符串是"aeiou",结果应该是"w r stdnts"。

package cglib;


public class jiekou {

    
         public static String fun1 ( String s, String b )
            {
                if (s.isEmpty ())
                {
                    return "";
                }
                char first = s.charAt (0);
                if (b.indexOf (first) != -1)//返回 String 对象b内第一次出现子字符串的字符位置
                {
                    return fun1 (s.substring (1), b);//截取s的下标为1的字符串,跟b继续比较
                }
                return first + fun1 (s.substring (1), b);//b中没有这个,则没有的这个字符返回
            }
        
            public static void print ( String s )
            {
                for ( int i = 0; i < s.length (); i++ )
                {
                    System.out.print (s.charAt (i));
                }
            }
        
            public static void main ( String args[] )
            {
                String str = "we are students";  
                String str1 = "aeiou";
                String str2 = fun1 (str, str1);
                print (str2);
            }
     }   


输出:

w r stdnts

 

拓展3: 定义一个函数,删除字符串中所有重复出现的字符。例如输入"google",则输出结果应该是"gole"。

package cglib;

import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

public class jiekou {

    static StringBuffer sb = new StringBuffer();

    // 普通的方法,不使用集合
    static void removeDuplicateByOriginalMethod(String str) {

        System.out.println("方法一:普通方法");
        char[] cy = str.toCharArray();
        String temp = "";
        for (int i = 0; i < cy.length; i++) {
            if (temp.indexOf(cy[i]) == -1) {
                temp += cy[i];
            }
        }
        System.out.println("去除重复字符后:" + temp);
        sb.setLength(0);
    }

    // 方法二,使用LinkedHashSet可以在去掉重复字符后按照原字符顺序排列字符
    static void removeDuplicateByLinkedHashSet(String str, String[] ss, int len) {
        System.out.println("方法二:LinkedHashSet");
        Set<String> set = new LinkedHashSet<String>();
        iterate(set, ss, len);
        System.out.println("去除重复字符后:" + sb.toString());
        // 清空StringBuffer对象sb
        sb.setLength(0);
    }

    // 方法三,使用ArrayList可以在去掉重复字符后按照原字符顺序排列字符
    static void removeDuplicateByArrayList(String str, String[] ss, int len) {
        System.out.println("方法三:ArrayList");
        List<String> list = new ArrayList<>();
        iterate(list, ss, len);
        System.out.println("去除重复字符后:" + sb.toString());
        // 记住要输出后才清空sb
        sb.setLength(0);
    }

    // 集合迭代器,用于去除重复字符并重新拼接字符
    static void iterate(Object obj, String[] ss, int len) {
        if (obj instanceof Set) {
            System.out.println("迭代器正在迭代Set");
            @SuppressWarnings("unchecked")
            Set<String> set = (Set<String>) obj;
            for (int i = 0; i < len; i++) {
                if (!set.contains(ss[i])) {
                    set.add(ss[i]);
                }
            }
            for (String s : set) {
                sb.append(s);
            }
        }
        if (obj instanceof List) {
            System.out.println("迭代器正在迭代List");
        
            @SuppressWarnings("unchecked")
            List<String> list = (List<String>) obj;
            for (int i = 0; i < len; i++) {
                if (!list.contains(ss[i])) {
                    list.add(ss[i]);
                }
            }
            for (String s : list) {
                sb.append(s);
            }
        }
    }

    public static void main(String[] args) {
        String str = "google";
        String[] ss = str.split(""); // 在此处先拆分字符串,处理后再传给各个需要用到的方法,提高程序性能。
        int len = ss.length;
        System.out.println("等待去除重复字符的字符串:" + str);
        //方法一
        removeDuplicateByOriginalMethod(str);
        // 方法二
        removeDuplicateByLinkedHashSet(str, ss, len);
        // 方法三
        removeDuplicateByArrayList(str, ss, len);
    }

    
     }   

输出:

方法一:普通方法
去除重复字符后:gole
方法二:LinkedHashSet
迭代器正在迭代Set
去除重复字符后:gole
方法三:ArrayList
迭代器正在迭代List
去除重复字符后:gole

 

拓展4:

请完成一个函数,判断输入的两个字符串是否是Anagram,即互为变位词

变位词(anagrams)指的是组成两个单词的字符相同,但位置不同的单词。比如说, abbcd和abcdb就是一对变位词。该题目有两种做法:

O(nlogn)的解法

由于组成变位词的字符是一模一样的,所以按照字典序排序后,两个字符串也就相等了。 因此我们可以用O(nlogn)的时间去排序,然后用O(n)的时间比较它们是否相等即可。

package cglib;

import java.util.Arrays;

public class jiekou {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        System.out.println(func("silent", "listen"));
        System.out.println(func("", ""));
        System.out.println(func("silent", "liste"));
        
    }

    public static boolean func(String str1, String str2) {
        
        if(str1.length() != str2.length()){  
            return false;  
        }
        char[] arr1 = str1.toCharArray();
        char[] arr2 = str2.toCharArray();
        Arrays.sort(arr1);
        Arrays.sort(arr2);
        for(int i = 0; i < arr1.length; i++) {
            if(arr1[i] != arr2[i]) {
                return false;
            }
        }
        return true;
    }
    
     }  

输出
true
true
false

 

O(n)的解法

由于组成变位词的字符是一模一样的, 因此我们可以先统计每个字符串中各个字符出现的次数, 然后看这两个字符串中各字符出现次数是否一样。如果是,则它们是一对变位词。 这需要开一个辅助数组来保存各字符的出现次数。我们可以开一个大小是256的整数数组, 遍历第一个字符串时,将相应字符出现的次数加1;遍历第二个字符串时, 将相应字符出现的次数减1。最后如果数组中256个数都为0,说明两个字符串是一对变位词。 (第1个字符串中出现的字符都被第2个字符串出现的字符抵消了), 如果数组中有一个不为0,说明它们不是一对变位词。

package cglib;

public class jiekou {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        System.out.println(anagram("silent", "listen"));
        //System.out.println(anagram("", ""));
        //System.out.println(anagram("silent", "liste"));
        
    }

    private static boolean anagram(String s1,String s2){  
        
        int[] nums = new int[26];  
          
        char[] s1_char = s1.toCharArray();  
        char[] s2_char = s2.toCharArray();  
          
        int s1_length = s1_char.length;  
        int s2_length = s2_char.length;  
          
        if(s1_length != s2_length){  
            return false;  
        }  
          
        for(int i=0; i<s1_length; i++){
            System.out.println("s1的s1_char[i]="+s1_char[i]);
            int index = s1_char[i] - 'a';
            System.out.println("s1的index="+index);
            nums[index]++;
            System.out.println("s1的nums[index]="+nums[index]);
        }  
 
        for(int i=0; i<s1_length; i++){
            System.out.println("s2的s2_char[i]="+s2_char[i]);
            int index = s2_char[i] - 'a';
            System.out.println("s2的index="+index);
            nums[index]--;
            System.out.println("s2的nums[index]="+nums[index]);
        }  
          
        for(int i=0; i<nums.length; i++){
            System.out.println("nums的i="+i);
            System.out.println("nums[i]="+nums[i]);
            if(nums[i]>0) return false;  
        }  
          
        return true;  
          
    }   


    
     }   


输出:

s1的s1_char[i]=s
s1的index=18
s1的nums[index]=1
s1的s1_char[i]=i
s1的index=8
s1的nums[index]=1
s1的s1_char[i]=l
s1的index=11
s1的nums[index]=1
s1的s1_char[i]=e
s1的index=4
s1的nums[index]=1
s1的s1_char[i]=n
s1的index=13
s1的nums[index]=1
s1的s1_char[i]=t
s1的index=19
s1的nums[index]=1
s2的s2_char[i]=l
s2的index=11
s2的nums[index]=0
s2的s2_char[i]=i
s2的index=8
s2的nums[index]=0
s2的s2_char[i]=s
s2的index=18
s2的nums[index]=0
s2的s2_char[i]=t
s2的index=19
s2的nums[index]=0
s2的s2_char[i]=e
s2的index=4
s2的nums[index]=0
s2的s2_char[i]=n
s2的index=13
s2的nums[index]=0
nums的i=0
nums[i]=0
nums的i=1
nums[i]=0
nums的i=2
nums[i]=0
nums的i=3
nums[i]=0
nums的i=4
nums[i]=0
nums的i=5
nums[i]=0
nums的i=6
nums[i]=0
nums的i=7
nums[i]=0
nums的i=8
nums[i]=0
nums的i=9
nums[i]=0
nums的i=10
nums[i]=0
nums的i=11
nums[i]=0
nums的i=12
nums[i]=0
nums的i=13
nums[i]=0
nums的i=14
nums[i]=0
nums的i=15
nums[i]=0
nums的i=16
nums[i]=0
nums的i=17
nums[i]=0
nums的i=18
nums[i]=0
nums的i=19
nums[i]=0
nums的i=20
nums[i]=0
nums的i=21
nums[i]=0
nums的i=22
nums[i]=0
nums的i=23
nums[i]=0
nums的i=24
nums[i]=0
nums的i=25
nums[i]=0
true

 

转载于:https://my.oschina.net/u/2822116/blog/725453

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值