【力扣周赛】第 118 场双周赛(⭐单调队列优化DP)

竞赛链接

https://leetcode.cn/contest/biweekly-contest-118

Q1:100121. 查找包含给定字符的单词

https://leetcode.cn/problems/find-words-containing-character/description/
在这里插入图片描述
提示:

1 <= words.length <= 50
1 <= words[i].length <= 50
x 是一个小写英文字母。
words[i] 只包含小写英文字母。

竞赛时代码——indexOf检查

class Solution {
    public List<Integer> findWordsContaining(String[] words, char x) {
        List<Integer> ans = new ArrayList<>();
        for (int i = 0; i < words.length; ++i) {
            String word = words[i];
            if (word.indexOf(x) != -1) ans.add(i);
        }
        return ans;
    }
}

Q2:100138. 最大化网格图中正方形空洞的面积

https://leetcode.cn/problems/maximize-area-of-square-hole-in-grid/description/

在这里插入图片描述
提示:

1 <= n <= 10^9
1 <= m <= 10^9
1 <= hBars.length <= 100
2 <= hBars[i] <= n + 1
1 <= vBars.length <= 100
2 <= vBars[i] <= m + 1
hBars 中的值互不相同。
vBars 中的值互不相同。

竞赛时代码——贪心 排序

结果应该是长的空格和宽的空格的最小值的平方。

要先排序把相邻的放在一起。

class Solution {
    public int maximizeSquareHoleArea(int n, int m, int[] hBars, int[] vBars) {
        Arrays.sort(hBars);
        Arrays.sort(vBars);
        int hd = 1, mh = 2;
        for (int i = 1; i < hBars.length; ++i) {
            if (hBars[i] == hBars[i - 1] + 1) hd++;
            else hd = 1;
            mh = Math.max(mh, hd + 1);
        }
        int wd = 1, mw = 2;
        for (int i = 1; i < vBars.length; ++i) {
            if (vBars[i] == vBars[i - 1] + 1) wd++;
            else wd = 1;
            mw = Math.max(mw, wd + 1);
        }
        int a = Math.min(mw, mh);
        return a * a;
    }
}

Q3:100133. 购买水果需要的最少金币数

https://leetcode.cn/problems/minimum-number-of-coins-for-fruits/description/
在这里插入图片描述
提示:

1 <= prices.length <= 1000
1 <= prices[i] <= 10^5

竞赛时代码——DP

dp[i][0] 和 dp[i][1] 分别表示买到 i,不花钱买 i 和 花钱买 i 的最小花费。

class Solution {
    public int minimumCoins(int[] prices) {
        int n = prices.length;
        int[][] dp = new int[n + 1][2];
        for (int i = 1; i <= n; ++i) dp[i][0] = dp[i][1] = Integer.MAX_VALUE / 2;
        for (int i = 1; i <= n; ++i) {
            for (int j = 0; j < i; ++j) {
                if (j + j >= i) dp[i][0] = Math.min(dp[i][0], dp[j][1]);						// 可以不花钱
                if (j + j >= i - 1) dp[i][1] = Math.min(dp[i][1], dp[j][1] + prices[i - 1]);	// 必须花钱
            }
        }
        return Math.min(dp[n][0], dp[n][1]);
    }
}

代码优化——DP:从记忆化搜索到递推,从 O ( n 2 ) O(n^2) O(n2) O ( n ) O(n) O(n) 🐂

https://leetcode.cn/problems/minimum-number-of-coins-for-fruits/solutions/2542044/dpcong-on2-dao-onpythonjavacgo-by-endles-nux5/

记忆化搜索

class Solution {
    int n;
    int[] memo, prices;

    public int minimumCoins(int[] prices) {
        n = prices.length;
        memo = new int[(n + 1) / 2];
        this.prices = prices;
        return dfs(1);
    }

    // dfs(i)表示获得第i个及其后面的水果所需的最少金币数
    public int dfs(int i) {
        if (i * 2 >= n) return prices[i - 1];
        if (memo[i] != 0) return memo[i];
        int res = Integer.MAX_VALUE;
        // 枚举下一个购买的位置
        for (int j = i + 1; j <= 2 * i + 1; ++j) {
            res = Math.min(res, dfs(j));
        }
        return memo[i] = res + prices[i - 1];
    }
}

翻译成递推
在这里插入图片描述

class Solution {
    public int minimumCoins(int[] prices) {
        int n = prices.length;
        for (int i = (n + 1) / 2 - 1; i > 0; --i) {
            int mn = Integer.MAX_VALUE;
            for (int j = i; j <= 2 * i; ++j) {
                mn = Math.min(mn, prices[j]);
            }
            prices[i - 1] += mn;
        }
        return prices[0];
    }
}

单调队列优化DP
维护一个单调队列代替对 j 的循环,队列单调递减,队首在左边,队尾在右边。

class Solution {
    public int minimumCoins(int[] prices) {
        int n = prices.length;
        // 单调递减的单调队列,队尾是最小值
        Deque<int[]> dq = new ArrayDeque<>();
        dq.offerLast(new int[]{n + 1, 0});   // 哨兵,方便后面的代码统一
        for (int i = n; i > 0; --i) {
            // 检查右端点超过窗口范围,离开窗口
            while (dq.peekLast()[0] > 2 * i + 1) {
                dq.pollLast();
            }
            int f = prices[i - 1] + dq.peekLast()[1];
            // 将更大的值移除单调队列
            while (f <= dq.peekFirst()[1]) {
                dq.pollFirst();
            }
            dq.offerFirst(new int[]{i, f});
        }
        return dq.peekFirst()[1];   // 返回位置1
    }
}

Q4:100135. 找到最大非递减数组的长度⭐

https://leetcode.cn/problems/find-maximum-non-decreasing-array-length/description/
在这里插入图片描述
提示:

1 <= nums.length <= 10^5
1 <= nums[i] <= 10^5

解法1——单调队列优化 DP

https://leetcode.cn/problems/find-maximum-non-decreasing-array-length/solutions/2542102/dan-diao-dui-lie-you-hua-dp-by-endlessch-j5qd/

从队首到队尾的 j 和 s[j]+last[j] 都是严格递增的。

下面是两种数组模拟单调队列的写法。

class Solution {
    public int findMaximumLength(int[] nums) {
        int n = nums.length;
        long[] s = new long[n + 1];
        int[] dp = new int[n + 1];
        long[] last = new long[n + 1];
        // 数组模拟单调队列,是为了方便取队列中任意位置的元素
        int[] q = new int[n + 1];
        int front = 1, rear = 1;    // 队列中元素的范围是front~rear-1
        for (int i = 1; i <= n; ++i) {
            s[i] = s[i - 1] + nums[i - 1];      // 前缀和
            // 1. 去掉队首无用数据(计算转移时,直接取队首)
            while (front < rear && s[q[front]] + last[q[front]] <= s[i]) front++;
            // 2. 计算转移
            dp[i] = dp[q[front - 1]] + 1;
            last[i] = s[i] - s[q[front - 1]];
            // 3. 去掉队尾无用数据
            while (rear > front && s[q[rear - 1]] + last[q[rear - 1]] >= s[i] + last[i]) {
                rear--;
            }
            q[rear++] = i;
        }
        return dp[n];
    }
}
class Solution {
    public int findMaximumLength(int[] nums) {
        int n = nums.length;
        long[] s = new long[n + 1];
        int[] dp = new int[n + 1];
        long[] last = new long[n + 1];
        // 数组模拟单调队列,是为了方便取队列中任意位置的元素
        int[] q = new int[n + 1];
        int front = 0, rear = 0;    // 队列中元素的范围是front+1~rear
        for (int i = 1; i <= n; ++i) {
            s[i] = s[i - 1] + nums[i - 1];      // 前缀和
            // 1. 去掉队首无用数据(计算转移时,直接取队首)
            while (front < rear && s[q[front + 1]] + last[q[front + 1]] <= s[i]) front++;
            // 2. 计算转移(最后一个被移出去的是有效的)
            dp[i] = dp[q[front]] + 1;
            last[i] = s[i] - s[q[front]];
            // 3. 去掉队尾无用数据
            while (rear > front && s[q[rear]] + last[q[rear]] >= s[i] + last[i]) {
                rear--;
            }
            q[++rear] = i;
        }
        return dp[n];
    }
}

当然也可以用 Deque 来做

class Solution {
    public int findMaximumLength(int[] nums) {
        int n = nums.length;
        long[] s = new long[n + 1];
        int[] dp = new int[n + 1];
        long[] last = new long[n + 1];
        Deque<Integer> dq = new ArrayDeque<>();
        int j = 0;
        for (int i = 1; i <= n; ++i) {
            s[i] = s[i - 1] + nums[i - 1];      // 前缀和
            // 1. 去掉队首无用数据(计算转移时,直接取队首)
            while (!dq.isEmpty() && s[dq.peekFirst()] + last[dq.peekFirst()] <= s[i]) j = dq.pollFirst();
            // 2. 计算转移(最后一个被移出去的是有效的)
            dp[i] = dp[j] + 1;
            last[i] = s[i] - s[j];
            // 3. 去掉队尾无用数据
            while (!dq.isEmpty() && s[dq.peekLast()] + last[dq.peekLast()] >= s[i] + last[i]) {
                dq.pollLast();
            }
            dq.offerLast(i);
        }
        return dp[n];
    }
}

成绩记录

在这里插入图片描述

T4 不会做!
前面的做的也不好!
我是~xx!
在这里插入图片描述

  • 22
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Wei *

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值