算法学习day20

位运算相关知识点:

1.与 & :0101&1110=0100 两个位置上都为1时,才为1

2.或 | : 0101&1110=1111 两个位置上都为0时, 才为0

3.异或 ^ :0101^1110=1111 两个位置上不相等的时,为1;相等时,为0

4.取反 ~:~0=1 ~1=0;

5.左移 << :十进制中左移就是*2;二进制中就是向左移动k位,右边补零 0001<<2=0100

6.右移 >>:十进制中右移就是/2;二进制中是向右移动k位,无符号数左边补0,有符号数左边补符号位。1000>>2=0010

位运算相关操作:

1.要将每一位上的数字取反。num^111...111(相同为0,不同为1)。num上的数字:0->1 1->0

2.取最后一位上的数字。num&1。0&1=0;1&1=1;

3.把最后一位变成1。num|1。不论num最后一位是0还是1,结果都为1

4.把最后一位变成0。(num|1)-1

5.把右数第k位变成1。num|(1<<(k-1))

6.把右数第k位取反。num^(1<<(k-1))

一、颠倒二进制位(取最后一位\有符号数的右移\|运算)

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

复制代码
输入:n = 00000010100101000001111010011100
输出:964176192 (00111001011110000010100101000000)
解释:输入的二进制串 00000010100101000001111010011100 表示无符号整数 43261596,
     因此返回 964176192,其二进制表示形式为 00111001011110000010100101000000。
思路:

将给定的字符串反转,使用for循环遍历,每次获取n串中的最后一个二进制数。

1.获取最后一位的操作:(n&1);2.然后把它放到最前面。(n&1)<<(31-i);

3.然后每次和结果累加。res|((n&1)<<(31-i));4.然后n右移,n>>>=1

代码:
复制代码
public class Solution {
    //将每一位的最后一个数字取出来 然后左移
    public int reverseBits(int n) {
        int res=0;
        for(int i=0;i<32&&n!=0;i++){
            res=res|(n&1)<<(31-i);
            n>>>=1;
        }
        return res;
    }
}

二、汉明距离总和(求某位置上1和0的个数)

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

暴力法:

双层for循环,对每一对数字进行判断,然后把每一对数字的汉明距离相加起来。

复制代码
class Solution {
    public int totalHammingDistance(int[] nums) {
        int sum=0;
        for(int i=0;i<nums.length;i++){
            for(int j=i+1;j<nums.length;j++){
                sum+=HammingDistance(nums[i],nums[j]);
            }
        }
        return sum;
    }
    public int HammingDistance(int num1,int num2){
        return Integer.bitCount(num1^num2);
        //1.首先先进行异或操作
        //int num3=num1^num2;
        //int count=0;
        //2.进行判断
        //while(num3!=0){
            //if((num3&1)==1)count++;
            //num3>>>=1;
        //}
        //return count;
    }
}
另一种解法:

要求汉明距离总和,暴力法是将每一个元素作为一个整体来考虑的。

该种解法是将同一个位置上所有的元素为整体考虑的。

汉明距离总和=第0列二进制位不同的数量+第1列xxx+第2列xxx+...

每一列上二进制为不同的数量=0的个数*1的个数。每一个0都可以和其他的1搭配。(for循环两两搭配)

代码:

复制代码
public int totalHammingDistance(int[] nums){
    int sum=0;
    for(int i=31;i>=0;i--){
        int l0=0,l1=0;
        for(int num:nums){
            if(((num>>i)&1)==1)l1++;
            else l0++;
        }
        sum+=l0*l1;
    }
    return sum;

}

三、阶乘后的零

给定一个整数 n ,返回 n! 结果中尾随零的数量。

提示 n! = n * (n - 1) * (n - 2) * ... * 3 * 2 * 1

0 <= n <= 104

思路:

只有出现2*5,才会出现0;所以尾随零的数量=2/5对数的数量;又因为阶乘中数字可分成2的数量一定比5的数量多,所以0的数量=min(2,5)也就是5的数量。

因此只需要在1->n这个范围里面找%5的数并且累加就可以

代码:
复制代码
class Solution {
    public int trailingZeroes(int n) {
        //判断0的个数->判断1->n中 可以分解5的个数
        int count=0;
        for(int i=1;i<=n;i++){
            int temp=i;
            while(temp>0){
                if(temp%5==0){
                    count++;
                    temp/=5;
                }else break;
            }
        }
        return count;
    }
}

四、数字转换为十六进制(位运算)

首先考虑到正负数,负数的话要使用补码。

思路:

如果是正数,补码就是本身;如果是负数,补码就是反码+1;如果是0,直接返回0;

如何求负数的补码:每一位取反再加1:num^0xffffffff+1。用异或进行取反,32位,十六进制,需要八位。(二进制)

如何将二进制转换为十六进制:二进制的每四位表示一个十六进制。val[num&0xf] 然后append。num>>>4;

代码:
复制代码
class Solution {
    public String toHex(int num) {
        if(num==0)return "0";
        if(num<0){
            //取反+1 32位 一个十六进制是4位
            num=num^0xffffffff+1;
        }
        char[] val={'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};
        StringBuilder sb=new StringBuilder();
        while(num!=0){
            sb.append(val[num&0xf]);
            num>>>=4;
        }
        return sb.reverse().toString();
    }
}

五、灯泡开关(数论题)

初始时有 n个灯泡处于关闭状态。第一轮,你将会打开所有灯泡。接下来的第二轮,你将会每两个灯泡关闭第二个。

第三轮,你每三个灯泡就切换第三个灯泡的开关(即,打开变关闭,关闭变打开)。第 i 轮,你每 i 个灯泡就切换第 i 个灯泡的开关。直到第 n 轮,你只需要切换最后一个灯泡的开关。

题意:

第一轮打开所有,然后接下来第n轮,每n个关闭最后一个(第n个);

思路:

第1个灯泡什么时候才会改变状态,只有第一轮的时候,剩下的几轮都涉及不到1。

第2个灯泡什么时候才会改变状态,只有第一轮和第二轮的时候。

第3个灯泡什么时候才会改变状态,只有第一轮和第三轮的时候。

第4个灯泡什么时候才会改变状态,只有第一轮和第二轮和第四轮的时候。

因此,我们发现了规律,第n个灯泡只会在它因数的时候改变状态。如果因数是奇数,就是亮的;如果因数是偶数,就是关闭的。但是一般因数都是成对出现的,只有在完全平方数的时候会出现奇数。所以这道题就是考察n以内的完全平方数有几个。

代码:
复制代码
class Solution {
    public int bulbSwitch(int n) {
        return (int)Math.sqrt(n);
    } 
}

六、Excel表列名称

复制代码
A -> 1
B -> 2
C -> 3
...
Z -> 26
AA -> 27
AB -> 28 
  • 1 <= columnTitle.length <= 7
  • columnTitle 仅由大写英文组成
  • columnTitle 在范围 ["A", "FXSHRXW"]
思路:

类似于二十六进制。x%26,就是最后一位上的值,再将值转换成对应的字母。

(x-x%26)/26,就是将最后一位去除的值,然后继续%26,就是倒数第二位上的值,再把它转换成对应的字母。

x%26->转换为字母->x=(x-x%26)/26->x%26->转换为字母->x=(x-x%26)/26

注意:

如果x%26==0,说明该位置上为'Z',此时应该是(x-26)/26

代码:
复制代码
/**
 * 规律:因为每隔26就晋一位 两位数字可以表示的范围在1*26->26*26 三位数字的范围是1*26*26->26*26*26
 */
class Solution {
    public String convertToTitle(int columnNumber) {
        StringBuilder sb=new StringBuilder();
        while(columnNumber>0){
            int mod=columnNumber%26;
            if(mod==0){
                sb.append('Z');
                columnNumber=(columnNumber-26)/26;
            }else{
                char ch =(char)('A' + mod - 1);
                sb.append(ch);
                columnNumber=(columnNumber-mod)/26;
            }
        }
        return sb.reverse().toString();
    }
}

七、Excel表列序号

已知一个数字上每一个位置上的数字,求该数字为多少。

number=number*26+num;

复制代码
class Solution {
    public int titleToNumber(String columnTitle) {
        int multi=0;
        for(int i=0;i<columnTitle.length();i++){
            int num=columnTitle.charAt(i)-'A'+1;
            multi=multi*26+num;
        }
        return multi;
    }
}

八、最大交换

给定一个非负整数,你至多可以交换一次数字中的任意两位。返回你能得到的最大值

复制代码
输入: 2736
输出: 7236
解释: 交换数字2和数字7。
思路:

1.将数字转化成字符串str,然后将每一位数字都放到list集合中。然后按照降序排。

2.一个for循环找到str中和list集合中第一次不同的位置(就是要交换的位置)

3.用变量swapA记录被交换数字的位置,然后list.get(i)为要交换数字的值,使用变量b记录。

4.如果(str.charAt(i)==b),就找到了在原始字符串中要交换数字的下标。

5.然后交换即可

代码:
复制代码
class Solution {
    public int maximumSwap(int num) {
        String str=String.valueOf(num);
        List<Character> list=new ArrayList<>();
        for(int i=0;i<str.length();i++){
            list.add(str.charAt(i));
        }
        Collections.sort(list,(a,b)->{
            return b-a;//按照降序排
        });
        int swapA=-1,swapB=-1,b=-1;
        for(int i=0;i<str.length();i++){
            if(str.charAt(i)!=list.get(i)&&swapA==-1){
                swapA=i;//如果和降序排的数字不相等的话 被交换的数字的下标
                b=list.get(i);//交换的数字
            }
            if(str.charAt(i)==b)swapB=i;//交换的数字的下标
        }
        if(swapA!=-1){
            char ch[]=str.toCharArray();
            char temp=ch[swapA];
            ch[swapA]=ch[swapB];
            ch[swapB]=temp;
            str=String.valueOf(ch);
        }
        return Integer.parseInt(str);
    }
}

九、两数相除(二分/位运算)

给你两个整数,被除数 dividend 和除数 divisor。将两数相除,要求 不使用 乘法、除法和取余运算。返回被除数 dividend 除以除数 divisor 得到的

注意: 假设我们的环境只能存储 32 位 有符号整数,其数值范围是 [−2 31, 231 − 1] 。本题中,如果商 严格大于 231 − 1 ,则返回 231 − 1 ;如果商 严格小于 -231 ,则返回 -231

1.如果被除数为-2 31次,除数为-1,那么就会出现商大于2 31次。这样就要返回Integer.MAX_VALUE;2.如果除数为0,直接返回0;

思路:

1.首先排除极端的情况。当被除数为最小值,除数为-1,返回Integer.MAX_VALUE:当除数为0的时候,直接返回0;

2.进入正题:首先取被除数和除数的绝对值,然后判断结果是正还是负。这里使用异或运算符判断。(被除数<0)^(除数<0),一正一负就为负,都正或者都负为㊣

3.while循环找商:使用二分法不断逼近。while(被除数>=(除数<<count)){count++;}

当除数*2的count次<=被除数,第一次循环找到的商为2的count次。然后第二轮循环继续找

代码:
复制代码
class Solution {
    public int divide(int dividend, int divisor) {
        //如果除数为0 直接返回0;
        if(divisor==0)return 0;
        //如果商大于2的31次-1 直接返回Integer.MAX_VALUE;
        if(dividend==Integer.MIN_VALUE&&divisor==-1){
            return Integer.MAX_VALUE;
        }
        //判断最后的符号
        boolean flag=(dividend<0)^(divisor<0);
        //取绝对值
        long absDividend=Math.abs((long)dividend);
        long absDivisor=Math.abs((long)divisor);
        //开始计算
        int res=0;
        while(absDivisor<=absDividend){
            //找每一次循环商的最大值,不断逼近的过程
            //商为2的count次
            int count=0;
            while(absDividend>=(absDivisor<<count)){
                count++;
            }
            count--;//上面while循环里面多加了一个
            res+=(1<<count);//可以整除的次数 增加1<<count次
            absDividend-=(absDivisor<<count);//数减少
        }
        return flag?-1*res:res;
    }
}

十、第n位数字

给你一个整数 n ,请你在无限的整数序列 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, ...] 中找出并返回第 n位上的数字。eg:第11位上的数字 12345678910 是0;

思路:

1.首先算出第n位数字在哪一个范围里面 1-9? 10-99? 100-999?

2.如何计算呢? n>length*count 就说明不在这个范围里面 length:一个数字的长度(比如两位数的长度是2,三位数的长度是3);count是这个范围里面数字的个数

3.找到范围之后,找第几个数字。long num=(n-1)/lenth

4.找到第几个数字之后,找在这个数字中的位置。int index=(n-1)%length

5.然后求出来就ok了 String str=String.valueOf(num); return str.charAt(index)-'0';

复制代码
class Solution {
    public int findNthDigit(int n) {
        long start=1;
        int length=1;//当前数字的位数
        long count=9;//当前位数的数量
        while(n>length*count){
            n-=length*count;
            length++;//变成两位数
            count*=10;//从1-9 变成 10-99 公式为:9*Math.pow(10,length-1);
            start*=10;
        }
        //计算第n位数字 具体所在的具体数字
        long num=start+(n-1)/length;
        //计算第n位数字在该数字中的位置
        int index=(n-1)%length;
        //提取第n位数字
        String str=String.valueOf(num);
        return str.charAt(index)-'0';
    }
}
相关推荐
西岸行者5 天前
学习笔记:SKILLS 能帮助更好的vibe coding
笔记·学习
悠哉悠哉愿意5 天前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
别催小唐敲代码5 天前
嵌入式学习路线
学习
毛小茛5 天前
计算机系统概论——校验码
学习
babe小鑫5 天前
大专经济信息管理专业学习数据分析的必要性
学习·数据挖掘·数据分析
winfreedoms5 天前
ROS2知识大白话
笔记·学习·ros2
在这habit之下5 天前
Linux Virtual Server(LVS)学习总结
linux·学习·lvs
我想我不够好。5 天前
2026.2.25监控学习
学习
im_AMBER5 天前
Leetcode 127 删除有序数组中的重复项 | 删除有序数组中的重复项 II
数据结构·学习·算法·leetcode
CodeJourney_J5 天前
从“Hello World“ 开始 C++
c语言·c++·学习