【回溯+剪枝】找出所有子集的异或总和再求和 全排列Ⅱ

news/2025/2/1 2:22:15 标签: 剪枝, 深度优先, 算法, c++

文章目录

  • 1863. 找出所有子集的异或总和再求和
  • 解题思路:子集问题解法(回溯 + 剪枝
  • 47. 全排列 II
  • 解题思路:排序 + 回溯 + 剪枝

在这里插入图片描述

1863. 找出所有子集的异或总和再求和

1863. 找出所有子集的异或总和再求和

一个数组的 异或总和 定义为数组中所有元素按位 XOR 的结果;如果数组为 ,则异或总和为 0

  • 例如,数组 [2,5,6]异或总和2 XOR 5 XOR 6 = 1

给你一个数组 nums ,请你求出 nums 中每个 子集异或总和 ,计算并返回这些值相加之

注意: 在本题中,元素 相同 的不同子集应 多次 计数。

数组 a 是数组 b 的一个 子集 的前提条件是:从 b 删除几个(也可能不删除)元素能够得到 a

示例 1:

输入:nums = [1,3]
输出:6
解释:[1,3] 共有 4 个子集:
- 空子集的异或总和是 0 。
- [1] 的异或总和为 1 。
- [3] 的异或总和为 3 。
- [1,3] 的异或总和为 1 XOR 3 = 2 。
0 + 1 + 3 + 2 = 6

示例 2:

输入:nums = [5,1,6]
输出:28
解释:[5,1,6] 共有 8 个子集:
- 空子集的异或总和是 0 。
- [5] 的异或总和为 5 。
- [1] 的异或总和为 1 。
- [6] 的异或总和为 6 。
- [5,1] 的异或总和为 5 XOR 1 = 4 。
- [5,6] 的异或总和为 5 XOR 6 = 3 。
- [1,6] 的异或总和为 1 XOR 6 = 7 。
- [5,1,6] 的异或总和为 5 XOR 1 XOR 6 = 2 。
0 + 5 + 1 + 6 + 4 + 3 + 7 + 2 = 28

示例 3:

输入:nums = [3,4,5,6,7,8]
输出:480
解释:每个子集的全部异或总和值之和为 480 。

提示:

  • 1 <= nums.length <= 12
  • 1 <= nums[i] <= 20

解题思路:子集问题解法(回溯 + 剪枝

​ 这道题其实变相在考察子集问题,因为它要求的就是将所有的子集的异或结果累加起来,那么我们只需要像求解子集问题时候一样,求出每个子集序列,然后算出它的异或结果,累加到一个全局变量 sum 上去,最后返回 sum 即可!

​ 只不过我们其实可以不用每次得到一个子集序列后再去遍历子集序列求异或结果,这样子时间复杂度是比较高的,我们可以用一个变量 path 记录下路径上已经遍历到的元素的异或结果,然后让其再异或上当前的元素,得到就是当前子集的异或结果,最后将其累加到 sum 变量上即可!仅仅用一个变量就能得到这个效果,只不过我们需要注意的是因为我们要列举出其它的同层路径,所以回溯的时候需要将临时变量 path 恢复到原来的样子,只需要让其再次异或上当前元素即可做到!

​ 剩下的其实细节和子集问题都是一样的,具体可以参考子集问题的笔记!

class Solution {
private:
    int path = 0; // 存放当前路径的异或结果
    int sum = 0;  // 结果集,存放所有异或结果的和
public:
    int subsetXORSum(vector<int>& nums) {
        dfs(nums, 0);
        return sum;
    }

    void dfs(vector<int>& nums, int index)
    {
        // 递归函数出口(其实也可以不写,因为下面的循环已经限制了在数组范围内了
        if(index >= nums.size())
            return;
        
        for(int i = index; i < nums.size(); ++i)
        {
            // 处理当前结果
            path ^= nums[i];
            sum += path;

            // 递归处理其它结果
            dfs(nums, i + 1);

            // 回溯处理
            path ^= nums[i];
        }
    }
};

47. 全排列 II

47. 全排列 II

​ 给定一个可包含重复数字的序列 nums按任意顺序 返回所有不重复的全排列。

示例 1:

输入:nums = [1,1,2]
输出:
[[1,1,2],
 [1,2,1],
 [2,1,1]]

示例 2:

输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

提示:

  • 1 <= nums.length <= 8
  • -10 <= nums[i] <= 10

解题思路:排序 + 回溯 + 剪枝

​ 还是一样,对于全排列问题,我们使用的是回溯也就是深度优先搜索方法遍历整棵决策树,最后叶子节点就是我们需要的结果,大体的思路是一样的,这里就不再细讲,具体可以参考 46. 全排列 的解题笔记!

​ 但是与 46. 全排列 不同的是,这道题给定的数字序列,是可包含重复元素的,也就是说决策树中可能会出现相同的子树,也就是有重复的结果出现,如下图所示:

在这里插入图片描述

​ 所以我们必须做点措施,防止重复决策子树出现!(也可以用哈希表去重,但是比较占空间,这里不考虑)

​ 方法其实很简单,我们仔细一想,会出现重复的情况,其实就是因为有重复的元素,那么我们只要让重复的元素只遍历一次决策子树,而其它重复的元素不处理即可,所以我们考虑 先将原数组进行排序,这样子使得重复的元素是相邻的,然后我们只需要用已有的 used 数组多加一层判断即可!具体判断的细节如下所示:

  1. 对于 不同层的元素剪枝处理:
    • 如果上一层走过了该节点,那么就不需要再走了,也就是如果 used[i] == true 则直接跳过即可!
  2. 对于 同层的元素剪枝处理:
    • 如果相邻元素重复的话,那么当前元素其决策子树是和前面重复的,必须得进行剪枝操作,也就是此时 i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false 成立的话则直接跳过即可!

​ 上面的判断,相比起 46. 全排列 这道题来说只不过多了一个对同层元素的剪枝处理,如下图所示:

在这里插入图片描述

​ 其它细节都是一样的,这里不再赘述!

class Solution {
private:
    vector<vector<int>> ret; // 存放结果集
    vector<int> path;        // 存放当前路径中的元素
    bool used[9];            // 保存元素是否已经走过,true表示走过
public:
    vector<vector<int>> permuteUnique(vector<int>& nums) {
        // 首先对原数组进行排序,使得重复的元素是相邻的
        sort(nums.begin(), nums.end());

        // 然后交给递归函数去求解结果即可
        dfs(nums);
        return ret;
    }

    void dfs(vector<int>& nums)
    {
        // 递归函数出口
        if(path.size() == nums.size())
        {
            ret.push_back(path);
            return;
        }

        for(int i = 0; i < nums.size(); ++i)
        {
            // 如果上一层走过了该节点,那么就不需要再走了(注意这是对不同层的剪枝处理)
            // 进行剪枝操作,如果相邻元素重复的话,其排列结果是和前面重复的(注意这是对同层的剪枝处理)
            if(used[i] == true || (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false))
                continue;
            
            // 处理当前元素
            path.push_back(nums[i]);
            used[i] = true;

            // 递归处理该节点以下的路径
            dfs(nums);

            // 回溯处理
            used[i] = false;
            path.pop_back();
        }
    }
};

在这里插入图片描述


http://www.niftyadmin.cn/n/5838966.html

相关文章

【Linux】makefile、进度条实现

目标的坚定是性格中最必要的力量源泉之一&#xff0c;也是成功的利器之一。没有它&#xff0c;天才会在矛盾无定的迷径中徒劳无功。&#x1f493;&#x1f493;&#x1f493; 目录 ✨说在前面 &#x1f34b;知识点一&#xff1a;Linux项目自动化构建工具Makefike •&#x1f…

AI在自动化测试中的伦理挑战

在软件测试领域&#xff0c;人工智能&#xff08;AI&#xff09;已经不再是遥不可及的未来技术&#xff0c;而是正在深刻影响着测试过程的现实力量。尤其是在自动化测试领域&#xff0c;AI通过加速测试脚本生成、自动化缺陷检测、测试数据生成等功能&#xff0c;极大提升了测试…

【现代深度学习技术】深度学习计算 | 层和块

【作者主页】Francek Chen 【专栏介绍】 ⌈ ⌈ ⌈PyTorch深度学习 ⌋ ⌋ ⌋ 深度学习 (DL, Deep Learning) 特指基于深层神经网络模型和方法的机器学习。它是在统计机器学习、人工神经网络等算法模型基础上&#xff0c;结合当代大数据和大算力的发展而发展出来的。深度学习最重…

前端进阶:深度剖析预解析机制

一、预解析是什么&#xff1f; 在前端开发中&#xff0c;我们常常会遇到一些看似不符合常规逻辑的代码执行现象&#xff0c;比如为什么在变量声明之前访问它&#xff0c;得到的结果是undefined&#xff0c;而不是报错&#xff1f;为什么函数在声明之前就可以被调用&#xff1f…

商业航天更青睐哪些芯片

随着以SpaceX Starlink为代表的小卫星组网服务的快速崛起,使得航天探测器的设计思路,正在发生转变。 一直以来,传统航天器以可靠性为最高目标,尽可能降低风险,通常是大尺寸、重量、功率和成本(SWaP-C)的旗舰系统。 现在各国争相发布的小型卫星组网概念,则完全不同。以…

2025年1月30日(任意截面、自定义截面梁的设置)

Ansys 在ANSYS中&#xff0c;以下是这些术语的详细解释&#xff1a; Nodal Solution (节点解): Nodal Solution指的是在有限元分析中计算出的节点处的物理量解。通常包括节点的位移、反应力等信息。节点解是分析结果的基础&#xff0c;因为它们可以用来计算其他重要的物理量&a…

Git 版本控制:基础介绍与常用操作

目录 Git 的基本概念 Git 安装与配置 Git 常用命令与操作 1. 初始化本地仓库 2. 版本控制工作流程 3. 分支管理 4. 解决冲突 5. 回退和撤销 6. 查看提交日志 前言 在软件开发过程中&#xff0c;开发者常常需要在现有程序的基础上进行修改和扩展。但如果不加以管理&am…

java求职学习day22

MySQL 基础 &SQL 入门 1. 数据库的基本概念 1.1 什么是数据库 1. 数据库 (DataBase) 就是 存储 和 管理 数据的仓库 2. 其本质是一个文件系统, 还是以文件的方式,将数据保存在电脑上 1.2 为什么使用数据库 数据存储方式的比较 通过上面的比较 , 我们可以看出 , 使…