1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| class Solution { private Integer[] memo;
public int jobScheduling(int[] startTime, int[] endTime, int[] profit) { int[][] jobs = new int[startTime.length][3]; for (int i = 0; i < startTime.length; i++) { jobs[i][0] = startTime[i]; jobs[i][1] = endTime[i]; jobs[i][2] = profit[i]; } Arrays.sort(jobs, (a, b) -> a[0] - b[0]); memo = new Integer[jobs.length]; return backtrack(jobs, 0); }
private int backtrack(int[][] jobs, int pos) { if (pos == jobs.length) { return 0; } if (memo[pos] != null) { return memo[pos]; } int max = 0; max = Math.max(backtrack(jobs, pos + 1), jobs[pos][2] + backtrack(jobs, binarySearch(jobs, pos + 1, jobs[pos][1]))); ans = Math.max(ans, max); return memo[pos] = max; }
private int binarySearch(int[][] jobs, int start, int target) { int left = start, right = jobs.length - 1, ans = jobs.length; while (left <= right) { int mid = left + (right - left) / 2; if (jobs[mid][0] < target) { left = mid + 1; } else { ans = mid; right = mid - 1; } } return ans; } }
|
这道题常规的top-down recursion + memoization就可以了. 但是我设置的递归函数的逻辑有些问题. 我想的是让每个period都作为第一个period, 然后看每个period做第一个的时候, max profit是多少, 之后得到全局最大. 但是这样非常低效. 比如我想知道从第0个period到最后一个period的区间能取到的最大值是多少, 我就让每个period轮流当开头. 于是就变为选第0个period, 然后看第0个period end time后最早开始的period是哪个index, 然后就是求[index, n]的max profit, 然后加上第0个period的profit即可. 然后让第1个period当开头, 然后找比第一个period end time大的最小的start time对应的index是多少, 然后求[index1, n]的max profit即可. 等于是需要一个for循环, 把每一个当前区间内的periods都做一次开头. 这样就会有重复, 比如递归在第一层的时候, 我们可能会尝试求某个区间内的periods, 而递归很深的时候, 我们也可能求同一个区间内的periods. 那么有没有更好的想法呢?
给我们一个区间, 要么第0个periods作为开头, 要么它不作为开头也就是不选它. 因此分成这两种情况. 这样就会好很多, 不需要一个循环了, 而是二叉树. 当我们选一个period的时候, 使用binary search找该period结束时间后start time最早的period的index, 得出该index往后框起来的区间的max profit即可.
时间复杂度: O(nlogn)
空间复杂度: O(n)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| class Solution {
public int jobScheduling(int[] startTime, int[] endTime, int[] profit) { int[][] jobs = new int[startTime.length][3]; for (int i = 0; i < startTime.length; i++) { jobs[i][0] = startTime[i]; jobs[i][1] = endTime[i]; jobs[i][2] = profit[i]; } Arrays.sort(jobs, (a, b) -> a[0] - b[0]); int[] dp = new int[jobs.length + 1]; for (int i = dp.length - 2; i >= 0; i--) { dp[i] = Math.max(dp[i + 1], jobs[i][2] + dp[binarySearch(jobs, i + 1, jobs[i][1])]); } return dp[0]; }
private int binarySearch(int[][] jobs, int start, int target) { int left = start, right = jobs.length - 1, ans = jobs.length; while (left <= right) { int mid = left + (right - left) / 2; if (jobs[mid][0] < target) { left = mid + 1; } else { ans = mid; right = mid - 1; } } return ans; } }
|
根据自顶向下的逻辑, 直接就能写出来转移方程. dp[i]表示的就是从i到最后一个构成的区间的max profit是多少. 根据答案1, 如果binary search找到的是jobs.length, 那么就返回0, 于是我们让dp的长度为jobs.length + 1. 最后一个位置留给pos == jobs.length这种情况. dp的长度对应情况的个数. pos一共有0到jobs.length种取值, 一共是jobs.length + 1种情况, 因此dp声明为这个长度. 然后我们反向遍历. 递归的主要逻辑就是求不取当前区间第一个period的max
以及取当前区间第一个period的max. 那就是dp[i + 1]和jobs[i][2] + dp[binarySearch(jobs, i + 1, jobs[i][1])]看哪个大了. dp[i + 1]表示不取, 后面那个表示取, 于是就要找比该period end time晚的period的最早的start time对应的index是多少, 然后看它到最后一个构成的区间的max是多少.
时间复杂度: O(nlogn) dp中每个元素都会调用binary search. binary search于是就是log1 + log2 + log3 +… +logn, 这就是O(nlogn)
空间复杂度: O(n) 因为有dp数组