力扣周赛日记350

这篇具有很好参考价值的文章主要介绍了力扣周赛日记350。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1. 前言

早上兴高采烈起床写周赛,结果写完两题开始坐牢。菜的很。

2. 赛题

LeetCode 第 350 场周赛

2.1 题1

LeetCode 6901. 总行驶距离

2.1.1 题意

卡车两个油箱,耗油1L行驶10km。油箱A耗5L,油箱B给邮箱A油1L。油箱A空后停止行驶,求可行使距离。

2.1.2 分析

开始想O(1)解法,发现这题主要问题在油箱B给了油箱A的油在消耗过后也计算在油箱A的油中。没理清O(1)思路,数据量不大,直接模拟。

1 <= mainTank, additionalTank <= 100

2.1.3 我的解法

class Solution {
public:
    int distanceTraveled(int mainTank, int additionalTank) {
        int res = 0;
        while(mainTank > 0){// 油箱A还有油
            if(mainTank >= 5){
                // 油箱A的油可以消耗5L
                // 也就是油箱B可以给油箱A油
                mainTank -= 5;
                res += 50;
                if(additionalTank >= 1){
                    // 前提是油箱B还有油
                    mainTank++;
                    additionalTank--;
                }
            }
            else{
                // 油箱A的油不足5L
                // 直接消耗
                res += (mainTank * 10);
                mainTank=0;
            }
        }
        return res;
    }
};

2.1.4 学习题解反思

我的解法:
时间复杂度O( (m+n)/5 ), (m,n为A、B油箱中的油量
空间复杂度O(1)

学习了一下O(1)解法,这个想法确实好。再加上一些油箱B的可补充量就可以算出总耗油量。

消耗5 升油 → 补充 1 升油,相当于花费 4 升油

2.2 题2

LeetCode 6890. 找出分区值

2.2.1 题意

将原数组分为非空两份,求第一份中最大值和第二份中最小值的差的绝对值最小的结果

2.2.2 分析

看一下数据,复杂度大概在O(nlogn)级别。

2 <= nums.length <= 10e5
1 <= nums[i] <= 10e9

这个时间复杂度级别可以很快想到排序。排序之后对数组切一刀,第一份中最大值和第二份中最小值就是排序后数组相邻的两个数,所以只需要找到排序后相邻两个数的最小差值即可

2.2.3 我的解法

class Solution {
public:
    int findValueOfPartition(vector<int>& nums) {
        sort(nums.begin(), nums.end()); // 排序
        int res = INT_MAX;  // 记录结果
        int n = nums.size();    
        for(int i = 1; i<n; i++){
            // 求相邻两个数的差
            res = min(res, abs(nums[i]-nums[i-1]));
        }
        return res;
    }
};

2.2.4 学习题解反思

我的解法:
时间复杂度O(nlogn),
空间复杂度O(1)

2.3 题3

LeetCode 6893. 特别的排列

2.3.1 题意

给定一个数组,求满足该条件的排列数:对于每个位置i,这个位置的数能被前一个数整除或者被后一个数整除。

2.3.2 分析

看一下数据,范围很小,时间复杂度可以到O( n 2 ∗ 2 n n^2*2^n n22n)

2 <= nums.length <= 14
1 <= nums[i] <= 109

我的思路是把他想象成图,每个数对应一个节点,然后每个边对应整除关系,那么从节点a到节点b并且经过所有点的一种路径,对应上就是一种数组的排列。最后求结果需要求这个图中所有的哈密尔顿回路的数量。(我觉得我这个想法还是很有趣的,但是没什么特别的用)

首先数据集很小,使用状态压缩来存状态信息,递推过程中需要关注的信息有:路径中上一个落点的,以及已访问的点的信息

高16位,表示上一个落点的位置
低16位,为1的位表示已经访问过

思考状态转移

转移到状态j:上一轮中所有的可以转移到状态k的和
判断可否转移,需要用到两个状态中存的信息。

2.3.3 我的解法

class Solution {
public:
    const int MOD = 1e9 + 7; 
    long res = 0;
    int specialPerm(vector<int>& nums) {
        int n = nums.size();
        vector<vector<int> > g(n, vector<int>(n, 0) );
        // build graph
        // 建图
        for(int i=0; i<n; i++){
            for(int j=0; j<n; j++){
                if(i!=j && ( (nums[j]%nums[i]==0) || (nums[i]%nums[j]==0) )){
                    g[i][j]=1;
                }
            }
        }
        // 状压 dp 
        // 按集合看,不关注顶点,只关注上一个点,和已经走过的点

        // dp[i][j] 表示第i步走到状态j有多少种走法
        // 状态j中包含了上一个点和已经走过的点的信息
        // 高16位,表示上一个落点的位置
        // 低16位,为1的位置表示已经访问过

        // 想一下递推式
        // dp[i][j] = sum(dp[i-1][k] 所有可转移到状态j的状态k)

        // 这里其实可以用两个map(滚动数组
        vector<unordered_map<int,int> > dp;
        // 初始化
        unordered_map<int, int> init;
        // 把所有顶点情况都初始化为1
        for(int i=0; i<n; i++)
            init[(i<<16) | (1<<i)] = 1;
        dp.emplace_back(init);
        // dp
        for(int k=1; k<n; k++){
            auto b = dp.back();
            unordered_map<int, int> tmp;
            for(auto it=b.begin(); it!=b.end(); it++){
                // 遍历所有状态
                // 解析出上一个位置,以及已经访问过的位置
                int lastPos = (it->first) >> 16;
                int vis = (it->first) & 0xffff;
                for(int l=0; l<n; l++){
                    if(g[lastPos][l] && ( (vis & (1<<l)) == 0)){
                        int index = (l<<16) | (vis | (1<<l) );
                        tmp[index]= (tmp[index]%MOD + ((it->second)%MOD) )%MOD;
                    }
                }
            }
            dp.emplace_back(tmp);
        }
        // get answer
        for(auto it=dp.back().begin(); it!=dp.back().end(); it++){
            if( ( ( (it) -> first ) & 0xffff) == (1<<n) - 1  )
                // 最后所有点都访问过的状态
                res = (res%MOD + (it->second)%MOD) %MOD;
        }
            
        return res % MOD;
    }
};
// [1,2,4,8,16,32,64,128,256,512,1024,2048,4096,8192]

2.3.4 学习题解反思

我的解法:
时间复杂度O( n 2 ∗ 2 n n^2*2^n n22n),
空间复杂度O( n ∗ 2 n n*2^n n2n) (这个可以优化到O( 2 n 2^n 2n) )

2.3.5 bug日记

2.3.5.1 复杂度估计错误

我从这题开始坐牢
使用回溯法去求哈密尔顿回路数量,当时估计时间复杂度的时候我估计的是O( n 2 ∗ 2 n n^2*2^n n22n),因为枚举左右端点,加上回溯法的时间。但实际上他是一个全排列O(n!),因为每种合理的情况都会遍历到。
错误估计的原因:使用状态压缩(使用14位记录,每一位如果为1表示已经访问,如果为0表示没有访问)来标记是否经过了该点,然后估计了所有的状态数2^n,但是,实际上并非每个状态只访问一次。提交后T了。

2.3.5.2 起点和终点并不重要

在赛后补题的时候,我想到了这几天刚写的题。
但是由于先入为主的觉得无向图,从起点到终点,枚举一半的情况结果乘2可以省时间,导致dp复杂度到了O( n 4 ∗ 2 n n^4*2^n n42n)(实际会比这小,因为不会枚举到2^n的状态),只能算到n=12的情况,然后想办法剪枝,针对完全图的情况哈密尔顿通路总数量就是n!,但是还是会有样例过不了。

看了灵茶山艾府的讲解,明白了(by the way 灵神太强了也,10min开始可以看榜单,刚好切出去看题,发现灵神已经全A了)。

灵神的讲解记忆化搜索中重复的状态是没有选的点集,和上一个选的点的下标的元组。也就是起点和终点不重要,重要的是已经走过的集合,以及已经走过的集合的上一个点,所以不需要枚举起点和终点,这样才能有重复状态被利用,时间复杂度O( n 2 ∗ 2 n n^2*2^n n22n)。

2.4 题4

LeetCode2742. 给墙壁刷油漆

2.4.1 题意

n堵墙,刷墙有对应的时间time和花费cost。2个油漆匠,一个免费用时1可刷1堵墙,一个付费用时和开销对应刷墙的time和cost,且免费油漆匠只在付费油漆匠工作时工作

2.4.2 分析

看一下数据,复杂度大概在O( n 3 n^3 n3)级别。

1 <= cost.length <= 500
cost.length == time.length
1 <= cost[i] <= 106
1 <= time[i] <= 500

先考虑贪心是否可以解,贪心的选择单位时间花销少的墙给付费油漆匠刷。但是如果单位时间花销少,但是他需要的时间长,就可能不是最优解,举一个极限的例子

cost = [1000, 1] time = [10000, 2]

那就考虑动态规划。因为每面墙都有两个选择,免费刷和付费刷。这里很关键的需要记录的信息是可够免费刷墙的时间,和状态息息相关。

dp[i][t] 表示刷到第i面墙,可够免费刷墙时间t 的最小花销
这个t应该要可以为负数范围[-n, n] 长度是2n+1 bias = n-1

(为什么最大免费刷墙的时间是这个?因为只要超过n就表示所有墙都可以免费刷,这在情况中需要做特殊处理。实际上这个负数范围取不到正负n,因为至少一个墙付费刷)

递推式分两个情况

dp[i][t] = dp[i-1][t+1] // 免费刷 免费刷时间-1,价格不变
dp[i][t] = dp[i-1][t-time[i]] + cost[i] // 付费刷,免费刷时间+time 价格+cost

对于特殊情况

特殊情况time[i] 很大 -> 最大免费刷墙时间的开销为cost[i]
(因为设定了最大免费刷墙的时间范围,多余的时间范围没有用)
免费刷的递推相同,付费刷的递归:dp[i][t_max] = cost[i]

初始化情况

dp[0][-1] = 0 // 免费刷
dp[0][time[0]] = cost[0] // 付费刷
还要注意time[0]很大情况的初始化

加上一点点细节,对于正负范围,加上一个偏置n即可。

2.4.3 我的解法

class Solution {
public:
    int paintWalls(vector<int>& cost, vector<int>& time) {
        // 贪? 单位时间花销少的 -> 时间大
        // 如: cost = [1000, 1]  time = [10000, 2]

        // 组合 1 <= cost.length <= 500
        // 复杂度: n^3
        
        // dp[i][t] 表示刷到第i面墙,可够免费刷墙时间t 的最小花销
        // 这个t应该要可以为负数范围[-n, n] 长度是2n+1 bias = n-1 
        
        // dp[i][t] = dp[i-1][t+1]     // 免费刷
        // dp[i][t] = dp[i-1][t-time[i]] + cost[i] // 付费刷
        // 特殊情况time[i] 很大 -> 最大免费刷墙时间的开销为cost[i]
        // 免费刷的递推相同,付费刷的递归:dp[i][t_max] = cost[i]

        // 初始化 dp[0][-1] = 0 // 免费刷
        //        dp[0][time[0]] = cost[0]
        int n = time.size();
        // 这里空间多开1,避免递推时越界
        vector<vector<int> > dp(n, vector<int> (n*2 + 2, INT_MAX) ); 
        // 初始化
        dp[0][n - 1] = 0; // 免费刷
        // 付费刷
        if(n+time[0] < 2 * n +1){  
            dp[0][n + time[0]] = cost[0];
        }
        else{
            dp[0][2 * n +1] = cost[0];
        }
        // 递推
        for(int i=1; i<n; i++){
            for(int t=0; t<(2*n + 1); t++){
                dp[i][t] = dp[i-1][t+1]; // 免费刷
                if(t >= time[i] && dp[i-1][t-time[i]] != INT_MAX){
                    // 付费刷
                    dp[i][t] = min(dp[i][t], dp[i-1][t-time[i]] + cost[i]);
                }
            }
            if(time[i] >= n){
                // time[i] 很大
                // 如果付费刷直接更新上限
                dp[i][2*n + 1] = min( dp[i][2*n + 1], min(dp[i-1][2*n + 1], cost[i]) );
            }
        }
        // 找答案
        // 从 0~n 范围找
        int res = INT_MAX;
        for(int t=n; t<2*n+1; t++){
            res = min(res, dp[n-1][t]);
        }
        return res;
    }
};
/*
30 51 3
 3  1 3

[-10, 10]

[82,30,94,55,76,94,51,82,3,89]
[2,  3, 3, 1, 2, 2, 1, 2,3 ,2]
     ^              ^    ^
*/

2.4.4 学习题解反思

我的解法:
时间复杂度O( n 2 n^2 n2),
空间复杂度O( n 2 n^2 n2)

灵神转化01背包这招太强了,直接不考虑免费刷墙的状态了。学习了。

2.4.5 bug日记

2.4.5.1 初始化

初始化时,忘记了付费刷墙可能有时间time[i]很大的情况

2.4.5.2 范围限制

加上范围限制后,关于time[i]很大的情况处理想了很久。

3. 后记

这次周赛补题补了好久,咱就是说菜哭了。
仅分享自己的想法,有意见和指点非常感谢文章来源地址https://www.toymoban.com/news/detail-498679.html

到了这里,关于力扣周赛日记350的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【力扣周赛】第 113 场双周赛(贪心&异或性质&换根DP)

    https://leetcode.cn/contest/biweekly-contest-113/ https://leetcode.cn/problems/minimum-right-shifts-to-sort-the-array/ 提示: 1 = nums.length = 100 1 = nums[i] = 100 nums 中的整数互不相同。 因为数据范围很小,所以可以从小到大枚举可能的答案。 https://leetcode.cn/problems/minimum-array-length-after-pair-removals/ 提示: 1

    2024年02月07日
    浏览(32)
  • 力扣刷题实录(大厂用题)—— 前言

    力扣刷题笔记与力扣官方的解答有什么区别吗?为什么不直接去看官方的解答呢 ?并且官方的解答部分还有视频讲解。 这个问题困扰了我很长时间,我不断地怀疑自己做笔记是否有意义。 后来有一个小伙伴问我问题的时候我悟了,那时手头事情比较多,我说为什么不看官方

    2024年02月09日
    浏览(110)
  • 力扣119双周赛

    模拟 贪心,一个变了下一个肯定不用变 滑动窗扣维持k个 二进制枚举+Floyd –

    2024年02月04日
    浏览(37)
  • 力扣376周赛

    map模拟 排序后直接模拟 找到中位数再二分最近的回文 滑动窗口统计化成中位数的最小步骤 –

    2024年02月04日
    浏览(28)
  • 力扣第357场周赛补题

    6925. 故障键盘 - 力扣(LeetCode) 思路:模拟 6953. 判断是否能拆分数组 - 力扣(LeetCode) 思路:脑筋急转弯 2812. 找出最安全路径 - 力扣(LeetCode) 思路:多源bfs+倒序枚举+并查集

    2024年02月14日
    浏览(37)
  • 【算法】力扣第 284 场周赛(最短代码)

    看数据范围 1 = nums.length = 1000 ,直接暴力 2行 搞定 看数据范围 1 = n = 1000 ,每个工件最多只覆盖4个单元格,直接哈希+暴力, 2行搞定 这题比较吃细节,推荐大家看一下灵茶山艾府大佬的题解, 1行 就搞定了 三次dijkstra,可可也是看了题解之后才做出来, 15行 解法👇 T4罚坐一

    2024年02月13日
    浏览(46)
  • 力扣377周赛第三题(图论题目)

     

    2024年02月04日
    浏览(53)
  • 力扣日记121

    LeetCode 121 买卖股票的最佳时机 给定每天股价,选择一天买入一天卖出,时间上满足先后顺序,求最大利润 时间复杂度在O(n) 1 = prices.length = 10e5 简单想到对于某个位置找前面出现的最小值和后面出现的最大值,然后做差。 对于每个位置每次查找时间复杂度上O(n^2),可以先查找

    2024年02月09日
    浏览(14)
  • 力扣日记88

    LeetCode 88. 合并两个有序数组 将两个有序数组合并 第一直接想到归并排序,但是本题有些特殊 合并后数组存储在数组 nums1 中。为了应对这种情况,nums1 的初始长度为 m + n,其中前 m 个元素表示应合并的元素,后 n 个元素为 0 ,应忽略。nums2 的长度为 n 。 这个题目一下子激发

    2024年02月09日
    浏览(14)
  • 力扣日记1262

    LeetCode 1262. 可被三整除的最大和 数组中取数,使得和可以整出3且最大,每个数最多选一次 看数据量,复杂度可以到O(nlogn)往上 1 = nums.length = 4 * 10^4 1 = nums[i] = 10^4 思考一下怎么选数, 先想贪心。看出来这个题的答案是需要选数的组合,贪心不出来。 想动态规划。 对于下标

    2024年02月11日
    浏览(35)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包