比赛地址:
https://lingkou.com/contest/weekly-contest-102
905. 按奇偶校验排序数组
905. Sort Array By Parity
给定一个非负整数数组 A
,返回一个由 A
的所有偶数元素组成的数组,后面跟 A
的所有奇数元素。
你可以返回满足此条件的任何数组作为答案。
示例:
输入:[3,1,2,4] 输出:[2,4,3,1] 输出 [4,2,3,1],[2,4,1,3] 和 [4,2,1,3] 也会被接受。
提示:
1 <= A.length <= 5000
0 <= A[i] <= 5000
题解:排序即可,时间o(N*logN)。本题的最优方案是时间o(N)空间o(1)
/** * @param {number[]} A * @return {number[]} */ var sortArrayByParity = function(A) { return A.sort((a,b)=>(a%2-b%2)); };
904. 水果成篮
904. Fruit Into Baskets
在一排树中,第 i
棵树产生 tree[i]
型的水果。
你可以从你选择的任何树开始,然后重复执行以下步骤:
- 把这棵树上的水果放进你的篮子里。如果你做不到,就停下来。
- 移动到当前树右侧的下一棵树。如果右边没有树,就停下来。
请注意,在选择一颗树后,你没有任何选择:你必须执行步骤 1,然后执行步骤 2,然后返回步骤 1,然后执行步骤 2,依此类推,直至停止。
你有两个篮子,每个篮子可以携带任何数量的水果,但你希望每个篮子只携带一种类型的水果。
用这个程序你能收集的水果总量是多少?
示例 1:
输入:[1,2,1] 输出:3 解释:我们可以收集 [1,2,1]。
示例 2:
输入:[0,1,2,2] 输出:3 解释:我们可以收集 [1,2,2]. 如果我们从第一棵树开始,我们将只能收集到 [0, 1]。
示例 3:
输入:[1,2,3,2,2] 输出:4 解释:我们可以收集 [2,3,2,2]. 如果我们从第一棵树开始,我们将只能收集到 [1, 2]。
示例 4:
输入:[3,3,3,1,2,1,1,2,3,3,4] 输出:5 解释:我们可以收集 [1,2,1,1,2]. 如果我们从第一棵树或第八棵树开始,我们将只能收集到 4 个水果。
提示:
1 <= tree.length <= 40000
0 <= tree[i] < tree.length
题解:维护一个长度为2的队列,遍历和上一棵树不一样时入队,并且如果该树不在队列中时刷新选择第一棵树的位置为下一棵树。
/** * @param {number[]} tree * @return {number} */ var totalFruit = function(tree) { var ans = 0; var first = 0; var q = [0]; for (var i = 1; i<= tree.length; i++) { if (tree[i] != tree[i - 1]) { ans = Math.max(ans, i - first); q.push(i); if (q.length > 2) { if (tree[i] != tree[q[0]]) { first = q[1]; } q.shift(); } } } return ans; };
907. 子数组的最小值之和
907. Sum of Subarray Minimums
给定一个整数数组 A
,找到 min(B)
的总和,其中 B
的范围为 A
的每个(连续)子数组。
由于答案可能很大,因此返回答案模 10^9 + 7
。
示例:
输入:[3,1,2,4] 输出:17 解释: 子数组为 [3],[1],[2],[4],[3,1],[1,2],[2,4],[3,1,2],[1,2,4],[3,1,2,4]。 最小值为 3,1,2,4,1,1,2,1,1,1,和为 17。
提示:
1 <= A <= 30000
1 <= A[i] <= 30000
题解:首先是一个暴力方案。i为起点j为终点的子数组最小值为minn。
/** * @param {number[]} A * @return {number} */ var sumSubarrayMins = function(A) { var m = 1e9 + 7; var ans = 0; var st = []; for (var i = 0; i < A.length; i++) { var minn = A[i]; for (var j = i; j < A.length; j++) { minn = Math.min(minn, A[j]); ans = (ans + minn) % m; } } return ans; };
以上o(N^2)显然超时了,考虑优化方案:用一个队列记录原数组中的递增序列的下标,依次遍历,以当前节点为最后一个元素的所有子数组的最小值累加。例如示例中[3,1,2,4]:
i=0,q=[-1,0],ans+=3;以3为最后一个元素的子数组只有一个[3]
i=1,q=[-1,1],ans+=A[1]*(q[1]-q[0])=1*2;以1为最后一个元素的子数组有[3,1]、[1]
i=2,q=[-1,1,2],ans+=A[1]*(q[1]-q[0])+A[2]*(q[2]-q[1]);以2为最后一个元素的子数组有[3,1,2]、[1,2]、[2],其中最小值是1的数组有两个,最小值是2的数组有1个
i=3,q=[-1,1,2,3],ans+=A[1]*(q[1]-q[0])+A[2]*(q[2]-q[1])+A[3]*(q[3]-q[2]);以此类推
/** * @param {number[]} A * @return {number} */ var sumSubarrayMins = function(A) { var m = 1e9 + 7; var ans = 0; var q = [-1]; for (var i = 0; i < A.length; i++) { while(q.length > 1 && A[q[q.length - 1]] >= A[i]) { q.pop(); } q.push(i); for (var j = 1; j < q.length; j++) { ans = (ans + A[q[j]] * (q[j] - q[j - 1])) % m; } } return ans; };
上面方案可以通过,但是耗时3秒多。不令人满意,考虑一种方案。遍历数组时如果以当前值为子数组的最小值,那么这些子数组的个数是左边比该数字大的个数乘以右边的比该数字大的个数。为了获得左右两边的个数,需要用到两个辅助数组:prev[i]代表上一个不小于A[i]的位置、next[i]代表下一个大于A[i]的位置,详见代码。
/** * @param {number[]} A * @return {number} */ var sumSubarrayMins = function(A) { var m = 1e9 + 7; var ans = 0; var q = [-1]; var prev = []; for (var i = 0; i < A.length; i++) { while(q.length > 1 && A[q[q.length - 1]] >= A[i]) { q.pop(); } prev[i] = q[q.length - 1]; q.push(i); } q = [A.length]; var next = new Array(A.length); for (var i = A.length - 1; i >= 0; i--) { while(q.length > 1 && A[q[q.length - 1]] > A[i]) { q.pop(); } next[i] = q[q.length - 1]; q.push(i); } for (var i = 0; i < A.length; i++) { ans = (ans + A[i] * (i - prev[i]) * (next[i] - i)) % m; } return ans; };