We have n jobs, where every job is scheduled to be done from startTime[i] to endTime[i], obtaining a profit of profit[i].
You're given the startTime , endTime and profit arrays, you need to output the maximum profit you can take such that there are no 2 jobs in the subset with overlapping time range.
If you choose a job that ends at time X you will be able to start another job that starts at time X.
 
Example 1:

Input: startTime = [1,2,3,3], endTime = [3,4,5,6], profit = [50,10,40,70]
Output: 120
Explanation: The subset chosen is the first and fourth job. 
Time range [1-3]+[3-6] , we get profit of 120 = 50 + 70.

Example 2:
 

Input: startTime = [1,2,3,4,6], endTime = [3,5,10,6,9], profit = [20,20,100,70,60]
Output: 150
Explanation: The subset chosen is the first, fourth and fifth job. 
Profit obtained 150 = 20 + 70 + 60.

Example 3:

Input: startTime = [1,1,1], endTime = [2,3,4], profit = [5,6,4]
Output: 6

 
Constraints:

1 <= startTime.length == endTime.length == profit.length <= 5 * 10^4
1 <= startTime[i] < endTime[i] <= 10^9
1 <= profit[i] <= 10^4

```python

USER_FILENAME = "<user_code>"
import sys, json, builtins
import math, heapq, bisect, itertools, functools, collections, statistics, fractions, decimal, operator, array, random, re, string, os, pathlib
try:
    import resource as _res
except Exception:
    _res = None
from collections import Counter, defaultdict, deque, OrderedDict
from functools import lru_cache, reduce
from itertools import accumulate, permutations, combinations, product
from math import gcd, ceil, floor, sqrt
from decimal import Decimal
try:
    from typing import *
except Exception:
    pass
try:
    sys.setrecursionlimit(1_000_000)
except Exception:
    pass
try:
    import site as _site
    try:
        _sys_sites = _site.getsitepackages() if hasattr(_site, "getsitepackages") else []
        for _p in _sys_sites:
            if _p and _p not in sys.path:
                sys.path.append(_p)
    except Exception:
        pass
    try:
        _usp = _site.getusersitepackages()
        if isinstance(_usp, str):
            _usp = [_usp]
        for _p in _usp:
            if _p and _p not in sys.path:
                sys.path.append(_p)
    except Exception:
        pass
except Exception:
    pass

def _disabled_input(*args, **kwargs):
    raise RuntimeError("input() is disabled in callable mode")
builtins.input = _disabled_input

ns = {"__name__": "__judge__"}  # prevent if __name__ == "__main__" blocks
for _k, _v in list(globals().items()):
    if not _k.startswith("_"):
        ns[_k] = _v
_CODE = 'class Solution:\n    def jobScheduling(self, startTime: List[int], endTime: List[int], profit: List[int]) -> int:\n        \'\'\'\n        diff from max intervals. \n        Sort by Start Time (dont do the greedy sort by earliest finish time)\n        Choose it or dont. \n        Always choose it if it doesnt interfere with the next one. \n        you can binary search the end time in the start times to find the next available start times you can take. \n        \n        Have 3 solutions.\n        \'\'\'\n       \n        # sort by start times\n        res = sorted(zip(startTime, endTime, profit), key=lambda x: x[0])\n        # unzip it now!\n        unzipped_res = list(zip(*res))\n        startTime = unzipped_res[0]\n        endTime = unzipped_res[1]\n        profit = unzipped_res[2]\n        \n        #return self.topDown(startTime, endTime, profit)\n        return self.backwardDP(startTime, endTime, profit)\n        # return self.forwardDP(startTime, endTime, profit)\n    \n    # TOP DOWN COMPLETED.\n    def topDown(self, startTime, endTime, profit):\n        N = len(startTime)\n        \n        # Only memoizing one param\n        \'\'\'\n        Thought I would need to do the other params like \n        endTime or maxProfit but dont have to because \n        parent call does not have to communicate to child recursive calls\n        to help child recursive calls to achieve max. Since we\n        dont need parent-child communication, we can get away with \n        passing very little info to child through its function params.\n        \'\'\'\n        @lru_cache(None)\n        def solve(i):\n            \n            if i == N:\n                return 0\n            \n            # either take or dont\n            start = startTime[i]\n            end = endTime[i]\n            \n            # you can skip to the index that has a start time ahead of \n            # end time -> so that you dont have to \n            # pass along endtime in memtable? \n            \n            nextI = N\n            \n            # CAN USE BINARY SEARCH HERE!\n            for j in range(i+1, N):\n                if startTime[j] >= end:\n                    nextI = j\n                    break\n                    \n            prof = profit[i]\n            \n            # take it\n            taken = solve(nextI) + profit[i]\n            \n            # dont take:\n            notTaken = solve(i+1)\n            # print(\\"nextI, TAKEN AND NOT TAKEN ARE\\", i,nextI, taken, notTaken)\n            return max(taken, notTaken)\n        \n        amt = solve(0)\n        return amt\n            \n    def backwardDP(self, startTime, endTime, profit):      \n        from bisect import bisect_left\n        \n        # Bottom Up.\n        \'\'\'\n        \n        So the states I need is \n        max profit achieved at each index. \n        then we binary search to the right an index we can use to include\n        in our max profit!\n        \n        and process backwards like that!\n        \n        OPT[i] = max(Take, DontTake)\n                    OPT[i`] + profit[i], OPT[i-1]\n                    \n        \n        \'\'\'\n        N = len(startTime)\n        OPT = [0 for i in range(N+1)]\n        \n        for i in range(N-1, -1, -1):\n            \n            start = startTime[i]\n            end = endTime[i]\n            prof = profit[i]\n            \n            # Take operation\n            # find the end index!\n            \n            # endI = bisect_left(end, startTime)\n            # if endI < \n            freeK = N\n            \'\'\'\n            Linear search that leads to O(N^2)\n            for k in range(i+1, N):\n                if end <= startTime[k]:\n                    freeK = k\n                    break    \n            \'\'\'\n            # Has to be bisect_left not bisect_right!\n            freeK = bisect_left(startTime, end)\n            take = profit[i] + OPT[freeK]\n            dontTake = OPT[i+1]\n            OPT[i] = max(take, dontTake)\n        return OPT[0]\n    \n    # COULD NOT DO FORWARD VERY HARD!\n    def forwardDP(self, startTime, endTime, profit):\n        \'\'\'\n        Ok look at idx 0\n            -> Is there a way to do it NlogN?\n            -> find all intervals to right that dont intersect with us!\n            -> add it in. \n            \n        Is this the brute force solution or what does the brute force \n        solution look like?\n\n        Actaully this one is very difficult\n        \'\'\'\n        N = len(startTime)\n        OPT = [profit[i] for i in range(N)]\n        \n        for i in range(N):\n            # find all intervals ahead of us! \n            # and add us in.\n            \n            prof = profit[i]\n            end = endTime[i]\n            \n            x = bisect_left(startTime, end)\n            for k in range(x, N):\n                OPT[k] += prof\n        \n        return OPT[-1]\n    \n                        \n            \n            \n            \n            \n        \n            \n            \n        \n        \n         \n    \n        \n        \n        \n        \n        \n        \n        \n        \n        \n        \n        \n        \n        \n        \n        \n        \n        \n    \n            \n            \n        \n'
_co = compile(_CODE, USER_FILENAME, "exec")
exec(_co, ns, ns)

_target = None
_fn_name = 'jobScheduling'
if _fn_name:
    if "Solution" in ns:
        try:
            _obj = ns["Solution"]()
            if hasattr(_obj, _fn_name):
                _target = getattr(_obj, _fn_name)
        except Exception:
            _target = None
    if _target is None and _fn_name in ns and callable(ns[_fn_name]):
        _target = ns[_fn_name]
# Fallback: if there's exactly one user-defined function, use it
if _target is None:
    _cands = [v for k, v in ns.items() if callable(v) and getattr(v, "__module__", "") in ("__judge__", "__main__")]
    _user_funcs = [c for c in _cands if getattr(c, "__name__", "").startswith("_") is False]
    if len(_user_funcs) == 1:
        _target = _user_funcs[0]
if _target is None:
    raise RuntimeError(f"Could not resolve callable '{_fn_name}'")

_raw = sys.stdin.read().strip()
if _raw:
    _parsed = json.loads(_raw)
    if isinstance(_parsed, dict):
        _kwargs = _parsed.get("kwargs", {})
        _args = _parsed.get("args", [])
    else:
        _args, _kwargs = _parsed, {}
else:
    _args, _kwargs = [], {}
if not isinstance(_args, (list, tuple)):
    _args = [_args]
_res = _target(*_args, **_kwargs)
try:
    sys.stdout.write(repr(_res) + "\n")
except Exception:
    pass

```

Measured memory_bytes: 7078