LeetCode算法笔记之简单数学题

持续更新中ing~

😀😀😀 P88 指的是在LeetCode中的题号为88

P88—合并两个有序数组

import java.util.Arrays;
class Solution {
    public void merge(int[] nums1, int m, int[] nums2, int n) {
        for (int i : nums2) {							//	int i = m - 1, j = n - 1, k = m + n - 1;
            nums1[m++] = i;								// 	从后往前比较并插入元素
        }											    //	while (i >= 0 && j >= 0) {
        Arrays.sort(nums1);    							//		if (nums1[i] > nums2[j]) {
                                                        //    		nums1[k--] = nums1[i--];
                                                        //  	}else {
                                                        //   		 nums1[k--] = nums2[j--];
                                                        // 		 }
                                                        //   }
                                                        //   如果nums2还有剩余元素,直接插入到nums1的前面
                                                        //   while (j >= 0) {
                                                        //      // 添加范围判断
                                                        //      if (k >= 0) {
                                                        //         nums1[k--] = nums2[j--i];
                                                        //      }
                                                        //   }   
        }
    }

Arrays.sort();对数组进行排序

P479–最大回文数乘机

  • 构造回文数的方法
long x = i;
long y = i;
//构造回文数  97  97  -> 9779
while (y > 0) {
    x = x * 10 + (y % 10);
    y /= 10;
}

假如x=y=97,经过第一轮 x=970 + 97 % 10 =970 +7=977,y=97/10=9;

​ 经过第二轮 x=9770 + 9 % 10=9770+9=9779 ;

x % 10 : 取到的x的(从后往前)最后一位

x / 10:去掉x的最后一位

  • 判断 x 是否有因数
// 从最大值开始逐个尝试可能的因数
for (long j = max; j * j >= x; j--) {
    if (x % j == 0) {
        return (int) (x % 1337);
    }
}

如果 x 对一个数取余(%),结果为 0,则该数为 x的因数

🌕P564–寻找最近的回文数 30.4%

答案可以做出来了,不过一直超时 下次TODO吧

class Solution {
        public String nearestPalindromic(String n) {
            long num = Long.parseLong(n);
            for (long i = 1;; i++) {
                if (isPalindrome(num - i))
                    return "" + (num - i);
                if (isPalindrome(num + i))
                    return "" + (num + i);
            }
        }
        boolean isPalindrome(long x) {
            long t = x, rev = 0;
            while (t > 0) {
                rev = 10 * rev + t % 10;
                t /= 10;
            }
            return rev == x;
        }
    }

P231–2的幂

如果存在一个整数 x 使得 n == 2x ,则认为 n 是 2 的幂次方。

if (n <= 0) {
    return false;
} else {
    return (n & (n - 1)) == 0;
}
  • 当一个数是2的幂时,它的二进制表示中只有一个1,其余位都是0

    • 2的幂的二进制表示为 1000...,而2的幂减去1的结果为 0111...。通过进行按位与运算 (n & (n - 1)),如果结果为0,则说明 n 的二进制表示中只有一个1,即 n 是2的幂。

    2:0010 ;4:0100 ; 8:1000 ;16:0001 0000 ;32:0010 0000

    1:0001 ;3:0011 ; 7:0111 ;15:1111 ; 31:0001 1111

    2的幂次方,那么它的二进制减去1后,原来的最高位的1会变成0,而其他位保持不变。将原数和减去1后的数进行按位与操作,结果应该是0。

  • 例子:

    假设 n = 8,它的二进制表示是 1000。现在,计算 n - 1 得到 7 的二进制表示是 0111。然后,

    ​ 进行按位与运算 n & (n - 1) 得到 1000 & 0111 = 0000,结果为0,说明 n 是2的幂。

    同样,如果 n = 6,其二进制表示为 110,计算 n - 1 得到 5 的二进制表示是 101

    ​ 进行按位与运算 110 & 101 得到 100,结果非零,说明 n 不是2的幂。

PS: 按位与运算 ( & )

按位与是一种二进制运算,它对两个二进制数的对应位进行逻辑与操作。按位与的规则如下:

  • 如果两个对应位都为1,则结果位为1。
  • 如果其中任意一个对应位为0,则结果位为0。

下面是一个简单的按位与示例:

   1010  (十进制:10)
 & 1101  (十进制:13)
 -------
   1000  (十进制:8)

P342–4的幂

在判断是否是2的幂次方的基础上再加以判断是否是4的幂次方

 return n > 0 && (n & (n - 1)) == 0 && (n & 0xaaaaaaaa) == 0;

(n & 0xaaaaaaaa) == 0 :将n与十六进制数 0xAAAAAAAA 进行按位与运算

                                            0100
&   1010  1010  1010  1010  1010  1010  1010  1010     
--------------------------------------------------------      
											   0000        

1010 1010 1010 1010 1010 1010 1010 1010
| | | | | | |
64 16 4

检查n与n/4、n/16、n/64等进行按位与操作的结果是否都为0。如果都为0,说明n是4的幂次方。

P326–3的幂

传入的值n是一个整数类型 -2^31 <= n <= 2^31 - 1,即 2147483648<=n<=2147483647 在这个整数范围内,最多可达到3^19,所以

  • 如果n<=0,直接返回flase
  • 如果n>0,判断 n是否能被3^19取余是否为0,为0则返回true;反之,返回false
return n>0 && Math.pow(3,19) % n ==0;

P504–七进制数

给定一个整数 num,将其转化为 7 进制,并以字符串形式输出。

思路:

  • 先判断是否为 0,若为0则直接返回字符串0
  • 判断 num<0,若小于零则,boolean isNegivate = false; 反之,为true;
  • 构造一个 StringBuilder result对象,迭代除以7来将一个整数转化为7进制,并将每一步的余数拼接起来,循环 while(num > 0),每次除以7,结果插入 result的头部,来构造七进制数,然后 num /=7,接着下一次循环
public String convertToBase7(int num) {
    if (num == 0){
        return "0";
    }
    StringBuilder result = new StringBuilder();
    boolean isNagetive = num < 0;
    num=Math.abs(num);
    
    while(num > 0){
        result.insert(0,num % 7);  //头部插入余数
        num /= 7;
    }
    if (isNagetive){
        result.insert(0,"-");
    }
    return result.toString();
}

P263–丑数

丑数 就是只包含质因数 235 的正整数

public boolean isUgly(int n) {
 if(n<=0){
     return false;
 }
 while (n%5==0){
     n/=5;
 }
 while (n%3==0){
     n/=3;
 }
 while (n%2==0){
     n/=2;
 }
 return n==1;
}
  • 先把能除以5除尽,再把除以3除尽,最后除以2除尽,如果最后 n==1了,则代表,n的质数只有 2、3、5

P190–点到二进制数

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

public int reverseBits(int n) {
        int ans = 0;
        for (int bitsSize = 31;n != 0;n = n >>>1,bitsSize--){  // 每次 n >>>1,无符号右移1位
            ans += (n & 1) << bitsSize;  //将原来最后一位移到对应的前面
        }
        return ans;
    }

思路:

  1. 取当前 n 的最后一位:n & 1
  2. 将最后一位移动到对应位置,第一次为 31 位,第二次是 30 位,即:31、30、29… 1、0,写作代码 bit << 31;
  3. 退出条件,二进制反转时,如果剩余位数全位 0,则可以不用再反转。

位移运算

PS:Java的整数类型都是有符号的

  • 对于有符号整数,Java使用了一种叫做二进制补码的方法来表示负数。在这种方法中,最高位(也就是最左边的位)被用作符号位。如果符号位是0,那么这个数就是正数;如果符号位是1,那么这个数就是负数。对于负数,其余的位表示这个数的绝对值的二进制补码。

  • 二进制补码是一种用于表示负整数的方法。对于一个负数,我们首先取它的绝对值,然后将这个绝对值转换为二进制形式,接着对这个二进制数取反(也就是将所有的1变为0,所有的0变为1),最后再加1。得到的结果就是这个负数的二进制补码。

    例如,我们来看一下如何得到-5的二进制补码:

    1. 首先,我们取5的绝对值,得到5
    2. 然后,我们将5转换为二进制形式,得到0101
    3. 接着,我们对0101取反,得到1010
    4. 最后,我们对1010加1,得到1011

    所以,-5的二进制补码就是1011

  1. 左移带符号运算符 (<<):
    • 语法: a << b
    • a 的二进制表示向左移动 b 位,右侧用零填充。相当于将 a 乘以 2 的 b 次方。
    • 例子: 5 << 2 的结果是 20,因为二进制表示 101 左移两位变为 10100,即 20。
  2. 右移带符号运算符 (>>):
    • 语法: a >> b
    • a 的二进制表示向右移动 b 位,左侧用原来的最高位填充。如果 a 是正数,则用零填充;如果 a 是负数,则用一填充。
    • 例子: -8 >> 2 的结果是 -2,因为二进制表示 -811111111111111111111111111111000,右移两位变为 11111111111111111111111111111110,即 -2。
    • 最高位为符号位,带符号位移,则最高位仍和原来的一样,即保留原来符号
  3. 右移无符号运算符 (>>>):
    • 语法: a >>> b
    • a 的二进制表示向右移动 b 位,左侧用零填充。不考虑正负号,始终在左侧填充零。
    • 例子: -8 >>> 2 的结果是 1073741822,因为二进制表示 -8 右移两位变为 00111111111111111111111111111110,即 1073741822。

P191–位1的个数

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

public int hammingWeight(int n) {
    return Integer.bitCount(n);
}

🗡 question: 直接调用Integer.bitCount(n);方法,返回一个整数n的二进制表示中1的个数

P476–数字的补数

对整数的二进制表示取反(0110)后,再转换为十进制表示,可以得到这个整数的补数。

  • 例如,整数 5 的二进制表示是 "101" ,取反后得到 "010" ,再转回十进制表示得到补数 2

给你一个整数 num ,输出它的补数。

  1. 暴力解
public int findComplement(int num) {
    int count = 0;
    int n = num;
    while (n>0){
        n = n>>1; // 将 n 右移一位
        count++;  // 递增位数计数
    }
    int mask = (1 << count) - 1; //构造一个全1的数
    return num ^ mask;
}

思路:

  1. 因为要得到补数,(0110),而一个二进制数对于一个全是1的二进制数异或操作 ^ ,可以实现(0110),所以先构造出和原来二进制数位数一样的全是一的二进制数

  2. 通过while循环,每次都将一个n右移一位,count++,当 n 变为0时,循环结束,此时count的值就是二进制数的位数

  3. 构造位数相等的全是一的二进制数mask,将 1 左移count位,得到的是头部位 1,后面有count个 0 的二进制数,所以要 - 1,得到count位全是 1 的二进制数

    int count = 3; int result = (1 << count) - 1;
    

    (1 << count) ------》 1 0 0 0

    (1 << count) - 1 -------》 1 1 1

  4. 最后 num ^ mask;做异或操作,得到的是一个二进制数,return直接返回十进制数;

2.内置函数

public int findComplement(int num) {
    int mask = Integer.highestOneBit(num) - 1;
    return num ^ mask;
}

思路:

  1. 确定整数的最高有效位(最左侧的1的位)的位置。

  2. 构建一个具有相同位数的掩码,将该位及其右边的所有位都设置为1。

    这里,Integer.highestOneBit(num) 返回的是 num 中最左侧的1的位置,然后我们减去1,得到的掩码将最左侧的1及其右边的所有位都设置为1。最后,执行异或操作得到补数。

P461–汉明距离

两个整数之间的 汉明距离 指的是这两个数字对应二进制位不同的位置的数目。

给你两个整数 xy,计算并返回它们之间的汉明距离。

public int hammingDistance(int x, int y) {
    return Integer.bitCount(x ^ y);
}

思路:

  1. 对比的是两者二进制数,位不相同的个数,可以直接用异或操作 ^来得出不相同的位数
  2. 异或操作 ^得出来的是,如果两个对应的二进制位相同,则结果为0;如果两个对应的二进制位不同,则结果为1,所以最后直接统计1的个数就是不同位的个数;

异或操作 ^

异或(XOR)操作是二进制运算中常见的一种。在异或操作中,如果两个对应的二进制位相同,则结果为0;如果两个对应的二进制位不同,则结果为1。

以下是异或操作的真值表:

vbnetCopy code0 XOR 0 = 0
0 XOR 1 = 1
1 XOR 0 = 1
1 XOR 1 = 0

在 Java 中,异或操作使用 ^ 运算符表示。在二进制操作中,对于两个数 aba ^ b 将对 ab 的每一位进行异或操作。例如:

javaCopy codeint a = 5;  // 二进制表示为 101
int b = 3;  // 二进制表示为 011
int result = a ^ b;  // 异或操作,结果为 110
System.out.println(Integer.toBinaryString(result));  // 输出二进制表示为 110

在上述代码中,ab 进行异或操作后,得到的结果是 110,这是因为对应的二进制位上有两个位不同。

P477–汉明距离总和

两个整数的 汉明距离 指的是这两个数字的二进制数对应位不同的数量。

给你一个整数数组 nums,请你计算并返回 nums 中任意两个数之间 汉明距离的总和

public int totalHammingDistance(int[] nums) {
        int totalDistance = 0;
        for (int i = 0; i < nums.length; i++) {
            for (int j = i+1; j < nums.length; j++) {
                totalDistance +=Integer.bitCount(nums[i]^nums[j]);
            }
        }
        return totalDistance;
    }

思路:

  1. 给了我一个数组,求数组中每个数之间的汉明距离,则实现每两个数依次握手一次即可;
  2. 从第一个数开始,依次向后面的数握手,然后再从第二个数依次向后面握手……

P693交替位二进制数

给定一个正整数,检查它的二进制表示是否总是 0、1 交替出现:换句话说,就是二进制表示中相邻两位的数字永不相同。

public boolean hasAlternatingBits(int n) {
    while (n!= 0) {
        if ((n & 1) == 1) {
            if ((n & 2) == 2) {
                return false;
            }
        } else {
            if ((n & 2) == 0) {
                return false;
            }
        }
        n = n >> 1;
    }
    return true;
}

思路:

  1. 通过循环逐位比较,每次右移一次n
  2. 每一层循环,只判断最后一位和倒数第二位是否不相同
  3. 先判断 n & 1 == 1是否等于1,等于1代表,n的尾数为1,然后再判断倒数第二位(n & 2) == 2是否为2,为2的意思值为 10,所以本次循环尾数和次尾数是不相同的,开启下一次循环。

按位与操作 &

n & 1 是按位与操作,它会将 n 和1的二进制表示进行按位与操作。

对于 n=4,二进制表示是 100,而1的二进制表示是 001

按位与操作的规则是,如果对应位都是1,则结果为1;否则,结果为0。

所以,4 & 1 的按位与操作过程如下:

  100  (4的二进制表示)
& 001  (1的二进制表示)
-------
  000  (按位与的结果,即0)

因此,n & 1 的结果为0。

🌕 P393–UTF-8编码验证 43.7%

给定一个表示数据的整数数组 data ,返回它是否为有效的 UTF-8 编码。

UTF-8 中的一个字符可能的长度为 1 到 4 字节,遵循以下的规则:

  1. 对于 1 字节 的字符,字节的第一位设为 0 ,后面 7 位为这个符号的 unicode 码。
  2. 对于 n 字节 的字符 (n > 1),第一个字节的前 n 位都设为1,第 n+1 位设为 0 ,后面字节的前两位一律设为 10 。剩下的没有提及的二进制位,全部为这个符号的 unicode 码。

这是 UTF-8 编码的工作方式:

      Number of Bytes  |        UTF-8 octet sequence
                       |              (binary)
   --------------------+---------------------------------------------
            1          | 0xxxxxxx
            2          | 110xxxxx 10xxxxxx
            3          | 1110xxxx 10xxxxxx 10xxxxxx
            4          | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

x 表示二进制形式的一位,可以是 01

**注意:**输入是整数数组。只有每个整数的 最低 8 个有效位 用来存储数据。这意味着每个整数只表示 1 字节的数据。

public boolean validUtf8(int[] data) {
        int i = 0;
        while (i < data.length){
            int numBytes = getNumBytes(data[i]); // 获取当前字符的字节数
            if (numBytes < 1 || numBytes > 4 || i+numBytes > data.length){ //还要判断字符是否越界
                return false;
            }
            for (int j = 1;j < numBytes;j++){
                if ((data[i+j] & 0xC0) != 0x80){
                    return false;
                }
            }
            i += numBytes; // 移动到下一个字符
        }
        return true;
    }
private int getNumBytes(int b){
    if ((b & 0x80) == 0){
        return 1;
    }else if ((b & 0xE0) == 0xC0){
        return 2;
    }else if ((b & 0xF0) == 0xE0){
        return 3;
    }else if ((b & 0xF8) == 0xF0){
        return 4;
    }else{
        return -1;
    }
}

思路:

  1. 先弄清什么是UTF-8编码,因为传进来的是一个数组,必定要实现数组的遍历,这里我们用while循环条件为i<data.length,而每次遍历都需要干什么呢?

  2. 首先UTF-8编码,字节数的取值范围为 [1,4],所以他传递过来的数组不知道有几个UTF-8数,我们可以根据遍历的顺序来逐个判断,因为UTF-8编码可以根据第一个字节中从头到尾有多少个1来判断,第一个数占了多少个字节,(几个1就是几个字节,如果为0,则为1个字节),我们就编写一个 getNumBytes函数来统计有多少个字节;

  3. 根据前面判断出来占用 numBytes 个字节数,来进行检查该数之后(他的剩下的字节部分)是否满足 后面字节的前两位一律设为 10,利用 for循环逐个利用 (data[i+j] & 0xC0) != 0x80来判断前两位是否为 10

    0xC0(11000000),0x80 (10000000)

  4. 如果遇到了后面的不满足 前两位为10,则 return false 不满足退出;满足的话就继续验证下一个数 i += numBytes; ,直到都满足 return true### P88—合并两个有序数组

import java.util.Arrays;
class Solution {
    public void merge(int[] nums1, int m, int[] nums2, int n) {
        for (int i : nums2) {							//	int i = m - 1, j = n - 1, k = m + n - 1;
            nums1[m++] = i;								// 	从后往前比较并插入元素
        }											    //	while (i >= 0 && j >= 0) {
        Arrays.sort(nums1);    							//		if (nums1[i] > nums2[j]) {
                                                        //    		nums1[k--] = nums1[i--];
                                                        //  	}else {
                                                        //   		 nums1[k--] = nums2[j--];
                                                        // 		 }
                                                        //   }
                                                        //   如果nums2还有剩余元素,直接插入到nums1的前面
                                                        //   while (j >= 0) {
                                                        //      // 添加范围判断
                                                        //      if (k >= 0) {
                                                        //         nums1[k--] = nums2[j--i];
                                                        //      }
                                                        //   }   
        }
    }

Arrays.sort();对数组进行排序

P479–最大回文数乘机

  • 构造回文数的方法
long x = i;
long y = i;
//构造回文数  97  97  -> 9779
while (y > 0) {
    x = x * 10 + (y % 10);
    y /= 10;
}

假如x=y=97,经过第一轮 x=970 + 97 % 10 =970 +7=977,y=97/10=9;

​ 经过第二轮 x=9770 + 9 % 10=9770+9=9779 ;

x % 10 : 取到的x的(从后往前)最后一位

x / 10:去掉x的最后一位

  • 判断 x 是否有因数
// 从最大值开始逐个尝试可能的因数
for (long j = max; j * j >= x; j--) {
    if (x % j == 0) {
        return (int) (x % 1337);
    }
}

如果 x 对一个数取余(%),结果为 0,则该数为 x的因数

🌕P564–寻找最近的回文数 30.4%

答案可以做出来了,不过一直超时 下次TODO吧

class Solution {
        public String nearestPalindromic(String n) {
            long num = Long.parseLong(n);
            for (long i = 1;; i++) {
                if (isPalindrome(num - i))
                    return "" + (num - i);
                if (isPalindrome(num + i))
                    return "" + (num + i);
            }
        }
        boolean isPalindrome(long x) {
            long t = x, rev = 0;
            while (t > 0) {
                rev = 10 * rev + t % 10;
                t /= 10;
            }
            return rev == x;
        }
    }

P231–2的幂

如果存在一个整数 x 使得 n == 2x ,则认为 n 是 2 的幂次方。

if (n <= 0) {
    return false;
} else {
    return (n & (n - 1)) == 0;
}
  • 当一个数是2的幂时,它的二进制表示中只有一个1,其余位都是0

    • 2的幂的二进制表示为 1000...,而2的幂减去1的结果为 0111...。通过进行按位与运算 (n & (n - 1)),如果结果为0,则说明 n 的二进制表示中只有一个1,即 n 是2的幂。

    2:0010 ;4:0100 ; 8:1000 ;16:0001 0000 ;32:0010 0000

    1:0001 ;3:0011 ; 7:0111 ;15:1111 ; 31:0001 1111

    2的幂次方,那么它的二进制减去1后,原来的最高位的1会变成0,而其他位保持不变。将原数和减去1后的数进行按位与操作,结果应该是0。

  • 例子:

    假设 n = 8,它的二进制表示是 1000。现在,计算 n - 1 得到 7 的二进制表示是 0111。然后,

    ​ 进行按位与运算 n & (n - 1) 得到 1000 & 0111 = 0000,结果为0,说明 n 是2的幂。

    同样,如果 n = 6,其二进制表示为 110,计算 n - 1 得到 5 的二进制表示是 101

    ​ 进行按位与运算 110 & 101 得到 100,结果非零,说明 n 不是2的幂。

PS: 按位与运算 ( & )

按位与是一种二进制运算,它对两个二进制数的对应位进行逻辑与操作。按位与的规则如下:

  • 如果两个对应位都为1,则结果位为1。
  • 如果其中任意一个对应位为0,则结果位为0。

下面是一个简单的按位与示例:

   1010  (十进制:10)
 & 1101  (十进制:13)
 -------
   1000  (十进制:8)

P342–4的幂

在判断是否是2的幂次方的基础上再加以判断是否是4的幂次方

 return n > 0 && (n & (n - 1)) == 0 && (n & 0xaaaaaaaa) == 0;

(n & 0xaaaaaaaa) == 0 :将n与十六进制数 0xAAAAAAAA 进行按位与运算

                                            0100
&   1010  1010  1010  1010  1010  1010  1010  1010     
--------------------------------------------------------      
											   0000        

1010 1010 1010 1010 1010 1010 1010 1010
| | | | | | |
64 16 4

检查n与n/4、n/16、n/64等进行按位与操作的结果是否都为0。如果都为0,说明n是4的幂次方。

P326–3的幂

传入的值n是一个整数类型 -2^31 <= n <= 2^31 - 1,即 2147483648<=n<=2147483647 在这个整数范围内,最多可达到3^19,所以

  • 如果n<=0,直接返回flase
  • 如果n>0,判断 n是否能被3^19取余是否为0,为0则返回true;反之,返回false
return n>0 && Math.pow(3,19) % n ==0;

P504–七进制数

给定一个整数 num,将其转化为 7 进制,并以字符串形式输出。

思路:

  • 先判断是否为 0,若为0则直接返回字符串0
  • 判断 num<0,若小于零则,boolean isNegivate = false; 反之,为true;
  • 构造一个 StringBuilder result对象,迭代除以7来将一个整数转化为7进制,并将每一步的余数拼接起来,循环 while(num > 0),每次除以7,结果插入 result的头部,来构造七进制数,然后 num /=7,接着下一次循环
public String convertToBase7(int num) {
    if (num == 0){
        return "0";
    }
    StringBuilder result = new StringBuilder();
    boolean isNagetive = num < 0;
    num=Math.abs(num);
    
    while(num > 0){
        result.insert(0,num % 7);  //头部插入余数
        num /= 7;
    }
    if (isNagetive){
        result.insert(0,"-");
    }
    return result.toString();
}

P263–丑数

丑数 就是只包含质因数 235 的正整数

public boolean isUgly(int n) {
 if(n<=0){
     return false;
 }
 while (n%5==0){
     n/=5;
 }
 while (n%3==0){
     n/=3;
 }
 while (n%2==0){
     n/=2;
 }
 return n==1;
}
  • 先把能除以5除尽,再把除以3除尽,最后除以2除尽,如果最后 n==1了,则代表,n的质数只有 2、3、5

P190–点到二进制数

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

public int reverseBits(int n) {
        int ans = 0;
        for (int bitsSize = 31;n != 0;n = n >>>1,bitsSize--){  // 每次 n >>>1,无符号右移1位
            ans += (n & 1) << bitsSize;  //将原来最后一位移到对应的前面
        }
        return ans;
    }

思路:

  1. 取当前 n 的最后一位:n & 1
  2. 将最后一位移动到对应位置,第一次为 31 位,第二次是 30 位,即:31、30、29… 1、0,写作代码 bit << 31;
  3. 退出条件,二进制反转时,如果剩余位数全位 0,则可以不用再反转。

位移运算

PS:Java的整数类型都是有符号的

  • 对于有符号整数,Java使用了一种叫做二进制补码的方法来表示负数。在这种方法中,最高位(也就是最左边的位)被用作符号位。如果符号位是0,那么这个数就是正数;如果符号位是1,那么这个数就是负数。对于负数,其余的位表示这个数的绝对值的二进制补码。

  • 二进制补码是一种用于表示负整数的方法。对于一个负数,我们首先取它的绝对值,然后将这个绝对值转换为二进制形式,接着对这个二进制数取反(也就是将所有的1变为0,所有的0变为1),最后再加1。得到的结果就是这个负数的二进制补码。

    例如,我们来看一下如何得到-5的二进制补码:

    1. 首先,我们取5的绝对值,得到5
    2. 然后,我们将5转换为二进制形式,得到0101
    3. 接着,我们对0101取反,得到1010
    4. 最后,我们对1010加1,得到1011

    所以,-5的二进制补码就是1011

  1. 左移带符号运算符 (<<):
    • 语法: a << b
    • a 的二进制表示向左移动 b 位,右侧用零填充。相当于将 a 乘以 2 的 b 次方。
    • 例子: 5 << 2 的结果是 20,因为二进制表示 101 左移两位变为 10100,即 20。
  2. 右移带符号运算符 (>>):
    • 语法: a >> b
    • a 的二进制表示向右移动 b 位,左侧用原来的最高位填充。如果 a 是正数,则用零填充;如果 a 是负数,则用一填充。
    • 例子: -8 >> 2 的结果是 -2,因为二进制表示 -811111111111111111111111111111000,右移两位变为 11111111111111111111111111111110,即 -2。
    • 最高位为符号位,带符号位移,则最高位仍和原来的一样,即保留原来符号
  3. 右移无符号运算符 (>>>):
    • 语法: a >>> b
    • a 的二进制表示向右移动 b 位,左侧用零填充。不考虑正负号,始终在左侧填充零。
    • 例子: -8 >>> 2 的结果是 1073741822,因为二进制表示 -8 右移两位变为 00111111111111111111111111111110,即 1073741822。

P191–位1的个数

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

public int hammingWeight(int n) {
    return Integer.bitCount(n);
}

🗡 question: 直接调用Integer.bitCount(n);方法,返回一个整数n的二进制表示中1的个数

P476–数字的补数

对整数的二进制表示取反(0110)后,再转换为十进制表示,可以得到这个整数的补数。

  • 例如,整数 5 的二进制表示是 "101" ,取反后得到 "010" ,再转回十进制表示得到补数 2

给你一个整数 num ,输出它的补数。

  1. 暴力解
public int findComplement(int num) {
    int count = 0;
    int n = num;
    while (n>0){
        n = n>>1; // 将 n 右移一位
        count++;  // 递增位数计数
    }
    int mask = (1 << count) - 1; //构造一个全1的数
    return num ^ mask;
}

思路:

  1. 因为要得到补数,(0110),而一个二进制数对于一个全是1的二进制数异或操作 ^ ,可以实现(0110),所以先构造出和原来二进制数位数一样的全是一的二进制数

  2. 通过while循环,每次都将一个n右移一位,count++,当 n 变为0时,循环结束,此时count的值就是二进制数的位数

  3. 构造位数相等的全是一的二进制数mask,将 1 左移count位,得到的是头部位 1,后面有count个 0 的二进制数,所以要 - 1,得到count位全是 1 的二进制数

    int count = 3; int result = (1 << count) - 1;
    

    (1 << count) ------》 1 0 0 0

    (1 << count) - 1 -------》 1 1 1

  4. 最后 num ^ mask;做异或操作,得到的是一个二进制数,return直接返回十进制数;

2.内置函数

public int findComplement(int num) {
    int mask = Integer.highestOneBit(num) - 1;
    return num ^ mask;
}

思路:

  1. 确定整数的最高有效位(最左侧的1的位)的位置。

  2. 构建一个具有相同位数的掩码,将该位及其右边的所有位都设置为1。

    这里,Integer.highestOneBit(num) 返回的是 num 中最左侧的1的位置,然后我们减去1,得到的掩码将最左侧的1及其右边的所有位都设置为1。最后,执行异或操作得到补数。

P461–汉明距离

两个整数之间的 汉明距离 指的是这两个数字对应二进制位不同的位置的数目。

给你两个整数 xy,计算并返回它们之间的汉明距离。

public int hammingDistance(int x, int y) {
    return Integer.bitCount(x ^ y);
}

思路:

  1. 对比的是两者二进制数,位不相同的个数,可以直接用异或操作 ^来得出不相同的位数
  2. 异或操作 ^得出来的是,如果两个对应的二进制位相同,则结果为0;如果两个对应的二进制位不同,则结果为1,所以最后直接统计1的个数就是不同位的个数;

异或操作 ^

异或(XOR)操作是二进制运算中常见的一种。在异或操作中,如果两个对应的二进制位相同,则结果为0;如果两个对应的二进制位不同,则结果为1。

以下是异或操作的真值表:

vbnetCopy code0 XOR 0 = 0
0 XOR 1 = 1
1 XOR 0 = 1
1 XOR 1 = 0

在 Java 中,异或操作使用 ^ 运算符表示。在二进制操作中,对于两个数 aba ^ b 将对 ab 的每一位进行异或操作。例如:

javaCopy codeint a = 5;  // 二进制表示为 101
int b = 3;  // 二进制表示为 011
int result = a ^ b;  // 异或操作,结果为 110
System.out.println(Integer.toBinaryString(result));  // 输出二进制表示为 110

在上述代码中,ab 进行异或操作后,得到的结果是 110,这是因为对应的二进制位上有两个位不同。

P477–汉明距离总和

两个整数的 汉明距离 指的是这两个数字的二进制数对应位不同的数量。

给你一个整数数组 nums,请你计算并返回 nums 中任意两个数之间 汉明距离的总和

public int totalHammingDistance(int[] nums) {
        int totalDistance = 0;
        for (int i = 0; i < nums.length; i++) {
            for (int j = i+1; j < nums.length; j++) {
                totalDistance +=Integer.bitCount(nums[i]^nums[j]);
            }
        }
        return totalDistance;
    }

思路:

  1. 给了我一个数组,求数组中每个数之间的汉明距离,则实现每两个数依次握手一次即可;
  2. 从第一个数开始,依次向后面的数握手,然后再从第二个数依次向后面握手……

P693交替位二进制数

给定一个正整数,检查它的二进制表示是否总是 0、1 交替出现:换句话说,就是二进制表示中相邻两位的数字永不相同。

public boolean hasAlternatingBits(int n) {
    while (n!= 0) {
        if ((n & 1) == 1) {
            if ((n & 2) == 2) {
                return false;
            }
        } else {
            if ((n & 2) == 0) {
                return false;
            }
        }
        n = n >> 1;
    }
    return true;
}

思路:

  1. 通过循环逐位比较,每次右移一次n
  2. 每一层循环,只判断最后一位和倒数第二位是否不相同
  3. 先判断 n & 1 == 1是否等于1,等于1代表,n的尾数为1,然后再判断倒数第二位(n & 2) == 2是否为2,为2的意思值为 10,所以本次循环尾数和次尾数是不相同的,开启下一次循环。

按位与操作 &

n & 1 是按位与操作,它会将 n 和1的二进制表示进行按位与操作。

对于 n=4,二进制表示是 100,而1的二进制表示是 001

按位与操作的规则是,如果对应位都是1,则结果为1;否则,结果为0。

所以,4 & 1 的按位与操作过程如下:

  100  (4的二进制表示)
& 001  (1的二进制表示)
-------
  000  (按位与的结果,即0)

因此,n & 1 的结果为0。

🌕 P393–UTF-8编码验证 43.7%

给定一个表示数据的整数数组 data ,返回它是否为有效的 UTF-8 编码。

UTF-8 中的一个字符可能的长度为 1 到 4 字节,遵循以下的规则:

  1. 对于 1 字节 的字符,字节的第一位设为 0 ,后面 7 位为这个符号的 unicode 码。
  2. 对于 n 字节 的字符 (n > 1),第一个字节的前 n 位都设为1,第 n+1 位设为 0 ,后面字节的前两位一律设为 10 。剩下的没有提及的二进制位,全部为这个符号的 unicode 码。

这是 UTF-8 编码的工作方式:

      Number of Bytes  |        UTF-8 octet sequence
                       |              (binary)
   --------------------+---------------------------------------------
            1          | 0xxxxxxx
            2          | 110xxxxx 10xxxxxx
            3          | 1110xxxx 10xxxxxx 10xxxxxx
            4          | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

x 表示二进制形式的一位,可以是 01

**注意:**输入是整数数组。只有每个整数的 最低 8 个有效位 用来存储数据。这意味着每个整数只表示 1 字节的数据。

public boolean validUtf8(int[] data) {
        int i = 0;
        while (i < data.length){
            int numBytes = getNumBytes(data[i]); // 获取当前字符的字节数
            if (numBytes < 1 || numBytes > 4 || i+numBytes > data.length){ //还要判断字符是否越界
                return false;
            }
            for (int j = 1;j < numBytes;j++){
                if ((data[i+j] & 0xC0) != 0x80){
                    return false;
                }
            }
            i += numBytes; // 移动到下一个字符
        }
        return true;
    }
private int getNumBytes(int b){
    if ((b & 0x80) == 0){
        return 1;
    }else if ((b & 0xE0) == 0xC0){
        return 2;
    }else if ((b & 0xF0) == 0xE0){
        return 3;
    }else if ((b & 0xF8) == 0xF0){
        return 4;
    }else{
        return -1;
    }
}

思路:

  1. 先弄清什么是UTF-8编码,因为传进来的是一个数组,必定要实现数组的遍历,这里我们用while循环条件为i<data.length,而每次遍历都需要干什么呢?

  2. 首先UTF-8编码,字节数的取值范围为 [1,4],所以他传递过来的数组不知道有几个UTF-8数,我们可以根据遍历的顺序来逐个判断,因为UTF-8编码可以根据第一个字节中从头到尾有多少个1来判断,第一个数占了多少个字节,(几个1就是几个字节,如果为0,则为1个字节),我们就编写一个 getNumBytes函数来统计有多少个字节;

  3. 根据前面判断出来占用 numBytes 个字节数,来进行检查该数之后(他的剩下的字节部分)是否满足 后面字节的前两位一律设为 10,利用 for循环逐个利用 (data[i+j] & 0xC0) != 0x80来判断前两位是否为 10

    0xC0(11000000),0x80 (10000000)

  4. 如果遇到了后面的不满足 前两位为10,则 return false 不满足退出;满足的话就继续验证下一个数 i += numBytes; ,直到都满足 return true

  • 19
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值