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

文章目录

  • 竞赛链接
  • Q1:7004. 判别首字母缩略词(模拟)
  • Q2:6450. k-avoiding 数组的最小总和
    • 解法1——贪心+哈希表
    • 解法2——数学公式
  • Q3:7006. 销售利润最大化⭐⭐⭐
    • 线性DP
    • 相似题目列表
      • 2008. 出租车的最大盈利(和本次周塞题几乎一模一样)
      • 1235. 规划兼职工作(数据范围更大的情况)⭐⭐⭐⭐⭐
        • 解法——动态规划 + 二分查找优化
      • 1751. 最多可以参加的会议数目 II(区间个数限制)(dp + 二分)
      • 2054. 两个最好的不重叠活动
        • 解法1——两次二分
        • 解法2——用优先队列维护另一个活动的最大价值
  • Q4:6467. 找出最长等值子数组
    • 竞赛时代码——传统双指针
    • 解法2——分组+双指针
    • 相关思考题——把[删除]改为[修改]k个数
  • 成绩记录

竞赛链接

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

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

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

【力扣周赛】第 359 场周赛(选择区间型DP⭐⭐⭐⭐⭐新题型 & 双指针)_第1张图片
提示:
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/

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

提示:
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/

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

换成一句话就是说—— 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/

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

提示:
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];
        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) {
                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/
【力扣周赛】第 359 场周赛(选择区间型DP⭐⭐⭐⭐⭐新题型 & 双指针)_第5张图片
提示:
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/

【力扣周赛】第 359 场周赛(选择区间型DP⭐⭐⭐⭐⭐新题型 & 双指针)_第6张图片
提示:
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/
【力扣周赛】第 359 场周赛(选择区间型DP⭐⭐⭐⭐⭐新题型 & 双指针)_第7张图片
提示:
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/

【力扣周赛】第 359 场周赛(选择区间型DP⭐⭐⭐⭐⭐新题型 & 双指针)_第8张图片
提示:
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/

【力扣周赛】第 359 场周赛(选择区间型DP⭐⭐⭐⭐⭐新题型 & 双指针)_第9张图片
提示:
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. 毯子覆盖的最多白色砖块数

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

提示:
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;
    }
} 

成绩记录

在这里插入图片描述

脑子有点木掉了。

【力扣周赛】第 359 场周赛(选择区间型DP⭐⭐⭐⭐⭐新题型 & 双指针)_第11张图片
这周掉大分!!!!!

你可能感兴趣的:(算法刷题记录,leetcode,java,算法,动态规划,双指针,二分查找)