1700+分了,加油!
双周赛111
两题局,第三题dp想不出来。
3. 将三个组排序
方法一:暴力
这题nums的最大长度为100,可以使用O(n^3)复杂度的算法。
暴力做法的思路是,把所有区间分为三段:[0, i - 1],[i, i + j - 1],[i + j, n - 1],三段分别存放1、2、3。我们令第一段长度为i,第二段长度为j,第三段的长度可以用总长度减去前两段的长度,这里的复杂度是O(n^2)。
遍历出所有的情况,再在该情况下,按位和原nums比较。
var minimumOperations = function(nums) {
const n = nums.length
let ans = n
// i、j、n - i - j 是三个区间的长度
for (let i = 0; i <= n; i++) {
for (let j = 0; i + j <= n; j++) {
let cnt = 0
for (let k = 0; k < n; k++) {
let t
if (k < i) t = 1 // [0, i - 1]
else if (k < i + j) t = 2 // [i, i + j - 1]
else t = 3 // [i + j, n]
if (t !== nums[k]) cnt++
}
ans = Math.min(ans, cnt)
}
}
return ans
};
方法二:LIS
原数组改变后的结果是类似于[1, 1, 2, 2, 3, 3]这样的非递减数组,只有这样才能满足题意。所以我们可以求出nums中最长非递减子序列的长度,让它们保持不变,修改其余位,即 总长度 - 最长非递减子序列长度。
var minimumOperations = function(nums) {
const n = nums.length
const dp = Array(n).fill(1)
let res = 1
for (let i = 1; i < n; i++) {
for (let j = 0; j < i; j++) {
if (nums[j] <= nums[i]) {
dp[i] = Math.max(dp[i], dp[j] + 1)
res = Math.max(res, dp[i])
}
}
}
return n - res
};
方法三:状态机DP
类似于股票利益含冷冻期问题。f[i + 1][j]表示前i个数中,以j结尾时需要修改的最少次数。因为原数组改变后的结果是类似于[1, 1, 2, 2, 3, 3]这样的非递减数组,可知:
- f[i + 1][1]只能从f[i][1]推出
- f[i + 1][2]可以从f[i][1]和f[i][2]推出
- f[i + 1][3]可以从f[i][1]、f[i][2]和f[i][3]推出
如果不满足上面的转移要求,数组的非递减特性会被破坏。这一点用最内层的k的循环来实现。并且,如果当前的数nums[i]等于j,不需任何操作,就能让它以j结尾,所以此时加0,否则需要增加一次操作。
var minimumOperations = function(nums) {
const n = nums.length;
const f = Array(n + 1)
.fill(0)
.map(() => Array(4).fill(Infinity));
for (let j = 1; j <= 3; j++) {
f[0][j] = 0;
}
for (let i = 0; i < n; i++) {
for (let j = 1; j <= 3; j++) {
for (let k = 1; k <= j; k++) {
f[i + 1][j] = Math.min(f[i + 1][j], f[i][k] + (nums[i] !== j));
}
}
}
return Math.min(f[n][1], f[n][2], f[n][3]);
};
周赛359
前两题快速解决,第三题dp手生卡壳,第四题滑窗反而简单一点。
3. 销售利润最大化
线性dp,从dp数组最后一个元素开始思考。
dp[i + 1]表示考虑前i个房能获取的最大金币数(这样dp[0]不考虑任何房,便于初始化),如果不考虑卖出i号房,dp[i + 1] = dp[i]。否则遍历所有以i为end的offer,该offer下对应的收益dp[i + 1] = dp[start] + gold。
var maximizeTheProfit = function(n, offers) {
const groups = Array(n + 1).fill(0).map(() => [])
const dp = Array(n + 1).fill(0)
for (const offer of offers) {
const [start, end, gold] = offer
groups[end + 1].push([start, gold])
}
for (let end = 0; end < n; end++) {
// dp[end + 1]考虑购买0~end的房屋
// 这一行不能去掉,相当于是给dp[end + 1]赋了初始值
dp[end + 1] = dp[end]
const g = groups[end + 1]
for (const [start, gold] of g) {
// dp[start]考虑购买0~start-1的房屋
dp[end + 1] = Math.max(dp[end + 1], dp[start] + gold)
}
}
return dp[n]
};
4. 找出最长等值子数组
维护一个滑动窗口和一个哈希表,哈希表存放了每个数在窗口内出现的次数。最长等值子数组的长度,就等于 当前出现次数最多的那个数 出现的次数(maxFreq)。
为了得到最长等值子数组,极限的情况是把其他的数都删掉,并且删掉的次数不能大于k,否则窗口左边界要向右移动。
var longestEqualSubarray = function(nums, k) {
let counter = {};
let maxFreq = 0;
let left = 0;
let ans = 0;
for (let right = 0; right < nums.length; right++) {
if (!counter[nums[right]]) {
counter[nums[right]] = 0;
}
counter[nums[right]]++;
maxFreq = Math.max(maxFreq, counter[nums[right]]);
if ((right - left + 1) - maxFreq > k) {
counter[nums[left]]--;
left++;
}
ans = Math.max(ans, maxFreq);
}
return ans;
};