可见山峰对问题

题意描述

假设我给你一个数组代表一个环形的山,每个元素都代表一座山,每个座山上可以放烽火。
两座山峰相互看见的前提:

  1. 相邻的山必能相互看见烽火
  2. 如果不相邻的两座山峰,只要存在一条路,这条路上的任意一个值都不大于路两头的山峰中较小值,就认为他们两座山可以相互看见

预热阶段
假设数组中所有的值都不一样,你要求所有能看见山峰对的数量,要求时间复杂度O(1)

简单解法
以下结论是在数组中所有值都不一样的前提下提出的。

  1. 如果只有1个元素,那么有0对
  2. 如果有两座元素,那么有1对。
  3. 如果有i个元素,那么有2*i-3对。

前两个结论很容易得到,下面解释如何得到的第三个结论
如果能从小的看到大的,那么从大的也一定能找到小的,我们的设计思路就是让小的去找大的以便降低难度,假设有三座山及以上我们可以有下图。
在这里插入图片描述
次高(8)到最高(9)之间的山峰一定可以找到一对,然后对于加在最高和次高山峰之间的任意一座山峰而言无论顺时针还是逆时针都可以找到比该山峰高的山峰,所以是(n-2)2,两部分相加就是2n-3。

难度提升
数组中可能出现重复值,请给出一个O(N)的解法

解题思路
整体思路还是用小的找大的。大的不找小的。首先找到环上的最大值,然后从该位置开始将数据(附加一个计数器,表示该山峰出现了几次)逐个压入到一个从栈底到栈顶从大到小的单调栈,如果当前值大于栈顶元素,那么就结算栈顶元素(设共有n个相同的值压在了一起,如果n为1,那么总数加上2,否则是总数加上n个不同元素中取出2个元素的组合数+2*n),并将栈顶元素弹出,最后将当前值压入。这样做最后栈中肯定会剩余数据,对于 这些数据我们采取以下方案:

  1. 如果当前栈的大小大于等于3,对于栈顶元素仍然可以使用以上的公式:总数加上n个不同元素中取出2个元素的组合数+2*n
  2. 如果当前栈的大小等于2了,分为两种情况,如果第一种元素有两个或者以上的时候公式为:总数加上n个不同元素中取出2个元素的组合数 +2*n,如果第一种元素只有一个的话,那么公式为:总数加上n个不同元素中取出2个元素的组合数+n
  3. 如果是当前栈只有一个元素了,那么公式为:总数加上n个不同元素中取出2个元素的组合数

为什么要将最大值作为栈底?
因为这样可以保证在结算栈顶元素的时候,我们可以在顺时针方向一定有一个比栈顶元素大的值,保证了公式的正确性。
示例代码

#include <iostream>
#include <vector>
#include <stack>
#include <algorithm>
using namespace std;


class Pair {
public:
	Pair(int value)
		:m_times(1)
		, m_value(value)
	{
	}
	int m_value;
	int m_times;
};

int nextIndex(int size, int i)
{
	return i < (size - 1) ? (i + 1) : 0;
}

long getInternalSum(int n)
{
	return n == 1L ? 0L : (long)n * (long)(n - 1) / 2L;
}

long communication(const vector<int> &arr)
{
	if (arr.size() < 2)
	{
		return 0;
	}
	int res = 0;
	stack<Pair> pairStack;
	// 获取最大值角标
	int maxIndex = 0;
	for (int i = 0; i < arr.size(); i++)
	{
		maxIndex = arr[maxIndex] < arr[i] ? i : maxIndex;
	}
	pairStack.push(Pair(arr[maxIndex]));
	int it = nextIndex(arr.size(), maxIndex);
	
	for(int it = nextIndex(arr.size(), maxIndex); it != maxIndex; 
		it= nextIndex(arr.size(), it))
	{
		while (!pairStack.empty() && arr[it] > pairStack.top().m_value)
		{
			Pair temp = pairStack.top();
			pairStack.pop();
			res += getInternalSum(temp.m_times)+2* temp.m_times;
		}
		if (!pairStack.empty() && pairStack.top().m_value == arr[it])
		{
			pairStack.top().m_times++;
		}
		else
		{
			pairStack.push(Pair(arr[it]));
		}
	}
	while (!pairStack.empty())
	{
		int times = pairStack.top().m_times;
		pairStack.pop();
		res += getInternalSum(times);
		if (!pairStack.empty())
		{
			res += times;
			if (pairStack.size() > 1)
			{
				res += times;
			}
			else
			{
				res += pairStack.top().m_times > 1 ? times : 0;
			}
		}
	}
	return res;
}
int main(int argc, char ** argv)
{
	vector<int> arr{5,3,4,5,2,5,1};

	int res = communication(arr);
	cout << res << endl;
	system("pause");
	return EXIT_SUCCESS;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值