LeetCode 力扣周赛 238

180 阅读5分钟

1837. K 进制表示下的各位数字总和

知识点进制转换

将十进制数字转换为K进制数字,只需不断进行两种操作 —— 取余和除K,直至被转换数字变为0

class Solution {
public:
    int sumBase(int n, int k) {
        int sum = 0;
        while(n > 0) {
            sum += n % k;
            n /= k;
        }
        return sum;
    }
};

1838. 最高频元素的频数

知识点双指针

为了方便操作,我们先将 nums 升序排列。

首先明确一个事情,一定存在i使得以nums[i] 为目标值,可以获得最大频次。以下是证明过程:

  1. 不妨先设 x 为可获得最大频次的目标值,且不在nums中。
  2. 因为每次操作只能加不能减,所以x必然是由小于 x 的某些数字变换得到的
  3. 不妨设这些数字为集合 S,其中最大的数字为 max
  4. 因为每次只能加一,所以S 中除max之外的其他数字,在变为x之前,必然先变为max
  5. 所以以 max为目标值也能得到与x相同的频次。

继续观察,当以nums[i]为目标值时,必然是将不超过nums[i]的,位于连续区间nums[j..i]中的数字转换为nums[i]是最优策略。

左指针j,右值针i 分别指向nums的某个位置且 jij \le i,其含义为将nums[j..i]这段区间内的元素都变为nums[i]

假设已知,以nums[i]为目标值时的最高频次 freifre_i 以及 达到该频次的最小操作次数 cnticnt_i。依次为基础可较快的计算出frei+1fre_{i+1} 以及 cnti+1cnt_{i+1}

  • 移动右指针i:将freifre_{i}nums[i] 全部修改为 nums[i+1],则 cnti+1=cnti+(numsi+1numsi)freicnt_{i+1} = cnt_{i} + (nums_{i+1} - nums_i)*fre_ifrei+1=frei+1fre_{i+1} = fre_i + 1i=i+1i = i + 1
  • 移动左指针j:如果cnti>kcnt_{i} > k,则抛弃最左边的元素,直到 cntikcnt_{i} \le kcnti=numsinumsjcnt_{i} -= nums_{i} - nums_jfrei=frei1fre_{i} = fre_{i} - 1j=j+1j = j + 1

初始状态,fre0=1cnt0=0fre_0 = 1,cnt_0 = 0

因为每个元素只会被左右指针各访问一次,所以整体的时间复杂度为O(n)O(n)

class Solution {
public:

    int maxFrequency(vector<int>& nums, int k) {
        sort(nums.begin(), nums.end());
        int l = 0, r = 0;
        int anw = 1;
        int64_t sum = 0;
        while(r+1 < nums.size()) {
            r++;
            sum += (nums[r] - nums[r-1])*1L*(r-l);
            while(sum > k) {
                sum -= nums[r]-nums[l];
                l++;
            }
            anw = max(anw,r-l+1);
        }
        return anw;
    }
};

1839. 所有元音按顺序排布的最长子字符串

知识点递推

dp[i] 代表以 word[i] 结尾的,符合题目要求的前缀的长度。 前缀的定义是:

  1. 前缀是word的一个子串。
  2. 该子串是有a,e,i,o,u中的第一种或者前几种组成的。
  3. 该子串是以a开头的。
  4. 该子串中元素都是升序排列的。

状态转移过程见注释:

class Solution {
public:
    bool check(char a, char b) {
        if (a == 'a' && b == 'e') return true;
        if (a == 'e' && b == 'i') return true;
        if (a == 'i' && b == 'o') return true;
        if (a == 'o' && b == 'u') return true;
        return false;
    }
    int longestBeautifulSubstring(string word) {
        // 因为 dp[i] 的计算仅依赖 dp[i-1]。
        // 所以使用 cur 指代 dp[i],pre 指代 dp[i-1]。这样可以节省内存~
        
        // 初始化 pre,即dp[0]
        int pre = (word[0] == 'a' ? 1 : 0), cur = 0;
        int anw = 0;
        for (int i = 1; i < word.size(); i++) {
            // 如果 dp[i-1] 是一个前缀,且 word[i] 与 word[i-1]的字典序是连续的
            if (pre > 0 && (word[i] == word[i-1] || check(word[i-1], word[i]))) {
                cur = pre+1;
            } else {
                cur = (word[i] == 'a' ? 1 : 0);
            }
            // 如果word[i] 为 'u',则说明该前缀也是一个合法子串,更新一下anw
            if (word[i] == 'u') {
                anw = max(anw, cur);
            }
            pre = cur;
        }
        return anw;
    }
};

1840. 最高建筑高度

知识点几何 首先,我们将restrictions转换为平面直角坐标系上的点,建筑编号为横坐标,高度限制为纵坐标。然后过每个点做两条斜率分别为 1-1 的直线

特别的,为了便于处理,如果restrictions中不包含n号建筑的高度限制,那么我们将<n,n-1>添加到restrictions中。

如上图所示,相邻的点对把[0,n]这个大区间分割为若干个一段小区间。如果我们能找到每段小区间中最优解,那么就可以从这些局部最优解中找到全局最优解。

接下来,对于每个小区间,通过小数操作找出最优解:

  • 找直线:
    • 找出过其左端点以及左端点左边那些点的,斜率为 1 的直线中,截距最小的直线,记为 L。
    • 同样的,找出过其右端点以及右端点右边那些点的,斜率为 -1 的直线中,截距最小的直线,记为 R。
  • 求交点:求出 L 和 R 的交点,记为 P。
  • 计算局部最优解:
    • 如果 P 的横坐标在该小区间内,则P的纵坐标记为该小区间的最优解。
    • 如果 P 在该小区间的左侧,则将左端点横坐标代入L和R,其中较小的纵坐标即为最优解。
    • 如果 P 在该小区间的有责,则将右端点横坐标代入L和R,其中较小的纵坐标即为最优解。

在所有的局部最优解中,寻找最大值,即为全局最优解。

为何只需关心左边的斜率为1的直线,以及右边斜率为-1的直线呢?因为这些直线决定了高度的上限,一旦超过该高度,就无法在指定位置下降到指定高度。剩下的一半直线并不没有该意义。

int64_t l[100001], r[100001];
class Solution {
public:
    int maxBuilding(int n, vector<vector<int>>& limit) {
        if (limit.empty()) {
            return n-1;
        }
        sort(limit.begin(), limit.end(), [](auto &lhs, auto &rhs) { return lhs[0] < rhs[0];});
        if (limit.back().at(0) != n) {
            limit.emplace_back(vector<int>{n, n-1});
        } 
        int top = -1;
        for (int i = 0; i < limit.size(); i++) {
            l[i] = top;
            top = min(top, limit[i][1]-limit[i][0]);
        }
        top = numeric_limits<int>::max();
        for (int i = limit.size()-1; i >= 0; i--) {
            top = min(top, limit[i][1]+limit[i][0]);
            r[i] = top;
        }
        int64_t anw = 0;
        for (int i = 0; i < limit.size(); i++) {
            int64_t y = (l[i]+r[i])/2;
            int64_t x = (r[i]-l[i])/2;
            int64_t lx = i == 0 ? 0 : limit[i-1][0];
            int64_t rx = limit[i][0];
            if (x < lx) {
                anw = max(anw, min(-lx + r[i], lx + l[i]));
            } else if (x > rx) {
                anw = max(anw, min(-rx + r[i], rx + l[i]));
            } else {
                anw = max(anw, y);
            }
        }
        return anw;
    }
};