LeetCode 477 / 剑指 Offer 63 / 64 /65 / 66

Posted Zephyr丶J

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LeetCode 477 / 剑指 Offer 63 / 64 /65 / 66相关的知识,希望对你有一定的参考价值。

477. 汉明距离总和

2021.5.28 每日一题

题目描述

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

计算一个数组中,任意两个数之间汉明距离的总和。

示例:

输入: 4, 14, 2

输出: 6

解释: 在二进制表示中,4表示为0100,14表示为1110,2表示为0010。(这样表示是为了体现后四位之间关系)
所以答案为:
HammingDistance(4, 14) + HammingDistance(4, 2) + HammingDistance(14, 2) = 2 + 2 + 2 = 6.

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

思路

首先在昨天求汉明距离基础上的暴力解法,超时了

class Solution {
    public int totalHammingDistance(int[] nums) {
        //任意两个数的汉明距离总和
        //先来暴力
        int sum = 0;
        int l = nums.length;
        for(int i = 0; i < l; i++){
            for(int j = i + 1; j < l; j++){
                int x = nums[i] ^ nums[j];
                while(x > 0){
                    x &= x - 1;
                    sum++;
                }
            }
        }
        return sum;
    }
}

想想怎么更快的计算
计算一个数与其他数汉明距离,实际上是计算这个数的每一位与其他数对应的这一位有多少个不同
那么就统计所有31位上,分别有多少个1或者0,然后对于任意一位,该位上能产生的汉明距离就是,1的个数乘以0的个数

class Solution {
    public int totalHammingDistance(int[] nums) {
        //任意两个数的汉明距离总和
        //想想怎么优化,计算一个数与其他数汉明距离,实际上是计算这个数的每一位与其他数对应的这一位有多少个不同
        //那么就统计所有31位上,分别有多少个1或者0,然后对于任意一位,该位上能产生的汉明距离就是,1的个数乘以0的个数
        //好的,应该就是这样
        int sum = 0;
        int l = nums.length;
        for(int k = 0; k < 31; k++){
	        int count0 = 0;
	        int count1 = 0;
            for(int i = 0; i < l; i++){
                if(((nums[i] >> k) & 1) == 1)
                    count1++;
                else
                    count0++;
            }
            sum += count0 * count1;
        }
        return sum;
    }
}

剑指 Offer 63. 股票的最大利润

题目描述

假设把某股票的价格按照时间先后顺序存储在数组中,请问买卖该股票一次可能获得的最大利润是多少?


示例 1:

输入: [7,1,5,3,6,4]
输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
     注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。
示例 2:

输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。

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

思路

股票问题,经典的动态规划问题,不过先来个贪心

class Solution {
    public int maxProfit(int[] prices) {
        //股票问题,贪心或者动规吧
        //贪心,实时记录最小值
        int min = 100000;
        int res = 0;
        for(int price : prices){
            min = Math.min(min, price);
            res = Math.max(res, price - min);
        }
        return res;
    }
}

动规

class Solution {
    public int maxProfit(int[] prices) {
        //股票问题,贪心或者动规吧
        //动规,dp[i][2]表示第i天手中持有或者不持有股票的最大利润,0不持有,1持有
        //注意买卖一次
        int l = prices.length;
        if(l == 0)
            return 0;
        int[][] dp = new int[l][2];
        //初始化
        dp[0][0] = 0;
        dp[0][1] = -prices[0];
        for(int i = 1; i < l; i++){
            dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i]);
            //买卖一次
            dp[i][1] = Math.max(dp[i - 1][1], -prices[i]);
        }
        return dp[l - 1][0];
    }
}

剑指 Offer 64. 求1+2+…+n

题目描述

求 1+2+...+n ,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。


示例 1:

输入: n = 3
输出: 6
示例 2:

输入: n = 9
输出: 45

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

思路

第一次看到这种题,啥也不让用,就想了下递归,但是递归还是得用条件判断,然后我就懵了
梳理一下现在还能用的东西,加减法,位运算,逻辑运算符

看了答案,直呼好家伙

递归

第一次看到这种递归,就是通过逻辑运算符,实现 if 的效果,即在达到一定条件时,使的递归不在执行,短路与或者短路或都能实现
学!

class Solution {
    public int sumNums(int n) {
        //啥也不能用,那就只能递归了,递归也得用if....这
        //学这种递归方法,就是让n在达到某个条件时不再递归,其实本质和if一样,只不过实现起来使用逻辑运算符实现的
        //答案是与,我用或试试,或就是得在小于等于0时,第一个条件就成立,而不用进入第二个条件
        boolean flag = n <= 0 || (n += sumNums(n - 1)) > 0; 
        return n;
    }
}
快速乘

快速乘,看了一下,其实就是我们手动计算乘法时候的方法,对于A,用B中的每一位乘以A,然后把计算的结果放在相应的位置,最后把所有结果加起来就是乘法结果
换到二进制呢,就可以用加法实现,因为二进制中只用0和1,所以对于A,遍历B中的每一个二进制位i,如果是1,那么就把A向左移动i位,并且加入到结果中,这样需要写14次(因为最大是10000)
官解里说是俄罗斯农民乘法,去看了一下,其实就是这种二进制的应用

class Solution {
    public int sumNums(int n) {
        int res = 0;
        int a = n + 1;  //首项 + 尾项
        int b = n;      //项数
        boolean flag = false;
        flag = ((b & 1) == 1) && (res += a) > 0;    //1
        a <<= 1;
        b >>= 1;
        flag = ((b & 1) == 1) && (res += a) > 0;    //2
        a <<= 1;
        b >>= 1;
        flag = ((b & 1) == 1) && (res += a) > 0;    //3
        a <<= 1;    
        b >>= 1;
        flag = ((b & 1) == 1) && (res += a) > 0;    //4
        a <<= 1;
        b >>= 1;
        flag = ((b & 1) == 1) && (res += a) > 0;
        a <<= 1;
        b >>= 1;
        flag = ((b & 1) == 1) && (res += a) > 0;
        a <<= 1;
        b >>= 1;
        flag = ((b & 1) == 1) && (res += a) > 0;    //7
        a <<= 1;
        b >>= 1;
        flag = ((b & 1) == 1) && (res += a) > 0;    //8
        a <<= 1;
        b >>= 1;
        flag = ((b & 1) == 1) && (res += a) > 0;
        a <<= 1;
        b >>= 1;
        flag = ((b & 1) == 1) && (res += a) > 0;
        a <<= 1;
        b >>= 1;
        flag = ((b & 1) == 1) && (res += a) > 0;
        a <<= 1;
        b >>= 1;
        flag = ((b & 1) == 1) && (res += a) > 0;    //12
        a <<= 1;
        b >>= 1;
        flag = ((b & 1) == 1) && (res += a) > 0;
        a <<= 1;
        b >>= 1;
        flag = ((b & 1) == 1) && (res += a) > 0;    //14
        a <<= 1;
        b >>= 1;
        return res >> 1;    //除以2
    }
}

评论看到一个捕获异常的,牛逼

class Solution {
    int[] test=new int[]{0};
    public int sumNums(int n) {
        try{
            return test[n];
        }catch(Exception e){
            return n+sumNums(n-1);
        }
    }
}

书上讲的几种,看不太懂,这里就不写了

剑指 Offer 65. 不用加减乘除做加法

题目描述

写一个函数,求两个整数之和,要求在函数体内不得使用 “+”、“-”、“*”、“/” 四则运算符号。


示例:

输入: a = 1, b = 1
输出: 2

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

思路

不能加减乘除,肯定是位运算咯
思路飘飞了,所以想的慢了,不过还是想出来了
异或就是不进位加法,所以当前位的结果肯定异或是没问题的
那么对于进位呢,进位就是与运算,但是怎么把进位的结果加在异或结果上呢,
其实还是两个数相加,迭代就行了,直到进位为0,那么就直接输出异或的结果

但是题目中特意提了一句,有负数,但是在计算机中,负数用补码表示,加减法和正数一样

class Solution {
    public int add(int a, int b) {
        //对于当前位,异或肯定没问题,异或俗称不进位加法
        //那么进位怎么处理呢,就是相与,遍历每一位,如果结果是1,那么就是1
   
        int res = 0;	//当前位
        int pre = 1;	//进位
        while(pre != 0){
            res = a ^ b;
            //进位
            pre = (a & b) << 1;
            a = res;
            b = pre;
        }
        return res;
    }
}
交换两个数

然后书中还给到了如何在不使用新的变量的情况下,交换两个变量的值

使用加减法
a = a + b;
b = a - b;
a = a - b;

使用异或
a = a ^ b;
b = a ^ b;
a = a ^ b;

剑指 Offer 66. 构建乘积数组

题目描述

给定一个数组 A[0,1,…,n-1],请构建一个数组 B[0,1,…,n-1],其中 B[i] 的值是数组 A 中除了下标 i 以外的元素的积, 即 B[i]=A[0]×A[1]×…×A[i-1]×A[i+1]×…×A[n-1]。不能使用除法。

 
示例:

输入: [1,2,3,4,5]
输出: [120,60,40,30,24]

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

思路

因为乘积的每一部分都是整个数组的前缀或者后缀,所以我这里先处理前后缀,然后相乘。但是超了不多,看来应该有更好的方法

class Solution {
    public int[] constructArr(int[] a) {
        //不能用除法,那就预处理前缀乘积和后缀乘积
        int l = a.length;
        if(l == 0)
            return new int[]{};
        int[] pre = new int[l];
        int[] tail = new int[l];
        int[] res = new int[l];
        pre[0] = a[0];
        tail[l - 1] = a[l - 1];
        for(int i = 1; i < l; i++){
            pre[i] = pre[i - 1] * a[i];
            tail[l - i - 1] = tail[l - i] * a[l - i - 1]; 
        }
        //知道前后缀以后,就可以求了
        for(int i = 0; i < l; i++){
            if(i == 0)
                res[i] = tail[1];
            else if(i == l - 1)
                res[l - 1] = pre[l - 2];
            else    
                res[i] = pre[i - 1] * tail[i + 1];
        }
        return res;

    }
}

看了一下题解,思路还是这个思路,就是实现上用一个数组就行了,改一下细节

class Solution {
    public int[] constructArr(int[] a) {
        //不能用除法,那就预处理前缀乘积和后缀乘积
        int l = a.length;
        if(l == 0)
            return new int[]{};
        int[] pre = new int[l];
        pre[0] = 1;
        //先求前缀,pre[i]表示前i-1位的乘积
        for(int i = 1; i < l; i++){
            pre[i] = pre[i - 1] * a[i - 1]; 
        }
        int temp = 1;   //记录当前i的后缀乘积
        //处理后缀的同时,计算结果
        for(int i = l - 2; i >= 0; i--){
            temp *= a[i + 1];
            pre[i] *= temp;
        }
        return pre;

    }
}

以上是关于LeetCode 477 / 剑指 Offer 63 / 64 /65 / 66的主要内容,如果未能解决你的问题,请参考以下文章

LeetCode(剑指 Offer)- 60. n个骰子的点数

[LeetCode]剑指 Offer 29. 顺时针打印矩阵

[LeetCode]剑指 Offer 29. 顺时针打印矩阵

leetcode-剑指 Offer 42连续子数组的最大和

leetcode-剑指 Offer 42连续子数组的最大和

[LeetCode]剑指 Offer 27. 二叉树的镜像