[力扣c语言实现] 位运算

位运算

231. 2的幂

给定一个整数,编写一个函数来判断它是否是 2 的幂次方。

%d %x
1 1
2 10
4 100
8 1000
16 10000

由以上表格可知,n为2的幂,那么n的二进制中1的个数一定为1,于是我们查找n的二进制中1的个数即可得到其是否为2的幂.

代码如下:

bool isPowerOfTwo(int n){
    
    
    int num_1 = 0;
    while (n > 0)
    {
    
    
        num_1 += (n&1);
        n >>= 1;
    }

    return num_1 == 1;
}

342. 4的幂

给定一个整数,写一个函数来判断它是否是 4 的幂次方。如果是,返回 true ;否则,返回 false 。

整数 n 是 4 的幂次方需满足:存在整数 x 使得 n == 4x

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/power-of-four
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

分析及其实现

一个数n 为4的幂函数,既n = 4x,而4x = 22x = 22x,假设22x == 2^y,那么y一定是个偶数,于是代码就可以很好地写出来了

bool isPowerOfFour(int n){
    
    
    int num = 0;
    int times = 0;
    while (n>0)
    {
    
    
        if ((n&1) == 0)//0的个数
        {
    
    
             times++;
        }
        else//1的个数
        {
    
    
            num+=1;
        }
        n >>= 1;
    }
    if ( num == 1 && times %2 == 0)//num等于1判断是否为2的幂,times为偶数,说明n的二进制中0的个数为偶数,那么就满足4^x = 2^2^y ,既x为偶数
    {
    
    
        return true; 
    }

    return false;
}

405. 数字转换为十六进制数

负整数于对应的正整数的相互转化

1.正整数转化负整数

1.取反

2.低位+1

if (num > 0)
{
    
    
    num ~= num;
    num +=1;
}

2.负整数转化为正整数

1.低位-1

2.取反

if (num < 0)
{
    
    
    num -= 1;
    num ~= num;
}

实现

char * toHex(int num){
    
    
    int tmp1 = 0;
    char buffer[9] = {
    
    0};
    char *ret = NULL;
    int i = 0;
    char hashtable[16] = {
    
    '0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};

    ret = (char *)malloc(9);
    memset(ret,0,9);
    
    do 
    {
    
    
        tmp1 = num & 0xf;
        sprintf(buffer+strlen(buffer),"%c",hashtable[tmp1]);
        i++;
        num >>= 4;
    }while (i < 8 &&num);

    #if 0
    if (num == 0)
    {
    
    
        sprintf(ret+strlen(ret),"%c",hashtable[num]);
        return ret;
    }

    for (int i = 0; num && i < 8; ++i) 
    {
    
    
            tmp1 = num & 0xf;
            sprintf(buffer+strlen(buffer),"%c",hashtable[tmp1]);
            num >>= 4;
    }
    #endif

    for (int i = 0;i < strlen(buffer);i++)
    {
    
    
        ret[i] = buffer[strlen(buffer)-1-i];
    }

    return ret;
}

191. 位1的个数

编写一个函数,输入是一个无符号整数(以二进制串的形式),返回其二进制表达式中数字位数为 ‘1’ 的个数(也被称为汉明重量)。

扫描二维码关注公众号,回复: 13128560 查看本文章

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/number-of-1-bits
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

int hammingWeight(uint32_t n) {
    
    
    int num = 0;
    while (n)
    {
    
    
        num += (n & 1);
        n>>=1;
    }

    return num;
}

190. 颠倒二进制位

颠倒给定的 32 位无符号整数的二进制位。

uint32_t reverseBits(uint32_t n) {
    
    
    uint32_t m = 0;
    int i = 0;
    while (i < 32)
    {
    
    
        i++;
        m <<=1;
        printf("0x%x\n",m);
        m |= n&1;
        n>>=1;
    }
    
    return m;
}

以上代码有个关键的地方,为什么m先左移1位再进行累加?假设n=0x1,即0000…0001,如果是先m |= n&1再m<<=1,那么第1次之后,m=10,第2次,m=100, …,第31次,m=1000…000(31个0),若再进行最后一次,就把最高位的1给左移没了,m变成了0x00000000,这显然是不行的,而,这又是为什么导致的呢?因为,初始时,m=0,本身就占了一位,但是我们只要它的32个bit位的空间,而不要它的初始值,所以m就算一开始=100也无所谓,反正我们会慢慢把它原来的各个bit位全部移走替换成我们的,所以我们从第一个bits时,就要保证不被m的原始值干扰,所以我们一定要先开始偏移1bit,得到一个bit空间,再用这个bit空间存储我们的值。

318. 最大单词长度乘积

给定一个字符串数组 words,找到 length(word[i]) * length(word[j]) 的最大值,并且这两个单词不含有公共字母。你可以认为每个单词只包含小写字母。如果不存在这样的两个单词,返回 0。

示例 1:

输入: [“abcw”,“baz”,“foo”,“bar”,“xtfn”,“abcdef”]
输出: 16
解释: 这两个单词为 “abcw”, “xtfn”。
示例 2:

输入: [“a”,“ab”,“abc”,“d”,“cd”,“bcd”,“abcd”]
输出: 4
解释: 这两个单词为 “ab”, “cd”。
示例 3:

输入: [“a”,“aa”,“aaa”,“aaaa”]
输出: 0
解释: 不存在这样的两个单词。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/maximum-product-of-word-lengths
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

#define max(a,b) (a)>(b)?(a):(b)
int maxProduct(char ** words, int wordsSize){
    
    
    int i,j;
    int max_size = 0;
    int num_array[10000] = {
    
    0};

    for (i=0;i<wordsSize;i++)
    {
    
    
        for (j=0;j<strlen(words[i]);j++)
        {
    
    
            num_array[i] |= 1<<(words[i][j] - 'a');
        }
    }

    for (i=0;i<wordsSize;i++)
    {
    
    
        for (j=i+1;j<wordsSize;j++)
        {
    
    
            if ((num_array[i] & num_array[j]) == 0)
            {
    
    
                max_size = max(max_size,strlen(words[i])*strlen(words[j]));
            }
        }
    }

    return max_size;
}

这道题比较暴力的方法就是先比较两个不同位置的字符串是否有相同元素,没有的话再求其长度的乘积,这样实现起来没有难度,而且时间复杂度很高。所以我们用位运算做。

对于一个字符串,如:“abcde”,首先由题目知道,字符串中的单词肯定不会重复,其次,只含有小写字母,那么可以得到的信息就是,此字符串的最大长度为26,那么我们可以用一个int整数的32位中的后26位来表示此字符串中出现了那些字母,即,出现的字母依据他们的字母序号,在int的后26位从按照从低到高位的顺序分布,即“abced”就对应00…11111,“abd”对应00…01011,这样,得到了每个字符串的“位图”.所以难点就是,如何根据字符串得到其位图。

for (j=0;j<strlen(words[i]);j++)
{
    
    
    num_array[i] |= 1<<(words[i][j] - 'a');
}

比如words[i][j]='a’时,那么words[i][j] - ‘a’ = 0;1 << (words[i][j] - ‘a’)表示第几位为1,num_array[i]初始为

0000…0000,那么后续,遍历第一个元素‘a’之后,就变为0000…0001,表示a存在。

338. 比特位计数

给定一个非负整数 num。对于 0 ≤ i ≤ num 范围中的每个数字 i ,计算其二进制数中的 1 的数目并将它们作为数组返回。

示例 1:

输入: 2
输出: [0,1,1]
示例 2:

输入: 5
输出: [0,1,1,2,1,2]
进阶:

给出时间复杂度为O(n*sizeof(integer))的解答非常容易。但你可以在线性时间O(n)内用一趟扫描做到吗?
要求算法的空间复杂度为O(n)。
你能进一步完善解法吗?要求在C++或任何其他语言中不使用任何内置函数(如 C++ 中的 __builtin_popcount)来执行此操作。
通过次数116,949提交次数148,081

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/counting-bits
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

首先观察规律

十进制数字 二进制
1 1
2 10
3 11
4 100
5 101
6 110
7 111
8 1000
9 1001
10 1010
11 1011

1.一个奇数,和他左相邻的偶数,其bit位为1的个数,总是比该奇数少1,用dp[i]表示奇数i的1bit的个数,那么就有

dp[i] = dp[i-1]+1;除了规律展示的,也因为偶数的最后一个比特位总是0,那么该偶数+1后为奇数,且不会产生进位,那么此奇数就多了一个bit为1的bit位。

2.一个偶数n,它的为1的bit位数目总是相等于数n/2的为1的bit位的数目。这时为啥呢?因为n和n/2的关系,n=n/2 * 2,而*2对于计算机就是左移1位。即,n为n/2左移动一位,而左移后低位补零,即新的bit位还是0,那么就有dp[i] = dp[i/2].

/**
 * Note: The returned array must be malloced, assume caller calls free().
 */
int* countBits(int num, int* returnSize){
    
    
    int i = 0;
    int *res = (int *)malloc(sizeof(int)*100000);
    memset(res,0,sizeof(int)*100000);
    res[0] = 0;
    *returnSize = 1;
    
    for (i = 1; i <= num;i++)
    {
    
    
        if (i%2==0)
        {
    
    
            res[i] = res[i/2];
        }
        else 
        {
    
    
            res[i] = res[i-1] + 1;
        }

        (*returnSize) += 1;
    }

    return res;
}

187. 重复的DNA序列

所有 DNA 都由一系列缩写为 ‘A’,‘C’,‘G’ 和 ‘T’ 的核苷酸组成,例如:“ACGAATTCCG”。在研究 DNA 时,识别 DNA 中的重复序列有时会对研究非常有帮助。

编写一个函数来找出所有目标子串,目标子串的长度为 10,且在 DNA 字符串 s 中出现次数超过一次。

示例 1:

输入:s = “AAAAACCCCCAAAAACCCCCCAAAAAGGGTTT”
输出:[“AAAAACCCCC”,“CCCCCAAAAA”]
示例 2:

输入:s = “AAAAAAAAAAAAA”
输出:[“AAAAAAAAAA”]

提示:

0 <= s.length <= 105
s[i] 为 ‘A’、‘C’、‘G’ 或 ‘T’

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/repeated-dna-sequences
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

//2021-03-13
char ** findRepeatedDnaSequences(char *s, int* returnSize)
{
    
    
	int map[85] = {
    
    0};
	char **res = NULL;
	int mask = 0x3ffff;
	int cur = 0;
	int i = 0;
    int len = strlen(s);
    char buffer[11] = {
    
    0};
	int *hash_table = NULL;
    int num = pow(2,20);
    
    *returnSize = 0;
	if (len <=10)
	{
    
      
		return res;
	}

	hash_table = (int *)malloc(sizeof(int)*num);
	memset(hash_table,0,sizeof(int)*num);

	map[(int)'A'] = 0;//00...00
	map[(int)'C'] = 1;//00...01
	map[(int)'G'] = 2;//00...10
	map[(int)'T'] = 3;//00...11
	
	for (i = 0; i < 9; ++i) {
    
    
		cur = (cur << 2) | (map[(int)s[i]]);
	}

	for (i = 9; i < len; i++) 
	{
    
    
		cur = ((cur & mask) << 2)|(map[(int)s[i]]);
		if (hash_table[cur] == 1)
		{
    
    
			res = realloc(res,sizeof(char *)*(*returnSize+1));
			res[*returnSize] = (char *)malloc(11);
			memset(res[*returnSize],0,11);
			strncpy(res[*returnSize],&s[i-9],10);
			(*returnSize) += 1;
		}
		
		hash_table[cur]++;
	}
	free(hash_table);
	return res;
}

关键之处

1.由于题目给我们4个不同的字母,那么我们就可以使用两个bit位来表示这四个字母,即{A,00},{C,01},{G,10},{T,11}
2.本题意:给定我们一个序列,找到这序列中长度为10且出现至少两次的子序列,那么子序列长度为10,共占20个bit位,可以用一个int的后20位来表示。这样不同的子序列构成的int型的后20位的值一定不同,通过这里我们就可以进行重复性判定。好,那么如何根据一个int值快速的得到其计数器呢?那就需要一个哈希表来将int的值和它出现的次数进行映射,这就是题目中的hash_table的作用。那它的大小,也即其子元素的个数,那肯定不能按照int的32位空间来,这样整个内存都被分配了,显示是不可能的,而且一个int的后20位就可以代表我们的一个子序列,那么我们的大小只需要2的20次幂,也即1M*4bytes = 4M空间(计算机的二进制确实牛逼,32和20之间的真实差距,比看上去大了这么多的数量级别,这也是内核中对内存进行管理的依据。)
3.hash表有了,子序列的二进制也知道怎么表示了,那么如何得到每一个序列呢?

for (i = 0; i < 9; ++i) 
{
    
    
    cur = (cur << 2) | (map[(int)s[i]]);
}

for (i = 9; i < len; i++) 
{
    
    
    cur = ((cur & mask) << 2)|(map[(int)s[i]]);
    if (hash_table[cur] == 1)
    {
    
    
        res = realloc(res,sizeof(char *)*(*returnSize+1));
        res[*returnSize] = (char *)malloc(11);
        memset(res[*returnSize],0,11);
        strncpy(res[*returnSize],&s[i-9],10);
        (*returnSize) += 1;
    }

    hash_table[cur]++;
}

如以上代码,我们先取给定序列的前9个的二进制组合,再遍历除开头的9个字符的后续字符串,具体操作就是,每次前进一个单位,且前进的新单位在cur的低2位补充进来,所以就要先将cur左移两位给新字符挪空间。这样就得到了一个新子串的二进制组合。从而可以通过hash_table查找其出现的次数,当它已经出现了一次,本次再出现,那么就满足题目意思:出现次数>=2,那么就要记录。整个思路和细节都在描述中,描述清楚一个算法及其步骤还是比较困难的,我详尽描述也是为了加深自己的理解。

猜你喜欢

转载自blog.csdn.net/dengwodaer/article/details/114767076