英文:
How to improve the time complexity of this algorithm that has nested loops?
问题
public static List<Integer> kthElement(int k, List<Integer> T, List<Integer> E) {
List<Integer> kthElements = new ArrayList<>();
int maxIdx = -1; // Store the index of the last valid element for each expiration time
int elementsFilled = 0; // Count of elements that have entered the container
// Iterate through the waiting times T and find the last valid element index for each expiration time E
for (int j = 0; j < T.size(); j++) {
while (maxIdx + 1 < E.size() && T.get(j) >= E.get(maxIdx + 1)) {
maxIdx++;
elementsFilled++;
if (elementsFilled >= k) {
break;
}
}
if (elementsFilled >= k) {
break;
}
}
// Now the maxIdx represents the last valid element index for all remaining expiration times
while (maxIdx < E.size() - 1) {
maxIdx++;
kthElements.add(maxIdx + 1); // Add 1 to convert from 0-based index to 1-based position
}
// Fill the remaining expiration times with 0
while (kthElements.size() < E.size()) {
kthElements.add(0);
}
return kthElements;
}
Note: The given code and the suggested optimized version assume that the input lists T
and E
are sorted in non-decreasing order. This is crucial for the correct functioning of the algorithm. The optimized version reduces the time complexity to O(T + E) by eliminating the nested loops and unnecessary checks.
英文:
Input:
Given an array of integers T = [t0, t1, t2, ... tk]
representing a row where each element is the maximum waiting time. And an array E = [e0, e1, e2, ... ei]
representing all possible expiration times. Finally, an integer K
is given which is the maximum size of a container.
Problem:
For each expiration time E
, it is necessary to obtain the position of the last element T
that was able to enter the container of size K, and each waiting time in T
must be greater than or equal to the expiration time E
to be able to enter the container.
Example Case 1:
Input:
K = 2; T = [1, 4, 4, 3, 1, 2, 6]; E = [1, 2, 3, 4, 5, 6, 7]
Output:
Kth = [2, 3, 3, 3, 0, 0, 0]
Explanation:
In E[0]
, the expiration time is 1, so the 2nd in row T
will be the last to enter the container, so Kth[0] = 2nd
;
In E[1]
, the expiration time is 2, so the 3rd in row T
will be the last to enter the container since the 1st element has expired, so Kth[1] = 3rd
;
In E[2]
, the expiration time is 3, so the 3rd in row T
will be the last to enter the container since the 1st element has expired, so Kth[2] = 3rd
;
In E[3]
, the expiration time is 4, so the 3rd in row T
will be the last to enter the container since the 1st element has expired, so Kth[3] = 3rd
;
In E[4]
, the expiration time is 5, in this case almost all elements of T
except the last one have expired, however, as it was not possible to complete the container, it must return position 0, therefore Kth[4] = 0
;
And so on for E[5]
and E[6]
.
Here is the code I've been able to come up with but it runs in O(E * T) time which is too slow for the performance constraints:
public static List<Integer> kthElement(int k, List<Integer> T, List<Integer> E) {
List<Integer> kthElements = new ArrayList<>();
for (int i = 0; i < E.size(); i++) {
System.out.println(E.get(i));
int currentElement = 0;
int elementsFilled = 0;
for (int j = 0; j < T.size(); j++) {
if (elementsFilled >= k || k > T.size()) {
break;
}
if (T.get(j) >= E.get(i)) {
elementsFilled++;
currentElement = j + 1;
}
}
if (elementsFilled >= k) {
kthElements.add(currentElement);
} else {
kthElements.add(0);
}
}
return kthElements;
}
How can I improve the performance of this algorithm?
答案1
得分: 1
我认为你可以轻松地在O(E + T)的时间复杂度内完成这个任务,而不是O(E x T)。
这个循环非常简单,乍看之下可能会认为它的时间复杂度是O(E x T),因为有嵌套循环,但内部循环从未在外部循环内部重置,因此实际上是O(E + T)。
在下面的代码中,我将假设 E < 65536。
List<Integer> out = new ArrayList<>();
int inPos = 0;
int kCnt = 0;
int last = 0;
int[] kTracker = new int[65536];
for (int i = 0; i < E.size(); i++)
{
// 移除过期元素
kCnt -= kTracker[i];
// 填充容器
while (kCnt < K && inPos < T.length)
{
int exp = i + T.get(inPos);
if (exp < kTracker.length)
{
// 如果 exp > E.max,则不需要跟踪,因为它不会影响输出。
kTracker[exp]++;
}
last = inPos;
kCnt++;
inPos++;
}
// 记录输出
out.add(last);
}
如果对 E.max 没有保证,但对 T.max 有保证,并且受到内存限制,那么我们可以使用滚动数组来实现。
例如,如果 T.max = 10,我们可以使用以下代码来替换上面的代码片段...
// 创建滚动 kTracker
int kTrackerNow = 0;
int[] kTracker = new int[10];
...
// 过期元素
kCnt -= kTracker[kTrackerNow];
kTracker[kTrackerNow] = 0;
// 切换到下一个时间段
kTrackerNow = (kTrackerNow + 1) % kTracker.length;
// 跟踪 T[inPos] 的新元素
kTracker[(kTrackerNow + T[inPos]) % kTracker.length]++;
最后,如果我们对输入没有任何保证,我们可以使用 HashMap<> 来替代跟踪数组。由于 HashMap 会为每个元素执行内存分配,所以它的速度会比前两种解决方案慢得多,但它可以处理各种类型的输入,没有限制。
List<Integer> out = new ArrayList<>();
int inPos = 0;
int kCnt = 0;
int last = 0;
Map<Integer, Integer> kTracker = new HashMap<>();
for (int i = 0; i < E.size(); i++)
{
// 移除过期元素
Integer removed = kTracker.remove(i);
if (removed != null)
{
kCnt -= removed;
}
// 填充容器
while (kCnt < K && inPos < T.length)
{
kTracker.put(i + T.get(inPos), kTracker.getOrDefault(i + T.get(inPos), 0) + 1);
last = inPos;
kCnt++;
inPos++;
}
// 记录输出
out.add(last);
}
英文:
I think you can easily do this in O(E + T) instead of O(E x T).
The loop is quite simple, and will appear to be O(E x T) at first glance because of the nesting, but the inner loop never gets reset within the outer loop so it is indeed O(E + T).
I will assume that E < 65536 in the following code.
List<Integer> out = new ArrayList<>();
int inPos = 0;
int kCnt = 0;
int last = 0;
int[] kTracker = new int[65536];
for (int i = 0; i < E.size(); i++)
{
// Remove Expired
kCnt -= kTracker[i];
// Fill Container
while (kCnt < K && inPos < T.length)
{
int exp = i + T.get(inPos);
if (exp < kTracker.length)
{
// don't bother tracking if > E.max, as it won't affect the output.
kTracker[exp]++;
}
last = inPos;
kCnt++;
inPos++;
}
// record output
out.add(last);
}
If there aren't guarantees on E.max, but there are guarantees on T.max, and we constrain memory, then we can implement a rolling array instead.
For example, if T.max = 10, we can use the below code to replace bits from the code above...
// Creating a rolling kTracker
int kTrackerNow = 0;
int[] kTracker = new int[10];
...
// Expiring elements
kCnt -= kTracker[kTrackerNow];
kTracker[kTrackerNow] = 0;
// Progressing to the next time period
kTrackerNow = (kTrackerNow + 1) % kTracker.length;
// Tracking a new item from T[inPos]
kTracker[(kTrackerNow + T[inPos]) % kTracker.length]++;
Finally, if we don't have any guarantees on the input, we can use HashMap<> to replace the tracking array. As the HashMap will perform memory allocation for each and every element, it will be far slower than the above 2 solutions, but it can deal with all kinds of input without restriction.
List<Integer> out = new ArrayList<>();
int inPos = 0;
int kCnt = 0;
int last = 0;
Map<Integer, Integer> kTracker = new HashMap<>();
for (int i = 0; i < E.size(); i++)
{
// Remove Expired
Integer removed = kTracker.remove(i);
if (removed != null)
{
kCnt -= removed;
}
// Fill Container
while (kCnt < K && inPos < T.length)
{
kTracker.put(i + T.get(inPos), kTracker.getOrDefault(i + T.get(inPos), 0) + 1);
last = inPos;
kCnt++;
inPos++;
}
// record output
out.add(last);
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论