【C++ 算法进阶】算法提升六

目录

子数组的最大异或和 (前缀树)

题目描述

求出一个数组中最大的子数组异或和

题目分析

这道题目和我们之前做的一道题很类似

求出一个数组中的最大累加和

最大累加和问题我们是使用动态规划来解决 而能够使用动态规划的根本原因则是 数字相加具有单调性 即两个大数相加得到的只可能是一个更大的数

但是异或和就不痛了 两个很大的数字异或的结果可能为一个很小的数

暴力解

当然 这道题我们还是可以借用 数组中最大子数组累加和 的思维来做

最大异或和肯定是以大数组中的某个数作为结尾计算出来的

所以说只要我们计算出以每个数作结尾的最大异或和 再比较一下 我们就能求出最终的答案

具体过程

假设我们是以5作为子数组的结尾 那么我们只需要计算出下面几种异或和即可求出最终答案

  • 0~5
  • 1~5
  • 2~5
  • 3~5
  • 4~5
  • 5~5

而要求出这些异或和 我们只需要计算出

  • 没有数的异或和
  • 0~0
  • 0~1
  • ...
  • 0~4

的异或和再让他们分别异或上0~5的异或和即可求出

所以说我们可以建立一张异或和表 (0~N) 时间复杂度为N

计算出以 i 位置的数为结尾的时间复杂度为N

计算出总的结果的时间复杂度为 N^2

优化解 (时间复杂度为N)

上面的暴力解的主要问题是 我们创建的表对于我们选择结果没有任何帮助 我们依旧需要遍历整张表(到i位置为止) 才能得出最终结果

所以说我们需要一个能帮助我们选择的数据结果 -- 前缀树

前缀数如何帮助我们进行选择

假设我们现在0~i 上的异或和为 1 1 0 1 0 1 0 1 (8位)

我们从8位开始选择异或 那么我们希望选择的异或和是 0 0 1 0 1 0 1 0

那么我们就可以从前缀树中按照我们的期望进行选择

当然 如果我们没有期望的话就只能选择另一种结果

如果有负数怎么办

是正数符号位为0 那么我们期望的结果为0 如果是负数的符号位为1 那么我们期望的结果为1 (尽量变成正数)

其他位只需要按照原来的选择即可


前缀树的设计

我们首先可以设计出前缀树的节点

cpp 复制代码
// 定义Trie节点,只包含两条路
class TrieNode {
public:
    TrieNode* children[2];  // 0 和 1 的两个分支

    TrieNode() {
        children[0] = children[1] = nullptr;  // 初始化两个分支为空
    }
};

接下来我们可以设计出两个函数

  • 一个add函数 将一个异或值插入到前缀树中
  • 一个maxeor函数 找出当前值的最大异或值
cpp 复制代码
    // 添加前缀异或值到Trie树中
    // move表示移动move位
    // num 表示插入前缀树的值 0或1
    void add(int lnNum) {
        TrieNode* cur = root;
        for (int move = 31; move >= 0; move--) {
            int num = (lnNum >> move) & 1;
            cur->children[num] = cur->children[num] == nullptr ? new TrieNode() : cur->children[num];
            cur = cur->children[num];
        }
    }
cpp 复制代码
    // 返回当前值lnNum与Trie树中的值的最大异或值
    // move 移动的位数
    // lnNum 参数 求出与他异或和的最大值
    // cur 当前节点
    // ans 返回的答案 32位整数
    int maxXor(int lnNum) {
        TrieNode* cur = root;
        int ans = 0;
        for (int move = 31; move >=0; move--)
        {
            // lnBit 当前的位数的值 0或1
            // Bset 期望走的路
            int lnBit = (lnNum >> move) & 1;
            int Best = lnBit ^ 1;

            if (cur->children[Best] == nullptr)
            {
                Best ^= 1;
            }

            ans |= (Best ^ lnBit) << move;
            cur = cur->children[Best];
        }

        return ans;
    }

代码详解

cpp 复制代码
using namespace std;

// 定义Trie节点,表示二进制数的两个可能路径
class TrieNode {
public:
    TrieNode* children[2];  // 0 和 1 的两个分支

    TrieNode() {
        children[0] = nullptr;
        children[1] = nullptr;
    }
};

// 定义Trie类
class Trie {
private:
    TrieNode* root;

public:
    // 构造函数,初始化根节点
    Trie() {
        root = new TrieNode();
    }

    // 添加前缀异或值到Trie树中
    // move表示移动move位
    // num 表示插入前缀树的值 0或1
    void add(int lnNum) {
        TrieNode* cur = root;
        for (int move = 31; move >= 0; move--) {
            int num = (lnNum >> move) & 1;
            cur->children[num] = cur->children[num] == nullptr ? new TrieNode() : cur->children[num];
            cur = cur->children[num];
        }
    }

    // 返回当前值lnNum与Trie树中的值的最大异或值
    // move 移动的位数
    // lnNum 参数 求出与他异或和的最大值
    // cur 当前节点
    // ans 返回的答案 32位整数
    int maxXor(int lnNum) {
        TrieNode* cur = root;
        int ans = 0;
        for (int move = 31; move >=0; move--)
        {
            // lnBit 当前的位数的值 0或1
            // Bset 期望走的路
            int lnBit = (lnNum >> move) & 1;
            int Best = lnBit ^ 1;

            if (cur->children[Best] == nullptr)
            {
                Best ^= 1;
            }

            ans |= (Best ^ lnBit) << move;
              cur = cur->children[Best];
        }

        return ans;
    }
};

// 主函数,计算子数组的最大异或和
int findMaxXorSubarray(const vector<int>& arr) {
    // x arr内数据的值拷贝
    // T 一个前缀树对象
    // eor 前面N个数的异或和
    // ans 子数组异或和的最大值 也就是我们要返回的值
    Trie T;
    int eor = 0;
    int ans = INT_MIN;
    for (auto x : arr) {
        T.add(eor);
        eor ^= x;
        ans = max(ans, T.maxXor(eor));
    }


    return ans;
}

int main() {
    vector<int> arr = { 3, 10, 5, 25, 2,};

    int lnMaxXor = findMaxXorSubarray(arr);
    cout << "最大子数组异或和为: " << lnMaxXor << endl;

    return 0;
}

两个数的最大异或和 (前缀树)

题目

本题为LC原题 题目如下

题目分析

有了上一题的经验我们要解决这题就简单多了

我们直接将数组中所有的数添加到前缀树中 之后遍历整个数组直接求出最大值即可 (时间复杂度N)

代码详解

cpp 复制代码
// 定义Trie节点,表示二进制数的两个可能路径
class TrieNode {
public:
    TrieNode* children[2];  // 0 和 1 的两个分支

    TrieNode() {
        children[0] = nullptr;
        children[1] = nullptr;
    }
};

// 定义Trie类
class Trie {
private:
    TrieNode* root;

public:
    // 构造函数,初始化根节点
    Trie() {
        root = new TrieNode();
    }

    // 添加前缀异或值到Trie树中
    // move表示移动move位
    // num 表示插入前缀树的值 0或1
    void add(int lnNum) {
        TrieNode* cur = root;
        for (int move = 31; move >= 0; move--) {
            int num = (lnNum >> move) & 1;
            cur->children[num] = cur->children[num] == nullptr ? new TrieNode() : cur->children[num];
            cur = cur->children[num];
        }
    }

    // 返回当前值lnNum与Trie树中的值的最大异或值
    // move 移动的位数
    // lnNum 参数 求出与他异或和的最大值
    // cur 当前节点
    // ans 返回的答案 32位整数
    int maxXor(int lnNum) {
        TrieNode* cur = root;
        int ans = 0;
        for (int move = 31; move >=0; move--)
        {
            // lnBit 当前的位数的值 0或1
            // Bset 期望走的路
            int lnBit = (lnNum >> move) & 1;
            int Best = lnBit ^ 1;

            if (cur->children[Best] == nullptr)
            {
                Best ^= 1;
            }

            ans |= (Best ^ lnBit) << move;
            cur = cur->children[Best];
        }

        return ans;
    }
};

class Solution {
public:
    // T TrieNode定义的
    // ans 最后的答案
    int findMaximumXOR(vector<int>& nums) {
        Trie T;
        int ans = 0;
        for (auto x : nums) {
            T.add(x);
        }

        for (auto x : nums) {
            ans = max(ans , T.maxXor(x));
        }

        return ans;
    }
};

尼姆博弈问题 (公式)

题目描述

给定一个数组 数组内有N个正整数 两个人开始拿数 每个人每次只能拿数组中的一个数 (至少为1 至多为这个数的大小)

假设两个人的选择都绝对理性且绝对正确

现在给你一个数组 要求你返回先手赢还是后手赢

题目分析

这里直接给出公式

所有数组的异或和如果为0 则后手赢

如果异或和为0则先手赢


本体的本质是 先手要让后手每次拿到数时 异或和都变为0

而当一开始异或和不为0时 我们就能很轻松的做到这个效果 所以说如果所有数组的异或和不为0 则先手赢

代码详解

cpp 复制代码
int eor = 0;
int ans = 0;
for (auto x : arr) {
   ans ^= x;
}

return ans == 0;
相关推荐
明明真系叻2 天前
第二十六周机器学习笔记:PINN求正反解求PDE文献阅读——正问题
人工智能·笔记·深度学习·机器学习·1024程序员节
希忘auto5 天前
详解Redis的常用命令
redis·1024程序员节
yaosheng_VALVE5 天前
探究全金属硬密封蝶阀的奥秘-耀圣控制
运维·eclipse·自动化·pyqt·1024程序员节
dami_king5 天前
SSH特性|组成|SSH是什么?
运维·ssh·1024程序员节
一个通信老学姐10 天前
专业125+总分400+南京理工大学818考研经验南理工电子信息与通信工程,真题,大纲,参考书。
考研·信息与通信·信号处理·1024程序员节
sheng12345678rui10 天前
mfc140.dll文件缺失的修复方法分享,全面分析mfc140.dll的几种解决方法
游戏·电脑·dll文件·dll修复工具·1024程序员节
huipeng92611 天前
第十章 类和对象(二)
java·开发语言·学习·1024程序员节
earthzhang202111 天前
《深入浅出HTTPS》读书笔记(19):密钥
开发语言·网络协议·算法·https·1024程序员节
爱吃生蚝的于勒12 天前
计算机基础 原码反码补码问题
经验分享·笔记·计算机网络·其他·1024程序员节
earthzhang202112 天前
《深入浅出HTTPS》读书笔记(20):口令和PEB算法
开发语言·网络协议·算法·https·1024程序员节