LeetCode第 31 场双周赛详解

LeetCode第 31 场双周赛

1.在区间范围内统计奇数数目[原题链接]

题意:

给你两个非负整数 lowhigh 。请你返回 lowhigh 之间(包括二者)奇数的数目。

数据范围:

0 <= low <= high <= 10^9

思路:

首先我们分析[low, high]总共有几对数(连续的两个数),那么每一对数有且只有一个奇数,再考虑只有奇数个数的情况下,最后一个数是否是奇数即可。

代码:
class Solution {
public:
    int countOdds(int low, int high) {
        int ret =  (high - low + 1) / 2;
        if ((low & 1) && (high & 1)) ++ret;
        
        return ret;
    }
};

2.和为奇数的子数组数目[原题链接]

题意:

给你一个整数数组 arr 。请你返回和为 奇数 的子数组数目。

由于答案可能会很大,请你将结果对 1e9 + 7 取余后返回。

数据范围:

1 <= arr.length <= 1e5, 1 <= arr[i] <= 100

思路:

可以枚举以a[i]为最后一个元素的子数组且满足和为奇数的数目是多少就行了,由于我们只关心子数组和是奇数还是偶数即可,所以我们就建立两个变量Odd, Even来分别记录a[i]为最后一个元素和为奇数子数组数目以及和为偶数的子数组数目即可。如果a[i]是奇数,则和为偶数的子数组则变成和为奇数,和为奇数的子数组变成和为偶数;如果a[i]是偶数,则和为偶数的子数组和依旧是偶数,和为奇数的子数组和依旧是奇数。另外a[i]可以单独看作一个子数组,分别对应代码中的++操作。

代码:
class Solution {
public:
    const int MOD = 1e9 + 7;
    int numOfSubarrays(vector<int>& arr) {
        int ret = 0;
        int Odd = 0, Even = 0;
        for (auto & v : arr) {
            if (v & 1) {
                swap(Odd, Even);
                ++Odd;
            } else {
                Even++;
            }
            ret = (ret + Odd) % MOD;
        }

        return ret;
    }
};

3.字符串的好分割数目[原题链接]

题意:

给你一个字符串 s ,一个分割被称为 「好分割」 当它满足:将 s 分割成 2 个字符串 pq ,它们连接起来等于 spq 中不同字符的数目相同。

请你返回 s 中好分割的数目。

数据范围:

1 <= s.length <= 10^5, 'a' <= s[i] <= 'z'

思路:

这道题直接按题意来就行了,先利用unordered_set记录下每个前缀和后缀的不同字符数目,然后遍历即可。

代码:
class Solution {
public:
    int numSplits(string s) {
        unordered_set<char> f, b;
        int n = s.size();
        vector<int> fc(n), bc(n);
        for (int k = 0; k < n; ++k) {
            f.insert(s[k]);
            fc[k] = f.size();
        }
        
        for (int k = n - 1; k >= 0; --k) {
            b.insert(s[k]);
            bc[k] = b.size();
        }
        
        int r = 0;
        for (int k = 1; k < n; ++k) {
            if (fc[k - 1] == bc[k]) ++r;
        }
        
        return r;
    }
};

4.形成目标数组的子数组最少增加次数[原题链接]

题意:

给你一个整数数组 target 和一个数组 initialinitial 数组与 target 数组有同样的维度,且一开始全部为 0 。

请你返回从 initial 得到 target 的最少操作次数,每次操作需遵循以下规则:

initial 中选择任意子数组,并将子数组中每个元素增加 1 。

数据范围:

1 <= target.length <= 1e5; 1 <= target[i] <= 1e5

思路:

这道题原来是用 dfs 做,但是超时了,思想有点类似于归并排序。后来我就想着用动态规划来做(我有个思考的惯性,如果这道题可以用动态规划来做,我一定会首先想到分治算法嗯~ o( ̄▽ ̄)o)。首先应该想到的一点就是从 initial 得到 target 和从 target 得到 initial 的操作次数是一样,只不过从 target 得到 initial是要将操作中的加1变为减1。
状态表示:f[i] 表示前 i 个数字由 target 变为全0的最小操作次数。
状态转移:

  • target[i] <= target[i - 1],那么我们总是可以在对target[i - 1] 减1的时候顺带对 target[i] 减1,直至target[i] = 0,所以不需要额外的操作 f[i] = f[i - 1]
  • target[i] > target[i - 1],那么我们总是可以在对target[i - 1] 减1的时候顺带对 target[i] 减1,但是还是需要 target[i] - target[i - 1] 个操作才能将 target[i] 减至0,所以 f[i] = f[i - 1] + target[i] - target[i - 1]
代码:
class Solution {
public:
    int minNumberOperations(vector<int>& target) {
        int n = target.size();
        
        vector<int> f(n);
        f[0] = target[0];
        for (int k = 1; k < n; ++k) {
            if (target[k] <= target[k - 1]) f[k] = f[k - 1];
            else f[k] = f[k - 1] + target[k] - target[k - 1];
        }
        
        return f[n - 1];
    }
};

战绩:

在这里插入图片描述
欢迎同学们交流讨论~

你可能感兴趣的:(算法竞赛)