我刚刚完成了以下Codility Peaks问题。问题如下:
给出了由N个整数组成的非空零索引数组A. 峰值是一个比其邻居更大的数组元素。更确切地说,它是指数P,使得0 <0。 P&lt; N - 1,A [P - 1]&lt; A [P]和A [P]> A [P + 1]。 例如,以下数组A:
A[0] = 1
A[1] = 2
A[2] = 3
A[3] = 4
A[4] = 3
A[5] = 4
A[6] = 1
A[7] = 2
A[8] = 3
A[9] = 4
A[10] = 6
A[11] = 2
正好有三个峰:3,5,10。 我们希望将此数组划分为包含相同数量元素的块。更准确地说,我们想要选择一个能产生以下块的数字K: A [0],A [1],...,A [K - 1], A [K],A [K + 1],...,A [2K - 1], ... A [N - K],A [N - K + 1],...,A [N - 1]。 更重要的是,每个块应包含至少一个峰值。请注意,块的极端元素(例如A [K-1]或A [K])也可以是峰值,但前提是它们同时具有两个邻居(包括相邻块中的一个)。 目标是找到阵列A可以划分的最大块数。 数组A可按如下方式划分为块:
一个区块(1,2,3,4,3,4,1,2,3,4,6,2)。该块包含三个峰值。
两个区块(1,2,3,4,3,4)和(1,2,3,4,6,2)。每个街区都有一个高峰。
三个块(1,2,3,4),(3,4,1,2),(3,4,6,2)。每个街区都有一个高峰。
特别注意第一个区块(1,2,3,4)在A [3]处有一个峰值,因为A [2]&lt; A [3]&gt; A [4],即使A [4]在相邻的块中。 但是,阵列A不能分为四个块,(1,2,3),(4,3,4),(1,2,3)和(4,6,2),因为(1,2, 3)块不包含峰值。特别注意(4,3,4)块包含两个峰:A [3]和A [5]。 阵列A可以分成的最大块数是三个。
写一个函数: class Solution {public int solution(int [] A); } 在给定由N个整数组成的非空零索引数组A的情况下,返回A可以划分的最大块数。 如果A不能分成若干个块,则该函数应返回0。 例如,给定:
A[0] = 1
A[1] = 2
A[2] = 3
A[3] = 4
A[4] = 3
A[5] = 4
A[6] = 1
A[7] = 2
A[8] = 3
A[9] = 4
A[10] = 6
A[11] = 2
该函数应返回3,如上所述。 假设:
N是[1..100,000]范围内的整数; 数组A的每个元素都是[0..1,000,000,000]范围内的整数。
复杂度:
预期的最坏情况时间复杂度为O(N * log(log(N)))
预期的最坏情况空间复杂度为O(N),超出输入存储(不计入输入参数所需的存储空间)。
可以修改输入数组的元素。
所以我用什么来解决这个问题似乎是强力解决方案 - 从1..N
遍历每个组大小,并检查每个组是否至少有一个峰值。我试图解决这个问题的前15分钟,我试图找出一些更优化的方法,因为所需的复杂度是O(N * log(log(N)))。
这是我的“暴力”代码,通过所有测试,包括大型测试,得分为100/100:
public int solution(int[] A) {
int N = A.length;
ArrayList<Integer> peaks = new ArrayList<Integer>();
for(int i = 1; i < N-1; i++){
if(A[i] > A[i-1] && A[i] > A[i+1]) peaks.add(i);
}
for(int size = 1; size <= N; size++){
if(N % size != 0) continue;
int find = 0;
int groups = N/size;
boolean ok = true;
for(int peakIdx : peaks){
if(peakIdx/size > find){
ok = false;
break;
}
if(peakIdx/size == find) find++;
}
if(find != groups) ok = false;
if(ok) return groups;
}
return 0;
}
我的问题是我如何推断这实际上是O(N * log(log(N))),因为它对我来说并不是很明显,我很惊讶我通过了测试用例。我正在寻找最简单的复杂性证明草图,这将使我相信这个运行时。我假设log(log(N))因子意味着在每次迭代时通过平方根来减少问题,但我不知道这是如何适用于我的问题的。非常感谢您的帮助
答案 0 :(得分:5)
你是完全正确的:要获得日志日志性能,需要减少问题。
python [下面]中的n.log(log(n))解决方案。 Codility不再测试此问题的“性能”(!),但python解决方案的准确性得分为100%。
正如你已经猜测的那样: 外循环将为O(n),因为它正在测试每个块的大小是否是一个干净的除数 内部循环必须是O(log(log(n)))才能给出O(n log(log(n)))。
我们可以获得良好的内循环性能,因为我们只需要执行d(n),即n的除数。我们可以存储 peak-so-far 的前缀和,它使用问题规范允许的O(n)空间。检查每个'组'中是否出现峰值,然后使用组开始和结束索引进行O(1)查找操作。
遵循此逻辑,当候选块大小为3时,循环需要执行n / 3峰值检查。复杂度变为总和:n / a + n / b + ... + n / n,其中分母(a,b,...)是n的因子。
短篇小说: n.d(n)操作的复杂性是O(n.log(log(n)))。
更长的版本: 如果您正在进行Codility课程,您将从Lesson 8: Prime and composite numbers中记住,谐波数运算的总和将给出O(log(n))复杂度。我们有一个减少的集合,因为我们只关注因子分母。 Lesson 9: Sieve of Eratosthenes显示素数的倒数之和如何为O(log(log(n)))并声称'证明是非平凡的'。在这种情况下,Wikipedia告诉我们除数sigma(n)的总和有一个上限(参见Robin的不等式,大约是页面的一半)。
这完全回答了你的问题吗?关于如何改进我的python代码的建议也非常受欢迎!
def solution(data):
length = len(data)
# array ends can't be peaks, len < 3 must return 0
if len < 3:
return 0
peaks = [0] * length
# compute a list of 'peaks to the left' in O(n) time
for index in range(2, length):
peaks[index] = peaks[index - 1]
# check if there was a peak to the left, add it to the count
if data[index - 1] > data[index - 2] and data[index - 1] > data[index]:
peaks[index] += 1
# candidate is the block size we're going to test
for candidate in range(3, length + 1):
# skip if not a factor
if length % candidate != 0:
continue
# test at each point n / block
valid = True
index = candidate
while index != length:
# if no peak in this block, break
if peaks[index] == peaks[index - candidate]:
valid = False
break
index += candidate
# one additional check since peaks[length] is outside of array
if index == length and peaks[index - 1] == peaks[index - candidate]:
valid = False
if valid:
return length / candidate
return 0
<强>现金:强> @tmyklebu对他SO answer的主要荣誉给了我很多帮助。
答案 1 :(得分:0)
我认为算法的时间复杂度不是O(Nlog(logN))。
然而,它肯定比O(N ^ 2)小得多。这是因为你的内部循环只输入了k次,其中k是N的因子数。整数的因子数可以在这个链接中看到:http://www.cut-the-knot.org/blue/NumberOfFactors.shtml
我可能不准确,但从链接看来,
k ~ logN * logN * logN ...
此外,内环的复杂度为O(N),因为在最坏的情况下峰的数量可以是N / 2.
因此,在我看来,算法的复杂性最多为O(NlogN) ,但它必须足以清除所有测试用例。
答案 2 :(得分:0)
@radicality
至少有一点可以将第二个循环中的遍数优化为O(sqrt(N)) - 收集N的除数并仅迭代它们。
这将使你的算法不那么“蛮力”。
问题定义允许O(N)空间复杂度。你可以存储除数而不会违反这个条件。
答案 3 :(得分:0)
这是我基于前缀和的解决方案。希望对您有所帮助:
class Solution {
public int solution(int[] A) {
int n = A.length;
int result = 1;
if (n < 3)
return 0;
int[] prefixSums = new int[n];
for (int i = 1; i < n-1; i++)
if (A[i] > A[i-1] && A[i] > A[i+1])
prefixSums[i] = prefixSums[i-1] + 1;
else
prefixSums[i] = prefixSums[i-1];
prefixSums[n-1] = prefixSums[n-2];
if (prefixSums[n-1] <= 1)
return prefixSums[n-1];
for (int i = 2; i <= prefixSums[n-2]; i++) {
if (n % i != 0)
continue;
int prev = 0;
boolean containsPeak = true;
for (int j = n/i - 1; j < n; j += n/i) {
if (prefixSums[j] == prev) {
containsPeak = false;
break;
}
prev = prefixSums[j];
}
if (containsPeak)
result = i;
}
return result;
}
}
答案 4 :(得分:0)
def solution(A):
length = len(A)
if length <= 2:
return 0
peek_indexes = []
for index in range(1, length-1):
if A[index] > A[index - 1] and A[index] > A[index + 1]:
peek_indexes.append(index)
for block in range(3, int((length/2)+1)):
if length % block == 0:
index_to_check = 0
temp_blocks = 0
for peek_index in peek_indexes:
if peek_index >= index_to_check and peek_index < index_to_check + block:
temp_blocks += 1
index_to_check = index_to_check + block
if length/block == temp_blocks:
return temp_blocks
if len(peek_indexes) > 0:
return 1
else:
return 0
print(solution([1, 2, 3, 4, 3, 4, 1, 2, 3, 4, 6, 2, 1, 2, 5, 2]))
答案 5 :(得分:0)
我刚发现因素时, 然后只需在A中进行迭代并测试所有块数,以查看哪个块划分最大。
这是有100个代码(在Java中)
答案 6 :(得分:0)
一个复杂度为O(N * log(log(N())))的javascript解决方案。
function solution(A) {
let N = A.length;
if (N < 3) return 0;
let peaks = 0;
let peaksTillNow = [ 0 ];
let dividers = [];
for (let i = 1; i < N - 1; i++) {
if (A[i - 1] < A[i] && A[i] > A[i + 1]) peaks++;
peaksTillNow.push(peaks);
if (N % i === 0) dividers.push(i);
}
peaksTillNow.push(peaks);
if (peaks === 0) return 0;
let blocks;
let result = 1;
for (blocks of dividers) {
let K = N / blocks;
let prevPeaks = 0;
let OK = true;
for (let i = 1; i <= blocks; i++) {
if (peaksTillNow[i * K - 1] > prevPeaks) {
prevPeaks = peaksTillNow[i * K - 1];
} else {
OK = false;
break;
}
}
if (OK) result = blocks;
}
return result;
}
答案 7 :(得分:0)
使用 C# 代码的解决方案
public int GetPeaks(int[] InputArray)
{
List<int> lstPeaks = new List<int>();
lstPeaks.Add(0);
for (int Index = 1; Index < (InputArray.Length - 1); Index++)
{
if (InputArray[Index - 1] < InputArray[Index] && InputArray[Index] > InputArray[Index + 1])
{
lstPeaks.Add(1);
}
else
{
lstPeaks.Add(0);
}
}
lstPeaks.Add(0);
int totalEqBlocksWithPeaks = 0;
for (int factor = 1; factor <= InputArray.Length; factor++)
{
if (InputArray.Length % factor == 0)
{
int BlockLength = InputArray.Length / factor;
int BlockCount = factor;
bool isAllBlocksHasPeak = true;
for (int CountIndex = 1; CountIndex <= BlockCount; CountIndex++)
{
int BlockStartIndex = CountIndex == 1 ? 0 : (CountIndex - 1) * BlockLength;
int BlockEndIndex = (CountIndex * BlockLength) - 1;
if (!(lstPeaks.GetRange(BlockStartIndex, BlockLength).Sum() > 0))
{
isAllBlocksHasPeak = false;
}
}
if (isAllBlocksHasPeak)
totalEqBlocksWithPeaks++;
}
}
return totalEqBlocksWithPeaks;
}
答案 8 :(得分:-1)
我同意GnomeDePlume的回答......关于在提议的解决方案中寻找除数的部分是O(N),并且可以通过使用课程文本中提供的算法将其减少到O(sqrt(N))
所以只需添加,这是我使用Java解决所需复杂性问题的解决方案。
请注意,它拥有的代码比你的更多 - 一些清理(调试系统和注释)总是可行的: - )
public int solution(int[] A) {
int result = 0;
int N = A.length;
// mark accumulated peaks
int[] peaks = new int[N];
int count = 0;
for (int i = 1; i < N -1; i++) {
if (A[i-1] < A[i] && A[i+1] < A[i])
count++;
peaks[i] = count;
}
// set peaks count on last elem as it will be needed during div checks
peaks[N-1] = count;
// check count
if (count > 0) {
// if only one peak, will need the whole array
if (count == 1)
result = 1;
else {
// at this point (peaks > 1) we know at least the single group will satisfy the criteria
// so set result to 1, then check for bigger numbers of groups
result = 1;
// for each divisor of N, check if that number of groups work
Integer[] divisors = getDivisors(N);
// result will be at least 1 at this point
boolean candidate;
int divisor, startIdx, endIdx;
// check from top value to bottom - stop when one is found
// for div 1 we know num groups is 1, and we already know that is the minimum. No need to check.
// for div = N we know it's impossible, as all elements would have to be peaks (impossible by definition)
for (int i = divisors.length-2; i > 0; i--) {
candidate = true;
divisor = divisors[i];
for (int j = 0; j < N; j+= N/divisor) {
startIdx = (j == 0 ? j : j-1);
endIdx = j + N/divisor-1;
if (peaks[startIdx] == peaks[endIdx]) {
candidate = false;
break;
}
}
// if all groups had at least 1 peak, this is the result!
if (candidate) {
result = divisor;
break;
}
}
}
}
return result;
}
// returns ordered array of all divisors of N
private Integer[] getDivisors(int N) {
Set<Integer> set = new TreeSet<Integer>();
double sqrt = Math.sqrt(N);
int i = 1;
for (; i < sqrt; i++) {
if (N % i == 0) {
set.add(i);
set.add(N/i);
}
}
if (i * i == N)
set.add(i);
return set.toArray(new Integer[]{});
}
谢谢, DAVI