子数组的最大异或和问题

数组,最大,异或,问题 · 浏览次数 : 339

小编点评

**子数组的最大异或和** **问题描述** 给定一个数组 `arr`,其中可能有正、有负,有零,请返回 `arr` 的最大子数组异或和。 **暴力解法** 1. 遍历数组,计算每个位置的异或和。 2. 找出 0 ~ i 位置的最大的异或和。 3. 使用前缀树快速查找 0 ~ j 之间的最大异或和。 **时间复杂度** * 最坏情况下,暴力解法的时间复杂度为 O(N),其中 N 是数组长度。 * 最优情况下,暴力解法的时间复杂度为 O(N log N),通过使用前缀树加速匹配。 **空间复杂度** * 最坏情况下,暴力解法的空间复杂度为 O(N),其中 N 是数组长度。 * 最优情况下,暴力解法的空间复杂度为 O(log N)。 **代码** ```java public static int maxEor1(int[] arr, int n) { int[] eor = new int[arr.length]; int max = arr[0]; eor[0] = arr[0]; for (int i = 1; i < n; i++) { eor[i] = eor[i - 1] ^ arr[i]; } for (int i = 1; i < n; i++) { max = Math.max(max, eor[i]); for (int j = i; j < n; j++) { max = Math.max(max, eor[j] ^ eor[i - 1]); } } return max; } ``` **复杂度分析** * 最坏情况下,暴力解法的时间复杂度为 O(N),其中 N 是数组长度。 * 最优情况下,暴力解法的时间复杂度为 O(N log N),通过使用前缀树加速匹配。 * 空间复杂度为 O(N),最坏情况下为 O(N),最优情况下为 O(log N)。

正文

子数组的最大异或和问题

作者:Grey

原文地址:

博客园:子数组的最大异或和问题

CSDN:子数组的最大异或和问题

题目描述

数组中所有数都异或起来的结果,叫做异或和。给定一个数组 arr,其中可能有正、有负,有零,返回 arr 的最大子数组异或和

题目链接见:牛客-子数组的最大异或和

暴力解

枚举每个子数组的异或和,抓取全局最大值返回,整个算法时间复杂度\(O(N^3)\),整个过程比较简单,不赘述,基于这个暴力解法,可以有优化一些的算法,就是利用前缀异或和数组,时间复杂度可以减少到\(O(N^2)\),思路如下

第一步

申请一个和原始数组一样长的前缀异或和数组

int[] eor = new int[arr.length];

其中eor[i]表示原始数组 0 位置到 i 位置的异或和是多少,实现代码如下:

    eor[0] = arr[0];
    for (int i = 1; i < n; i++) {
      eor[i] = eor[i - 1] ^ arr[i];
    }

有了 eor 数组以后,对于任意 i 位置,0 到 i 区间的异或和就可以直接获取到了,接下来是枚举数组中任意两个位置 i 和 j 区间的异或和,由于

i ~ j 之间的异或和等于 eor[j] ^ eor[i-1](i > 0),所以

任何两个位置之间的异或和信息可以通过如下代码求得,其中 max 是全局异或和的最大值

    for (int i = 1; i < n; i++) {
      max = Math.max(max, eor[i]);
      for (int j = i; j < n; j++) {
        max = Math.max(max, eor[j] ^ eor[i - 1]);
      }
    }

完整代码如下

  public static int maxEor1(int[] arr, int n) {
    int[] eor = new int[arr.length];
    int max = arr[0];
    eor[0] = arr[0];
    for (int i = 1; i < n; i++) {
      eor[i] = eor[i - 1] ^ arr[i];
    }
    for (int i = 1; i < n; i++) {
      max = Math.max(max, eor[i]);
      for (int j = i; j < n; j++) {
        max = Math.max(max, eor[j] ^ eor[i - 1]);
      }
    }
    return max;
  }

整个算法复杂度是\(O(N^2)\),并不是最优解。

最优解

根据上述暴力解法,时间复杂度比较高的部分是:

当确定了 0 ~ i 位置的异或和以后,如何定位 0 ~ j 这个区间,使得 j ~ i 之间的异或和最大。

暴力解法使用的是遍历的方式,而最优解,可以使用前缀树进行加速匹配,关于前缀树的内容,可以参考:前缀树的设计和实现

以数组[11,1,15,10,13,4]为例,我们把其前缀异或和数组转换成二进制,结果如下(其中eor[i..j]表示i~j的异或和)

eor[0..0] = 1011

eor[0..1] = 1010

eor[0..2] = 0101

eor[0..3] = 1111

eor[0..4] = 0010

eor[0..5] = 0110

把这些前缀异或和加入前缀树,

img

接下来,对于任何一个eor[i](0~i的异或和)来说,进入前缀树以后,前缀树需要快速找到和其匹配的eor[j],使得i~j之间的异或和最大,规则就是最高位(符号位)期待一样,紧着高位要期待不一样的值

例如:

eor[2] = 0101

eor[2] 期待和它符号位一样为0的值,紧着高位(由于前面28都是0,所以不存在和它符号不一样的,只看最后4位,

img

通过这个贪心,就可以在\(O(1)\)时间复杂度直接得到结果。

说明:如果期待遇到 0 可是前缀树没有往 0 方向的路,那直接返回 1 即可,反之亦然。

完整代码如下

  public static int maxEor(int[] arr, int n) {
    int[] eor = new int[arr.length];
    int max = 0;
    eor[0] = arr[0];
    for (int i = 1; i < n; i++) {
      eor[i] = eor[i - 1] ^ arr[i];
    }
    Trie trie = new Trie(eor);
    trie.add(eor[0]);
    for (int i = 1; i < n; i++) {
      max = Math.max(max, trie.get(eor[i]));
    }
    return max;
  }

  public static class Trie {
    public Node head;

    public Trie(int[] arr) {
      head = new Node();
      for (int eor : arr) {
        add(eor);
      }
    }

    public void add(int num) {
      Node cur = head;
      for (int bit = 31; bit >= 0; bit--) {
        int i = ((num >>> bit) & 1);
        if (cur.next[i] == null) {
          cur.next[i] = new Node();
        }
        cur = cur.next[i];
      }
    }

    public int get(int eor) {
      int expect = 0;
      Node cur = head;
      for (int bit = 31; bit >= 0; bit--) {
        // 符号位期待一样的
        // 非符号位期待相反的
        int expectBit = bit == 31 ? ((eor >>> bit) & 1) : (eor >>> bit & 1 ^ 1);
        if (cur.next[expectBit] != null) {
          expect = ((expectBit << bit) | expect);
          cur = cur.next[expectBit];
        } else {
          expectBit = (expectBit ^ 1);
          cur = cur.next[expectBit];
          expect = ((expectBit << bit) | expect);
        }
      }
      return expect ^ eor;
    }
  }

  public static class Node {
    public Node[] next = new Node[2];
  }

整个算法时间复杂度\(O(N)\),最优解。

更多

算法和数据结构笔记

与子数组的最大异或和问题相似的内容:

子数组的最大异或和问题

子数组的最大异或和问题 作者:Grey 原文地址: 博客园:子数组的最大异或和问题 CSDN:子数组的最大异或和问题 题目描述 数组中所有数都异或起来的结果,叫做异或和。给定一个数组 arr,其中可能有正、有负,有零,返回 arr 的最大子数组异或和 题目链接见:牛客-子数组的最大异或和 暴力解 枚

Codechef - Longest AND Subarray(位运算)

题目大意 给定一个正整数N,其序列为[1, 2, 3, ..., N],找到一个长度最大的连续子列,使得其所有元素取与运算的结果为正(最终输出只需要输出最大长度即可)。 思路 刚开始可能并不好注意到,可以举一些小的样例来找规律。比如2:1 & 2 = 0,不满足条件,所以只能取长度为1的数组[1]或

C#堆排序算法

前言 堆排序是一种高效的排序算法,基于二叉堆数据结构实现。它具有稳定性、时间复杂度为O(nlogn)和空间复杂度为O(1)的特点。 堆排序实现原理 构建最大堆:将待排序数组构建成一个最大堆,即满足父节点大于等于子节点的特性。 将堆顶元素与最后一个元素交换:将最大堆的堆顶元素与堆中的最后一个元素交换位

最大值减去最小值小于或等于 num 的子数组数量问题

最大值减去最小值小于或等于 num 的子数组数量问题 作者:Grey 原文地址: 博客园:最大值减去最小值小于或等于 num 的子数组数量问题 CSDN:最大值减去最小值小于或等于 num 的子数组数量问题 题目描述 给定数组 arr 和整数 num,共返回有多少个子数组满足如下情况: max(ar

C#快速排序算法

快速排序实现原理 快速排序(Quick Sort)是一种常用的排序算法,它基于分治的思想,通过将一个无序的序列分割成两个子序列,并递归地对子序列进行排序,最终完成整个序列的排序。 其基本思路如下: 选择数组中的一个元素作为基准(pivot)。 将数组中小于等于基准的元素放在基准的左边,将大于基准的元

6.2 Sunday搜索内存特征

Sunday 算法是一种字符串搜索算法,由`Daniel M.Sunday`于1990年开发,该算法用于在较长的字符串中查找子字符串的位置。算法通过将要搜索的模式的字符与要搜索的字符串的字符进行比较,从模式的最左侧位置开始。如果发现不匹配,则算法将模式向右`滑动`一定数量的位置。这个数字是由当前文本...

leetcode 二叉树的最小深度

给定一个二叉树,找出其最小深度。 最小深度是从根节点到最近叶子节点的最短路径上的节点数量。 说明:叶子节点是指没有子节点的节点。 示例 1: 输入:root = [3,9,20,null,null,15,7] 输出:2 示例 2: 输入:root = [2,null,3,null,4,null,5,

二叉树的最小深度问题

二叉树的最小深度问题 作者:Grey 原文地址: 博客园:二叉树的最小深度问题 CSDN:二叉树的最小深度问题 题目描述 给定一个二叉树,找出其最小深度。 最小深度是从根节点到最近叶子节点的最短路径上的节点数量。 说明:叶子节点是指没有子节点的节点。 题目链接见:LeetCode 111. Mini

【前端动画】—— 再看tweenJS

16开始接触前端,一直对一个问题特别感兴趣,那就是js动画,也就是从那时起开始探究动画的各种表现形式,也是那个时候开始意识到编程这块东西最终考验的就是抽象和逻辑,而这一切完全是数学里边的东西。最早接触tweenJS是去年还是前年来着有点忘了,不过当时有点不大看得懂,勉勉强强算是过了一遍,不过有了这个

14.3 Socket 字符串分块传输

首先为什么要实行分块传输字符串,一般而言`Socket`套接字最长发送的字节数为`8192`字节,如果发送的字节超出了此范围则后续部分会被自动截断,此时将字符串进行分块传输将显得格外重要,分块传输的关键在于封装实现一个字符串切割函数,将特定缓冲区内的字串动态切割成一个个小的子块,当切割结束后会得到该数据块的个数,此时通过套接字将个数发送至服务端此时服务端在依次循环接收数据包直到接收完所有数据包之后