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. 顺时针打印矩阵