LeetCode 力扣周赛 268

254 阅读3分钟

错了两发,我好恨呐~

5930. 两栋颜色不同且距离最远的房子

思路一:枚举

时间复杂度O(n2)O(n^2)

空间复杂度O(1)O(1)

使用嵌套的两个 for 循环,枚举任意一对房子 (i,j), i<j(i,j),\ i\lt j。记录不同颜色的 (i,j)(i,j) 的最大差值即可。

一个小优化是,可根据已知的最大差值,加速内层循环,详见注释。

class Solution {
public:
    int maxDistance(vector<int>& c) {
        // anw 用以存储答案。
        int anw = 0;
        for (int i = 0; i < c.size(); i++) {
            // [i+1,i+anw] 区间内的房子不可能比 anw 更远啦
            // 因此直接从 i+anw+1 开始枚举咯
            for (int j = i+anw+1; j < c.size(); j++) {
                if (c[i] != c[j]) {
                    anw = j-i;
                }
            }
        }
        return anw;
    }
};

5201. 给植物浇水

思路一:模拟

时间复杂度O(n)O(n)

空间复杂度O(1)O(1)

假设在给第 ii 棵植物浇水前,位置为 pospos,剩余水量为 waterwater,已走步数为 sumsum。根据题意,此时有两种情形:

  • waterplantsiwater \ge plants_i。此时只能从 pospos 走至 ii,然后倒出 plantsiplants_i 单位的水。各变量更新如下:

    • sum+=ipossum \mathrel{+}= i - pos
    • pos=ipos = i
    • water=plantsiwater \mathrel{-}= plants_i
  • water<plantsiwater \lt plants_i。此时只能从 pospos 走回 1-1,灌满水后再走至 ii,然后倒出 plantsiplants_i 单位的水。各变量更新如下:

    • sum+=pos(1)+i(1)sum \mathrel{+}= pos - (-1) + i - (-1)
    • pos=ipos = i
    • water=capacityplantsiwater = capacity - plants_i

另外,根据题意,不难得出初始时的各变量值:

  • sum=0sum = 0
  • pos=1pos = -1
  • water=capacitywater = capacity

于是,我们可通过一层 for 计算出答案。

class Solution {
public:
    int wateringPlants(vector<int>& plants, int capacity) {
        int sum = 0;
        int pos = -1;
        int water = capacity;
        for (int i = 0; i < plants.size(); i++) {
            if (water >= plants[i]) {
                sum += i - pos;
                pos = i;
                water -= plants[i];
            } else {
                sum += pos - (-1) + i - (-1);
                pos = i;
                water = capacity - plants[i];
            }
        }
        return sum;
    }
};

5186. 区间内查询数字的频率

思路一:分块统计

时间复杂度O(nn)O(n*\sqrt n)

空间复杂度O(n)O(n)

首先将 nn 个数字,按位置均分为 n \lceil \sqrt n\ \rceil 个块,每块约有 n\sqrt n 个数字。

如上图所示,长度为 10 的数组,依次被分为三个块。每个块可由三个变量描述:

  • L:该块的左端点
  • R:该块的右端点
  • 哈希表:统计该块内每种颜色的出新次数。

上述块信息,可在 O(n)O(n) 时间内完成初始化。

对于每次查询,记作 (l,r,v)(l,r,v) 表示查询区间 [l,r][l,r]vv 的出现次数。第 ii 个块代表的区间 [Li,Ri][L_i,R_i] 与 区间 [l,r][l,r] 的关系可归纳为以下三种:

  • 完全不包含。即 r<Lir \lt L_i 或者 Ri<lR_i \lt l。此情形下该块不可能对答案有影响。
  • 完全包含。即 lLiRirl\le L_i \le R_i \le r。此情形下该块对答案的影响,可通过哈希表 O(1)O(1) 的获得。
  • 部分包含。该块中的部分数字对答案有影响,需要依次对这些数字进行检查。值得注意的是,对于每次查询,这样的块不会超过两个,换言之,每次查询需检查的数字不过超过2n2*\sqrt n

综上所述,每次查询,最多检查 n\sqrt n 个块,2n2*\sqrt n 个数字,因此时间复杂度为 O(n)O(\sqrt n)

class RangeFreqQuery {
    // 块信息,tuple 的三个元素依次为 L, R, dict。
    vector<tuple<int, int, unordered_map<int, int>>> block;
    
    // data: 输入数组的拷贝
    vector<int> &data;
public:
    RangeFreqQuery(vector<int>& arr) : data(arr) {
        int n = arr.size();
        // 计算块的长度上限 step。
        int step = max(1, int(sqrt(n)));
        
        // 初始化 block
        for (int i = 0; i < n; i++) { 
            if (block.empty() || get<1>(block.back()) < i) {
                // 前一个块装满了,需要新创建一个了。
                block.emplace_back(i, i+step-1, unordered_map<int, int>());
            }
            // 更新 dict
            (get<2>(block.back()))[arr[i]]++;
        }
    }
    
    int query(int left, int right, int value) {
        int anw = 0;
        
        // 遍历所有块
        for (int i = 0; i < block.size(); i++) {
            // 去除左右端点
            int L = get<0>(block[i]);
            int R = get<1>(block[i]);
            
            if (left <= L && R <= right) {
                // 完全包含的情形
                const auto &dict = get<2>(block[i]);
                auto it = dict.find(value);
                if (it != dict.end()) {
                    anw += it->second;
                }
            } else if (right < L) {
                // 完全不包含的情形
                break;
            } else if (R < left) {
                // 完全不包含的情形
                continue;
            } else {
                // 部分包含的情形,需依次检查包含的那些数字
                for (int j = max(L,left), rr = min(right, R); j <= rr; j++) {
                    if (data[j] == value) {
                        anw += 1;
                    }
                }
            }
        }
        return anw;
    }
};

/**
 * Your RangeFreqQuery object will be instantiated and called as such:
 * RangeFreqQuery* obj = new RangeFreqQuery(arr);
 * int param_1 = obj->query(left,right,value);
 */

思路二:二分

时间复杂度O(nlgn)O(n*\lg n)

空间复杂度O(n)O(n)

这个思路就很好理解啦。

在构造函数中,记录每个数字出现的位置。每个数字的位置信息构成了一个有序的一维数组。

对于每次查询 (l,r,v)(l,r,v),首先检查 vv 是否出现过,如果没有则答案为 0。如果出现过,则在 vv 对应的一维数组上,通过二分求得答案。

class RangeFreqQuery {
    unordered_map<int, vector<int>> pos;
public:
    RangeFreqQuery(vector<int>& arr) {
        for (int i = 0; i < arr.size(); i++) {
            pos[arr[i]].push_back(i);
        }
    }
    
    int query(int left, int right, int value) {
        auto it = pos.find(value);
        if (it == pos.end()) {
            return 0;
        }
        const auto &p = it->second;
        return upper_bound(p.begin(), p.end(), right) - lower_bound(p.begin(), p.end(), left);
    }
};

5933. k 镜像数字的和

思路一:预处理

时间复杂度O(n)O(n)

空间复杂度O(nk)O(n*k)

先预处理出 (92)30=240(9-2)*30=240 个镜像数字,每种进制 3030 个。

最大的数字为 644545454466101064454545446 \approx 6*10^{10}。因为要求数字镜像,我们可仅枚举数字的前半部分,这样枚举的范围就大大缩小啦。

bool init_flag = false;
vector<int64_t> arr[10];

bool is_mirror(const std::string &p) {
    int n = p.size();
    for (int i = n/2; i >= 0; i--) {
        if (p[i] != p[n-i-1]) {
            return false;
        }
    }
    return true;
}

void init() {
  // 根据题意,最多找出 (9-2+1)*30 = 240 个镜像数字
  int cnt = 0;
  for (int i = 1; cnt < 240; i++) {
    // 枚举数字长度 i
    int part = (i+1)/2;
    int64_t L = 1;
    for (int i = 1; i < part; i++) {
      L *= 10;
    }
    int64_t R = L * 10;
    // 枚举前一半数字 num ∈ [L, R)
    for (int num = L; num < R && cnt < 240; num++) {
      auto tmp = std::to_string(num);
      // 根据长度 i 以及 前一半数组 num,构造出整个数字 tmp。
      if (i&1) {
        for (int i = tmp.size()-2; i >= 0; i--) {
          tmp += tmp[i];
        }
      } else {
        for (int i = tmp.size()-1; i >= 0; i--) {
          tmp += tmp[i];
        }
      }
    
      int64_t tmp_num = std::atol(tmp.c_str());

      for (int j = 2; j <= 9; j++) {
        // j 进制已经有 30 个了,没必要再算了
        if (arr[j].size() >= 30) {
          continue;
        }
         
        std::string knum;
        int64_t tmp = tmp_num;
        // 转化成 k 进制数字 knum
        while(tmp > 0) {
          knum += ('0' + tmp%j);
          tmp /= j;
        }
        // 检查 knum 是否镜像
        if (is_mirror(knum)) {
          arr[j].push_back(tmp_num);
          cnt++;
        }
      }
    }
  }
}

class Solution {
public:
    long long kMirror(int k, int n) {
        if (!init_flag) {
            init_flag = true;
            // 开始打表
            init();
        }
        
        // 统计答案
        int64_t sum = 0;
        for (int i = 0; i < n; i++) {
            sum += arr[k][i];
        }
        return sum;
    }
};