力扣方法总结:数学(位运算等)

1250. 检查「好数组」 Hard 数学 最大公约数 2023/2/15

给你一个正整数数组 nums,你需要从中任选一些子集,然后将子集中每一个数乘以一个 任意整数,并求出他们的和。
假如该和结果为 1,那么原数组就是一个「好数组」,则返回 True;否则请返回 False。
示例:
输入:nums = [12,5,7,23]
输出:true
解释:挑选数字 5 和 7。
53 + 7(-2) = 1

本题看上去无从下手,其实是个数学题。
有定理:多元一次不定方程 A 1 X 1 + A 2 X 2 + . . . + A n X n = C A_1X_1+A_2X_2+...+A_nX_n=C A1X1+A2X2+...+AnXn=C A 1 , A 2 , . . . A n , C A_1,A_2,...A_n,C A1,A2,...An,C 均为正整数)存在整数解的充要条件是: ( A 1 , A 2 . . . A n ) ∣ C (A_1,A_2...A_n)|C (A1,A2...An)C,即 C C C 能够被 A 1 , A 2 . . . A n A_1,A_2...A_n A1,A2...An 的最大公约数整除;
题目中, C = 1 C=1 C=1,因此 A 1 , A 2 . . . A n A_1,A_2...A_n A1,A2...An 的最大公约数只能为1;

class Solution {
public:
    bool isGoodArray(vector<int>& nums) {
        int g = nums[0];
        for (int i = 1; i < nums.size(); i++) {
            g = gcd(g, nums[i]);
        }
        return g == 1;
    }
    // 求最大公约数
    int gcd(int a, int b) {
        if (b == 0) return a;
        return gcd(b, a % b);
    }
};

剑指 Offer II 003. 前 n 个数字二进制中 1 的个数 Easy 位运算 类动态规划 2023/2/22

给定一个非负整数 n ,请计算 0 到 n 之间的每个数字的二进制表示中 1 的个数,并输出一个数组。
示例:
输入: n = 5
输出: [0,1,1,2,1,2]
解释:
0 --> 0
1 --> 1
2 --> 10
3 --> 11
4 --> 100
5 --> 101

本题最直观的想法是先构造一个计算每个数中二进制为1的数字个数,再遍历一遍即可。但这样每次计算的时间复杂度为 O ( log ⁡ n ) O(\log{n}) O(logn),总时间复杂度为 O ( n log ⁡ n ) O(n\log{n}) O(nlogn)

class Solution {
public:
    vector<int> countBits(int n) {
        vector<int> c;
        for (int i = 0; i <= n; i++)
            c.push_back(count(i));
        return c;
    }
    int count(int x) {
        int res = 0;
        while (x) {
            res += x % 2;
            x /= 2;
        }
        return res;
    }
};

可以考虑使用动态规划的思想:如果一个数是偶数,那么它二进制含1的个数等于其右移1位含1的个数;如果一个数是偶数,那么它二进制含1的个数等于其右移1位含1的个数+1;所以有: r e s [ i ] = r e s [ i > > 1 ] + ( i & 1 ) res[i] = res[i >> 1] + (i \& 1) res[i]=res[i>>1]+(i&1)

class Solution {
public:
    vector<int> countBits(int n) {
        vector<int> res(n + 1, 0);
        for (int i = 0; i <= n; i++) {
            res[i] = res[i >> 1] + (i & 1);
        }
        return res;
    }
};

剑指 Offer II 004. 只出现一次的数字 Medium 位运算 2023/2/22

给你一个整数数组 nums ,除某个元素仅出现 一次 外,其余每个元素都恰出现 三次 。请你找出并返回那个只出现了一次的元素。
示例:
输入:nums = [0,1,0,1,0,1,100]
输出:100

常规思路是哈希表遍历一次,但会消耗额外的空间复杂度,可以考虑使用位运算:累加所有数字的每一位,对于出现3次的数字来说,累加值必定是3的倍数,如果累加值÷3余1,说明该数字的二进制的该位为1,用1左移i位去与即可。

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int ans = 0;
        for (int i = 0; i < 32; i++) {
            // 计算每一位二进制的和
            int total = 0;
            for (int num: nums) {
                total += ((num >> i) & 1);
            }
            if (total % 3) ans |= (1 << i);
        }
        return ans;
    }
};

剑指 Offer II 005. 单词长度的最大乘积 Medium 位掩码 2023/2/22

给定一个字符串数组 words,请计算当两个字符串 words[i] 和 words[j] 不包含相同字符时,它们长度的乘积的最大值。假设字符串中只包含英语的小写字母。如果没有不包含相同字符的一对字符串,返回 0。
示例:
输入: words = [“abcw”,“baz”,“foo”,“bar”,“fxyz”,“abcdef”]
输出: 16
解释: 这两个单词为 “abcw”, “fxyz”。它们不包含相同字符,且长度的乘积最大。

对于每个字符串,肯定都要与所有其他字符串比较1次,因此 O ( n 2 ) O(n^2) O(n2) 的时间复杂度是跑不了了,那么如何将每次比较的时间复杂度降到 O ( 1 ) O(1) O(1) 呢?

预处理构建位掩码:对于字符串的每个字符 c c c ,把1左移 c − ′ a ′ c-'a' ca位再与掩码相或。

比较时,只需将两个字符串的位掩码相与,为0则表示不包含相同字符。

class Solution {
public:
    int maxProduct(vector<string>& words) {
        vector<int> masks(words.size());
        // 位掩码构建
        for (int i = 0; i < words.size(); i++)
            for (char c: words[i])
                masks[i] |= 1 << (c - 'a');
        int res = 0;
        // 比较掩码
        for (int i = 0; i < words.size(); i++)
            for (int j = i + 1; j < words.size(); j++)
                if (!(masks[i] & masks[j])) res = max(res, (int)(words[i].size() * words[j].size()));
        return res;
    }
};

89. 格雷编码 Medium 格雷码构造 2023/2/24

n 位格雷码序列 是一个由 2n 个整数组成的序列,其中:
1.每个整数都在范围 [0, 2n - 1] 内(含 0 和 2n - 1)
2.第一个整数是 0
3.一个整数在序列中出现 不超过一次
4.每对 相邻 整数的二进制表示 恰好一位不同 ,且
5.第一个 和 最后一个 整数的二进制表示 恰好一位不同
给你一个整数 n ,返回任一有效的 n 位格雷码序列 。
示例:
输入:n = 2
输出:[0,1,3,2]
解释:
[0,1,3,2] 的二进制表示是 [00,01,11,10] 。

需要熟悉格雷码构造的性质:将含有 2 i − 1 2^{i-1} 2i1 个元素的格雷码扩容成 2 i 2^{i} 2i 个元素,只需要将其倒序排列,再在其首位添1即可。

原格雷码 ⇒ 倒序 ⇒ 添1 ⇒ 扩充后的格雷码
( 0 ) 0 , ( 0 ) 1 (0)0,(0)1 (0)0,(0)1 ( 0 ) 1 , ( 0 ) 0 (0)1,(0)0 (0)1,(0)0 11 , 10 11,10 11,10 00 , 01 , 11 , 10 00,01,11,10 00,01,11,10

class Solution {
public:
    vector<int> grayCode(int n) {
        vector<int> ret;
        ret.reserve(1 << n); // 预留2^n位
        ret.push_back(0);
        for (int i = 1; i <= n; i++) {
            // 当前数组含有2^(i-1)个元素,需扩成2^i个元素
            int m = ret.size();
            // 倒序
            for (int j = m - 1; j >= 0; j--) {
                // 倒序并将首位添1
                ret.push_back(ret[j] | (1 << (i - 1)));
            }
        }
        return ret;
    }
};

另一种方法需要记住格雷码的公式 g i = i ⊕ ⌊ ı 2 ⌋ g_i=i \oplus\left\lfloor\frac{\imath}{2}\right\rfloor gi=i2 不推荐。

class Solution {
public:
    vector<int> grayCode(int n) {
        vector<int> ret(1 << n);
        for (int i = 0; i < ret.size(); i++) {
            ret[i] = (i >> 1) ^ i;
        }
        return ret;
    }
};

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值