文章

贪心5

贪心算法通过每步选择局部最优解,期望最终达到全局最优,适用于最优化和排序问题。

贪心5

贪心5

45. 跳跃游戏 II

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <vector>
#include <algorithm>

using namespace std;

class Solution {
public:
    int jump(vector<int> &nums) {
        int n = nums.size();
        // 当前步以内能到达的最右位置
        int right = 0;
        // 当前步再跳一步,能到达的最右位置
        int next = 0;
        // 总共需要跳的次数
        int res = 0;
        for (int i = 0; i < n; ++i) {
            if (right < i) {
                // 如果当前位置 i 已经超出了当前步能到的范围,说明需要跳一步了
                res++;      // 增加跳跃次数
                right = next; // 更新当前能到的最远位置
            }
            // 更新下一步能跳到的最远位置
            next = max(next, i + nums[i]);
        }
        return res;
    }
};

1326. 灌溉花园的最少水龙头数目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <vector>
#include <algorithm>

using namespace std;

class Solution {
public:
    int minTaps(int n, vector<int> &ranges) {
        // right[i] 表示所有左边界 <= i 的水龙头能灌溉到的最远右边界
        vector<int> right(n + 1, 0);
        for (int i = 0; i <= n; ++i) {
            int start = max(0, i - ranges[i]);
            int end = i + ranges[i];
            right[start] = max(right[start], end);
        }

        // 当前已打开的水龙头能灌溉到的最远位置
        int cur = 0;
        // 再打开一个水龙头能扩展到的最远位置
        int next = 0;
        // 打开的水龙头数量
        int res = 0;

        for (int i = 0; i < n; ++i) {
            // 更新下一步可达到的最远位置
            next = max(next, right[i]);
            // 如果当前水龙头灌溉范围到不了 i+1,就必须开启新水龙头
            if (i == cur) {
                if (next > i) {
                    cur = next;
                    res++;
                } else {
                    return -1; // 无法继续灌溉
                }
            }
        }
        return res;
    }
};

字符串转化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <string>
#include <vector>
#include <algorithm>
#include <iostream>

// 字符串转化
// 给出两个长度相同的字符串str1和str2
// 请你帮忙判断字符串str1能不能在 零次 或 多次 转化后变成字符串str2
// 每一次转化时,你可以将str1中出现的所有相同字母变成其他任何小写英文字母
// 只有在字符串str1能够通过上述方式顺利转化为字符串str2时才能返回true

using namespace std;

class Solution {
public:
    // 判断 str1 是否可以通过若干次规则转换变为 str2
    // 每次转换可以将 str1 中所有相同字母转换成任意小写字母
    bool canConvert(string str1, string str2) {
        if (str1 == str2) return true;

        // 统计 str2 中不同字符的种类数
        vector<int> map(26, 0);
        int kinds = 0;
        for (char c: str2)
            if (map[c - 'a']++ == 0)
                kinds++;

        // 如果 str2 用满了所有 26 个字母,且 str1 != str2,则必然无法转换
        if (kinds == 26) return false;

        // 在 str1 中,一个字符出现的所有位置,在 str2 中的这些位置的字符必须也是相同
        // 检查 str1 中的每个字符是否映射一致地转换为 str2 中对应字符
        fill(map.begin(), map.end(), -1);  // map[x] 表示字符 x 上次在 str1 出现的位置
        for (int i = 0; i < str1.size(); ++i) {
            // str1 中当前位置的字符,map[cur] 表示当前字符上次在 str1 出现的位置
            // 也就是说str1 中当前 i 位置与 map[cur] 位置是相同字符
            int cur = str1[i] - 'a';
            // 那么 str2 中的这两个位置的字符也必须是相同的,否则返回 false
            if (map[cur] != -1 && str2[map[cur]] != str2[i]) return false;
            map[cur] = i;
        }

        return true;
    }
};

int main() {
    Solution sol;
    string str1 = "aabcc";
    string str2 = "ccdee";
    cout << (sol.canConvert(str1, str2) ? "true" : "false") << endl;  // 输出 true
    return 0;
}

P1809 过河问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <cstdio>
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

const int MAXN = 100001;

int nums[MAXN]; // 存储每个人的过河时间
int dp[MAXN];   // dp[i] 表示前 i+1 个最少用时
int n;

// 返回最小的过河总时间
int minCost() {
    // 按过河时间升序排列
    sort(nums, nums + n);

    // 只有一个人,直接过河
    if (n >= 1) dp[0] = nums[0];
    // 两个人,一起过河,耗时较慢者
    if (n >= 2) dp[1] = nums[1];
    // 三个人:先 1 和 2 过去,1 返回,1 和 3 过去
    if (n >= 3) dp[2] = nums[0] + nums[1] + nums[2];

    for (int i = 3; i < n; ++i) {
        // 两种策略:
        // 1. 最快的人送当前人:dp[i-1] + nums[i] + nums[0]
        // 2. 两个最慢的一起过,对岸最快的返回送:dp[i-2] + nums[0] + 2 * nums[1] + nums[i]
        dp[i] = min(dp[i - 1] + nums[i] + nums[0],
                    dp[i - 2] + nums[0] + 2 * nums[1] + nums[i]);
    }

    return dp[n - 1];
}

int main() {
    while (cin >> n) {
        for (int i = 0; i < n; ++i) {
            cin >> nums[i];
        }
        cout << minCost() << '\n';
    }

    return 0;
}

517. 超级洗衣机

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <cstdio>
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

class Solution {
public:
    int findMinMoves(vector<int> &machines) {
        int n = machines.size();
        int sum = 0;
        for (int x: machines) sum += x;
        // 无法平均分配,直接返回 -1
        if (sum % n != 0) return -1;

        int avg = sum / n;      // 每台洗衣机最终的目标值
        int leftSum = 0;        // 左侧衣物数量总和
        int leftNeed = 0;       // 左侧需要的衣物数量
        int rightNeed = 0;      // 右侧需要的衣物数量
        int bottleNeck = 0;     // 当前点操作的瓶颈值
        int res = 0;            // 最终所需的最少操作次数

        for (int i = 0; i < n; ++i) {
            leftNeed = i * avg - leftSum;
            rightNeed = (n - i - 1) * avg - (sum - leftSum - machines[i]);
            if (leftNeed > 0 && rightNeed > 0) {
                // 两边都需要从当前位置获得衣物
                bottleNeck = leftNeed + rightNeed;
            } else {
                // 只需要满足一边,或者多出来了,取最大绝对值
                bottleNeck = max(abs(leftNeed), abs(rightNeed));
            }
            res = max(res, bottleNeck);
            leftSum += machines[i];
        }

        return res;
    }
};
本文由作者按照 CC BY 4.0 进行授权