力扣题库982——巧换if,减少循环

原题描述

给你一个整数数组 nums ,返回其中 按位与三元组 的数目。

按位与三元组 是由下标 (i, j, k) 组成的三元组,并满足下述全部条件:

0 <= i < nums.length
0 <= j < nums.length
0 <= k < nums.length
nums[i] & nums[j] & nums[k] == 0 ,其中 & 表示按位与运算符。

1 <= nums.length <= 1000
0 <= nums[i] < 2^16
来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/triples-with-bitwise-and-equal-to-zero
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
链接

解题过程

  1. 首先想到用三层for循环。
int countTriplets(int *nums, int numsSize){
    
    
    int result = 0;
    for (int i = 0; i < numsSize; i++){
    
    
        for (int j = 0; j < numsSize; j++){
    
    
            for (int k = 0; k < numsSize; k++){
    
    
                if((nums[i] & nums[j] & nums[k]) == 0){
    
    
                    result++;
                }
            }
        }
    }
    return result;
}

时间复杂度为 O(n^3)。提交后时间超限。

  1. 官方题解
    (大概意思差不多,官方是声明的动态数组。)
int countTriplets(int* nums, int numsSize) {
    
    
    int tmp_num[1 << 16];
    memset(tmp_num,0,sizeof(tmp_num));
    for (int i = 0; i < numsSize; i++) {
    
    
        int x = nums[i];
        for (int j = 0; j < numsSize; j++) {
    
    
            int y = nums[j];
            tmp_num[x & y]++;
        }
    }
    int result = 0;
    for (int i = 0; i < numsSize; i++) {
    
    
        int x = nums[i];
        for (int j = 0; j < (1 << 16); ++j) {
    
    
            if ((x & j) == 0) {
    
    
                result += tmp_num[j];
            }
        }
    }
    return result;
}

官方讲解的很好,用空间换时间,将前两遍的循环结果存储下来记为一个新矩阵。再将其进行下一个阶段的循环(第三给位置),将时间复杂度降了下来。

  1. 官方示例代码
//前面是一样的空间换时间
//后一个循环定位 x 时,直接采用异或操作定位到最大的和x相与为0的元素,之后逐次遍历更小的可能相与为0的元素。(妙)
for (int i=0; i<numsSize; i++){
    
    
        int x = nums[i] ^ 0xffff;
        for (int sub = x; sub; sub = (sub - 1) & x) {
    
    
            result += tmp_num[sub];
        }
        result += tmp_num[0];
    }

分享一下自己对这个x和sub的理解:
博主64位机器,x是int类型,32位,且0 <= nums[i] < 2^16,所以nums[i]前16位均是0。

  1. x = nums[i] ^ 0xffff:此过程相当于将nums[i]的低16位01相反(即0变为1,1变为0),此时便会得到最大的与nums[i]相交为0的数x。
  2. 接下来寻找小于x但大于0的数,此时示例代码没有用判断语句来寻找和nums[i]相与为0的数,(写成代码大概是下面这样
for (int sub = x; sub; sub --) {
    
    
		if((nums[i] & sub) == 0) {
    
    
            result += tmp_num[sub];
        }
}

)。
而是sub = (sub - 1) & x。
首先x是最大的(因为x的2进制表示中能为1的都为1了)。
其次:要找小于x的sub元素,只需要将x中的1依次变为0即可。
例如:假设nums[i] = 1111 1111 1100 1000
则 x = 0000 0000 0011 0111
则sub依次为x,0000 0000 0011 0110
0000 0000 0011 0101
0000 0000 0011 0100
0000 0000 0011 0011

0000 0000 0011 0000
接下来为0000 0000 0010 0110这步就是&x的好处,跳过了多次循环。

将其用数学语言表示即为sub = (sub - 1) & x 。其中&x保证了 sub&nums[i]=0;sub-1负责将sub逐渐减小;由此便会得到所有和nums[i]相与为0的sub元素。

作者才疏学浅,文章多有不足,请大家多多指教。

猜你喜欢

转载自blog.csdn.net/2201_75691823/article/details/129331328
982