面试题50-第一个只出现一次的字符

题目一

在一个字符串(1<=字符串长度<=10000,全部由字母组成)中找到第一个只出现一次的字符,并返回它的位置。

解题思路

建立一个哈希表,第一次扫描的时候,统计每个字符的出现次数。第二次扫描的时候,如果该字符出现的次数为1,则返回这个字符的位置。

C++实现

class Solution
{
public:
	char FirstNotRepeatingChar(char*pString)
	{
		if(pString==nullptr)
			return '\0';
		const int tableSize=256;
		unsigned int hashTable[tableSize];
		for(int i=0;i<tableSize;++i)
			hashTable[i]=0;
		char *pHashKey=pString;
		while(*(pHashKey)!='\0')
			hashTable[*(pHashKey++)]++;
		pHashKey=pString;
		while(*pHashKey!='\0')
		{
			if(hashTable[*pHashKey]==1
				return *pHashKey;
			pHashKey++;
		}
		return '\0';
	}
};
  1. 时间复杂度:O(n),两次哈希表的扫描属于并行,时间复杂度都是O(n),所以总的时间复杂度是O(n)。
  2. 空间复杂度:O(1),我们需要一个包含256个字符的辅助数组,它的大小是1KB。由于这个数组的大小是常数,因此可以认为这种算法的空间复杂度是O(1)。

题目二

请实现一个函数用来找出字符流中第一个只出现一次的字符。例如,当从字符流中只读出前两个字符"go"时,第一个只出现一次的字符是"g"。当从该字符流中读出前六个字符“google"时,第一个只出现一次的字符是"l"。
如果当前字符流没有存在出现一次的字符,返回结束字符’\0’

解题思路

这道题还是很简单的。将字节流保存起来,通过哈希表统计字符流中每个字符出现的次数,顺便将字符流保存在occurrence中,然后再遍历occurrence,从哈希表中找到第一个出现一次的字符。

C++实现

class CharStatistics
{
public:
	CharStatistics() :index(0)
	{
		for (int i = 0; i < 256; ++i)
			occurrence[i] = -1;
	}
	void Insert(char ch)
	{
		if (occurrence[ch] == -1)
			occurrence[ch] = index;
		else if (occurrence[ch] >= 0)
			occurrence[ch] = -2;
	}
	char FirstAppearingOnce()
	{
		char ch = '\0';
		int minIndex = numeric_limits<int>::max();
		for (int i = 0; i < 256; ++i)
		{
			if (occurrence[i] >= 0 && occurrence[i] < minIndex)
			{
				ch = (char)i;
				minIndex = occurrence[i];
			}
		}
		return ch;
	}

private:
	//occurrence[i]:表征字符流中当前字符的特征值
	//occurrence[i]=-1:表征该字符还没有出现
	//occurrence[i]=-2:表征该字符出现次数>=2
	//occurrence[i]>=0:表征该字符仅出现一次
	int occurrence[256];
	int index;

};

Leetcode同类型题

136-只出现一次的数字

给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
说明:
你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?

在这里插入图片描述

解题思路

对于这道题,可使用异或运算⊕。异或运算有以下三个性质。

任何数和 0 做异或运算,结果仍然是原来的数,即 a⊕0=a。
任何数和其自身做异或运算,结果是 0,即 a⊕a=0。
异或运算满足交换律和结合律,即 a⊕b⊕a=b⊕a⊕a=b⊕(a⊕a)=b⊕0=b。

C++实现

class Solution {
public:
    int singleNumber(vector<int>& nums) {
	    int res=0;
	    for(auto &n:nums)
	        res^=n;
	    return res;
    }
};
  1. 时间复杂度:O(n)
  2. 空间复杂度:O(1)

461-汉明距离

两个整数之间的 汉明距离 指的是这两个数字对应二进制位不同的位置的数目。
给你两个整数 x 和 y,计算并返回它们之间的汉明距离。

在这里插入图片描述

解题思路

其本质是求这两个整数取异或结果后二进制数中1的个数,可以使用n&(n-1)消除n中最后一个1的思想。
因为 n & (n - 1) 可以消除最后一个 1,所以可以用一个循环不停地消除 1 同时计数,直到 n 变成 0 为止。

C++实现

class Solution {
public:
    int hammingDistance(int x, int y) {
        int n=x^y,res=0;
        while(n!=0)
        {
            n=n&(n-1);
            ++res;
        }
        return res;
    }
};

260-数组中只出现一次的两个数字

给定一个整数数组 nums,其中恰好有两个元素只出现一次,其余所有元素均出现两次。 找出只出现一次的那两个元素。你可以按 任意顺序 返回答案。
进阶:你的算法应该具有线性时间复杂度。你能否仅使用常数空间复杂度来实现?

在这里插入图片描述

解题思路

这两个数字异或后的数字必然不为0,也就是说,在这个结果数字的二进制表示中至少有一位是1。我们在结果数字中找到第一个为1的位的位置,记为第n位。现在我们以第n位是不是1为标准把原数组中的数字分成两个子数组,第一个子数组的第n位全是1,而第二个子数组中的第n位都是0。我们已经知道如何在数组中找出唯一一个只出现一次的数字。因此,分别在两个子数组中找出唯一出现一次的数字,就可以了。

C++实现

class Solution {
public:
    vector<int> singleNumber(vector<int>& nums) {
        int n=(int)nums.size(),res=0;
        vector<int>vec(2);
        int nums1=0,nums2=0;
        for(int i=0;i<n;++i)
            res^=nums[i];
        unsigned int firstBitIs1=FindFirstBitIs1(res);
        for(int i=0;i<n;++i)
        {
            if(IsBit1(nums[i],firstBitIs1))
                nums1^=nums[i];
            else
                nums2^=nums[i];
        }
        vec[0]=nums1,vec[1]=nums2;
        return vec;
    }
    //在整数num的二进制中找到最右边是1的位
    unsigned int FindFirstBitIs1(int num)
    {
        int res=0;
        while((num&1)==0&&(res<8*sizeof(int)))
        {
            num=num>>1;
            res++;
        }
        return res;    
    }
    //判断在num的二进制中从右边数字的第indexBit位是不是1
    bool IsBit1(int num,int indexBit)
    {
        num=num>>indexBit;
        return (num&1);
    }  
};
  1. 时间复杂度:O(n),循环数组
  2. 空间复杂度:O(1)

137-只出现一次的数字Ⅲ

给你一个整数数组 nums ,除某个元素仅出现 一次 外,其余每个元素都恰出现 三次 。请你找出并返回那个只出现了一次的元素。
在这里插入图片描述

解题思路

为了方便叙述,我们称「只出现了一次的元素」为「答案」。

由于数组中的元素都在int(即 32 位整数)范围内,因此我们可以依次计算答案的每一个二进制位是 0 还是 1。

具体地,考虑答案的第 i个二进制位(i 从 0 开始编号),它可能为 0 或 1。对于数组中非答案的元素,每一个元素都出现了 3 次,对应着第 i 个二进制位的 3 个 0 或 3 个 1,无论是哪一种情况,它们的和都是 3的倍数(即和为 0 或 3)。因此:

答案的第 i 个二进制位就是数组中所有元素的第 i 个二进制位之和除以 3 的余数。

这样一来,对于数组中的每一个元素 x,我们使用位运算(x >> i) & 1 得到 x 的第 i 个二进制位,并将它们相加再对 3 取余,得到的结果一定为 0 或 1,即为答案的第 i 个二进制位。

细节

需要注意的是,如果使用的语言对「有符号整数类型」和「无符号整数类型」没有区分,那么可能会得到错误的答案。这是因为「有符号整数类型」(即int 类型)的第 31 个二进制位(即最高位)是补码意义下的符号位,对应着−2 ^31,而「无符号整数类型」由于没有符号,第 31 个二进制位对应着2^ 31。因此在某些语言(例如Python)中需要对最高位进行特殊判断。

C++实现

class Solution {
public:
    int singleNumber(vector<int>& nums) {
  		int ans = 0;
        for (int i = 0; i < 32; ++i) {
            int total = 0;
            for (int num: nums) {
                total += ((num >> i) & 1);
            }
            if (total % 3) {
                ans |= (1 << i);//让其他位的1原封不动
            }
        }
        return ans;
    }
};
  1. 时间复杂度:O(n)
  2. 空间复杂度:O(1)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值