剑指offer总结一:字符、数字重复问题

问题1:字符串中第一个不重复的字符

题目描述
请实现一个函数用来找出字符流中第一个只出现一次的字符。例如,当从字符流中只读出前两个字符"go"时,第一个只出现一次的字符是"g"。当从该字符流中读出前六个字符“google"时,第一个只出现一次的字符是"l"。
输出描述:
如果当前字符流没有存在出现一次的字符,返回#字符。

解题思路:遍历字符串,用map保存每个字符出现的次数,map的key-value分别是字符-字符出现的次数count,输出第一个count为1的字符。但是最先想到的hashmap是无序的,遍历是不能保证第一个找到的满足条件的字符就是字符串中第一个出现的不重复的字符,因此需要改用LinkedHashMap.因为LinkedHashMap是有序的,可以按照输入顺序遍历。

代码

import java.util.LinkedHashMap;
import java.util.LinkedList;

public class Solution {
    LinkedHashMap<Character, Integer> map = new LinkedHashMap<>();
    LinkedList<Character> input = new LinkedList<>();
    //Insert one char from stringstream
    public void Insert(char ch)
    {
        if(map.containsKey(ch)){
            map.put(ch,map.get(ch)+1);
        }else{
            map.put(ch,1);
        }
        input.add(ch);
    }
  //return the first appearence once char in current stringstream
    public char FirstAppearingOnce()
    {
        for(char c:input){
            if(map.get(c)==1){
                return c;
            }
        }
        return '#';
    }
}

问题2:数组中重复的数字

在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字2。

解题思路:
常规解法:和问题1类似用map保存出现的次数,然后在遍历map
优化解法:因为数组中数字有一个特点:都在[0,n-1]范围内,利用这个特点。遍历数组,将numbers[i]对于的下标处的值加上一个n,这样遍历到大于n的值时就说明这个值所在的下标数已经出现过,则找出了重复数字——下标值。

代码

   public boolean duplicate(int numbers[],int length,int [] duplication) {
        for(int i=0;i<length;i++){
            int index = numbers[i]>=length?numbers[i]-length:numbers[i];
            if(numbers[index]>=length){
                duplication[0] = index;
                return true;
            }else{
                numbers[index]+=length;
            }
        }
        return false;
    }

问题3:数组中只出现一次的数字

题目描述一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。

解题思路:这道题突破点在于其他数字都出现了两次,重复次数确定是2,而只存在两个数字只出现一次。用异或运算,两个相同的数异或为0,任何一个数与0异或都是它本身,将所有数字都异或之后的结果其实就是那两个不重复的数字异或的结果,然后找出这个结果中第一个不为1的位index,按照第index位是否为0,可以将数组分成两组,每一组中包含一个不重复的数字和其他重复两遍的数组,在两组内再异或就可得到两个不重复的数字

代码

//num1,num2分别为长度为1的数组。传出参数
//将num1[0],num2[0]设置为返回结果
public class Solution {
    public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) {
        int len = array.length;
        if(len<2){
            return ;
        }
        int bitResult = 0;
        for(int i=0;i<array.length;i++){
            bitResult ^= array[i];
        }
        int index = findFirst1(bitResult);
        num1[0] = num2[0] = 0;
        for(int i=0;i<len;i++){
            if(isBit1(array[i],index)){
                num1[0] ^= array[i];
            }else{
                num2[0] ^= array[i];
            }
        }
    }
    
    private int findFirst1(int num){
        int index = 0;
        while((num&1)==0){
            num>>=1;
            index++;
        }
        return index;
    }
    
    private boolean isBit1(int num,int index){
        return (num>>index&1)==1;
    }
}

问题4:数组中出现次数超过一半的数字

题目描述:数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。

解题思路
解法1:排序,若存在满足条件的数则它一定在中间位置
解法2:摩尔投票法,若满足条件的数存在,将其看做是1,其他数看作是-1

代码(解法2)

public class Solution {
    public int MoreThanHalfNum_Solution(int [] array) {
        int candicate=0;
        int count = 0;
        for(int i=0;i<array.length;i++){
            if(count==0){
                candicate = array[i];
            }
            count+=candicate==array[i]?1:-1;
        }
        //verfying
        count=0;
        for(int i=0;i<array.length;i++){
            if(array[i]==candicate){
                count++;
            }
        }
        return count>array.length/2?candicate:0;
    }
}

总结

对于求解重复字符或重复数字问题,基本思路用hashmap(linkedhashmap)解决。
对于题目中给定的特定环境一般会有一些优化解法。关键点就是抓住题目中的特性。

posted @ 2019-08-22 11:12  huanglf714  阅读(318)  评论(0编辑  收藏  举报