返回

从O(n)到O(logn):位运算面试题中的时空复杂度优化

闲谈

探索位运算:高效解决常见的编程难题

在编程世界中,位运算是一种强大的技术,它通过对数字的二进制表示进行操作来解决各种问题。通过利用位运算,我们可以大幅提高算法的效率和性能。在本文中,我们将深入探讨一些常见的编程难题,并展示如何使用位运算来优雅地解决这些难题。

1. 寻找不同

问题: 给定两个长度为 n 的整数数组 A 和 B,其中 A 中有一个数字与 B 中所有数字都不相同,找出这个不同的数字。

优化方案: 利用异或运算,异或运算具有交换律和结合律,即 A 异或 B 异或 C = A 异或 (B 异或 C)。我们可以将 A 和 B 中所有数字异或起来,得到结果 x。然后将 x 与 A 中每个数字异或,如果结果不为 0,则该数字即为答案。

int find_different(int A[], int B[], int n) {
  int x = 0;
  for (int i = 0; i < n; i++) {
    x ^= A[i];
    x ^= B[i];
  }
  for (int i = 0; i < n; i++) {
    if ((x ^ A[i]) != 0) {
      return A[i];
    }
  }
  return -1;
}

时间复杂度:O(n)
空间复杂度:O(1)

2. 找出一个数组中缺失的数字

问题: 给定一个长度为 n-1 的整数数组 A,其中包含从 1 到 n 的所有整数,但有一个数字缺失。找出这个缺失的数字。

优化方案: 再次利用异或运算。我们可以将 A 中所有数字异或起来,并与从 1 到 n 的所有整数异或起来,结果即为缺失的数字。

int find_missing(int A[], int n) {
  int x = 0;
  for (int i = 0; i < n - 1; i++) {
    x ^= A[i];
  }
  for (int i = 1; i <= n; i++) {
    x ^= i;
  }
  return x;
}

时间复杂度:O(n)
空间复杂度:O(1)

3. 寻找一个数字在数组中出现的次数

问题: 给定一个长度为 n 的整数数组 A 和一个整数 x,找出 x 在数组 A 中出现的次数。

优化方案: 利用与运算。与运算也具有交换律和结合律,即 A 与 B 与 C = A 与 (B 与 C)。我们可以将 x 与 A 中每个数字依次与运算,如果结果不为 0,则说明 x 在这个数字中出现过。

int find_count(int A[], int n, int x) {
  int count = 0;
  for (int i = 0; i < n; i++) {
    if ((A[i] & x) != 0) {
      count++;
    }
  }
  return count;
}

时间复杂度:O(n)
空间复杂度:O(1)

4. 找出两个数字异或值最大的两个数字

问题: 给定一个长度为 n 的整数数组 A,找出数组 A 中异或值最大的两个数字。

优化方案: 仍然是异或运算。我们可以将 A 中所有数字异或起来,得到结果 x。然后将 x 与 A 中每个数字依次异或,得到一个新的数组 B。数组 B 中最大的两个数字即为异或值最大的两个数字。

int find_max_xor(int A[], int n) {
  int x = 0;
  for (int i = 0; i < n; i++) {
    x ^= A[i];
  }
  int max_xor = 0;
  for (int i = 0; i < n; i++) {
    int y = x ^ A[i];
    if (y > max_xor) {
      max_xor = y;
    }
  }
  return max_xor;
}

时间复杂度:O(n^2)
空间复杂度:O(n)

5. 找出两个数字与给定数异或值最小的两个数字

问题: 给定一个长度为 n 的整数数组 A 和一个整数 x,找出数组 A 中与 x 异或值最小的两个数字。

优化方案: 同样是异或运算。我们可以将 A 中所有数字与 x 异或起来,得到一个新的数组 B。然后将数组 B 排序,找到异或值最小的两个数字。

int find_min_xor(int A[], int n, int x) {
  int B[n];
  for (int i = 0; i < n; i++) {
    B[i] = A[i] ^ x;
  }
  sort(B, B + n);
  return B[0] ^ B[1];
}

时间复杂度:O(n log n)
空间复杂度:O(n)

6. 给定一个整数,找出其二进制表示中 1 的个数

问题: 给定一个整数 x,找出其二进制表示中 1 的个数。

优化方案: 利用与运算。我们可以将 x 与 1 依次与运算,如果结果不为 0,则说明 x 的二进制表示中存在 1。我们可以将 x 右移一位,并继续与 1 与运算,直到 x 为 0。

int count_ones(int x) {
  int count = 0;
  while (x > 0) {
    if ((x & 1) != 0) {
      count++;
    }
    x >>= 1;
  }
  return count;
}

时间复杂度:O(log x)
空间复杂度:O(1)

7. 给定一个整数,找出其二进制表示中最长的连续 1 的个数

问题: 给定一个整数 x,找出其二进制表示中最长的连续 1 的个数。

优化方案: 利用与运算和右移运算。我们可以将 x 与 1 依次与运算,如果结果不为 0,则说明 x 的二进制表示中存在 1。我们可以将 x 右移一位,并继续与 1 与运算,直到 x 为 0。在与运算的过程中,我们可以统计连续 1 的个数。

int count_max_ones(int x) {
  int count = 0;
  int max_count = 0;
  while (x > 0) {
    if ((x & 1) != 0) {
      count++;
    } else {
      max_count = max(max_count, count);
      count = 0;
    }
    x >>= 1;
  }
  max_count = max(max_count, count);
  return max_count;
}

时间复杂度:O(log x)
空间复杂度:O(1)

结论

位运算是一项强大的技术,它可以极大地提高算法的效率和性能。通过对数字的二进制表示进行操作,我们可以优雅地解决各种常见的编程难题。掌握位运算将为你的编程之旅增添宝贵的工具,帮助你轻松应对各种编码挑战。

常见问题解答

  1. 为什么位运算如此高效?
    位运算直接在硬件级别操作数字,避免了昂贵的函数调用和数据类型转换,从而提高了效率。

  2. 异或运算有什么特殊之处?
    异或运算满足交换律和结合律,使其非常适用于组合多个操作。

  3. 与运算和右移运算如何一起使用?
    与运算用于检查二进制位的值,而右移运算用于遍历二进制表示。将两者结合起来,可以有效地操作数字的二进制位。

  4. 位运算有哪些实际应用?
    位运算在计算机图形学、密码学、数据压缩和网络编程等领域都有着广泛的应用。

  5. 如何提高我使用位运算的技能?
    练习是关键。尝试解决本文中提到的问题,并不断探索位运算的新应用场景。