FastPrepWorst-Performing Ads by CTR in a Campaign Tree

Worst-Performing Ads by CTR in a Campaign Tree

Uber logoUber● MediumFULLTIMEPHONE SCREEN

Problem statement

An ad platform keeps its ads in a tree. A campaign is a root. A campaign can hold ad groups and ads, and an ad group can hold ads. Process a list of operations in order:

  • ["ADD", nodeId, parentId, type] registers a campaign, ad group or ad under parentId. A campaign has no parent, so its parentId is the empty string.
  • ["EVENT", adId, date, eventType] records one CLICK or one IMPRESSION for the ad on that date.
  • ["WORST", nodeId, date, k] returns the k worst-performing ads under the campaign or ad group nodeId on that date.

An ad is under a node if the node is its parent or its parent's parent. For a WORST query, use only the events recorded before it. An ad's click-through rate (CTR) on a date is its clicks on that date divided by its impressions on that date. Only ads under the node with at least one impression on that date are ranked. Rank them by CTR, lowest first. If two ads have the same CTR, the one with more impressions comes first; if they also have the same number of impressions, the smaller ID (in lexicographic order) comes first. Clicks are not limited by impressions, so a CTR can be above 1. Dates are labels: two events are on the same date only when their date strings are equal.

For each WORST operation, in order, return one string: the IDs of the first k ranked ads, worst first, joined with commas. If fewer than k ads are ranked, list all of them. If none are ranked, return the empty string.

Function

runAdOperations(operations: String[][]) → String[]

Examples

Example 1

operations = [["ADD","c1","","CAMPAIGN"],["ADD","g1","c1","AD_GROUP"],["ADD","a1","c1","AD"],["ADD","a2","g1","AD"],["ADD","a3","g1","AD"],["EVENT","a1","2026-11-05","IMPRESSION"],["EVENT","a1","2026-11-05","IMPRESSION"],["EVENT","a1","2026-11-05","IMPRESSION"],["EVENT","a1","2026-11-05","IMPRESSION"],["EVENT","a1","2026-11-05","CLICK"],["EVENT","a2","2026-11-05","IMPRESSION"],["EVENT","a2","2026-11-05","IMPRESSION"],["EVENT","a3","2026-11-05","IMPRESSION"],["EVENT","a3","2026-11-05","IMPRESSION"],["EVENT","a3","2026-11-05","IMPRESSION"],["EVENT","a3","2026-11-05","IMPRESSION"],["EVENT","a3","2026-11-05","CLICK"],["EVENT","a3","2026-11-05","CLICK"],["WORST","c1","2026-11-05","2"],["WORST","g1","2026-11-05","5"]]return = ["a2,a1","a2,a3"]

Campaign c1 holds ad a1 directly and ads a2 and a3 through ad group g1. On 2026-11-05 the CTRs are: a1 1/4 = 0.25, a2 0/2 = 0, a3 2/4 = 0.5. The 2 worst ads under c1 are a2 then a1. Ad group g1 holds only a2 and a3, so asking for 5 returns just those two, worst first.

Example 2

operations = [["ADD","c2","","CAMPAIGN"],["ADD","b1","c2","AD"],["ADD","b2","c2","AD"],["ADD","b3","c2","AD"],["EVENT","b1","2026-01-01","IMPRESSION"],["EVENT","b1","2026-01-01","IMPRESSION"],["EVENT","b1","2026-01-01","CLICK"],["EVENT","b2","2026-01-01","IMPRESSION"],["EVENT","b2","2026-01-01","IMPRESSION"],["EVENT","b2","2026-01-01","IMPRESSION"],["EVENT","b2","2026-01-01","IMPRESSION"],["EVENT","b2","2026-01-01","CLICK"],["EVENT","b2","2026-01-01","CLICK"],["EVENT","b3","2026-01-01","CLICK"],["EVENT","b3","2026-01-02","IMPRESSION"],["WORST","c2","2026-01-01","3"],["WORST","c2","2026-01-03","1"]]return = ["b2,b1",""]

On 2026-01-01, b1 has CTR 1/2 and b2 has CTR 2/4, the same 0.5. They tie, so the ad with more impressions, b2, comes first. b3 has a click but no impression on 2026-01-01 (its only impression is on 2026-01-02), so it is not ranked. No ad has an impression on 2026-01-03, so that query returns an empty string.

Constraints

  • 2 <= operations.length <= 5000
  • Each operation has exactly 4 strings: ["ADD", nodeId, parentId, type], ["EVENT", adId, date, eventType] or ["WORST", nodeId, date, k].
  • Every ID is 1 to 10 lowercase English letters or digits, and no two nodes share an ID.
  • type is CAMPAIGN, AD_GROUP or AD. A campaign's parentId is the empty string, an ad group's parent is a campaign, and an ad's parent is a campaign or an ad group. Every parent is added before its children.
  • In an EVENT operation, adId is an ad added earlier and eventType is CLICK or IMPRESSION.
  • Every date has the form YYYY-MM-DD.
  • In a WORST operation, nodeId is a campaign or an ad group added earlier, and k is a decimal integer with 1 <= k <= 5000.
  • There is at least one WORST operation.

More Uber problems

See Uber hiring insights
Saved
public String[] runAdOperations(String[][] operations) {
    // Write your code here.
}
Checking account…