C++
 Computer >> コンピューター >  >> プログラミング >> C++

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

  1. C++で解くリスのナッツ収集シミュレーション ― 最小移動距離を求めるアルゴリズム

    問題概要 1本の木、1匹のリス、そして複数のナッツがフィールド上にあります。それぞれの位置は2次元グリッドのセルで表現されます。この問題の目的は、リスがすべてのナッツを集めて木の下に1個ずつ運ぶときの最小移動距離を求めることです。 リスの行動には次の制約があります。 一度に持てるナッツは最大1個 移動は上下左右の4方向で、隣接するセルへのみ可能 距離は移動回数(ステップ数)で表される たとえば、入力が「高さ: 5 / 幅: 7 / 木の位置: [2,2] / リスの位置: [4,4] / ナッツ: [[3,0], [2,5]]」の場合、出力は 12 となります。 解法のポイント まず、

  2. C++で解く長方形エリアII ― 座標圧縮と走査線法による被覆面積の計算

    問題概要 軸に平行な長方形のリストが与えられるものとします。各 rectangle[i] = {x1, y1, x2, y2} において、(x1, y1) は i 番目の長方形の左下隅の座標、(x2, y2) は右上隅の座標を表します。 求めたいのは、平面上でこれらすべての長方形が覆っている領域の合計面積です。答えは非常に大きな値になる可能性があるため、109 + 7 で割った余りを返すことになっています。 たとえば、入力が次のような場合を考えてみましょう。 このとき、出力は 6 となります。 解法の方針:座標圧縮+走査線(スイープライン) この問題は、座標圧縮(座標の離散化)と走査線法(