C++双指针算法:统计点对的数目

news2024/11/29 10:43:01

本周推荐阅读

C++二分算法:得到子序列的最少操作次数

本题其它解法

C++二分查找:统计点对的数目

题目

给你一个无向图,无向图由整数 n ,表示图中节点的数目,和 edges 组成,其中 edges[i] = [ui, vi] 表示 ui 和 vi 之间有一条无向边。同时给你一个代表查询的整数数组 queries 。
第 j 个查询的答案是满足如下条件的点对 (a, b) 的数目:
a < b
cnt 是与 a 或者 b 相连的边的数目,且 cnt 严格大于 queries[j] 。
请你返回一个数组 answers ,其中 answers.length == queries.length 且 answers[j] 是第 j 个查询的答案。
请注意,图中可能会有 多重边 。
示例 1:
输入:n = 4, edges = [[1,2],[2,4],[1,3],[2,3],[2,1]], queries = [2,3]
输出:[6,5]
在这里插入图片描述

解释:每个点对中,与至少一个点相连的边的数目如上图所示。
answers[0] = 6。所有的点对(a, b)中边数和都大于2,故有6个;
answers[1] = 5。所有的点对(a, b)中除了(3,4)边数等于3,其它点对边数和都大于3,故有5个。
示例 2:
输入:n = 5, edges = [[1,5],[1,5],[3,4],[2,5],[1,3],[5,1],[2,3],[2,5]], queries = [1,2,3,4,5]
输出:[10,10,9,8,6]
参数范围
2 <= n <= 2 * 104
1 <= edges.length <= 105
1 <= ui, vi <= n
ui != vi
1 <= queries.length <= 20
0 <= queries[j] < edges.length

分析

时间复杂度

每个查询的时间复杂度是O(n+m)。m是边数。

只优化了每个查询的第一步

m_vCounts[x]和m_vCounts[left]都严格大于iQue,x的取值范围是的左开右开空间(right,n)。
为了不重复计算,我们要确保left <=right,也就是:

if (right < left)
			{
				right = left;
			}

代码

核心代码

class Solution {
public:
	vector<int> countPairs(int n, vector<vector<int>>& edges, vector<int>& queries) {		
		m_iN = n;
		m_vNodeToCount.resize(n);
		for (auto& v : edges)
		{
			if (v[0] < v[1])
			{
				swap(v[0], v[1]);
			}
			v[0]--;
			v[1]--;			
			m_vNodeToCount[v[0]]++;
			m_vNodeToCount[v[1]]++;
			m_mMaskCount[Mask(v[0], v[1])]++;
		}
		m_vCounts = m_vNodeToCount;
		sort(m_vCounts.begin(), m_vCounts.end());
		vector<int> vRet;
		for (const auto& que : queries)
		{
			vRet.emplace_back(Query(que));
		}
		return vRet;
	}
	int Query(int iQue)const
	{
		int iNum = 0;// 包括a或b的边数大于iQue的数量,(a,b)和(b,a)会重复结算
		int right = m_iN - 1;
		for (auto left = 0 ; left < m_iN ; left++ )
		{		
			if (right < left)
			{
				right = left;
			}
			while ((right > left) && (m_vCounts[left] + m_vCounts[right] > iQue))
			{				
				right--;
			}
			iNum += m_iN - right - 1;
		}
		//扣处重复数量
		for (const auto& [iMask, iNum1] : m_mMaskCount)
		{
			auto [a, b] = Parse(iMask);
			const int tmp = m_vNodeToCount[a] + m_vNodeToCount[b] - iQue;
			if (tmp > 0 )
			{
				if (tmp <= iNum1)
				{
					iNum--;
				}
			}
		}	
		return iNum;
	}
	int Mask(int a, int b)
	{
		return a * m_iUnit + b;
	}
	std::pair<int,int> Parse(const int iMask)const
	{
		return std::make_pair(iMask / m_iUnit, iMask % m_iUnit);
	}
	const int m_iUnit = 1000 * 100;
	unordered_map<int, int> m_mMaskCount;
	vector<int> m_vNodeToCount;
	vector<int> m_vCounts;
	
	int m_iN;
};

测试用例

class Solution {
public:
vector countPairs(int n, vector<vector>& edges, vector& queries) {
m_iN = n;
m_vNodeToCount.resize(n);
for (auto& v : edges)
{
if (v[0] < v[1])
{
swap(v[0], v[1]);
}
v[0]–;
v[1]–;
m_vNodeToCount[v[0]]++;
m_vNodeToCount[v[1]]++;
m_mMaskCount[Mask(v[0], v[1])]++;
}
m_vCounts = m_vNodeToCount;
sort(m_vCounts.begin(), m_vCounts.end());
vector vRet;
for (const auto& que : queries)
{
vRet.emplace_back(Query(que));
}
return vRet;
}
int Query(int iQue)const
{
int iNum = 0;// 包括a或b的边数大于iQue的数量,(a,b)和(b,a)会重复结算
int right = m_iN - 1;
for (auto left = 0 ; left < m_iN ; left++ )
{
if (right < left)
{
right = left;
}
while ((right > left) && (m_vCounts[left] + m_vCounts[right] > iQue))
{
right–;
}
iNum += m_iN - right - 1;
}
//扣处重复数量
for (const auto& [iMask, iNum1] : m_mMaskCount)
{
auto [a, b] = Parse(iMask);
const int tmp = m_vNodeToCount[a] + m_vNodeToCount[b] - iQue;
if (tmp > 0 )
{
if (tmp <= iNum1)
{
iNum–;
}
}
}
return iNum;
}
int Mask(int a, int b)
{
return a * m_iUnit + b;
}
std::pair<int,int> Parse(const int iMask)const
{
return std::make_pair(iMask / m_iUnit, iMask % m_iUnit);
}
const int m_iUnit = 1000 * 100;
unordered_map<int, int> m_mMaskCount;
vector m_vNodeToCount;
vector m_vCounts;

int m_iN;

};
template
void Assert(const T& t1, const T& t2)
{
assert(t1 == t2);
}

template
void Assert(const vector& v1, const vector& v2)
{
if (v1.size() != v2.size())
{
assert(false);
return;
}
for (int i = 0; i < v1.size(); i++)
{
Assert(v1[i], v2[i]);
}
}

int main()
{
int n;
vector<vector> edges;
vector queries;
vector res;
{
n = 4;
edges = { {1,2},{2,4},{1,3},{2,3},{2,1} };
queries = { 2,3 };
Solution slu;
res = slu.countPairs(n, edges, queries);
Assert(res, vector{6, 5});
}
{
n = 5;
edges = { {1,5},{1,5},{3,4},{2,5},{1,3},{5,1},{2,3},{2,5} };
queries = { 1,2,3,4,5 };
Solution slu;
res = slu.countPairs(n, edges, queries);
Assert(res, vector{10, 10, 9, 8, 6});
}

//CConsole::Out(res);

}

小的优化

双指针先反向,再相同方向。其实同向就可以提前退出了。

if (right < left)
		{
			iNum += (m_iN - left-1) * (m_iN - left) / 2;
			break;
		}

class Solution {
public:
vector countPairs(int n, vector<vector>& edges, vector& queries) {
m_iN = n;
m_vNodeToCount.resize(n);
for (auto& v : edges)
{
if (v[0] < v[1])
{
swap(v[0], v[1]);
}
v[0]–;
v[1]–;
m_vNodeToCount[v[0]]++;
m_vNodeToCount[v[1]]++;
m_mMaskCount[Mask(v[0], v[1])]++;
}
m_vCounts = m_vNodeToCount;
sort(m_vCounts.begin(), m_vCounts.end());
vector vRet;
for (const auto& que : queries)
{
vRet.emplace_back(Query(que));
}
return vRet;
}
int Query(int iQue)const
{
int iNum = 0;// 包括a或b的边数大于iQue的数量,(a,b)和(b,a)会重复结算
int right = m_iN - 1;
for (auto left = 0 ; left < m_iN ; left++ )
{
if (right < left)
{
iNum += (m_iN - left-1) * (m_iN - left) / 2;
break;
}
while ((right > left) && (m_vCounts[left] + m_vCounts[right] > iQue))
{
right–;
}
iNum += m_iN - right - 1;
}
//扣处重复数量
for (const auto& [iMask, iNum1] : m_mMaskCount)
{
auto [a, b] = Parse(iMask);
const int tmp = m_vNodeToCount[a] + m_vNodeToCount[b] - iQue;
if (tmp > 0 )
{
if (tmp <= iNum1)
{
iNum–;
}
}
}
return iNum;
}
int Mask(int a, int b)
{
return a * m_iUnit + b;
}
std::pair<int,int> Parse(const int iMask)const
{
return std::make_pair(iMask / m_iUnit, iMask % m_iUnit);
}
const int m_iUnit = 1000 * 100;
unordered_map<int, int> m_mMaskCount;
vector m_vNodeToCount;
vector m_vCounts;
int m_iN;
};

扩展阅读

视频课程

有效学习:明确的目标 及时的反馈 拉伸区(难度合适),可以先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。
https://edu.csdn.net/course/detail/38771

如何你想快

速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程
https://edu.csdn.net/lecturer/6176

相关下载

想高屋建瓴的学习算法,请下载《喜缺全书算法册》doc版
https://download.csdn.net/download/he_zhidan/88348653

我想对大家说的话
闻缺陷则喜是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。
如果程序是一条龙,那算法就是他的是睛

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

IO和NIO的区别 BIO,NIO,AIO 有什么区别? Files的常用方法都有哪些?

文章目录 IO和NIO的区别BIO,NIO,AIO 有什么区别?Files的常用方法都有哪些&#xff1f; 今天来对java中的io, nio, bio, aio进行了解&#xff0c;有何区别。 IO和NIO的区别 NIO与IO区别 IO是面向流的&#xff0c;NIO是面向缓冲区的Java IO面向流意味着每次从流中读一个或多个字…

【开源】基于Vue.js的快乐贩卖馆管理系统

项目编号&#xff1a; S 064 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S064&#xff0c;文末获取源码。} 项目编号&#xff1a;S064&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 数据中心模块2.2 搞笑视频模块2.3 视…

Java核心知识点整理大全15-笔记

Java核心知识点整理大全-笔记_希斯奎的博客-CSDN博客 Java核心知识点整理大全2-笔记_希斯奎的博客-CSDN博客 Java核心知识点整理大全3-笔记_希斯奎的博客-CSDN博客 Java核心知识点整理大全4-笔记-CSDN博客 Java核心知识点整理大全5-笔记-CSDN博客 Java核心知识点整理大全6…

steam搬砖还能做吗?CSGO饰品未来走势如何?

steam/csgo搬砖项目真能月入过万吗&#xff1f;到底真的假的&#xff1f; 如何看待CSGO饰品市场的整体走向&#xff1f; 从整体来说&#xff0c;CSGO的饰品市场与规模肯定会持续不断的上升&#xff0c;大盘不会发生特别大的波动&#xff0c;目前处于稳定期&#xff01;&#x…

elasticsearc DSL查询文档

文章目录 DSL查询文档DSL查询分类全文检索查询使用场景基本语法示例 精准查询term查询range查询总结 地理坐标查询矩形范围查询附近查询 复合查询相关性算分算分函数查询1&#xff09;语法说明2&#xff09;示例3&#xff09;小结 布尔查询1&#xff09;语法示例&#xff1a;2&…

C语言公交车之谜(ZZULIOJ1232:公交车之谜)

题目描述 听说郑州紫荆山公园有英语口语角&#xff0c;还有很多外国人呢。为了和老外对上几句&#xff0c;这周六早晨birdfly拉上同伴早早的就坐上了72路公交从学校向紫荆山进发。一路上没事干&#xff0c;birdfly开始思考一个问题。 从学校到紫荆山公园共有n(1<n<20)站路…

LeetCode Hot100 33.搜索旋转排序数组

题目&#xff1a; 整数数组 nums 按升序排列&#xff0c;数组中的值 互不相同 。 在传递给函数之前&#xff0c;nums 在预先未知的某个下标 k&#xff08;0 < k < nums.length&#xff09;上进行了 旋转&#xff0c;使数组变为 [nums[k], nums[k1], ..., nums[n-1], nu…

Docker Swarm总结+基础、集群搭建维护、安全以及集群容灾(1/4)

博主介绍&#xff1a;Java领域优质创作者,博客之星城市赛道TOP20、专注于前端流行技术框架、Java后端技术领域、项目实战运维以及GIS地理信息领域。 &#x1f345;文末获取源码下载地址&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;&#x1f3fb;…

C#,《小白学程序》第二十课:大数的加法(BigInteger Add)

大数的&#xff08;加减乘除&#xff09;四则运算、阶乘运算。 乘法计算包括小学生算法、Karatsuba和Toom-Cook3算法。 重复了部分 19 课的代码。 1 文本格式 using System; using System.Linq; using System.Text; using System.Collections.Generic; /// <summary>…

网络视频播放卡顿原因分析

一、问题描述 某项目通过拉摄像机rtsp流转rtmp/http-flv/ws-flv的方案&#xff0c;使用户可以在网页中观看摄像机的视频画面。在 观看视频时偶发出现卡顿现象。 二、卡顿现象分析和解决 此问题涉及的原因较多&#xff0c;所以得考虑各环节的问题可能性&#xff0c;并根据现场实…

C语言盐水的故事(ZZULIOJ1214:盐水的故事)

题目描述 挂盐水的时候&#xff0c;如果滴起来有规律&#xff0c;先是滴一滴&#xff0c;停一下&#xff1b;然后滴二滴&#xff0c;停一 下&#xff1b;再滴三滴&#xff0c;停一下...&#xff0c;现在有一个问题&#xff1a;这瓶盐水一共有VUL毫升&#xff0c;每一滴是D毫升&…

黑马点评-Feed流的实现方案,基于推拉结合模式实现笔记推送

Feed流实现方案 我们关注了博主之后,当用户发布了动态后我们应该把这些数据推送给粉丝,关注推送也叫作Feed(投喂)流,通过无限下拉刷新获取新的信息 传统的模式内容检索: 粉丝需要主动通过搜索引擎或者是其他方式去查找想看的内容新型Feed流的效果: 系统分析用户到底想看什么,…

CSDN C4模拟题

《计算机常识》 进制转换 一、任务目标 理解二进制/八进制/十进制/十六进制的原理 掌握各种不同的进制间的转换方法 二、任务背景 进制转换是软件工程师的必备技能,也是C1阶段的计算机通识模块之一,实际开发中的多媒体数据采集、分割、压缩、编解转码、传输、纠错、合并等…

队列详解(C语言实现)

文章目录 写在前面1 队列的定义2 队列的初始化3 数据入队列4 数据出队列5 获取队头元素6 获取队尾元素7 获取队列元素个数8 判断队列是否为空8 队列的销毁 写在前面 本片文章详细介绍了另外两种存储逻辑关系为 “一对一” 的数据结构——栈和队列中的队列&#xff0c;并使用C语…

WorkPlus稳定服务助力行业千万用户,打造无界沟通协作平台

在企业移动数字化领域&#xff0c;WorkPlus以其十年如一日的研发实力和千万级用户案例&#xff0c;成为众多企业首选的移动数字化平台。究竟是什么样的力量支撑着WorkPlus在市场上占据如此重要的地位呢&#xff1f;接下来&#xff0c;让我们一起揭开WorkPlus的神秘面纱&#xf…

【开源】基于Vue.js的陕西非物质文化遗产网站

文末获取源码&#xff0c;项目编号&#xff1a; S 065 。 \color{red}{文末获取源码&#xff0c;项目编号&#xff1a;S065。} 文末获取源码&#xff0c;项目编号&#xff1a;S065。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 设计目标2.2 研究内容2.3 研究方法与…

网络唤醒原理浅析(Wake On LAN)

原理 将唤醒魔术包发送的被唤醒机器的网卡上&#xff0c;魔术包指AMD公司开发的唤醒数据包&#xff0c;具有远程唤醒的网卡都支持这个标准&#xff0c;用16进制表示如下&#xff1a; 6对“FF”前缀16次重复MAC地址,举个例子假如我的网卡MAC地址是&#xff1a;AA:BB:CC:DD:EE:…

现代 C++ 函数式编程指南

现代 C 函数式编程指南 什么是 柯里化 &#xff08;Curry&#xff09;什么是 部分应用 &#xff08;Partial Application&#xff09; 二元函数 &#xff08;Partial Application&#xff09;参数排序 &#xff08;Partial Application&#xff09; 应用场景 计算碳衰减周期求年…

Shell脚本:Linux Shell脚本学习指南(第二部分Shell编程)三

第二部分&#xff1a;Shell编程&#xff08;三&#xff09; 二十一、Shell declare和typeset命令&#xff1a;设置变量属性 declare 和 typeset 都是 Shell 内建命令&#xff0c;它们的用法相同&#xff0c;都用来设置变量的属性。不过 typeset 已经被弃用了&#xff0c;建议…

MySql之索引,视图,事务以及存储过程举例详解

一.数据准备 数据准备可参考下面的链接中的数据准备步骤 MySql之内连接&#xff0c;外连接&#xff0c;左连接&#xff0c;右连接以及子查询举例详解-CSDN博客 &#xff08;如有问题可在评论区留言&#xff09; 二.存储过程 1.定义 存储过程 PROCEDURE &#xff0c;也翻译…