『力扣周赛』第359场周赛、第111场双周赛

40 阅读1分钟

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;
};