【力扣】2025.10 每日一题
本文最后更新于55 天前,其中的信息可能已经过时,如有错误请发送邮件到marcelozoeng@qq.com

【5】417. 太平洋大西洋水流问题

思路

DFS/BFS,反向搜索,判断每个新位置是否能流到现在的位置。用矩阵记录是否能到达,使用位运算进行优化(1代表太平洋,2代表大西洋)。

代码

DFS

static vector<vector<int>> dirs{{-1, 0}, {0, 1}, {1, 0}, {0, -1}};
class Solution {
public:
    void dfs(int x, int y, int v, vector<vector<int>> &heights, vector<vector<int>> &waves){
        if(waves[x][y] & v) return;
        waves[x][y] |= v;

        int m = waves.size();
        int n = waves[0].size();

        for(const auto &d : dirs){
            int nx = x + d[0];
            int ny = y + d[1];
            if(nx >= 0 && nx < m && ny >= 0 && ny < n && heights[nx][ny] >= heights[x][y])
            dfs(nx, ny, v, heights, waves);
        }
    }
    vector<vector<int>> pacificAtlantic(vector<vector<int>>& heights) {
        const int M = heights.size();
        const int N = heights[0].size();
        // 流动记录矩阵
        vector<vector<int>> waves(M, vector<int>(N));

        for(int i = 0; i < M; i++){
            dfs(i, 0, 1, heights, waves);// 左边一列 太平洋
            dfs(i, N - 1, 2, heights, waves);// 右边一列 大西洋
        }
        for(int j = 0; j < N; j++){
            dfs(0, j, 1, heights, waves);// 上面一列 太平洋
            dfs(M - 1, j, 2, heights, waves);// 下面一列 大西洋
        }

        vector<vector<int>> ans;
        for(int i = 0; i < M; i++)
            for(int j = 0; j < N ; j++)
            if(waves[i][j] == 3)
                ans.push_back({i, j});
        return ans;
    }
};
// 简洁写法
class Solution {
public:
    vector<vector<int>> pacificAtlantic(vector<vector<int>>& heights) {
        const int m = heights.size();
        const int n = heights[0].size();
        vector<vector<int>> s(m, vector<int>(n));
        vector<vector<int>> dirs{{-1, 0}, {0, 1}, {1, 0}, {0, -1}};

        auto dfs = [&](this auto&& dfs, int x, int y, int v) {
            if (s[x][y] & v) return;
            s[x][y] |= v;
            for (const auto& d : dirs) {
                int nx = x + d[0];
                int ny = y + d[1];
                if (nx >= 0 && nx < m && ny >= 0 && ny < n && heights[x][y] <= heights[nx][ny]) {
                    dfs(nx, ny, v);
                }
            }
        };

        for (int i = 0; i < m; ++i) {
            dfs(i, 0, 1);
            dfs(i, n - 1, 2);
        }
        for (int j = 0; j < n; ++j) {
            dfs(0, j, 1);
            dfs(m - 1, j, 2);
        }

        vector<vector<int>> ans;
        for (int i = 0; i < m; ++i) {
            for (int j = 0; j < n; ++j) {
                if (s[i][j] == 3) ans.push_back({i, j});
            }
        }
        return ans;
    }
};

BFS

懒得写了

【6】778. 水位上升的泳池中游泳

思路

注意:题目上游泳不需要时间,即可以瞬间移动(神人)。所以,在只要有一条能从0, 0通到N – 1, N – 1的路径存在的情况下,使等待时间t变得最小。

1.二分查找 + 遍历

二分查找相关题目:单调性分析 -> 区间搜索范围 -> 区间搜索方法
单调性分析:题目中提供等待的时间t,而t又跟可游泳的网格分布有关。t越大,可游泳的网格分布越大,满足条件的可能性越大;t越小,可游泳的网格分布越小,满足条件的可能性越小。
区间搜索范围:[0, N * N – 1]
区间搜索方法:当小于等于该数值时,如果路径存在,那么值一定小于等于该数值;如果路径不存在,那么值一定大于该数值。

2.并查集

模拟下雨,

3.Dijkstra

应用前提:1.正权 2.单源最短路径

代码

1.二分查找 + 遍历

static vector<vector<int>> dirs = {{-1, 0}, {0, 1}, {1, 0}, {0, -1}};
class Solution {
public:
    bool dfs(vector<vector<int>> &grid, int x, int y, vector<vector<bool>>& visited, int th){
        if(x == grid.size() - 1 && y == grid.size() - 1) return true;
        for(const auto &d : dirs){
            int nx = x + d[0];
            int ny = y + d[1];
            if(nx >= 0 && nx < grid.size() && ny >= 0 && ny < grid[0].size() && visited[nx][ny] == false && grid[nx][ny] <= th){
                visited[nx][ny] = true;
                if(dfs(grid, nx, ny, visited, th)) return true;
            }
        }
        return false;
    }
    int swimInWater(vector<vector<int>>& grid) {
        const int N = grid.size();
        int left = 0, right = N * N - 1;
        while(left < right){
            int mid = (left + right) >> 1;
            vector<vector<bool>>visited(N,vector<bool>(N,false));
            if(grid[0][0] <= mid&&dfs(grid, 0, 0, visited, mid)) right = mid;
            else{
                left = mid + 1;
            }
        }
        return left;
    }
};

【7】1488. 避免洪水泛滥

思路

下雨不可抽,不下雨可抽。导致洪水泛滥的原因是:前面湖泊已满,后面还要下雨。那么,我们是否可以看未来哪个湖泊最可能先引发洪水泛滥,进而考虑我们抽水的湖泊呢?
换句话说就是:延迟抽水,等到后面要发生洪水的时候,再决定之前抽水的湖泊

1.贪心 + 二分查找

使用有序集合st来存晴天的下标。在哈希表mp中,first存有水的湖泊,second存下标。有序集合 st 的排序方式是按照晴天日子的顺序排列的,这就确保了我们总是在最早的晴天日子中进行抽干操作,以最大程度地避免洪水的发生

对于最后剩余的晴天,我们可以将它们用于抽干任意一个湖泊,为了方便,我们令其为 1。初始化时,我们将所有晴天的默认操作设为抽干湖泊1,这是一种“安全”的占位符。因为即使湖泊1是空的,抽干一个空的湖泊也不会产生负面影响(题目描述中抽干空湖泊是无事发生的)。

  • 若 rains[i]=0,则将 i 加入有序集合 st。
  • 若 rains[i]>0,表示第 rains[i] 湖泊将下雨,令 ans[i]=−1 表示这一天的湖泊不可抽干:
  • 若第 rains[i] 是第一次下雨,则此时不会发生洪水。
  • 否则我们需要在有序集合 st 中找到大于等于该湖泊上一次下雨天数的最小索引 idx(可以用二分查找实现),如果 idx 不存在(即没有晴天可以用于抽干),此时不能避免洪水的发生,按照题目要求返回一个空数组。否则我们令 ans[idx]=rains[i],并在 st 中删除 idx,表示我们会在第 idx 天抽干 rains[i] 湖泊的水来避免第 i 天洪水的发生。

代码

1.贪心 + 二分查找

class Solution {
public:
    vector<int> avoidFlood(vector<int>& rains) {
        vector<int> ans(rains.size(), 1);
        set<int> st;
        unordered_map<int, int> mp;
        for (int i = 0; i < rains.size(); ++i) {
            if (rains[i] == 0) {
                st.insert(i);
            } else {
                ans[i] = -1;
                if (mp.count(rains[i])) {
                    auto it = st.lower_bound(mp[rains[i]]);
                    if (it == st.end()) {
                        return {};
                    }
                    ans[*it] = rains[i];
                    st.erase(it);
                }
                mp[rains[i]] = i;
            }
        }
        return ans;
    }
};

【8】2300. 咒语和药水的成功对数

思路

1.二分查找

公式:spells[i] * potions[j] >= success

spells[i]越大或potions[j]越大,成功概率越高。因为我们要咒语组合成功的药水数目,所以我们使用spells[i]来遍历数组。

逆推出:potions[j] >= success / spells[i]

可见,具有单调性,可以使用二分查找解决。

代码

1.二分查找

class Solution {
public:
    vector<int> successfulPairs(vector<int>& spells, vector<int>& potions, long long success) {
        sort(potions.begin(), potions.end());
        vector<int> res;
        for(auto & i : spells){
            // ​向上取整 ceil(a/b) = (a + b - 1) / b
            // 不小于 success / spells[i] 的最小整数,所以需要-1
            long long t = (success + i - 1)/ i - 1;
            // 取位置,算剩下potions的数量
            res.push_back(potions.size() - (upper_bound(potions.begin(), potions.end(), t) - potions.begin()));
        }
        return res;
    }
};

【9】3494. 酿造药水需要的最少总时间

思路

题目要求:每个药水都必须经过每个巫师处理,每个巫师同时间只能够处理一瓶药水,并且每个药水开始酿造后就无法停下。

注意:题目中每个药水要 必须 依次通过 所有 巫师处理

1.动态规划

从时刻0开始酿造药水,可以计算出后面的巫师结束酿造的时间。后面的巫师开始酿造的时间取决于前面的巫师结束酿造的时间,具有连续性,不能确定下来。所以,我们假设允许第 j 瓶药水的酿造过程不连续,前 j – 1 瓶仍连续。

公式推导:
设第 i 位巫师处理完第 j 瓶药水的时间是times[i][j],那么有:

times[i][j]=max(times[i−1][j],times[i][j−1])+skill[imana[j]

由于times[i−1][j]与旧纪录有关,times[i][j−1]与现记录有关,所以我们可以把times压缩成一维,记录旧值与新值。(一位数组滚动优化)

在经过每瓶药水递推后,由于之前假设允许第 j 瓶药水的酿造过程不连续产生了间隔,所以要反向遍历更新。

代码

1.动态规划

class Solution {
public:
    using LL = long long;
    long long minTime(vector<int>& skill, vector<int>& mana) {
        int n = skill.size(), m = mana.size();
        vector<LL> times(n);//降维

        for(int j = 0; j < m; j++){
            LL cur = 0;
            for(int i = 0; i < n; i++)
                cur = max(cur, times[i]) + skill[i] * mana[j]; 
            
            times[n - 1] = cur;
            for(int i = n - 2; i >= 0; i--)
                times[i] = times[i + 1] - skill[i + 1] * mana[j];
        }
        return times[n - 1];
    }
};

【10】3147. 从魔法师身上吸取的最大能量

思路

1.逆序遍历

从前往后看,数组是这样的:
[i]
[i, i + k]
[i, i + k, i + 2k]
[i, i + k, i + 2k, i + 3k]
[i, i + k, i + 2k, i + 3k, i + 4k]

从后往前看,
[i]
[i, i – k]
[i, i – k, i – 2k]
[i, i – k, i – 2k, i – 3k]
[i, i – k, i – 2k, i – 3k, i – 4k]

在这些个路径中,发现都是差为|k|的等差数列,即周期为k。于是,就i从0开始遍历到k-1。从题目中可以知道,最后要到达末尾,而起点是未知的,所以从末尾到起点逆着推比较好(就i从n-k开始遍历到n),这样也避免了一个问题(当energy数组中出现负数时,避免写多余代码修正数字)。

代码

1.逆序遍历

class Solution {
public:
    int maximumEnergy(vector<int>& energy, int k) {
        int n = energy.size(), ans = INT_MIN;
        //采用逆序是为了避免修正数字
        for(int i = n - k; i < n; i++){
            int sum = 0;
            for(int j = i;j >= 0; j -= k){
                sum += energy[j];
                ans = max(ans, sum);
            }
        }
        return ans;
    }
};

【11】3186. 施咒的最大总伤害

思路

1.动态规划

使用一个咒语power[i]后,就不能使用伤害绝对值相差1-2的咒语。所以,咒语的选择与power的顺序无关,与咒语伤害值有关。于是,可以先把一样伤害的咒语存到一起去。

设伤害为poweri 的咒语数量为counti 。令f(i)表示从 0 到第 i 种咒语中选择,并且最后选择第 i 种咒语的最大总伤害。公式如下:

f(i) = f(j)max(powerj < poweri – 2) + poweri * counti

按伤害递增顺序遍历咒语,用单调指针维护f(j)max ,即用于转移的最大值,最后答案为f中的最大值。

代码

1.动态规划

class Solution {
public:
    long long maximumTotalDamage(vector<int>& power) {
        map<int, int> mp;
        for(int i: power){
            mp[i]++;
        }
        vector<pair<int, int>> p = {{-1e9, 0}};
        for(auto &i: mp){
            p.push_back(i);
        }
        int n = p.size();
        vector<long long> dp(n, 0);
        long long mx = 0;
        for(int i = 1, j = 1; i < n; i++){
            while(j < i && p[i].first - 2 > p[j].first){
                mx = max(mx, dp[j]);
                j++;
            }
            dp[i] = mx + 1LL * p[i].first * p[i].second;
        }
        long long ans = 0;
        for(int i = 0; i < n; i++)
            ans = max(ans, dp[i]);
        return ans;
    }
};
文末附加内容
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇