スライド最小値・最大値アルゴリズム from ABC352
アルゴリズム力強化のため、AtCoderに初参加してきました。D問題(ABC352)で敢え無く撃沈。
目下の第一目標を「D問題レベルをコンスタントに解けるようになること」に定め、備忘のため、毎週のAtCoder参加+学習したアルゴリズムを記録しておきます。
When to use
配列
Time Complexity
ループによる愚直な実装は
ABC352のD問題の制約は
How to implement
Deque(先頭・末尾の両方向から値の挿入・取り出しが可能なデータ構造)を使うことで、
実際にABC352 D - Permutation Subsequenceを考える。まず問題を注意深く読むと、各
具体例を挙げると、10 1 6 8 7 2 5 9 3 4
(
-
について、a=1 、よって\{P_1, P_5, P_6, P_8, P_9\} = \{1, 2, 3, 4, 5\} i_K - i_1 = 8 -
について、a=2 、よって\{P_2, P_5, P_6, P_8, P_9\} = \{2, 3, 4, 5, 6\} i_K - i_1 = 7
...
このことから、下図のように
Dequeを使ってスライド最小値を求める場合、下記のようにQueueを更新することで各区間最小値を取得することができる。
- DequeにはQのindexを保持する
- Dequeは常にindexに対応するQの値が単調増加するように、要素を追加・削除する
つまり、C++の実装としては下記のようになる。
/**
* @brief Sliding window minimum algorithm
* @param[in] target Original array to calculate sliding minimum values
* @param[out] window_size Sliding window size
* @return Minimum values for each sliding window
*/
std::vector<int> CalculateSlideMinValues(const std::vector<int>& target, const int window_size) {
std::vector<int> ans{};
std::deque<int> deq{};
const int array_size = static_cast<int>(target.size());
for (int i = 0; i < array_size; ++i) {
// Remove elements if new value is smaller than last element of deque
while (!deq.empty() && target[deq.back()] >= target[i]) {
deq.pop_back();
}
deq.push_back(i);
if (i - window_size + 1 >= 0) {
ans.push_back(target[deq.front()]);
// Remove elements out of slide window
if (deq.front() == i - window_size + 1) {
deq.pop_front();
}
}
}
return ans;
}
Dequeの追加・削除の回数が
スライド最大値については、上記の関数についてQの値が単調減少するようにDequeを更新することで実装できる。もしくは、オリジナルの配列の符号を反転させることでスライド最小値の問題へと帰着させることができる。
const auto min_values = CalculateSlideMinValues(q, k);
const auto max_values_sign_reversed = CalculateSlideMinValues(q_sign_reversed, k);
感想
一つ一つのアルゴリズムを丁寧に学び、理解を深めることで、問題に対するアプローチを増やすことが大事だと感じました。
Discussion