C++で手札を連続するW枚のグループ(ストレート)に分割できるか判定する方法
問題の概要
Rimaは、整数の配列として与えられるカードの手札を持っています。彼女はこの手札を組み替えて、それぞれサイズWのグループに分けたいと考えています。ただし、各グループは「W枚の連続したカード」、つまり値が1ずつ増えていく並びで構成されている必要があります。このような分割が可能かどうかを判定するのが課題です。
例えば、手札が [1,2,3,6,2,3,4,7,8]、W = 3 の場合、答えは true になります。実際に [1,2,3]、[2,3,4]、[6,7,8] という3つのグループに再配置できるからです。
解法のアプローチ
この問題は、各カードの値ごとの出現回数をマップで管理し、常に最小の値から順に連続するW枚を取り出していく貪欲法(グリーディ法)で効率よく解くことができます。具体的な手順は以下の通りです。
- マップ m を定義し、手札 hands に含まれる各要素の頻度(出現回数)を m に格納します。
- 手札の残り枚数 n が 0 になるまで、次の処理を繰り返します。
- prev := 0 と初期化します。
- it := マップ m の先頭の(キー, 値)ペアを指すイテレータとします。
- i を 0 から W − 1 までループします。
- it の値が 0 である間、it を次のペアへ進めます。
- i > 0 かつ it のキー − 1 = prev、または i = 0 の場合は、
- it の値を 1 減らします。
- prev := it のキー とします。
- それ以外の場合は false を返します。
- it を次のペアへ進めます。
- n := n − W とします。
- すべてのグループを作り切れたら true を返します。
ポイントは、必ず現時点で最も小さい値から使い切ることです。最小値を含むストレートを作れない状況では、それ以降どのような組み替えをしても成立しないため、その時点で false を返せばよいのです。
C++での実装例
理解を深めるために、以下の実装を見てみましょう。
#include <bits/stdc++.h>
using namespace std;
class Solution {
public:
bool isNStraightHand(vector<int>& hand, int W) {
map <int, int> m;
int n = hand.size();
if(n % W != 0) return false;
for(int i = 0; i < n; i++){
m[hand[i]]++;
}
while(n){
map <int, int> :: iterator it = m.begin();
int prev = 0;
for(int i = 0; i < W; i++){
while(it->second == 0) it++;
if((i > 0 && it->first - 1 == prev) || i == 0){
it->second--;
prev = it->first;
}else{
return false;
}
it++;
}
n -= W;
}
return true;
}
};
main(){
vector<int> v = {1,2,3,6,2,3,4,7,8};
Solution ob;
cout << (ob.isNStraightHand(v, 3));
}
コードのポイント
- まず
n % W != 0のチェックにより、手札の総枚数がWの倍数でない場合は即座に false を返します。これは分割不可能なケースを素早く排除するためです。 std::mapはキーで自動的にソートされるため、先頭から順に走査することで「最も小さい値」を常に参照できます。- 頻度が 0 になったエントリは
while(it->second == 0) it++;でスキップし、残っているカードだけを対象にします。
計算量の目安
マップの操作1回あたり O(log n) かかるため、全体の時間計算量は O(n log n)、頻度マップの分だけ追加の空間計算量は O(n) となります。カードの種類数が多い場合でも十分高速に動作します。
入力
[1,2,3,6,2,3,4,7,8] 3
出力
1
-
C++で解くリスのナッツ収集シミュレーション ― 最小移動距離を求めるアルゴリズム
問題概要 1本の木、1匹のリス、そして複数のナッツがフィールド上にあります。それぞれの位置は2次元グリッドのセルで表現されます。この問題の目的は、リスがすべてのナッツを集めて木の下に1個ずつ運ぶときの最小移動距離を求めることです。 リスの行動には次の制約があります。 一度に持てるナッツは最大1個 移動は上下左右の4方向で、隣接するセルへのみ可能 距離は移動回数(ステップ数)で表される たとえば、入力が「高さ: 5 / 幅: 7 / 木の位置: [2,2] / リスの位置: [4,4] / ナッツ: [[3,0], [2,5]]」の場合、出力は 12 となります。 解法のポイント まず、
-
C++で解く長方形エリアII ― 座標圧縮と走査線法による被覆面積の計算
問題概要 軸に平行な長方形のリストが与えられるものとします。各 rectangle[i] = {x1, y1, x2, y2} において、(x1, y1) は i 番目の長方形の左下隅の座標、(x2, y2) は右上隅の座標を表します。 求めたいのは、平面上でこれらすべての長方形が覆っている領域の合計面積です。答えは非常に大きな値になる可能性があるため、109 + 7 で割った余りを返すことになっています。 たとえば、入力が次のような場合を考えてみましょう。 このとき、出力は 6 となります。 解法の方針:座標圧縮+走査線(スイープライン) この問題は、座標圧縮(座標の離散化)と走査線法(