1290. 二进制链表转整数
给你一个单链表的引用结点
head
。链表中每个结点的值不是0
就是1
。已知此链表是一个整数数字的二进制表示形式。
请你返回该链表所表示数字的 十进制值 。
输入: head = [1,0,1]
输出: 5
解释: 二进制数 (101) 转化为十进制数 (5)
输入: head = [0]
输出: 0
提示:
- 链表不为空。
- 链表的结点总数不超过
30
。 - 每个结点的值不是
0
就是1
。
解题思路
采用位运算,result << 1
就相当于 result * 2
, result |= 1(result |= 0)
相当于 result++
(不变)
注:
x & 0 = 0 (与运算,一假则假),x | 1 = 1(或运算,一真则真)
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
int getDecimalValue(struct ListNode* head){
struct ListNode* cur = head;
int ans = 0;
while (cur != NULL) {
ans = ans * 2 + cur->val;
cur = cur->next;
}
return ans;
}
191. 位1的个数
解题思路
方法一:循环移位,和1进行与运算
int hammingWeight(uint32_t n) {
int count = 0;
for(int i = 0; i < 32; i++){
if(n & 1 == 1)
count++;
n >>= 1;
}
return count;
}
方法二:将 n 和 n−1 做与运算会将最低位的 1 变成 0
在二进制表示中,数字 n 中最低位的 1 总是对应 n−1 中的 0 。因此,将 n 和 n - 1 与运算总是能把 n 中最低位的 1 变成 0 ,并保持其他位不变。
int hammingWeight(uint32_t n) {
int count = 0;
while(n != 0){
n = n & (n - 1); //n与n-1只对最低一位数进行与运算,相当于n-=1
count++;
}
return count;
}
1356. 根据数字二进制下 1 的数目排序
提示:
1 <= arr.length <= 500
0 <= arr[i] <= 10^4
思路(没有用到位运算): 暴力循环排序
用hash map的思想排序1的个数,注意代码中的注释位置
/**
* Note: The returned array must be malloced, assume caller calls free().
*/
typedef struct map{
int index;
int value;
}Map;
int hammingWeight(int n) { //上面那道题 计算位1的个数
int count = 0;
while(n != 0){
n = n & (n - 1);
count++;
}
return count;
}
int* sortByBits(int* arr, int arrSize, int* returnSize){
int *res = malloc(sizeof(int) * arrSize);
memset(res, 0, sizeof(int) * arrSize);
Map temp[arrSize];
//计算并记录arr[i]中1的个数
for(int i = 0; i < arrSize; i++){
temp[i].index = arr[i];
temp[i].value = hammingWeight(arr[i]);
}
//冒泡排序,按照arr[i]进行递增排序,防止因所有的二进制数的1的数目都相同产生的arr[i]非递增排列
for(int i = 0; i < arrSize - 1; i++){
for(int j = 0; j < arrSize - i - 1; j++){
if(temp[j].index > temp[j + 1].index){
Map tmp = temp[j];
temp[j] = temp[j + 1];
temp[j + 1] = tmp;
}
}
}
//冒泡排序,按照1的个数进行递增排序
for(int i = 0; i < arrSize - 1; i++){
for(int j = 0; j < arrSize - i - 1; j++){
if(temp[j].value > temp[j + 1].value){
Map tmp = temp[j];
temp[j] = temp[j + 1];
temp[j + 1] = tmp;
}
}
}
for(int i = 0; i < arrSize; i++){
res[i] = temp[i].index;
}
* returnSize = arrSize;
return res;
}
136. 只出现一次的数字
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
说明:你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
输入: [2,2,1]
输出: 1
输入: [4,1,2,1,2]
输出: 4
思路:
我想到的又是 hash 散列
但是参考了大佬的方法后恍然大悟 —— 位运算之异或运算^
- 将十进制数字转为二进制数进行运算
- 异或运算的原理 —— 异1,同0。可以较好地筛选出唯一的数字,因为相同的数字进行异或运算都变为0
int singleNumber(int* nums, int numsSize){
int temp = nums[0];
for(int i = 1; i < numsSize; i++){
temp ^= nums[i]; //异或运算大法好
}
return temp;
}
389. 找不同
给定两个字符串 s 和 t,它们只包含小写字母。
字符串 t 由字符串 s 随机重排,然后在随机位置添加一个字母。
请找出在 t 中被添加的字母。
输入:
s = “abcd”
t = “abcde”
输出:
e
解释:
‘e’ 是那个被添加的字母。
思路: 和上一题类似,s[i] ^ t[i] 得到被添加的数
char findTheDifference(char * s, char * t){
int len = strlen(t);
char res = 0;
for(int i = 0; i < len; i++){
res ^= s[i] ^ t[i];
}
return res;
}
时间复杂度:
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
1
)
O(1)
O(1)
190. 颠倒二进制位
题目描述
颠倒给定的 32 位无符号整数的二进制位。
思路
从右到左每取出一位,就放一位到
ans
uint32_t reverseBits(uint32_t n) {
uint32_t ans = 0;
for(int i = 0; i < 8 * sizeof(uint32_t); i++){
ans = (ans << 1) + (n & 1);
n = n >> 1;
}
return ans;
}
总结
关于位运算的讲解:位运算(&、|、^、~、>>、<<)
位运算的思路对于一个能转化为处理二进制数较为方便的问题有较好的作用,如八进制、十六进制、十进制等需要转为二进制计算的题目。
关于C语言位运算
-
不管是算术左右移和逻辑左右移,最好是unsigned类型,因为这样算术与逻辑是一样的结果。1
-
C编译器,默认是算术移位,如是signed类型,这一点一定要把握好。1
-
编程过程中,一定要注意右移操作,注意signed 和unsigned的区别。1
-
C语言中移位操作符实现的是逻辑左移和算术右移,但是算术左移和逻辑左移的效果相同,算术右移和逻辑右移的效果不同,要实现逻辑右移可将操作数强制类型转化为无符号数。2
位运算的优点
位操作可以减少除法和取模的运算,在计算机程序中数据的位是可以操作的最小数据单位,理论上可以用“位运算”来完成所有的运算和操作。一般的位操作是用来控制硬件的,或者做数据变换使用,但是,灵活的位操作可以有效地提高程序运行的效率。