LCP 12. 小张刷题计划
为了提高自己的代码能力,小张制定了 LeetCode 刷题计划,他选中了 LeetCode 题库中的 n 道题,编号从 0 到 n-1,并计划在 m 天内按照题目编号顺序刷完所有的题目(注意,小张不能用多天完成同一题)。
在小张刷题计划中,小张需要用 time[i] 的时间完成编号 i 的题目。此外,小张还可以使用场外求助功能,通过询问他的好朋友小杨题目的解法,可以省去该题的做题时间。为了防止“小张刷题计划”变成“小杨刷题计划”,小张每天最多使用一次求助。
我们定义 m 天中做题时间最多的一天耗时为 T(小杨完成的题目不计入做题总时间)。请你帮小张求出最小的 T是多少。
示例 1:
输入:
time = [1,2,3,3], m = 2输出:
3解释:第一天小张完成前三题,其中第三题找小杨帮忙;第二天完成第四题,并且找小杨帮忙。这样做题时间最多的一天花费了 3 的时间,并且这个值是最小的。
示例 2:
输入:
time = [999,999,999], m = 4输出:
0解释:在前三天中,小张每天求助小杨一次,这样他可以在三天内完成所有的题目并不花任何时间。
限制:
1 <= time.length <= 10^51 <= time[i] <= 100001 <= m <= 1000
题意概述
给定一个数组,将其划分成 MM 份,使得每份元素之和最大值最小,每份可以任意减去其中一个元素。
题解
如果不考虑每份可以任意减去一个元素,就是一个经典的二分问题,具有单调最优的性质:如果最大值为 tt 可以满足条件划分,那么最大值为 t+1t+1 也可以。所以就直接二分最大值 tt,找到最小满足条件的 tt 即可。
本题加了一个条件:每份可以删除任意一个数组。为了能够让最大值最小,显然每份中删去的一定是最大元素,所以在二分判定可行性时,维护当前序列的最大值,然后记录删除最大值的结果,也就是说二分的判定是:是否可以让每组删除最大值之后,总和都小于等于 tt。
class Solution { public: bool Check(int limit, vector<int> cost, int day) { // 每组划分 limit 的最大和,贪心划分看有多少组 int useday, totaltime, maxtime; useday = 1; totaltime = maxtime = 0; for (int i=0; i<cost.size(); ++i) { int nexttime = min(maxtime, cost[i]); if (nexttime + totaltime <= limit) { totaltime += nexttime; maxtime = max(maxtime, cost[i]); } else { ++useday; totaltime = 0; maxtime = cost[i]; } } return (useday <= day); } int minTime(vector<int>& time, int m) { int left, right, middle; left = right = 0; for (int i=0; i<time.size(); ++i) { right += time[i]; } while (left <= right) { middle = (left + right) >> 1; if (Check(middle, time, m)) right = middle - 1; else left = middle + 1; } return left; } };
复杂度分析
时间复杂度:O(N\log M)O(NlogM),其中 NN 是数组的大小,MM 是数组所有元素之和。
空间复杂度:O(N)O(N)。
如果题目改一下,小张不可以使用场外求助功能,怎么解答呢?
解题如下:
public int minTime(int[] time, int m) { if (time.length <= m) { return 0; } int left, right,mid; left = right = 0; for (int i = 0; i < time.length; i++) { right += time[i]; } while (left <= right) { mid = (left + right) >> 1; if (check2(mid, time, m)) { right = mid -1; } else { left = mid +1; } } return left; } private boolean check2(int limit, int[] time, int day) { int useday = 1, totalTime =0 ; for (int i = 0; i < time.length; i++) { int nextTime = time[i]; if (nextTime + totalTime <= limit) { totalTime += nextTime; } else { ++useday; totalTime = nextTime; } } return (useday <= day); }
作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/xiao-zhang-shua-ti-ji-hua/solution/xiao-zhang-shua-ti-ji-hua-er-fen-cha-zhao-by-leetc/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
给你一个 m * n 的矩阵 mat,以及一个整数 k ,矩阵中的每一行都以非递减的顺序排列。
你可以从每一行中选出 1 个元素形成一个数组。返回所有可能数组中的第 k 个 最小 数组和。
示例 1:
输入:mat = [[1,3,11],[2,4,6]], k = 5 输出:7 解释:从每一行中选出一个元素,前 k 个和最小的数组分别是: [1,2], [1,4], [3,2], [3,4], [1,6]。其中第 5 个的和是 7 。
示例 2:
输入:mat = [[1,3,11],[2,4,6]], k = 9 输出:17
示例 3:
输入:mat = [[1,10,10],[1,4,5],[2,3,6]], k = 7 输出:9 解释:从每一行中选出一个元素,前 k 个和最小的数组分别是: [1,1,2], [1,1,3], [1,4,2], [1,4,3], [1,1,6], [1,5,2], [1,5,3]。其中第 7 个的和是 9 。
示例 4:
输入:mat = [[1,1,10],[2,2,9]], k = 7 输出:12
提示:
m == mat.lengthn == mat.length[i]1 <= m, n <= 401 <= k <= min(200, n ^ m)1 <= mat[i][j] <= 5000mat[i]是一个非递减数组
public int kthSmallest(int[][] mat, int k) { int m = mat.length; int n = mat[0].length; int left = 0, right = 0; for (int i = 0; i < m; i++) { left += mat[i][0]; right += mat[i][n - 1]; } int initSum = left; while (left < right) { int mid = left + (right - left) / 2; int count = count(mat, mid, initSum, 0, k); if (count >= k) { right = mid; } else { left = mid + 1; } } return left; } private int count(int[][] mat, int mid, int lastSum, int rowIndex, int k) { int m = mat.length; int n = mat[0].length; if (rowIndex == m) { return 1; } int count = 0; for (int col = 0; col < n; col++) { int nextSum = lastSum - mat[rowIndex][0] + mat[rowIndex][col]; if (nextSum <= mid) { count += count(mat, mid, nextSum, rowIndex + 1, k); if (count >= k) { return count; } } else { break; } } return count; }
浙公网安备 33010602011771号