每日一道C++题:

问题:给定一个序列a1,a2,…,an,如果存在i<j并且ai>aj,那么我们称之为逆序对,求逆序对的数目。

要求:输入第一行为n,表示序列长度,接下来的n行,第i+1行表示序列中的第i个数;输出所有逆序对总数。

  1. 暴力解法
    • 思路:通过两层循环,外层循环遍历序列中的每一个元素,内层循环遍历当前元素之后的所有元素。每次内层循环中,如果当前外层循环元素大于内层循环元素,就找到了一个逆序对,计数加1。
#include <iostream>
using namespace std;int main() {int n;cin >> n;int *arr = new int[n];for (int i = 0; i < n; i++) {cin >> arr[i];}int count = 0;for (int i = 0; i < n - 1; i++) {for (int j = i + 1; j < n; j++) {if (arr[i] > arr[j]) {count++;}}}cout << count << endl;delete[] arr;return 0;
}
  • 首先读取序列长度n,并动态分配一个大小为n的整数数组arr来存储序列元素。
  • 通过一个for循环读取序列中的每一个数并存储到数组中。
  • 使用两层嵌套的for循环来统计逆序对。外层循环从0到n - 2,内层循环从外层循环变量i的下一个位置到n - 1。如果arr[i] > arr[j],则找到一个逆序对,count加 1。
  • 最后输出逆序对的总数,并释放动态分配的数组内存。
  • 时间复杂度:O(n 2 ),因为有两层嵌套循环,对于长度为n的序列,总的操作次数是1+2+⋯+(n−1)= n(n−1)/2。
  • 空间复杂度:O(n),主要用于存储输入的序列。
  1. 归并排序优化解法
    • 思路:归并排序是一种分治算法,在合并两个有序子数组的过程中,可以统计跨越两个子数组的逆序对。将序列不断地分成两半,分别对左右两半进行排序,然后合并两个有序子数组。在合并过程中,如果左边子数组的当前元素大于右边子数组的当前元素,那么左边子数组中从当前位置到末尾的所有元素都与右边子数组的当前元素构成逆序对,记录逆序对的数量并进行合并操作。
#include <iostream>
using namespace std;// 合并两个有序数组并统计逆序对
long long merge(int arr[], int temp[], int left, int mid, int right) {int i = left; // 左子数组的起始索引int j = mid + 1; // 右子数组的起始索引int k = left; // 临时数组的起始索引long long inv_count = 0;while (i <= mid && j <= right) {if (arr[i] <= arr[j]) {temp[k++] = arr[i++];} else {temp[k++] = arr[j++];inv_count += (mid - i + 1);}}// 复制左子数组剩余元素while (i <= mid) {temp[k++] = arr[i++];}// 复制右子数组剩余元素while (j <= right) {temp[k++] = arr[j++];}// 将临时数组的内容复制回原数组for (i = left; i <= right; i++) {arr[i] = temp[i];}return inv_count;
}// 归并排序主函数并统计逆序对
long long mergeSort(int arr[], int temp[], int left, int right) {long long inv_count = 0;if (left < right) {int mid = (left + right) / 2;// 递归地对左半部分和右半部分进行排序inv_count += mergeSort(arr, temp, left, mid);inv_count += mergeSort(arr, temp, mid + 1, right);// 合并两个有序子数组并统计逆序对inv_count += merge(arr, temp, left, mid, right);}return inv_count;
}int main() {int n;cin >> n;int *arr = new int[n];int *temp = new int[n];for (int i = 0; i < n; i++) {cin >> arr[i];}long long result = mergeSort(arr, temp, 0, n - 1);cout << result << endl;delete[] arr;delete[] temp;return 0;
}
  • merge函数用于合并两个有序子数组并统计逆序对。left和right分别是当前要合并的子数组的左右边界,mid是中间位置。通过比较左右子数组的元素,将较小的元素放入临时数组temp中,并统计逆序对。
  • mergeSort函数是归并排序的主函数,通过递归地将数组分成两半并排序,然后调用merge函数合并并统计逆序对。
  • 在main函数中,读取序列长度n,动态分配两个数组arr和temp,arr用于存储输入序列,temp用于辅助合并操作。读取序列元素后,调用mergeSort函数进行排序并统计逆序对,最后输出结果并释放内存。
  • 时间复杂度:O(n log n),因为归并排序每次将数组分成两半,共需要(log n)层递归,每层递归中合并操作的时间复杂度是O(n)。
  • 空间复杂度:O(n),主要用于存储临时数组temp。
  1. 应用场景拓展
    -排序算法稳定性分析:逆序对的数量与排序算法的稳定性有关。例如,冒泡排序、插入排序等稳定排序算法在排序过程中可以通过计算逆序对的减少来分析其工作过程。对于不稳定排序算法,如快速排序,了解逆序对的分布有助于优化算法,使其在某些情况下更接近稳定排序的效果。
    • 数据相似性度量:在数据挖掘和机器学习领域,逆序对的概念可以用于衡量两个序列的相似性。如果两个序列的逆序对数量较少,说明它们在某种程度上具有相似的顺序结构。例如,在推荐系统中,可以通过比较用户对不同物品的排序(形成序列)之间的逆序对数量,来判断用户兴趣的相似性,进而为用户提供更精准的推荐。
  • 算法拓展
    • 树状数组解法:可以使用树状数组来解决逆序对问题。树状数组是一种支持高效单点更新和区间查询的数据结构。具体做法是,从序列的最后一个元素开始,依次将每个元素插入树状数组中,并查询当前元素之前小于它的元素个数,累加这些个数就得到逆序对的总数。树状数组解法的时间复杂度也是 (O(n \log n)),但在某些情况下,其实现可能比归并排序更简洁,并且可以灵活地支持动态更新序列并重新计算逆序对。
#include <iostream>
#include <vector>// 树状数组类
class FenwickTree {
private:std::vector<int> bit; // 树状数组int n; // 数组大小// 计算lowbitint lowbit(int x) {return x & (-x);}public:// 初始化树状数组FenwickTree(int size) : n(size), bit(size + 1, 0) {}// 更新操作void update(int idx, int val) {while (idx <= n) {bit[idx] += val;idx += lowbit(idx);}}// 查询操作int query(int idx) {int sum = 0;while (idx > 0) {sum += bit[idx];idx -= lowbit(idx);}return sum;}
};// 计算逆序对
long long countInversions(const std::vector<int>& arr) {int n = arr.size();// 离散化数组std::vector<int> sortedArr = arr;std::sort(sortedArr.begin(), sortedArr.end());std::vector<int> ranks(n);for (int i = 0; i < n; ++i) {ranks[i] = std::lower_bound(sortedArr.begin(), sortedArr.end(), arr[i]) - sortedArr.begin() + 1;}FenwickTree ft(n);long long invCount = 0;for (int i = n - 1; i >= 0; --i) {invCount += ft.query(ranks[i] - 1);ft.update(ranks[i], 1);}return invCount;
}int main() {int n;std::cin >> n;std::vector<int> arr(n);for (int i = 0; i < n; ++i) {std::cin >> arr[i];}std::cout << countInversions(arr) << std::endl;return 0;
}
  • FenwickTree 类实现了树状数组的基本操作,包括初始化、更新和查询。lowbit 函数用于计算一个数的二进制表示中最低位的 1 及其后面的 0 所构成的数值。

  • countInversions 函数用于计算逆序对。首先对输入数组进行离散化处理,将数组中的元素映射到 1 到 n 的范围内,以便树状数组处理。然后从数组末尾开始,依次将每个元素的排名插入树状数组,并查询小于该排名的元素个数,累加这些个数得到逆序对的总数。

    • 线段树解法:线段树同样可以用于解决逆序对问题。线段树是一种二叉树结构,每个节点代表一个区间。通过在线段树中插入元素并查询区间信息,可以统计逆序对。线段树的优点在于它能够更灵活地处理区间相关的操作,比如在序列动态变化时,能够高效地更新逆序对的统计结果。但线段树的实现相对复杂,需要对其原理有深入理解。
#include <iostream>
#include <vector>
#include <algorithm>// 线段树节点
struct SegmentTreeNode {int left, right;int count;SegmentTreeNode* leftChild;SegmentTreeNode* rightChild;SegmentTreeNode(int l, int r) : left(l), right(r), count(0), leftChild(nullptr), rightChild(nullptr) {}
};// 构建线段树
SegmentTreeNode* buildSegmentTree(int left, int right) {SegmentTreeNode* root = new SegmentTreeNode(left, right);if (left < right) {int mid = (left + right) / 2;root->leftChild = buildSegmentTree(left, mid);root->rightChild = buildSegmentTree(mid + 1, right);}return root;
}// 更新线段树
void updateSegmentTree(SegmentTreeNode* root, int idx) {if (root->left == root->right) {root->count++;return;}int mid = (root->left + root->right) / 2;if (idx <= mid) {updateSegmentTree(root->leftChild, idx);} else {updateSegmentTree(root->rightChild, idx);}root->count = root->leftChild->count + root->rightChild->count;
}// 查询线段树
int querySegmentTree(SegmentTreeNode* root, int left, int right) {if (root->left >= left && root->right <= right) {return root->count;}int mid = (root->left + root->right) / 2;if (right <= mid) {return querySegmentTree(root->leftChild, left, right);} else if (left > mid) {return querySegmentTree(root->rightChild, left, right);} else {return querySegmentTree(root->leftChild, left, mid) + querySegmentTree(root->rightChild, mid + 1, right);}
}// 计算逆序对
long long countInversions(const std::vector<int>& arr) {int n = arr.size();// 离散化数组std::vector<int> sortedArr = arr;std::sort(sortedArr.begin(), sortedArr.end());std::vector<int> ranks(n);for (int i = 0; i < n; ++i) {ranks[i] = std::lower_bound(sortedArr.begin(), sortedArr.end(), arr[i]) - sortedArr.begin();}SegmentTreeNode* root = buildSegmentTree(0, n - 1);long long invCount = 0;for (int i = 0; i < n; ++i) {invCount += querySegmentTree(root, ranks[i] + 1, n - 1);updateSegmentTree(root, ranks[i]);}return invCount;
}int main() {int n;std::cin >> n;std::vector<int> arr(n);for (int i = 0; i < n; ++i) {std::cin >> arr[i];}std::cout << countInversions(arr) << std::endl;return 0;
}
  • SegmentTreeNode 结构体定义了线段树的节点,每个节点包含区间范围 left 和 right,以及该区间内元素的计数 count,还有左右子节点指针。
  • buildSegmentTree 函数用于构建线段树,递归地将区间划分为左右子区间,并创建相应的节点。
  • updateSegmentTree 函数用于更新线段树,当插入一个新元素时,根据元素的位置更新相应节点的计数。
  • querySegmentTree 函数用于查询线段树中指定区间内的元素计数。
  • countInversions 函数首先对输入数组进行离散化处理,然后从数组开头开始,依次将每个元素插入线段树,并查询大于该元素的元素个数,累加这些个数得到逆序对的总数。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/918717.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/918717.shtml
英文地址,请注明出处:http://en.pswp.cn/news/918717.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java—CompletableFuture 详解

参考&#xff1a; CompletableFuture原理与实践-外卖商家端API的异步化 - 美团技术团队 CompletableFuture 详解 | JavaGuide 1.CompletableFuture介绍 CompletableFuture是由Java 8引入的&#xff0c;在Java8之前我们一般通过Future实现异步。 Future用于表示异步计算的结…

大模型部署基础设施搭建 - 向量数据库milvus

一、docker方式安装参考官网&#xff1a;https://milvus.io/docs/zh/install_standalone-docker.md#Install-Milvus-in-Docker1.1 安装 curl -sfL https://raw.githubusercontent.com/milvus-io/milvus/master/scripts/standalone_embed.sh -o standalone_embed.shbash standal…

(25.08)Ubuntu20.04复现KISS-ICP

主页&#xff1a;https://github.com/PRBonn/kiss-icp?tabreadme-ov-file 仓库&#xff1a;https://github.com/PRBonn/kiss-icp.git 非 ROS 使用流程 1. 克隆仓库 git clone https://github.com/PRBonn/kiss-icp.git cd kiss-icp 2. 使用 micromamba 创建 Python 虚拟环…

linux 软硬链接详解

一、核心区别总览特性硬链接&#xff08;Hard Link&#xff09;软链接&#xff08;Symbolic Link&#xff09;本质直接指向文件的 inode&#xff08;数据块的入口地址&#xff09;指向文件的 路径名&#xff08;相当于快捷方式&#xff09;跨文件系统支持❌ 仅限同一文件系统✅…

基于SpringBoot+Vue的房屋匹配系统(WebSocket实时通讯、协同过滤算法、地图API、Echarts图形化分析)

&#x1f388;系统亮点&#xff1a;WebSocket实时通讯、协同过滤算法、地图API、Echarts图形化分析&#xff1b;一.系统开发工具与环境搭建1.系统设计开发工具后端使用Java编程语言的Spring boot框架 项目架构&#xff1a;B/S架构 运行环境&#xff1a;win10/win11、jdk17前端&…

第2节:多模态的核心问题(多模态大模型基础教程)

前言 本节课我们聚焦多模态大模型最核心的问题&#xff1a;文本、图像、语音这些“不同语言”的信息&#xff0c;是怎么被模型“翻译”并互相理解的&#xff1f;我们从“差异”入手&#xff0c;一步步搞懂其中的逻辑。 一、先搞懂&#xff1a;什么是“模态差异”&#xff1f; 生…

Java stream distinct findAny anyMatch实现 :DistinctOp、FindOp、MatchOp

DistinctOpsDistinctOps 是一个专门用于实现 Stream.distinct() 操作的工厂类。正如它的名字所示&#xff0c;它的核心职责就是创建能够去除流中重复元素的操作。distinct() 是一个有状态的中间操作 (stateful intermediate operation)&#xff0c;这意味着它通常需要看到所有元…

锁的基本介绍

锁 并发编程的一个最基本问题就是原子性地执行一系列指令。锁有助于直接解决这一问题。 锁的基本思想 锁就是一个变量。这个变量保存了锁在某一时刻的状态。它要么是可用的&#xff0c;表示没有线程持有锁&#xff0c;要么是被占用的&#xff0c;表示有线程持有锁&#xff0c;正…

【读代码】开源流式语音编码器SecoustiCodec

引言:从LLM到深度语义 在大型语言模型(LLM)驱动的语音交互时代,神经语音编解码器 (Neural Speech Codec) 扮演着至关重要的角色。它如同 LLM 的“耳朵”和“嘴巴”,负责将连续的语音波形转换为离散的、可供模型处理的 token,并将模型生成的 token 还原为自然的人声。 一…

P5967 [POI 2016] Korale 题解

P5967 [POI 2016] Korale 题目描述 有 nnn 个带标号的珠子&#xff0c;第 iii 个珠子的价值为 aia_iai​。 现在你可以选择若干个珠子组成项链&#xff08;也可以一个都不选&#xff09;&#xff0c;项链的价值为所有珠子的价值和。 给出所有可能的项链排序&#xff0c;先按…

SwiftUI 页面弹窗操作

SwiftUI 页面弹窗操作指南一、基础弹窗实现1. Alert 基础警告框2. ActionSheet 操作菜单3. Sheet 模态视图4. Popover 浮动视图二、高级自定义弹窗1. 自定义弹窗组件2. 使用自定义弹窗三、弹窗状态管理1. 使用环境对象管理弹窗2. 弹窗路由系统四、动画与过渡效果1. 自定义弹窗动…

OpenCV图像处理2:边界填充与平滑滤波实战

前面学了一些关于opencv图像处理的内容&#xff0c;现在继续。一 图像填充边界填充&#xff08;Border Padding&#xff09;​&#xff0c;即在图像四周添加指定宽度的像素区域。其核心函数是cv2.copyMakeBorder()&#xff0c;通过不同的填充方式&#xff08;borderType&#x…

imx6ull-驱动开发篇22——Linux 时间管理和内核定时器

目录 内核时间管理 系统节拍率 高/低节拍率的优缺点 jiffies 节拍数 时间绕回 时间转换函数 内核定时器 timer_list 结构体 定时器API函数 init_timer 函数 add_timer 函数 del_timer 函数 del_timer_sync 函数 mod_timer 函数 Linux 内核短延时函数 内核时间管…

路由器数据控制管理层面安全

数据层面&#xff1a;FPM Flexible Packet MatchingFPM是CisCOIOS新一代的ACL根据任意条件&#xff0c;无无状态的匹配数据包的头部负载&#xff0c;或者全部分析协议&#xff0c;更易于规则的创建用于替代传统ACL&#xff0c;对特定恶意流量的基础架构过滤无状态ipv4单播不支持…

Vue内置组件全解析:从入门到面试通关

文章目录Vue内置组件全解析&#xff1a;从入门到面试通关引言&#xff1a;为什么需要内置组件&#xff1f;一、Vue内置组件全景图二、核心内置组件详解1. <component> - 动态组件2. <transition> - 过渡动画3. <keep-alive> - 组件缓存4. <slot> - 内容…

VUE+SPRINGBOOT从0-1打造前后端-前后台系统-会议记录

在当今快节奏的工作环境中&#xff0c;会议记录是每个职场人士都必须要面对的任务。传统的手动记录方式不仅效率低下&#xff0c;而且容易遗漏重要信息。随着Web技术的发展&#xff0c;基于浏览器的实时语音转写技术为会议记录提供了全新的解决方案。本文将详细介绍如何利用Web…

WEB3——水龙头,如何获得开发用的测试币、 Sepolia 测试币?

注意&#xff1a; 有些水龙头渠道&#xff0c;要求以太坊币至少有0.01ETH,设有这个门槛&#xff0c;下面并不是所有渠道都能领取到测试币&#xff0c;有些可能对领取测试币有要求&#xff0c;如果想获得获取以太坊币的方法&#xff0c;可以看我其他的文章。 本文整理了多个免费…

C++调试革命:时间旅行调试实战指南

还在为C的悬垂指针、内存泄漏和并发竞态抓狂&#xff1f;让调试器学会“时光倒流” 凌晨三点&#xff0c;std::thread创建的六个线程中有一个突然吞掉了你的数据&#xff0c;valgrind只告诉你“Invalid read”&#xff0c;而时间旅行调试&#xff08;TTD&#xff09;​​ 能让你…

mysql8.0笔记

1.DDL数据定义语言 DDL是什么——————创建、修改、删除 数据库和表结构的命令。 基本语法 针对数据库的操作 -- 创建数据库 CREATE DATABASE 数据库名; -- 比如 CREATE DATABASE myschool; --查看所有数据库 SHOW DATABASES; --使用某个数据库 USE myschool; -- 删除数据库…

大模型微调【1】之入门

文章目录说明一 大模型微调技术1.1 微调基础1.2 量化概念1.3 高效微调方法LoRA&QLoRA1.4 LoRA VS QLoRA1.5 高效微调的应用场景二 主流微调工具2.1 unsloth2.2 LLama-Factory2.3 ms-SWIFT2.4 ColossalAI2.5 底层微调框架推荐2.6 模型性能评估框架EvalScope三 微调所需软硬件…