[LeetCode] - プレフィックスの合計と差

プレフィックスの合計

整数の 1 次元配列の場合、プレフィックス合計アルゴリズムは次のように構成されます。(ここで言及されている構造は、質問を行うときにそのような配列を構築する必要があるという意味ではありません。ここでの説明は、接頭辞の合計の概念を理解するためだけです。概念を理解した後、このアルゴリズムを使用できます。自分の考え)元の配列 nums と同じ長さの接頭辞合計配列 sum を作成し、接頭辞合計配列の sum[i] は元の配列の要素 [0,i] の合計を表します。 1 次元配列の間隔合計を
プレフィックスと図の例
使用すると、問題を解決するためにプレフィックス合計を使用できます。特に、同じ 1 次元配列の間隔合計を複数回求めたい場合は、配列を 1 回走査するだけで済みます。 O(n) を使用してプレフィックス合計配列を生成します。その後、プレフィックス合計配列の場合、右側の境界に対応するプレフィックス合計から区間の左側の境界に対応するプレフィックス合計を減算することで答えを得ることができます。区間の境界。計算量は O(1)

303.地域と検索

class NumArray {
    
    
    private int[] sum;
    public NumArray(int[] nums) {
    
    
        int len = nums.length;
        if(len == 0) sum = null;
        else{
    
    
        	//开始生成前缀和数组
            sum = new int[len];
            sum[0] = nums[0];
            for(int i = 1;i < len;i++){
    
    
            	//生成前缀和数组的递推公式
                sum[i] = sum[i - 1] + nums[i];
            }
        }
    }
    public int sumRange(int left, int right) {
    
    
        if(sum == null) return 0;
        if(left == 0) return sum[right];
        return sum[right] - sum[left - 1];
    }
}

304. 二次元領域と検索

この問題は 2 次元配列の接頭辞和のアルゴリズムに相当します. 1 次元配列の方法と同様に、元の 2 次元配列と行と列が等しい 2 次元接頭辞和配列 sum を構築しますここで、 sum[i][j] は、元の配列の 0 <= row <= i および 0 <=col <= j のすべての要素 num[row][col] の合計、つまり [0,i]を意味します
したがって、(row1,col1) から (row2,col2) までの範囲の四角形内の要素の合計を求める方法は次のとおりです。
2Dエリアと検索例図
(row1,col1) から (row2,col2) までの範囲の長方形は、(0,0) から (row2,col2) までの大きな長方形から、赤い領域と青い領域の小さな長方形を引いたものであることがわかります図の赤い領域と青い領域を 2 回引いた領域、つまり sum[ row2][col2] - sum[row1 - 1][col2] - sum[row2][col1 - 1] + sum [行1 - 1][列1 - 1]

class NumMatrix {
    
    
    private int[][] sum;
    public NumMatrix(int[][] matrix) {
    
    
        int m = matrix.length;
        int n = matrix[0].length;
        sum = new int[m][n];
        sum[0][0] = matrix[0][0];
        //矩形的第一行跟第一列的前缀和计算方法与其它位置的不同
        for(int i = 1;i < n;i++) sum[0][i] = sum[0][i - 1] + matrix[0][i];
        for(int i = 1;i < m;i++) sum[i][0] = sum[i - 1][0] + matrix[i][0];
        for(int i = 1;i < m;i++){
    
    
            for(int j = 1;j < n;j++){
    
    
                sum[i][j] = matrix[i][j] + sum[i][j - 1] + sum[i - 1][j] - sum[i - 1][j - 1];
            }
        } 
    }
    public int sumRegion(int row1, int col1, int row2, int col2) {
    
    
        if(row1 == 0 && col1 == 0) return sum[row2][col2];
        if(row1 == 0) return sum[row2][col2] - sum[row2][col1 - 1];
        if(col1 == 0) return sum[row2][col2] - sum[row1 - 1][col2];
        //下面这个计算公式是应对于普通情况的,可以发现不适用的情况是 col1 == 0 或者 row1 == 0,对应的就有三种输入情况
        //分别是row1跟col1都为0,row1为0col1不为0.row1不为0col1为0 
        return sum[row2][col2] - sum[row1 - 1][col2] - sum[row2][col1 - 1] + sum[row1 - 1][col1 - 1];
    }
}

560. 和が K である部分配列

1 次元配列の場合、その連続部分配列は要素 0 (要素 0 を含む) から始まるか、要素 0 から始まらないかのどちらかです。前者の場合、プレフィックスを使用して問題を解決するには
、プレフィックスと配列の合計 k の要素はいくつですか?
後者の場合、部分配列 [i, j] の要素の合計が k、0 < i <= j であると仮定すると、対応して、部分配列の要素の合計 [ 0, i) は sum[ j] - k で、これは配列内のプレフィックスと sum[i] です。特にこの質問では、配列全体を走査し、現在走査されている要素の合計を記録し、map を使用して各合計の出現回数を記録し、マップ内のキー sum - k の値を判断します。 、つまり、走査されたプレフィックスの合計の合計がいくつあるか - k

public int subarraySum(int[] nums, int k) {
    
    
    int len = nums.length;
    Map<Integer,Integer> map = new HashMap<>();
    //map记录数组中,前缀和为某个值的前缀子数组的个数
    //初始设置一个前缀和为0时有一个子数组,这一个表示的是一个数组元素都不选时长度为0的子数组
    //这样后续遇到刚好有前缀和为k的时候,按照上面说的第二种情况来处理即可
    map.put(0,1);
    int sum = 0;
    int res = 0;
    for(int i = 0;i < len;i++){
    
    
    	//这里就不用真的去构造一个前缀和数组,只需遍历计算出每一个前缀和直接使用即可
        sum += nums[i];
        int key = sum - k;
        if(map.containsKey(key)){
    
    
            res += map.get(key);
        }
        map.put(sum,map.getOrDefault(sum,0) + 1);
    }
    return res;
}

1442. 2 つの排他的または等しい配列を形成するトリプルの数 (1 日あたり 1 つの質問)

以下の説明はLeetCode の公式ソリューションからのものですが、
公式ソリューション
s[0] は 0 を表すことに注意してください。a = bを満たすには、jに関係なく、s[i] = s[k + 1]が必要です(0 <= i < j <= k < arr.lengthの条件に従って、i < kを取得できます)。つまり、 s[i] = s[k + 1] のセットが見つかるたびに、k - i 個の対応するトリプルが存在します。したがって、以下のコードを取得できます

public int countTriplets(int[] arr) {
    
    
    int n = arr.length;
    int[] s = new int [n + 1];
    for(int i = 1;i <= n;i++){
    
    
        s[i] = arr[i - 1] ^ s[i - 1];
    }
    int ans = 0;
    for(int i = 0;i < n;i++){
    
    
        for(int k = i + 1;k < n;k++){
    
    
            if(s[i] == s[k + 1]){
    
    
                ans += k - i; //k + 1 - i - 1
            }
        }
    }
    return ans;
}

上記のコードは最適であると言えます
. k の観点から始めましょう: 各 k について、実際にはその前に s[i] = s[k + 1] がいくつあるかを探しています。 k を加算して i を減算するだけです。つまり、各添字 k について、k の前の s[i] = s[k + 1] に対応する添字 i の数と、これらすべての添字 i の合計がわかっていれば、各 k の最終的な回答への貢献。実装のアイデアは、ハッシュ レコードを使用して、走査された s[i] の同じ値の出現回数と、対応するすべての添え字 i の合計を記録することで、O(n) の複雑さを達成できるようにすることです。

public int countTriplets(int[] arr) {
    
    
    int n = arr.length;
    int[] s = new int [n + 1];
    //count即记录每个前缀和及其出现的次数
    HashMap<Integer,Integer> count = new HashMap<>();
    //indexSum记录每个相等的前缀和s[i]的下标i的总和
    HashMap<Integer,Integer> indexSum = new HashMap<>();
    int ans = 0;
    //k从1开始,所以把s[0]对应的数据先放入哈希表
    count.put(0,1);
    indexSum.put(0,0);
    for(int k = 1;k <= n;k++){
    
    
        s[k] = arr[k - 1] ^ s[k - 1];
        if(count.containsKey(s[k])){
    
    
            ans += (k - 1) * count.get(s[k]) - indexSum.get(s[k]);
        }
        count.put(s[k],count.getOrDefault(s[k],0) + 1);
        indexSum.put(s[k],indexSum.getOrDefault(s[k],0) + k);
    }
    return ans;
}

テストデータの複雑さは O(n) ですが、ハッシュ テーブルの検索と設定を考慮すると、実際の時間効率は依然として上記の最初の方法よりも高くなります。

525. 連続配列

参照質問の解決策は次のように構成されます。
01 の数の差を、部分配列内の 0 の数から 1 の数を減算することによって得られる差として定義します。マイナス[i]は、部分配列[0,i]内の01の数の差を意味します。

次に、0 <= j < i およびminus[j] == minus[i] を満たす添え字 j を見つけることができたとしても、それは部分配列 [j + 1,i] 内の 01 の数の差が次のようになることを意味するわけではありません。 0、つまり0の数 1に等しい数ですか?

これには、i までトラバースするときに、minus[i] を計算し、同時にハッシュ テーブルを使用して、トラバースした部分の 01 の数が minus[i] に等しい j を記録する必要があります。その後、[ を計算できます。 j + 1,i] の長さ。条件を満たすすべての部分配列の最大長が最終的な答えになります。

マイナス[i]を計算するにはどうすればよいですか? minus[i] を明示的に計算する代わりに、接頭辞と配列接頭辞を使用します。prefix[i] は [0,i] 内の 1 の数を表し、[0,i] 内の要素の合計数は i + 1 になります。 、1 の数は prefix[i]、0 の数は i + 1 - prefix[i]、01 の数の差は i + 1 - 2 * prefix[i]

ハッシュテーブルに記録するにはどうすればよいですか? キーの値はマイナス[j]を表し、値の値はjを表します。i にトラバースすると、i のマイナス[i]は i + 1 - 2 * prefix[i] として計算され、ハッシュ テーブルが一致するかどうかを確認できます。 is i + 1 - 2 * prefix[i] (存在する場合)、値 j を取り出し、部分配列の長さを i - j として計算します (i - j + 1 ではなく、ここでの修飾された部分配列は [ j + 1 ,i]); そうでない場合は、キーと値のペア k = i + 1 - 2 * prefix[i], v = i を入力します。

走査プロセスでは最大長が維持されます

また、01個の数量の差が分かりやすい範囲は[-n,n]なので、Mapを使わずにサイズ2n+1の配列をハッシュテーブルとして使用することができ、Map構造にかかる時間を節約できます。

public int findMaxLength(int[] nums) {
    
    
    int n = nums.length, max = 0;
    //计算前缀和数组
    int[] prefix = new int[n];
    prefix[0] = nums[0] ;
    for (int i = 1; i < n; i++) 
        prefix[i] = prefix[i - 1] + nums[i];
    int[] ht = new int[2 * n + 1]; //哈希表
    //哈希表初始化,由于后续计算中ht中元素值可能为0,所以需要初始化为-1表示未被赋值过
    Arrays.fill(ht,-1); 
    int minus,index;
    for (int i = 0; i < n; i++) {
    
    
        minus = i + 1 - 2 * prefix[i]; //计算i对应的 01数量差
        index = minus + n;             //计算 01数量差 对应的哈希表中的槽
        //如果ht[index]不为-1说明该槽被覆盖过,可以计算[index + 1,i]的长度
        if(ht[index] != -1) max = Math.max(max,i - ht[index]);
        //如果01数量差为0说明[0,i]就符合条件,长度为i + 1
        else if(minus == 0) max = Math.max(max,i + 1);
        //如果ht[index]为-1才更新ht[index]的值,因为我们需要的是符合条件的子数组的最大长度
        //对于不同的j拥有同一个01数量差,我们记录的应该是最小的j,这样计算得到的子数组长度i - j才会是最大的
        else ht[index] = i;
    }
    return max;
}

違い

整数 1 次元配列 nums に対して、等しい長さの差分配列 dif を構築します。ここで、dif[i] は、nums[i] から nums[i - 1] を減算することによって得られる差分を表します (i > 0)。 diff[0] = nums[0] は、
差動例図
差分配列に基づいて元の配列を取得するために推論できます。差分配列の意味は、配列の隣接する要素間の差分であり、
数値 k を加算するなど、1 次元配列の一定間隔内のすべての配列要素に対して同じ演算を実行したい場合 (k は負の数)、演算後 区間内のすべての数値の差は演算前と同じです。つまり、差分配列では、この区間の値は変化していません。区間内の最初の数値のみです。 [4,2,2,-5] のように、その差は k だけ増加し、区間内の最後の数値 (存在する場合) とそれとの差は k だけ減ります。区間 [1,2 ] は 3 を加算する操作を実行します。つまり、[4,5,5,-5] が得られ、差分配列は [4,1,0,-10] になります。演算、 dif[1] = dif[1] + 3、 dif [3] = dif[3] - 3
元の配列の最後の要素には後続の番号がないことを考慮し、区間演算に最後の要素が含まれる場合は、差分配列の値に対応する区間の最初の番号に k を追加するだけで済みます。区間の右境界に k を追加する必要はありません
差分配列がない場合、元の配列に対して間隔演算を実行する複雑さは O(n) です。間隔演算が m 回ある場合、最終配列を取得する複雑さの合計は O(n * m) です。差分配列, 各区間演算の複雑さは O(1) です. 2 つの差分配列の要素の値を変更するだけで済みます. m 回の区間演算後の最終配列を取得するには, 次の操作のみが必要です差分配列を 1 回走査して、元の配列を取得します。はい、合計の複雑さは O(1 * m + n) = O(m + n) です。質問に区間演算が含まれていることがわかった場合は、まず次のことができるかどうかを検討する必要が
あります。問題を解決するには差分配列を使用します。

1094.相乗り

この質問の元の配列の長さは駅の総数に等しく、配列の各要素は添字に対応する駅に到着したときのバスの最大乗車人数を表します。

各trips[i]は間隔演算であることに注意してください。つまり、元の配列[ start_locationend_location )の間隔上のすべての数値にnum_passengersを加算します。end_locationこのtripsのnum_passengersを表すため、end_location含まれないことに注意してください[ i]乗客が降りたい駅は、これらの乗客がこの駅に到着するときにカウントされないことを意味するため、差分配列のdif[ start_location ] は + num_passengersで演算する必要があり、dif[ end_location ]は次のようにする必要があります。 dif[ end_location + 1]ではなくnum_passengersで操作されます

public boolean carPooling(int[][] trips, int capacity) {
    
    
    int len = trips.length;
    //题目中说到0 <= trips[i][1] < trips[i][2] <= 1000,说明
    //车站的范围是[0,1000],所以数组最长为1001
    int[] dif = new int[1001];
    //记录区间操作过程中操作到的最右边的位置,在后面遍历差分数组时只需遍历到这个位置即可不用遍历到1000
    int maxR = -1;
    for(int i = 0;i < len;i++){
    
    
        dif[trips[i][1]] += trips[i][0];
        dif[trips[i][2]] -= trips[i][0];
        maxR = Math.max(maxR,trips[i][2]);
    }
    //逆推得到原数组
    for(int i = 1;i <= maxR;i++){
    
    
        dif[i] += dif[i - 1];
    }
    //判断原数组中是否有超过capacity的元素,即一个站出现的人数会不会超出capacity
    for(int i = 0;i <= maxR;i++){
    
    
        if(dif[i] > capacity){
    
    
            return false;
        }
    }
    return true;
}

1109.航空券予約統計

この質問の元の配列は、長さが n の配列 num です。ここで、num[i] は、便名 i + 1 に予約されている座席の数を表します。各bookings[i]は間隔操作であり、num配列の指定された間隔ですべての配列要素に座席を追加することを意味します。

public int[] corpFlightBookings(int[][] bookings, int n) {
    
    
    int[] res = new int[n];
    for(int i = 0;i < bookings.length;i++){
    
    
    	//数组下标从0开始,但是航班编号从1开始,所以区间操作的对象其实是
    	//[bookings[i][0] - 1,bookings[i][1] - 1]
        res[bookings[i][0] - 1] += bookings[i][2];
        //区间操作涉及到右边界的话,不对差分数组处理
        if(bookings[i][1] == n) continue;
        res[bookings[i][1]] -= bookings[i][2];
    }
    for(int i = 1;i < n;i++){
    
    
        res[i] += res[i - 1];
    }
    return res;
}

おすすめ

転載: blog.csdn.net/Pacifica_/article/details/123034552