【LeetCode】動的プログラミング研修 (7)

918. 円形部分配列の最大合計

クリックして表示:円形部分配列の最大合計


長さ n の循環整数配列 nums を指定すると、nums の空ではない部分配列の最大の合計を返します。
円形配列は、配列の終端がリングの先頭に接続されることを意味します。正式には、 nums[i] の次の要素は nums[(i + 1) % n] で、 nums[i] の前の要素は nums[(i - 1 + n) % n] です。
サブ配列には、固定バッファー num 内の各要素を最大 1 回だけ含めることができます。正式には、部分配列 nums[i]、nums[i + 1]、…、nums[j] には、i <= k1、k2 <= j (k1 % n == k2 % n) は存在しません。

例 1:
入力: nums = [1,-2,3,-2]
出力: 3
説明: 部分配列 [3] から 3 の最大合計を取得します。
例 2:
入力: nums = [5,-3,5]
出力: 10
説明: 部分配列 [5,5] から最大合計を取得します 5 + 5 = 10

トピック分析

配列は循環であるため、3 を取得するか
、3 と -2 を取得することしかできません。そのため、3 と -2 に基づいて 1 と -2 を取得し
て比較し、最大合計を 3 とします。


状態遷移方程式

まず円形配列を通常の配列に変換してから、


ケース 1:

現在の配列の最大合計を取得したい場合は、3 を取るだけで済みます。そうすると、
現在の配列の最大合計は配列内にあり、配列は現状では通常の配列とみなすことができます。現在の問題を解決するには、部分配列の最大合計
の解を使用します。


ケース 2:

現在の配列の最大値の合計を取得したい場合は、
リング接続の最後の 5 つの配列とその前の 5 つの配列の合計を固定値として取得する必要があります 現在の赤い領域の最大値を取得する場合, 空白領域の最小値を取得する必要があります。
赤色のため、領域は不連続であり、空白領域は連続区間であるため、最初に空白領域の最小部分配列合計
を見つけてから、最小配列合計を減算できます。配列全体の合計から空白領域を取り出して、赤い領域の最大サブ配列合計を取得します。


ケース 1 の最大部分配列合計は f で示されます。
ケース 2 の最小部分配列合計は g で示されます。

f[i]: i で終わるすべてのサブ配列の最大合計を示します。 g[i]:
i で終わるすべてのサブ配列の最小合計を示します。

f[i] 状態遷移方程式

部分配列を 2 つのクラスに分割する

1.この場合、位置 i の要素自体 (長さ 1) : f[i]=nums[i]

2. 位置 i で終わるサブ配列の最大合計が必要なため、位置 i の要素と前の要素の組み合わせ (1 より大きい長さ) が必要です。

i-1 を最初に計算する必要があります。つまり、f [i-1]
と nums[i] を計算します。これは位置 i で終わるサブ配列の最大合計です。
この場合: f[i]=f[i-1] ]+数値[i]


状態遷移方程式は次のとおりです:
f[i]=max(nums[i],f[i-1]+nums[i])

g[i] 状態遷移方程式

g[i] と f[i] の違いは、f[i] が最大値を必要とするのに対し、g[i] は最小値を必要とすることです。他の分析は同じです。


g[i] は 2 つのカテゴリに分類できます

ケース 1: i 位置の要素自体 (長さは 1)
この場合: g[i]=nums[i]

ケース 2: 位置 i の要素が前の要素と結合されます (長さが 1 より大きい)

i で終わる最小の部分配列の合計を見つけたい場合は、最初に i-1 で終わる最小の部分配列の合計を見つける必要があります。つまり、
nums[i] の追加の g[i-1] は i で終わる最小の部分配列であり、
この場合: g[i]=g[i-1]+nums[i]


状態遷移方程式は次のとおりです。
g[i]=min(nums[i],g[i-1]+nums[i])

初期化

状態遷移方程式によれば、i が 0 の場合、範囲外問題が発生します。

このような範囲外の問題を防ぐために、
展開された配列に仮想ノードが追加されます。仮想ノードの添字は 0 で、元の配列の要素の添字は 1 から始まります。


f[i] の状態遷移方程式では、i が 1 の場合、f[1]=max(nums[1],f[0]+nums[1]) 添字が 0 であるため、仮想ノードであるため、
f [0] は結果に影響を与えるべきではないため、f[0] を 0 に設定します。


g[i] の状態遷移方程式では、i が 1 の場合、g[1]=max(nums[1],g[0]+nums[1]) となります。添字が 0 は仮想ノードなので、
g [0] は結果に影響しないため、g[0] を 0 に設定します。

戻り値

分析開始時の 2 つのケースでは、
ケース 1 は
f テーブルの最大値、つまりfmax
fmax、つまりケース 1 の最大サブ配列合計を取得します。


ケース 2 では、
g テーブルの最小値、つまりgminを取得します
。ケース 2 の赤い領域の最大サブ配列合計は、配列全体から白い領域のサブ配列合計を引いたものであるため、
ケース 2 の最大サブ配列合計は合計です。 -gmin

リング配列の最大部分配列合計は次のとおりです: max(fmax,sum-gmin)


g は連続部分配列であり、合計が最小であるため、gmin は現在の配列の 3 つの要素すべてを加算したときの最小合計になります。sum-gmin を使用すると、ケース 2 の最大の部分配列合計が得られます
。が 0 である
ため、最終的な円形配列が取得されます。 の部分配列の最大合計が最大の負の数であると予想される場合、結果は 0 となり、エラーが発生します。


したがって、 sum==gmin (配列の要素がすべて負の場合) の場合は判定条件を追加し、 fmax を直接返すだけでよく、上記の判定が真でない場合は max(fmax,sum-gmin) を返す必要があります。

完全なコード

class Solution {
    
    
public:
    int maxSubarraySumCircular(vector<int>& nums) {
    
    
       int n=nums.size();
       //为了防止越界问题,所以多开辟一个空间
       vector<int>f(n+1,0);
       vector<int>g(n+1,0);
       //初始化
       f[0]=0;
       g[0]=0;
       int i=0;

       //sum为整体数组和
       int sum=0;
       for(i=0;i<n;i++)
       {
    
    
         sum+=nums[i];
       }
         //取f表中的最大值
       int fmax=INT_MIN;
         //取g表中的最小值
       int gmin=INT_MAX;
       for(i=1;i<=n;i++)
       {
    
    
           //由于当前下标为扩展数组f/g的下标
           //想要使用当前下标 去寻找 对应nums的值
           //需要下标减1
           f[i]=max(nums[i-1],f[i-1]+nums[i-1]);
           g[i]=min(nums[i-1],g[i-1]+nums[i-1]);
           fmax=max(fmax,f[i]);
           gmin=min(gmin,g[i]);
       }
        //有可能存在数组元素全为负的情况,所以需要判断
        return sum==gmin?fmax:max(fmax,sum-gmin);
    }
};

152.積の最大サブアレイ

クリックして表示:最大積サブ配列


整数配列 nums を指定すると、配列内で最大の積を持つ空でない連続サブ配列 (サブ配列には少なくとも 1 つの数値が含まれます) を見つけて、そのサブ配列に対応する積を返します。
テスト ケースの答えは 32 ビット整数です。
サブ配列は、配列の連続したサブシーケンスです。

例 1:
入力: nums = [2,3,-2,4]
出力: 6
説明: サブ配列 [2,3] の積は最大 6 です。
例 2:
入力: nums = [-2,0,-1]
出力: 0
説明: [-2,-1] は部分配列ではないため、結果が 2 になることはありません。

トピック分析

2 と 3 の積を部分配列の最大積としてとると、
積は 6 になります。

これに基づいて -2 を取ると、負の数を乗算すると乗算がどんどん小さくなり
、積は -12 になります。

状態遷移方程式

f[i]: 位置 i で終わるすべてのサブ配列の最大積を示します。
g[i]: 位置 i で終わるすべてのサブ配列の最小積を示します。

f[i] 状態遷移方程式

f[i] は 2 つのカテゴリに分類できます。

ケース 1: 位置 i の要素自体 (長さ 1)
この場合: f[i]=nums[i]

ケース 2: 位置 i の要素が前の要素と結合されます (長さが 1 より大きい)

ケース 1 nums[i] が 0 より大きく、
位置 i の最大積を見つけたい場合は、最初に最大積を見つける必要があります。位置 i-1 での f[i -1] に
nums[i] を掛けます。つまり、nums[i] が 0 より大きい場合、位置 i での最大の積は
f [i]=f[ i-1]*数値[i]

ケース 2 nums[i] が 0 より小さい場合
、nums[i] が負の場合、および f[i-1] が i-1 で終わる最大の積である場合、乗算が小さくなる原因となるため、次のようにする必要があります。前に1 を掛けます。最小の積により
2 つの値はさらに増えます。つまり、
i-1 で終わるすべての部分配列の最小積g[ i-1]
に nums[i] が掛けられます。 nums[i] が 0 未満の場合、位置 i での最大積はf[i]=g[i-1]*nums[i]
となります。

状態遷移方程式は次のとおりです:
f[i]=max(nums[i] , max(f[i-1]*nums[i],g[i-1]*nums[i]) )

g[i] 状態遷移方程式

ケース 1 の位置 i の要素 (長さ 1)
g[i]=nums[i]


ケース 2 位置 i の要素が前の要素と結合されます (長さが 1 より大きい)

nums[i] が 0 より大きく
、位置 i で最小の積を見つけたい場合は、まず位置 i-1 で最小の積、つまり g[i-1] を見つけてから、それに nums[ を乗算する必要があります
。 i]、つまり、nums[i] は 0 より大きいです。この場合、位置 i での最小積はg[i]
= g[i-1]*nums[i] となります。


nums[i] が 0 より小さい場合
、位置 i で最小の積を見つけたい場合は、まず位置 i-1 で最大の積、つまり f[i-1] を見つけてから、それに
nums を乗算する必要があります。 [i]、つまり nums[i] が 0 未満の場合、位置 i の最小積はg[i]
= f[i-1]*nums[i] となります。


状態遷移方程式は次のとおりです。
g[i]=min(nums[i] ,min(g[i-1]*nums[i],f[i-1]*nums[i]))

初期化

i が 0 の場合、範囲外の問題が発生します。


i が 1 のとき、f[0 ]
またはg[0] は結果に影響を与えるため、f[0] と g[0] の両方を 1 に設定します。

完全なコード

class Solution {
    
    
public:
    int maxProduct(vector<int>& nums) {
    
    
       int n=nums.size();
       //为了防止越界问题,所以进行将数组多加一个虚拟节点
       vector<int>f(n+1,0);
       vector<int>g(n+1,0);
       //初始化
       f[0]=1;
       g[0]=1;
       int i=0;
       int ret=INT_MIN;
       for(i=1;i<=n;i++)
       {
    
    
           //当前下标为扩展数组f/g的下标
           //想要使用当前下标 去寻找对应的原数组的值
           //需要将下标减1
           f[i]=max(nums[i-1],max(f[i-1]*nums[i-1],g[i-1]*nums[i-1]));
           g[i]=min(nums[i-1],min(g[i-1]*nums[i-1],f[i-1]*nums[i-1]));
           ret=max(ret,f[i]);
       }
       return ret;
    }
};

戻り値は最大積であるため、f テーブルの最大値を返すだけで済みます。

1567. 正の積を持つ最長のサブアレイの長さ

クリックして表示:積が正の数となる最長の部分配列の長さ


整数の配列 nums を指定して、その積が正の数になる最長の部分配列の長さを見つけてください。
配列の部分配列は、元の配列内の 0 個以上の連続する数値の配列です。
積が正の数になる最長の部分配列の長さを返してください。

例 1:
入力: nums = [1,-2,-3,4]
出力: 4
説明: 配列自体の積は、値が 24 の正の数です。
例 2:
入力: nums = [0,1,-2,-3,-4]
出力: 3
説明: 最長の積が正の数である部分配列は [1,-2,-3] であり、その積は6.
積は正の数ではない 0 になるため、部分配列に 0 を含めることもできないことに注意してください。

トピック分析

サブ配列は連続的である必要があり、サブ配列の積は正になります。積が最も長いサブ配列のうちどれが最も長いかを見つけます。

積 1 -2 -3 を選択してください。積は正です。長さは 3 です
。積 -2 -3 4 を選択してください。積は正です。長さは 3 です。
積 1 -2 -3 4 を選択してください。積は正です。 、長さは 4
なので、最大長は 4 です。

状態遷移方程式

f[i]:位置 i で終わるすべての部分配列の正の積の最長の長さを示します。 g[i]:
位置 i で終わるすべての部分配列の負の積の最長の長さを示します。

f[i] 状態遷移方程式

ケース 1 位置 i の要素自体 (長さ 1)

num[i] が 0 より大きい場合、f[i] は 1
nums[i] が 0 より小さい場合、f[i] は 0


ケース 2 i 位置要素と前の要素の組み合わせ (長さは 1 より大きい)

nums[i[ が 0 より大きい場合、積が正の数になる最長の長さを乗算する必要があります。

位置 i で終わるすべてのサブ配列の最長の長さを見つけたい場合 (その積が正の数になる場合)、nums[i] が 0 より大きいため、終わるすべてのサブ配列の最長の長さを見つける必要があります。位置 i-1 で、積は正の数です。長い長さは、i 位置を追加した後のf[i-1]の長さ (+1) です。

この場合: f[i]=f[i-1]+1


nums[i] が 0 未満の場合は、負の数の最長の長さを乗算する必要があります。

このようにして、全体の積が正の数であることが保証されます。

位置 i で終わるすべてのサブ配列の最長の長さを見つけたい場合 (その積が正の数になる場合)、nums[i] が 0 より小さいため、最初に位置 i で終わるすべてのサブ配列の最長の長さを見つける必要があります。 -1 (負の積) 長さは、
i 位置を追加した後の g[i-1] の長さ (+1)

f[i]=g[i-1]+1 と直接書いた場合、
配列内の i-1 で終わる積がすべて 0 より大きく、i の位置が 0 より小さい場合は、積が正の数になる長さを生成する方法では、期待される結果は 0 になり、f[i] の値は 1 となりエラーになります。そのため、最初に g[i-1] かどうかを判断する必要があります
。は 0 に等しい(0 に等しい場合は、i-1 で終わるすべての積が 0 より大きいことを意味する)
g[i-1] ] が 0 に等しい場合、f[i]=0
If g[ i-1] が 0 に等しくない場合、f[i]=g[i-1]+1、
つまり f[i] =g[i-1]== 0?0:g[i-1]+ 1


状態遷移方程式は次のとおりです。
if(nums[i]>0)
f[i]=f[i-1]+1

if(nums[i]<0)
f[i]=g[i-1]==0?0:g[i-1]+1

g[i] 状態遷移方程式

ケース 1 の長さは 1

g[i] は、i で終わるすべての部分配列内の負の積の最長の長さを表します。nums
[i] が 0 より大きい場合、g[i] は 0 です。nums
[i] が 0 未満の場合、g[i] 』は1です。


ケース 2 の長さが 1 より大きい

nums[i] が 0 より大きい場合、
配列全体の積が負になるように、積が負になる最長の長さを乗算する必要があります。

位置 i で終わるすべてのサブ配列の負の積の最長の長さを見つけたい。nums[i] が 0 より大きいため、位置 i-1 で終わるすべてのサブ配列の負の積の最長の長さを見つける必要がある。つまり、追加後の位置 i のg[i-1]の長さは+1 になります。

g[i]=g[i-1]+1 と直接書いた場合、
配列内の i-1 で終わる積がすべて 0 より大きく、i の位置が 0 より大きい場合は、積が負の数になる長さを生成する方法で、期待される結果が 0 の場合、f[i] の値が 1 となりエラーが発生するため、
g[i-1] が に等しいかどうかを判断する必要があります。 0 (0 に等しい場合は、i-1 で終わる積がすべて 0 より大きいことを意味します)
g[i-1] が 0 に等しい場合、g[i]=0 g
[i-1] の場合が 0 に等しくない場合、g[i]=g[i-1]+1、
つまり g[i] =g[i-1]==0 ?0:g[i-1]+1


nums[i] が 0 未満の場合、積が正になる最長の長さを乗算する必要があります。

積が負の数である位置 i で終わるすべての部分配列の最長の長さを見つけたい場合は、nums[i] が 0 より小さいため、まず次の式で位置 i-1 で終わるすべての部分配列の最長の長さを見つける必要があります。正の積 長さは、i 位置を加算した後のf[i-1] の長さ(+1)

この場合: g[i]=f[i-1]+1


状態遷移方程式は次のとおりです:
if(nums[i]>0)
g[i]=g[i-1]==0?0:g[i-1]+1

if(nums[i]<0)
g[i]=f[i-1]+1

初期化

i が 0 の場合、範囲外の問題が発生します。

nums[i] が 0 未満の場合、i が 1 の場合、 g[1]=f[0]+1
f[0] が結果に影響を与えないように、f[0]=0とします。


現在の添字は f/g 拡張配列の添字であるため、
現在の添字を使用して nums に対応する値を見つけたい場合は、
添字から 1 を減算する必要がある
ため、i が 1 の場合は、次の値を取得します。 nums[0]、nums[0]>0 の場合、g[0]==0
(g[0] は添字 0 で終わるすべての部分配列の負の積の最長の長さを表します)
nums[0] は次より大きいです0、積が負になるケースがないことを示すため、g[0] は 0 になります。

完全なコード

class Solution {
    
    
public:
    int getMaxLen(vector<int>& nums) {
    
    
       int n=nums.size();
       //为了避免越界问题,所以设置一个虚拟节点
       vector<int>f(n+1,0);
       vector<int>g(n+1,0);
       //初始化
       f[0]=0;
       g[0]=0;
       int i=0;
       int ret=INT_MIN;
       for(i=1;i<=n;i++)
       {
    
    
           //由于当前使用下标为f/g扩展数组的下标
           //所以想要使用当前下标 ,寻找到对应nums的值
           //需要下标减1
            if(nums[i-1]>0)
            {
    
    
                f[i]=f[i-1]+1;
                g[i]=g[i-1]==0?0:g[i-1]+1;
            }
            else if(nums[i-1]<0)
            {
    
    
                f[i]=g[i-1]==0?0:g[i-1]+1;
                g[i]=f[i-1]+1;
            }
            ret=max(ret,f[i]);
       }
       //返回 f表中的最大值
       return ret;
    }
};

f テーブルは積が正となる部分配列の最長の長さを表すため、f テーブルの最大値を返します。

おすすめ

転載: blog.csdn.net/qq_62939852/article/details/131482013