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

C++で解く「最後の石の重み II」:動的計画法による効率的な解法


問題概要

正の整数の重みを持つ石が複数与えられます。各ターンごとに、任意の2つの石を選んで衝突させます。2つの石の重みをそれぞれ x と y(x ≤ y)としたとき、衝突の結果は以下のようになります。

  • x = y の場合:両方の石は完全に砕けて消滅します。
  • x ≠ y の場合:重み x の石は完全に破壊され、重み y の石は新しい重み y − x となります。

最終的に残る石は高々1つです。このとき、残った石の重みとして可能な最小値を求めてください(すべての石が消滅した場合は 0 とします)。

入力例と実行手順

たとえば入力が [2,7,4,1,8,1] の場合、出力は 1 になります。具体的な手順は次の通りです。

  • (2, 4) を衝突させる → 配列は [2,7,1,8,1] になる
  • (7, 8) を衝突させる → 配列は [2,1,1,1] になる
  • (2, 1) を衝突させる → 配列は [1,1,1] になる
  • (1, 1) を衝突させる → 最後に残るのは 1 のみ

解法のポイント:部分和問題への帰着

この問題は、単純なシミュレーションでは非効率ですが、「石を2つのグループに分けるとどうなるか」という視点で考えると動的計画法(DP)で解けます。

石をグループ A とグループ B に分けたとき、衝突操作を繰り返して最終的に残る重みは |sum(A) − sum(B)| に等しくなります。したがって、合計の半分(total / 2)以下で実現できる最大の部分和を見つければ、答えは「total − 2 × その部分和」となります。これは典型的な 0/1 ナップサック型の DP で求められます。

アルゴリズムの手順

  • n を石の配列のサイズとし、total := 0 で初期化する
  • i を 0 から n − 1 まで回し、total に stones[i] を加算して総和を求める
  • req := total / 2 とする(目標とする部分和の上限)
  • サイズ req + 1 のブール型配列 dp を用意し、すべて false で初期化する
  • dp[0] := true とし、reach := 0 で初期化する
  • i を 0 から n − 1 まで繰り返す
    • j を req から開始し、j − stones[i] ≥ 0 を満たす間、j を 1 ずつ減らしながら以下を実行する
      • dp[j] = dp[j] || dp[j − stones[i]] で更新する
      • dp[j] が true であれば、reach = max(reach, j) で更新する
  • total − 2 × reach を返す

ここで dp[j] は「いくつかの石を選んで重みの合計をちょうど j にできるか」を表します。内側のループを大きい j から小さい j へ向かって処理することで、同じ石を二度使うことを防いでいます。これは 0/1 ナップサック DP の定番テクニックです。

C++ 実装例

#include <bits/stdc++.h>
using namespace std;
class Solution {
    public:
    int lastStoneWeightII(vector<int>& stones) {
        int n = stones.size();
        int total = 0;
        for(int i = 0; i < n; i++){
            total += stones[i];
        }
        int req = total / 2;
        vector<bool> dp(req + 1, false);
        dp[0] = true;
        int reach = 0;
        for(int i = 0; i < n; i++){
            for(int j = req; j - stones[i] >= 0; j--){
                dp[j] = dp[j] || dp[j - stones[i]];
                if(dp[j]) reach = max(reach, j);
            }
        }
        return total - (2 * reach);
    }
};
main(){
    vector<int> v = {2,7,4,1,8,1};
    Solution ob;
    cout << (ob.lastStoneWeightII(v));
}

入力

[2,7,4,1,8,1]

出力

1

計算量

時間計算量は O(n × total / 2)、空間計算量は O(total / 2) です。ここで n は石の個数、total は石の重みの合計を表します。石の総重量がそれほど大きくなければ、非常に効率的に解ける問題です。


  1. C++で文字列内の特定文字の最後の出現位置(インデックス)を検索する方法

    文字列 str と、検索対象となる文字 ch が与えられたとします。この課題では、文字列の中に ch が最後に出現する位置(インデックス)を見つける必要があります。例えば、文字列が「Hello」で、検索する文字が ch = l の場合、l はインデックス2と3に出現するため、最後のインデックスは 3 となります。解決のアプローチこの問題を解くには、文字列を右から左へ(末尾から先頭へ)順番に走査します。各位置の文字が l と一致しなければインデックスを1つずつ減らしていき、一致する文字が見つかった時点で処理を停止し、そのインデックスを結果として返します。もし文字列全体を走査しても一致する文字が見

  2. Pythonで解く「最後の石の重さ」問題 ― アルゴリズムと実装をわかりやすく解説

    問題の概要 それぞれ正の整数の重さを持つ石がいくつか与えられます。毎ターン、最も重い2つの石を選んで砕きます。2つの石の重さを x、y(x ≤ y)とすると、砕いた結果は次の2通りのいずれかになります。 x = y の場合: 2つの石はともに完全に破壊されます。 x ≠ y の場合: 重さ x の石は完全に破壊され、重さ y の石は新しい重さ y − x となります。 この操作を繰り返すと、最後には最大で1個の石が残ります。残った石の重さを求めてください(石が1個も残らない場合は 0 を返します)。 具体例 例として、石の重さが [2, 7, 4, 1, 8, 1] の場合を考えてみまし