位运算:
注意事项:位运算加括号会避免一些不必要的错误
java中的位运算一般有:
^ 异或
& 与
|或
~ 非
>> 右移 常用(右移运算符,符号左侧数值 按位右移 符号右侧数值指定的位数,若为正数则高位补0,若为负数则高位补1)
>>>无符号右移(无符号右移运算符,符号左侧数值 按位右移 符号右侧数值指定的位数,无论正负高位补0)
<<左移(没有无符号左移)
一般是异或运算:一堆数字,其他的数都出现偶数次(比如2次),有一个数出现了奇数次(比如一次),把所有的数做异或运算,因为异或运算有性质:X1 ^ X2 ^ X3 = X1 ^(X2 ^ X3),最后的结果就得到出现奇数次的数。详见下面解析。
268. 缺失数字
解法:位运算,异或https://leetcode-cn.com/problems/missing-number/solution/que-shi-shu-zi-by-leetcode/
java中异或的符号是^;异或是相同为0,相异为1;因此x^x = 0
class Solution {
//异或是逐位异或的,比如0^3=3
public int missingNumber(int[] nums) {
//任何数字和0异或都为它本身
int answer = 0;
for(int i = 0;i < nums.length;i ++){
answer ^= (i ^ nums[i]);
}
return answer ^ nums.length;
}
}
136. 只出现一次的数字
解法:位运算:异或运算,看解析什么时候用异或运算。
class Solution {
//位运算
public int singleNumber(int[] nums) {
if(nums.length % 2 == 0) {
throw new IllegalArgumentException();
}else if(nums.length == 1) {
return nums[0];
}else {
/*
//0和任何数x异或结果为x
int answer = 0;
for(int i = 0;i < nums.length;i ++) {
answer ^= nums[i];
}
return answer;
*/
for(int i = 1;i < nums.length;i ++) {
nums[0] ^= nums[i];
}
return nums[0];
}
}
}
剑指 Offer 56 - I. 数组中数字出现的次数
解法:根据异或运算的性质,原来的数组相当于那两个只出现一次的数进行异或,根据异或的定义异或的结果任何一位为1的位表示这一位这两个只出现一次的数该位不一样,比如4和6的异或结果是010,也就是十进制的2,那么将4和6右移一位,与1进行与运算,可以将4和6分开。
下面的无符号右移改成有符号右移也对。
class Solution {
public int[] singleNumbers(int[] nums) {
if(nums.length <= 2) {
return nums;
}else {
//任何数和0异或就是它本身
int answer = 0;
for(int i = 0;i < nums.length;i ++) {
//下面也是可以的
//answer = answer ^ nums[i];
answer ^= nums[i];
}
int rightmove = 0;
//System.out.println(answer & 1);
//answer & 1 != 1 不对
//应该是(answer & 1) != 1
while((answer & 1) != 1) {
answer = answer >> 1;
rightmove ++;
}
int[] result = new int[2];
for(int i = 0;i < nums.length;i ++) {
if(((nums[i] >> rightmove) & 1) != 1) {
result[0] ^= nums[i];
}else {
result[1] ^= nums[i];
}
}
return result;
}
}
}
645. 错误的集合
解法:类似于上面一道题:剑指 Offer 56 - I. 数组中数字出现的次数 ,比如说数组值是1 2 2 4,下标是0 1 2 3,下标加一是:1 2 3 4;对于数组值和1 2 3 4 来说,这八个数进行异或,结果相当于2和3进行异或,任何数字与本身异或结果为0,任何数字与0异或结果为它本身。
class Solution {
public int[] findErrorNums(int[] nums) {
int[] answer = new int[2];
if(nums.length == 0) {
return answer;
}else if(nums.length == 1) {
answer[0] = nums[0];
return answer;
}else {
//下面的写法类似于数组中出现的数字的次数
int number = 0;
for(int i = 0;i < nums.length;i ++) {
number ^= ((i + 1) ^ nums[i]);
}
int rightmove = 0;
while((number & 1) != 1) {
rightmove ++;
number >>= 1;
}
for(int i = 0;i < nums.length;i ++) {
if((((i + 1) >> rightmove) & 1) == 1) {
answer[0] ^= (i + 1);
}else {
answer[1] ^= (i + 1);
}
if(((nums[i] >> rightmove) & 1) == 1) {
answer[0] ^= nums[i];
}else {
answer[1] ^= nums[i];
}
}
for(int i = 0;i < nums.length;i ++) {
if(answer[0] == nums[i]) {
return answer;
}
}
int tem = answer[0];
answer[0] = answer[1];
answer[1] = tem;
return answer;
}
}
}
剑指 Offer 56 - II. 数组中数字出现的次数 II
解法:3,3,3,4,最后的array[0-31]结果是:3,3,1,0,0,0......
另外注意无符号右移,还有从array恢复数字的时候,注意恢复成有符号int,要考虑正负号,详见注释
class Solution {
public int singleNumber(int[] nums) {
if(nums.length == 0) {
throw new IllegalArgumentException();
}else if(nums.length == 1) {
return nums[0];
}else {
//一个int型的数32位,array数组存储的是一比特
//根据题意,array数组的元素是要么是3的倍数要么不是3的倍数
int[] array = new int[32];
int answer = 0;
//
for(int i = 0;i < nums.length;i ++) {
//采用无符号右移,无符号右移高位补0,有符号右移负数高位补1
for(int j = 0;j < 32;j ++) {
//array[j] = (nums[i] >>> j) & 1;
array[j] += (nums[i] >>> j) & 1;
}
}
/*
for(int number : array) {
System.out.println(number);
}
*/
//因为原数组的数是有符号的,因此要考虑array[31] % 3是1还是0
//下面遍历array的[0,30]不是[0,31]
for(int i = 0;i < 31;i ++) {
if(array[i] % 3 != 0) {
//answer += (2 << i);
answer += (1 << i);
}
}
if(array[array.length - 1] % 3 == 1) {
return -1 * answer;
}else {
return answer;
}
}
}
}
137. 只出现一次的数字 II
解法:和上面一样。
class Solution {
public int singleNumber(int[] nums) {
if(nums.length == 0) {
throw new IllegalArgumentException();
}else if(nums.length == 1) {
return nums[0];
}else {
//一个int型的数32位,array数组存储的是一比特
//根据题意,array数组的元素是要么是3的倍数要么不是3的倍数
int[] array = new int[32];
int answer = 0;
//
for(int i = 0;i < nums.length;i ++) {
//采用无符号右移,无符号右移高位补0,有符号右移负数高位补1
for(int j = 0;j < 32;j ++) {
//array[j] = (nums[i] >>> j) & 1;
array[j] += (nums[i] >>> j) & 1;
}
}
/*
for(int number : array) {
System.out.println(number);
}
*/
//因为原数组的数是有符号的,因此要考虑array[31] % 3是1还是0
//下面遍历array的[0,30]不是[0,31]
for(int i = 0;i < 31;i ++) {
if(array[i] % 3 != 0) {
//answer += (2 << i);
answer += (1 << i);
}
}
if(array[array.length - 1] % 3 == 1) {
return -1 * ((int)Math.pow(2,31) + 1 - answer);
}else {
return answer;
}
}
}
}
字符串或者链表模拟加减乘法
67. 二进制求和
解法:模拟进位加法,遍历的时候,index1 >= 0 || index2 >= 0,哪个index变成-1,对应的数字用0相加
class Solution {
//存储答案的Stringbuilder的先存储
public String addBinary(String a, String b) {
if(b == null || b.equals("0")){
return a;
}else if(a == null || a.equals("0")){
return b;
}else{
int index1 = a.length() - 1;
int index2 = b.length() - 1;
//number表示进位
int number = 0;
int number1 = 0;
int number2 = 0;
int sum = 0;
StringBuilder sb = new StringBuilder();
while(index1 >= 0 || index2 >= 0){
number1 = (index1 >= 0 ? a.charAt(index1) - '0' : 0);
number2 = (index2 >= 0 ? b.charAt(index2) - '0' : 0);
sum = number1 + number2 + number;
number = sum / 2;
sb.append(sum % 2);
if(index1 >= 0) {
index1 --;
}
if(index2 >= 0) {
index2 --;
}
}
if(number != 0){
sb.append(number);
}
sb = sb.reverse();
return sb.toString();
}
}
}
2. 两数相加
解法:用链表模拟加法,思想是短链表,补0的思想。
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
if(l1 == null) {
return l2;
}else if(l2 == null) {
return l1;
}else {
ListNode answer = new ListNode(0);
ListNode cur = answer;
ListNode cur1 = l1;
ListNode cur2 = l2;
//number表示相加产生的进位
int number = 0;
int n = 0;
int m = 0;
int sum = 0;
//为了预防两链表长度不一样的情况,采用哪个链表先遍历完,这个链表相加的时候相当于加的是0
while(cur1 != null || cur2 != null) {
n = (cur1 == null ? 0 : cur1.val);
m = (cur2 == null ? 0 : cur2.val);
sum = n + m + number;
//结果new节点
cur.next = new ListNode(sum % 10);
number = sum / 10;
cur = cur.next;
if(cur1 != null) {
cur1 = cur1.next;
}
if(cur2 != null) {
cur2 = cur2.next;
}
}
//注意最后一个进位
if(number != 0) {
cur.next = new ListNode(number);
}
return answer.next;
}
}
}
706. 设计哈希映射
设计一个简单的取余的哈希函数,使用链地址法
class MyHashMap {
public static class Pair{
int key;
int value;
public Pair(int key,int value) {
this.key = key;
this.value = value;
}
}
public LinkedList<Pair>[] array;
/** Initialize your data structure here. */
public MyHashMap() {
array = new LinkedList[1000];
for(int i = 0;i < 1000;i ++) {
array[i] = new LinkedList<>();
}
}
/** value will always be non-negative. */
public void put(int key, int value) {
LinkedList<Pair> tem = array[key % 1000];
for(int i = 0;i < tem.size();i ++) {
if(tem.get(i).key == key) {
tem.get(i).value = value;
return;
}
}
tem.add(new Pair(key,value));
}
/** Returns the value to which the specified key is mapped, or -1 if this map contains no mapping for the key */
public int get(int key) {
LinkedList<Pair> tem = array[key % 1000];
for(Pair pair : tem) {
if (pair.key == key) {
return pair.value;
}
}
return -1;
}
/** Removes the mapping of the specified value key if this map contains a mapping for the key */
public void remove(int key) {
LinkedList<Pair> tem = array[key % 1000];
for(int i = 0;i < tem.size();i ++) {
if(tem.get(i).key == key) {
tem.remove(i);
return;
}
}
}
}