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

C++で2つの数列を厳密に増加させるための最小スワップ回数を求めるアルゴリズム

同じ非ゼロの長さを持つ2つの整数列 A と B が与えられているとします。ここで、A[i] と B[i] の要素を入れ替える(スワップする)ことができます。ただし、スワップできるのは、それぞれの数列において同じインデックス位置にある要素同士のみです。いくつかのスワップを行った後、A と B の両方が厳密に増加する(strictly increasing)状態にしたいと考えます。このとき、両方の数列を厳密に増加させるために必要な最小のスワップ回数を求めるのがこの問題です。

問題の例

例として、入力が A = [1,3,5,4]、B = [1,2,3,7] の場合を考えてみましょう。このとき答えは 1 になります。A[3] と B[3] を1回スワップすると、A = [1,3,5,7]、B = [1,2,3,4] となり、どちらの数列も厳密に増加している状態になるからです。

解法のアプローチ(動的計画法)

この問題は動的計画法(DP)を用いて効率的に解くことができます。各インデックス i について、「その位置でスワップした場合の最小コスト」と「スワップしなかった場合の最小コスト」の2つの状態を管理します。

具体的な手順は以下の通りです。

  • n を配列 A のサイズとし、それぞれサイズ n の2つの配列 swapCnt と noSwapCnt を用意します。
  • swapCnt[0] に 1 を、noSwapCnt[0] に 0 を設定します。
  • i を 1 から n - 1 まで繰り返します。
    • swapCnt[i] := n、noSwapCnt[i] := n で初期化します。
    • A[i] > A[i - 1] かつ B[i] > B[i - 1] の場合(スワップせずにそのまま維持できる場合):
      • noSwapCnt[i] := noSwapCnt[i - 1]
      • swapCnt[i] := swapCnt[i - 1] + 1
    • A[i] > B[i - 1] かつ B[i] > A[i - 1] の場合(スワップによって条件を満たせる場合):
      • swapCnt[i] := min(swapCnt[i], 1 + noSwapCnt[i - 1])
      • noSwapCnt[i] := min(swapCnt[i - 1], noSwapCnt[i])
  • 最後に swapCnt[n - 1] と noSwapCnt[n - 1] の小さい方を返します。

C++での実装例

理解を深めるために、以下の実装を見てみましょう。

#include <bits/stdc++.h>
using namespace std;
class Solution {
    public:
    int minSwap(vector<int>& A, vector<int>& B) {
        int n = A.size();
        vector <int> swapCnt(n), noSwapCnt(n);
        swapCnt[0] = 1;
        noSwapCnt[0] = 0;
        for(int i = 1; i < n; i++){
            swapCnt[i] = n;
            noSwapCnt[i] = n;
            if(A[i] > A[i - 1] && B[i] > B[i - 1]){
                noSwapCnt[i] = noSwapCnt[i - 1];
                swapCnt[i] = swapCnt[i - 1] + 1;
            }
            if(A[i] > B[i - 1] && B[i] > A[i - 1]){
                swapCnt[i] = min(swapCnt[i], 1 + noSwapCnt[i - 1]);
                noSwapCnt[i] = min(swapCnt[i - 1], noSwapCnt[i]);
            }
        }
        return min(swapCnt[n - 1], noSwapCnt[n - 1]);
    }
};
main(){
    vector<int> v1 = {1,3,5,4};
    vector<int> v2 = {1,2,3,7};
    Solution ob;
    cout << (ob.minSwap(v1, v2));
}

入力

[1,3,5,4]
[1,2,3,7]

出力

1

計算量について

このアルゴリズムの時間計算量は O(n)、空間計算量も O(n) です。各インデックスごとの状態遷移を一度の走査で処理できるため、非常に効率的な解法となっています。

  1. C++で文字列を回文にするために必要な最小削除文字数を求める方法

    問題の概要長さ n の文字列が与えられたとき、その文字列を回文にするために削除が必要な最小の文字数を求めるのがこの問題の目的です。たとえば、入力文字列が「abcda」の場合、先頭と末尾以外の2文字を削除すれば回文を作ることができます。「b」と「c」を削除すると → 「ada」(回文になります)「c」と「d」を削除すると → 「aba」(回文になります)「b」と「d」を削除すると → 「aca」(回文になります)アルゴリズムの考え方この問題は「最長回文部分列(Longest Palindromic Subsequence: LPS)」の考え方を使うと効率的に解けます。手順は次のとおりです。与えら

  2. 【C++】配列を互いに素な配列に変換するための最小挿入回数を求める方法

    問題の概要 今回は、与えられた配列を互いに素な配列(コプライム配列)に変換するために必要な最小の挿入回数を求める、興味深い問題を取り上げます。互いに素な配列とは、隣り合う任意の2つの要素の最大公約数(GCD)が必ず1になる配列のことです。この記事では、必要な挿入回数に加えて、変換後の配列そのものも出力します。 例として、{5, 10, 20} という配列を考えてみましょう。この配列は隣接要素同士のGCDが5や10となるため、互いに素な配列ではありません。しかし、5と10の間、そして10と20の間にそれぞれ「1」を挿入すれば、{5, 1, 10, 1, 20} となり、すべての隣接ペアのGCD