【力扣周赛】第 359 场周赛(选择区间型DP⭐⭐⭐⭐⭐新题型 & 双指针)

竞赛链接

https://leetcode.cn/contest/weekly-contest-359/

Q1:7004. 判别首字母缩略词(模拟)

https://leetcode.cn/problems/check-if-a-string-is-an-acronym-of-words/

在这里插入图片描述
提示:
1 <= words.length <= 100
1 <= words[i].length <= 10
1 <= s.length <= 100
words[i] 和 s 由小写英文字母组成

class Solution {
    public boolean isAcronym(List<String> words, String s) {
        if (words.size() != s.length()) return false;
        for (int i = 0; i < s.length(); ++i) {
            if (words.get(i).charAt(0) != s.charAt(i)) return false;
        }
        return true;
    }
}

Q2:6450. k-avoiding 数组的最小总和

https://leetcode.cn/problems/determine-the-minimum-sum-of-a-k-avoiding-array/

在这里插入图片描述

提示:
1 <= n, k <= 50

解法1——贪心+哈希表

从小到大枚举所有正整数,只要与目前数组中的元素求和不等于 k ,就可以放入。
直到放入了 n 个元素。

class Solution {
    public int minimumSum(int n, int k) {
        Set<Integer> s = new HashSet<>();
        int i = 1, ans = 0;     // 从1开始尝试
        while (s.size() < n) {
            if (!s.contains(k - i)) {   // 只要可以放入数组就放
                s.add(i);
                ans += i;
            }
            i++;
        }
        return ans;
    }
}

解法2——数学公式

https://leetcode.cn/problems/determine-the-minimum-sum-of-a-k-avoiding-array/solutions/2396408/o1-gong-shi-pythonjavacgo-by-endlesschen-cztk/

在这里插入图片描述

换成一句话就是说—— 0 ~ k 之间可以选最多 k / 2 个,缺少的从 k + 1 开始选,一定不会有冲突(因为大于 k 的数字和任意正整数之和不可能为 k)。

class Solution {
    public int minimumSum(int n, int k) {
        int m = Math.min(k / 2, n);
        return (m * (m + 1) + (k * 2 + n - m - 1) * (n - m)) / 2;
    }
}

Q3:7006. 销售利润最大化⭐⭐⭐

https://leetcode.cn/problems/maximize-the-profit-as-the-salesman/

在这里插入图片描述

提示:
1 <= n <= 10^5
1 <= offers.length <= 10^5
offers[i].length == 3
0 <= starti <= endi <= n - 1
1 <= goldi <= 10^3

线性DP

定义 f[i+1] 表示销售编号不超过 i 的房屋的最大盈利。

dp 数组的范围是 n,即房屋数量而不是买家数量(注意这和下面的相似题目的解法是不同的,原因是数据范围的不同)。

class Solution {
    public int maximizeTheProfit(int n, List<List<Integer>> offers) {
        Collections.sort(offers, (x, y) -> {
            return x.get(1) - y.get(1);
        });
        int[] dp = new int[n + 1];              // dp[i+1]表示销售编号不超过i的房屋的最大盈利
        int j = 0, m = offers.size();
        for (int i = 0; i <= n; ++i) {
            dp[i] = i == 0? 0: dp[i - 1];       // 至少和上一个一样
            while (j < m && offers.get(j).get(1) == i) {    // 枚举每个以i为结尾的方案offers[j]
                dp[i] = Math.max(dp[i], offers.get(j).get(2) + (offers.get(j).get(0) == 0? 0: dp[offers.get(j).get(0) - 1]));
                ++j;
            }
        }
        return dp[n];
    }
}

相似题目列表

2008. 出租车的最大盈利(和本次周赛题几乎一模一样)

https://leetcode.cn/problems/maximum-earnings-from-taxi/
在这里插入图片描述
提示:
1 <= n <= 10^5
1 <= rides.length <= 3 * 10^4
rides[i].length == 3
1 <= starti < endi <= n
1 <= tipi <= 10^5

注意每次赚的钱是 rides[j][1] - rides[j][0] + rides[j][2]

class Solution {
    public long maxTaxiEarnings(int n, int[][] rides) {
        long[] dp = new long[n + 1];
        // 按照到达目的地的位置排序
        Arrays.sort(rides, (a, b) -> a[1] - b[1]);
        for (int i = 1, j = 0; i <= n; ++i) {
            dp[i] = dp[i - 1];
            while (j < rides.length && rides[j][1] == i) {
                dp[i] = Math.max(dp[i], dp[rides[j][0]] + (rides[j][1] - rides[j][0] + rides[j][2]));
                j++;
            }
        }
        return dp[n];
    }
}

1235. 规划兼职工作(数据范围更大的情况)⭐⭐⭐⭐⭐

https://leetcode.cn/problems/maximum-profit-in-job-scheduling/

在这里插入图片描述
提示:
1 <= startTime.length == endTime.length == profit.length <= 5 * 10^4
1 <= startTime[i] < endTime[i] <= 10^9
1 <= profit[i] <= 10^4

解法——动态规划 + 二分查找优化

https://leetcode.cn/problems/maximum-profit-in-job-scheduling/solutions/1913089/dong-tai-gui-hua-er-fen-cha-zhao-you-hua-zkcg/

由于数据范围更大了,所以只能选择 n 份兼职工作的 n 作为 dp 数组的长度,与此同时要使用二分查找确定上一份可以被同时选择的工作。
dp 数组的范围设置成 方案数 n(而不是时间的范围),使用二分查找来确定上一份可以被选择的工作。

class Solution {
    public int jobScheduling(int[] startTime, int[] endTime, int[] profit) {
        int n = startTime.length;
        int[][] p = new int[n][3];
        for (int i = 0; i < n; ++i) {
            p[i] = new int[]{startTime[i], endTime[i], profit[i]};
        }
        Arrays.sort(p, (a, b) -> a[1] - b[1]);  // 按结束时间排序
        int[] dp = new int[n + 1];
        for (int i = 1; i <= n; ++i) {
            dp[i] = Math.max(dp[i - 1], dp[bs(p, i - 1, p[i - 1][0]) + 1] + p[i - 1][2]);
        }
        return dp[n];
    }

    public int bs(int[][] p, int r, int t) {
        int l = -1;
        while (l < r) {
            int mid = l + r + 1 >> 1;
            if (p[mid][1] <= t) l = mid;
            else r = mid - 1;
        }
        return l;
    }
}

1751. 最多可以参加的会议数目 II(区间个数限制)(dp + 二分)

https://leetcode.cn/problems/maximum-number-of-events-that-can-be-attended-ii/
在这里插入图片描述
提示:
1 <= k <= events.length
1 <= k * events.length <= 10^6
1 <= startDayi <= endDayi <= 10^9
1 <= valuei <= 10^6

class Solution {
    public int maxValue(int[][] events, int k) {
        Arrays.sort(events, (a, b) -> a[1] - b[1]);     // 按照结束时间排序
        int n = events.length;
        int[][] f = new int[n + 1][k + 1];
        for (int i = 0; i < n; ++i) {
            int p = search(events, i, events[i][0]);    // 使用二分查找上一个可以参加的会议
            for (var j = 1; j <= k; ++j) {
                f[i + 1][j] = Math.max(f[i][j], f[p + 1][j - 1] + events[i][2]);
            }
        }
        return f[n][k];
    }

    // 返回 endDay < upper 的最大下标
    private int search(int[][] events, int right, int upper) {
        var left = -1;
        while (left + 1 < right) {
            var mid = (left + right) / 2;
            if (events[mid][1] < upper) left = mid;
            else right = mid;
        }
        return left;
    }
}

有点儿 k 次股票交易那味。

2054. 两个最好的不重叠活动

https://leetcode.cn/problems/two-best-non-overlapping-events/

在这里插入图片描述
提示:
2 <= events.length <= 10^5
events[i].length == 3
1 <= startTimei <= endTimei <= 10^9
1 <= valuei <= 10^6

看到数据范围中 startTime 和 endTime 都比较大,不能作为 dp 数组的范围。

解法1——两次二分
class Solution {
    public int maxTwoEvents(int[][] events) {
        Arrays.sort(events, (a, b) -> {
            return a[1] != b[1]? a[1] - b[1]: a[2] - b[2];
        });
        int n = events.length;
        int[][] dp = new int[n + 1][2];
        for (int i = 0; i < n; ++i) {
            int j1 = bs(events, i, events[i][1]), j2 = bs(events, i, events[i][0]);
            // while (j1 + 1 < i && events[j1 + 1][1] == events[i][0]) j1++;
            dp[i + 1][0] =  Math.max(events[i][2], dp[j1 + 1][0]);;
            dp[i + 1][1] = Math.max(dp[i][1], dp[j2 + 1][0] + events[i][2]);
        }
        return Math.max(dp[n][1], dp[n][0]);
    }

    public int bs(int[][] p, int r, int u) {
        int l = -1;
        while (l < r) {
            int mid = l + r + 1 >> 1;
            if (p[mid][1] >= u) r = mid - 1;
            else l = mid;
        }
        return l;
    }
}
解法2——用优先队列维护另一个活动的最大价值

https://leetcode.cn/problems/two-best-non-overlapping-events/solutions/1075386/yong-you-xian-dui-lie-wei-hu-ling-yi-ge-8ld3x/
由于按照开始时间排序,因为当前元素可以使用的,后面的元素一定也可以使用,即维护 mx。

class Solution {
    public int maxTwoEvents(int[][] events) {
        // 按开始时间排序
        Arrays.sort(events, (a, b) -> (a[0] - b[0]));   
        // 按结束时间排序的堆
        PriorityQueue<int[]> pq = new PriorityQueue<>((a,b) -> a[1] - b[1]);    
        int ans = 0, mx = 0;
        for (int[] event: events) {
            int s = event[0], e = event[1], p = event[2];
            // 当前元素作为后一个,判断前面可以使用的最大值mx
            while (!pq.isEmpty() && pq.peek()[1] < s) {     
                mx = Math.max(mx, pq.poll()[2]);
            }
            // 更新答案
            ans = Math.max(ans, mx + p);
            pq.offer(event);
        }
        return ans;
    }
}

Q4:6467. 找出最长等值子数组

https://leetcode.cn/problems/find-the-longest-equal-subarray/

在这里插入图片描述
提示:
1 <= nums.length <= 10^5
1 <= nums[i] <= nums.length
0 <= k <= nums.length

竞赛时代码——传统双指针

思路就是:枚举右指针,尝试移动左指针,过程中更新答案。

class Solution {
    public int longestEqualSubarray(List<Integer> nums, int k) {
        Map<Integer, Integer> m = new HashMap<>();      // 记录子数组中每个元素的数量
        int n = nums.size(), ans = 0;
        for (int i = 0, j = 0; i < n; ++i) {
            m.merge(nums.get(i), 1, Integer::sum);      // 加入当前元素作为等值子数组的值

            // 尝试移动左端点(向左向右都尝试)
            while (i - j + 1 - m.get(nums.get(i)) <= k && j > 0) {
                m.merge(nums.get(j - 1), 1, Integer::sum);
                --j;
            }
            while (i - j + 1 - m.get(nums.get(i)) > k) {
                m.merge(nums.get(j), -1, Integer::sum);
                ++j;
            }
            ans = Math.max(ans, m.get(nums.get(i)));    // 获得当前等值子数组的长度
        }
        return ans;
    }
}

执行用时 421ms,还是挺慢的。

解法2——分组+双指针

https://leetcode.cn/problems/find-the-longest-equal-subarray/solutions/2396401/fen-zu-shuang-zhi-zhen-pythonjavacgo-by-lqqau/

将各个元素按照值分组之后,分别使用双指针计算以各个值作为等值子数组时的最大长度。

注意如果直接存下标的话没办法在进行双指针时快速获得应该删去的元素数量,所以存储的是 i - pos[x].size(),即存储的是每个位置之前有多少个非当前值。

class Solution {
    public int longestEqualSubarray(List<Integer> nums, int k) {
        int n = nums.size(), ans = 0;
        List<Integer>[] pos = new ArrayList[n + 1];
        Arrays.setAll(pos, e -> new ArrayList<>());
        for (int i = 0; i < n; ++i) {
            int x = nums.get(i);
            pos[x].add(i - pos[x].size());      // 记录i-pos[x].size()而不是i
        }

        for (List<Integer> ls: pos) {
            if (ls.size() < ans) continue;
            for (int l = 0, r = 0; r < ls.size(); ++r) {
                while (ls.get(r) - ls.get(l) > k) ++l;
                ans = Math.max(ans, r - l + 1);
            }
        }
        return ans;
    }
}

相关思考题——把[删除]改为[修改]k个数

在这篇博文中有提到过这道题目:【LeetCode周赛】2022上半年题目精选集——双指针

2271. 毯子覆盖的最多白色砖块数

在这里插入图片描述

提示:
1 <= tiles.length <= 5 * 10^4
tiles[i].length == 2
1 <= li <= ri <= 10^9
1 <= carpetLen <= 10^9
tiles 互相 不会重叠 。

排序 + 双指针

class Solution {
    public int maximumWhiteTiles(int[][] tiles, int carpetLen) {
        Arrays.sort(tiles, (a, b) -> a[0] - b[0]);
        int ans = 0, cur = 0;
        for (int i = 0, j = 0; j < tiles.length; ++j) {
            cur += tiles[j][1] - tiles[j][0] + 1;
            while (tiles[j][1] - tiles[i][1] >= carpetLen) {
                cur -= tiles[i][1] - tiles[i][0] + 1;
                i++;
            }
            ans = Math.max(ans, cur - Math.max(0, tiles[j][1] - carpetLen - tiles[i][0] + 1)); 
        }
        return ans;
    }
} 

成绩记录

在这里插入图片描述

脑子有点木掉了。

在这里插入图片描述
这周掉大分!!!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Wei *

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

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

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

打赏作者

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

抵扣说明:

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

余额充值