如何改进具有嵌套循环的此算法的时间复杂度?

huangapple go评论83阅读模式
英文:

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&lt;Integer&gt; kthElement(int k, List&lt;Integer&gt; T, List&lt;Integer&gt; E) {
	List&lt;Integer&gt; kthElements = new ArrayList&lt;&gt;();

	for (int i = 0; i &lt; E.size(); i++) {
		System.out.println(E.get(i));

		int currentElement = 0;
		int elementsFilled = 0;

		for (int j = 0; j &lt; T.size(); j++) {
			if (elementsFilled &gt;= k || k &gt; T.size()) {
				break;
			}

			if (T.get(j) &gt;= E.get(i)) {
				elementsFilled++;
				currentElement = j + 1;
			}
		}

		if (elementsFilled &gt;= 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&lt;Integer&gt; out = new ArrayList&lt;&gt;();
    int inPos = 0;
    int kCnt = 0;
    int last = 0;
    int[] kTracker = new int[65536];
    for (int i = 0; i &lt; E.size(); i++)
    {
        // 移除过期元素
        kCnt -= kTracker[i];

        // 填充容器
        while (kCnt &lt; K &amp;&amp; inPos &lt; T.length)
        {
            int exp = i + T.get(inPos);
            if (exp &lt; kTracker.length)
            {
                // 如果 exp &gt; 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&lt;Integer&gt; out = new ArrayList&lt;&gt;();
    int inPos = 0;
    int kCnt = 0;
    int last = 0;
    Map&lt;Integer, Integer&gt; kTracker = new HashMap&lt;&gt;();
    for (int i = 0; i &lt; E.size(); i++)
    {
        // 移除过期元素
        Integer removed = kTracker.remove(i);
        if (removed != null)
        {
            kCnt -= removed;
        }

        // 填充容器
        while (kCnt &lt; K &amp;&amp; inPos &lt; 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&lt;Integer&gt; out = new ArrayList&lt;&gt;();
    int inPos = 0;
    int kCnt = 0;
    int last = 0;
    int[] kTracker = new int[65536];
    for (int i = 0; i &lt; E.size(); i++)
    {
        // Remove Expired
        kCnt -= kTracker[i];

        // Fill Container
        while (kCnt &lt; K &amp;&amp; inPos &lt; T.length)
        {
            int exp = i + T.get(inPos);
            if (exp &lt; kTracker.length)
            {
                // don&#39;t bother tracking if &gt; E.max, as it won&#39;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&lt;Integer&gt; out = new ArrayList&lt;&gt;();
    int inPos = 0;
    int kCnt = 0;
    int last = 0;
    Map&lt;Integer, Integer&gt; kTracker = new HashMap&lt;&gt;();
    for (int i = 0; i &lt; E.size(); i++)
    {
        // Remove Expired
        Integer removed = kTracker.remove(i);
        if (removed != null)
        {
            kCnt -= removed;
        }

        // Fill Container
        while (kCnt &lt; K &amp;&amp; inPos &lt; 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);
    }

huangapple
  • 本文由 发表于 2020年7月25日 23:52:10
  • 转载请务必保留本文链接:https://go.coder-hub.com/63090375.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定