Skip to content
DSAPrep

    ↑↓ to move · Enter to open · Esc to close

    Invalid Transactions

    A transaction is possibly invalid if:

    • the amount exceeds $1000, or;
    • if it occurs within (and including) 60 minutes of another transaction with the same name in a different city.

    You are given an array of strings transaction where transactions[i] consists of comma-separated values representing the name, time (in minutes), amount, and city of the transaction.

    Return a list of transactions that are possibly invalid. You may return the answer in any order.

    Example 1

    Input: transactions = ["alice,20,800,mtv","alice,50,100,beijing"]

    Output: ["alice,20,800,mtv","alice,50,100,beijing"]

    Explanation: The first transaction is invalid because the second transaction occurs within a difference of 60 minutes, have the same name and is in a different city. Similarly the second one is invalid too.

    Example 2

    Input: transactions = ["alice,20,800,mtv","alice,50,1200,mtv"]

    Output: ["alice,50,1200,mtv"]

    Example 3

    Input: transactions = ["alice,20,800,mtv","bob,50,1200,mtv"]

    Output: ["bob,50,1200,mtv"]

    Constraints

    • transactions.length <= 1000
    • Each transactions[i] takes the form "{name},{time},{amount},{city}"
    • Each {name} and {city} consist of lowercase English letters, and have lengths between 1 and 10.
    • Each {time} consist of digits, and represent an integer between 0 and 1000.
    • Each {amount} consist of digits, and represent an integer between 0 and 2000.
    View original on LeetCode ↗

    Two independent rules flag a transaction. The amount rule is local — it only needs the transaction itself. The second rule is relational: it needs a partner with the same name, in a different city, no more than 60 minutes apart. The important pruning fact is that a partner must share the name, so transactions with different names can never invalidate each other. Bucket the rows by name, compare each new row only against the earlier rows of its own bucket, and when a pair qualifies mark both endpoints invalid — not just the later one.

    Brute Force: Check Every Other Transaction

    Time O(n²)Space O(n)

    Parse each string once into (name, time, amount, city). Then take every transaction and compare it against every other transaction with the same name: if the cities differ and the times are within 60 minutes, mark it invalid. The amount rule is evaluated independently. A pair only has to be detected once, because a conflicting transaction is marked when it is the outer loop’s turn just as it is when it is the inner loop’s turn.

    class Solution:
    def invalidTransactions(self, transactions: list[str]) -> list[str]:
    parsed = []
    for x in transactions:
    name, time, amount, city = x.split(",")
    parsed.append((name, int(time), int(amount), city))
    invalid = set()
    n = len(transactions)
    for i in range(n):
    name_i, time_i, amount_i, city_i = parsed[i]
    if amount_i > 1000:
    invalid.add(i)
    for j in range(n):
    if i == j:
    continue
    name_j, time_j, _, city_j = parsed[j]
    if name_i == name_j and city_i != city_j and abs(time_i - time_j) <= 60:
    invalid.add(i)
    return [transactions[i] for i in invalid]

    Why it is quadratic: the nested loops compare all n² ordered pairs even though the name is often different, and every comparison is constant work, so time is O(n²). The parsed copy and the result set hold at most n entries each, so space is O(n) (the output itself can be all n transactions).

    Group by Name, Compare Within a Name

    OptimalTime O(n²) worst caseSpace O(n)

    Only transactions with the same name can invalidate each other, so build a hash map from name to the list of indices that carry it. Then scan the transactions left to right. For transaction i, first apply the local amount rule; then compare it with the earlier entries in its own bucket only, one at a time. A comparison is a conflict exactly when both conditions hold together — the cities differ and the time gap is at most 60 minutes. When they do, add i and the earlier partner j to the invalid set. Restricting the scan to j < i avoids checking each pair twice, while still marking both sides.

    class Solution:
    def invalidTransactions(self, transactions: list[str]) -> list[str]:
    parsed = []
    for x in transactions:
    name, time, amount, city = x.split(",")
    parsed.append((name, int(time), int(amount), city))
    by_name = {}
    for i, (name, _, _, _) in enumerate(parsed):
    by_name.setdefault(name, []).append(i)
    invalid = set()
    for i, (name, time, amount, city) in enumerate(parsed):
    if amount > 1000:
    invalid.add(i)
    for j in by_name[name]:
    if j >= i:
    continue
    other_time = parsed[j][1]
    other_city = parsed[j][3]
    if other_city != city and abs(time - other_time) <= 60:
    invalid.add(i)
    invalid.add(j)
    return [transactions[i] for i in invalid]

    Watch how each new row only ever looks at the earlier rows of its own name bucket, and how a conflicting pair marks both indices:

    start0 invalid so far

    Transactions

    #0alice#1alice#2alice#3bob#4carol

    Current transaction

    —

    Invalid set

    none yet

    Name groups

    no names yet

    Conflict checks

    no earlier same-name transaction

    only same-name pairs are compared — a conflict needs different cities and a gap of at most 60 minutes

    1 / 10
    currentcomparingseendiscardedresult

    Invalid when the amount exceeds 1000, or the same name appears in a different city within 60 minutes. Group by name so only same-name pairs are compared.

    Why it works: the name bucket contains every transaction that could possibly be a partner, and the j < i guard pairs each comparison exactly once while still marking both endpoints. The amount rule is checked without reference to any partner. Complexity: building the buckets is O(n), and the comparisons total sum of C(k, 2) over the name bucket sizes k, which is O(n²) in the worst case when every transaction shares one name and O(n) when all names are distinct. The buckets and parsed rows use O(n) space.

    More Arrays & Hashing practice