首先,对于相当广泛的问题感到抱歉。
我正在练习我的算法技巧。目前,我正在尝试找到this problem的解决方案。
你有N个计数器,最初设置为0,你有两个 他们可能采取的行动:
- 增加(X) - 计数器X增加1,
- 最大计数器 - 所有计数器都设置为任何计数器的最大值。
给出了M个整数的非空零索引数组A.这个 array表示连续操作:
- 如果A [K] = X,使得1≤X≤N,则操作K增加(X),
- 如果A [K] = N + 1,则操作K为最大计数器。
例如,给定整数N = 5且数组A使得:
A[0] = 3 A[1] = 4 A[2] = 4 A[3] = 6 A[4] = 1 A[5] = 4 A[6] = 4
每次连续操作后计数器的值为:
(0, 0, 1, 0, 0) (0, 0, 1, 1, 0) (0, 0, 1, 2, 0) (2, 2, 2, 2, 2) (3, 2, 2, 2, 2) (3, 2, 2, 3, 2) (3, 2, 2, 4, 2)
目标是在所有操作之后计算每个计数器的值。
假设给出以下声明:
struct Results { int * C; int L; };
写一个函数:
struct Results solution(int N, int A[], int M);
给定整数N和非空零索引数组A. 由M个整数组成,返回一个表示整数的序列 计数器的值。
序列应返回为:
- 结构结果(在C中)或
- 整数向量(在C ++中)或
- 记录结果(以Pascal表示)或
- 整数数组(使用任何其他编程语言)。
例如,给定:
A[0] = 3 A[1] = 4 A[2] = 4 A[3] = 6 A[4] = 1 A[5] = 4 A[6] = 4
该函数应返回
[3, 2, 2, 4, 2]
,如上所述。假设:
N和M是[1..100,000]范围内的整数;每个元素 数组A是[1..N + 1]范围内的整数。
复杂度:
- 预期的最坏情况时间复杂度为O(N + M);
- 预期的最坏情况空间复杂度为O(N),超出输入存储(不计入输入参数所需的存储空间)。
可以修改输入数组的元素。
以下是我在c中的尝试:
struct Results solution(int N, int A[], int M) {
struct Results result;
int i, j,
maxCounter = 0;
result.C = calloc(N, sizeof(int));
result.L = N;
for (i = 0; i < M; i++) {
if (A[i] <= N) {
result.C[A[i] - 1]++;
if (result.C[A[i] - 1] > maxCounter) {
maxCounter = result.C[A[i] - 1];
}
} else {
for (j = 0; j < N; j++) {
result.C[j] = maxCounter;
}
}
}
return result;
}
解决方案可以正常工作,但在某些性能测试中失败了。问题是由于嵌套循环,此算法为O(N*M)
。问题表明最坏情况下的复杂性应该是O(N+M)
。
我有一些关于如何不需要循环的想法,比如存储max counters
操作发生了多少次,并以某种方式找出将它加到计数器上的正确方法,但我无法实现它。 / p>
另一种可能性是找到一种方法,使用像struct.C
之类的东西一次设置memset
中的所有元素,但我认为这会是作弊。
有什么想法吗?
答案 0 :(得分:3)
您可以随时更新maxCounter值,而不是在实际需要时从头开始计算。有点类似的策略可能适用于将所有计数器设置为当前最大值的问题。如果您跟踪重置计数器的值,但只有在真正需要时才这样做,该怎么办?
答案 1 :(得分:3)
循环嵌套是问题的症状,而不是原因。这里的基本问题是可能存在 O (N
)非平凡MAX
操作,因此您无法承担超过 O ( 1
)他们的费用超过 O (1
)。无论您是否循环,测试或更新所有M
计数器的值都会花费 O (M
),因此如果您在一个MAX
中执行N
操作在正常情况下需要执行其中一个或两个的方式,然后总成本为 o (M
* MAX
)。
假设您在更新计数器时跟踪全局最大值,就像您一样,您可以以不立即处理所有计数器的方式实现MAX
。相反,您可以记录最近应用MAX
操作的时间,当时的全局最大值,以及上次应用MAX
操作时的每个计数器记录到那个柜台。
在这种情况下,每当您对特定计数器执行更新时,您都可以检查是否存在先需要应用的先前1
操作以及要应用的值。所有这些都是 O (1
),因此每次更新的费用仍为 O (MAX
)。 1
操作本身只需要更新两个标量,因此 O (M
)也是如此。因此,处理所有指令需要花费 O (MAX
)。最后,您必须通过计数器一次以应用任何剩余的未应用1
操作;每个N
计数器的成本 O (N
)。总费用: O (M
+ 1
)。
请注意,这表现出经典的空间与速度之间的权衡。这个问题的简单方法有 O (N
)内存开销,但在操作次数方面更为渐近复杂。上面提到的替代解决方案在操作数量上具有更好的渐近复杂性,但需要 O (MAX
)内存开销。
<强>更新强>
但正如@Quinchilion正确观察到的那样,你可以做得更好。考虑每个计数器的正确当前值是上一个MAX
操作设置的值加上自上一个MAX
以来在该计数器上执行的增量数。没有计数器的价值下降。因此,我们不需要明确跟踪MAX
操作的时间 - 每个计数器记录的最新值固有地指示是否仍然需要应用最后一个MAX
。如果它小于最新MAX
时记录的最大计数器值,则必须在增量之前应用@Override
public boolean onTouchEvent(MotionEvent event) {
if(isTouchable) {
int maskedAction = event.getActionMasked();
if (maskedAction == MotionEvent.ACTION_DOWN) {
this.setTextColor(resources.getColor(R.color.octane_orange));
initialClick = event.getX();
} else if (maskedAction == MotionEvent.ACTION_UP) {
this.setTextColor(defaultTextColor);
endingClick = event.getX();
checkIfSwipeOrClick(initialClick, endingClick, range);
} else if(maskedAction == MotionEvent.ACTION_CANCEL)
this.setTextColor(defaultTextColor);
}
return true;
}
;否则,不是。这可以与上述方法结合使用,以消除对辅助阵列的需求。
答案 2 :(得分:1)
我非常感谢@Quinchilion和@JohnBollinger对这个问题的全面分析。
我找到了一个通过正确性和性能测试的答案。在这里,完全评论:
struct Results solution(int N, int A[], int M) {
struct Results result;
int i,
maxCounter = 0,
lastMaxCounter = 0;
result.C = calloc(N, sizeof(int));
result.L = N;
/* One way or another, We have to iterate over all the counter
* operations input array, which gives us an O(M) time...
*/
for (i = 0; i < M; i++) {
/* There is a little gotcha here. The counter operations
* input array is 1-based, while our native C arrays are 0-based.
* So, in order check if we have a `increment` or a `max counters`
* operation, we have to consider the interval ]0, N].
*/
if (A[i] > 0 && A[i] <= N) {
/* This is an `increment` operation.
*
* First we need to check if there is no pending `max counter`
* operation in this very counter.
* This is done by checking if the value of current counter is
* **less than** the value of `lastMaxCounter`.
*/
if (result.C[A[i] - 1] < lastMaxCounter) {
/* If it is, means that we have to discard the counter's
* current value and increment it from the value of
* `lastMaxCounter`.
*/
result.C[A[i] - 1] = lastMaxCounter + 1;
} else {
/* If it ain't, we just increment this counter's value.
*/
result.C[A[i] - 1]++;
}
/* We also want to keep track of the maximum counter value.
*/
if (result.C[A[i] - 1] > maxCounter) {
maxCounter = result.C[A[i] - 1];
}
} else {
/* This is a `max counter` operation.
*
* What we need to do is buffer the current `maxCounter`
* in order to be able to update the counters later.
*/
lastMaxCounter = maxCounter;
}
}
/* At this point, if all counters have been incremented at least
* once after the last `max counter` operation, we are good to go.
*
* Since this is a rather pretentious assumption, we need to
* double check it.
*
* We iterate over all counters, checking if any of them is lower
* than the buffered `lastMaxCounter`. If it is, it means that no
* `increment` was performed on this counter after the last
* `max counter`, so this means that its value should be equal
* to `lastMaxCounter`.
*
* This is an O(N) operation.
*
* So, the algorithm's time complexity is O(N) + O(M) = O(N+M)
* and the space complexity is O(N).
*/
for (i = 0; i < N; i++) {
if (result.C[i] < lastMaxCounter) {
result.C[i] = lastMaxCounter;
}
}
return result;
}