超ハードコア!BATに横たわった後、私は15の最も頻繁に発生する配列の質問を要約しました

筋金入りの作家として、私はあなたとナンセンスな話をすることは決してありません、ルーチンなしであなたに乾物を送ります

トピック1:

    配列arrが与えられた場合、ソートする必要がある最短のサブ配列の長さを見つけます

請求:

    時間o(n)、空間o(1)

アイデア:

    順序付けられた配列では、任意の数は左側の数より小さく、右側の数より大きくなければなりません。

    並べ替える必要があることがわかったサブ配列は、明らかに右側の最小値よりも大きいか、左側の最大値よりも小さくなっています。

    変数noMinindex = -1を初期化し、右から左にトラバースし、minの最小値を記録します。現在の数値がminより大きい場合、順序付けが必要な場合は、minを現在の左側に配置する必要があることを意味します。番号、およびnoMinindexを更新します。

    言い換えると、noMinindexは、この状況の左端の位置を記録する責任があります。反対方向にnoMaxindexを扱います

    それらが形成する間隔は、ソートする必要がある最短の部分です

public class MinLengthForSort {
	public static int getMinLength(int[] arr) {
		if (arr == null || arr.length < 2) {
			return 0;
		}
		int min = arr[arr.length - 1];
		int noMinIndex = -1;
		for (int i = arr.length - 2; i != -1; i--) {
			if (arr[i] > min) {
				noMinIndex = i;
			} else {
				min = Math.min(min, arr[i]);
			}
		}
		if (noMinIndex == -1) {
			return 0;
		}
		int max = arr[0];
		int noMaxIndex = -1;
		for (int i = 1; i != arr.length; i++) {
			if (arr[i] < max) {
				noMaxIndex = i;
			} else {
				max = Math.max(max, arr[i]);
			}
		}
		return noMaxIndex - noMinIndex + 1;
	}
	public static void main(String[] args) {
		int[] arr = { 1, 2, 4, 7, 10, 11, 7, 12, 6, 7, 16, 18, 19 };
		System.out.println(getMinLength(arr));

	}

}

 

トピック2:

    配列を指定して、半分以上の時間で発生する数を見つけます

愚かな考え:真ん中を見つけるために並べ替える

アイデア:

    DP:変数カウントをスキャンして、ソリューションの発生数を記録します。現在のソリューションの場合は++になります。それ以外の場合、カウントが負の場合は、現在のソリューションが置き換えられます。(説明:解がすべて隣り合っている(前にある)と想像してください。カウントは最初に最大に達し、次に1または0に減少し、他の数値が最初に表示されます。これにより、正しい解のカウントがaに減少する可能性があります。負の数ですが、後で正しい解決策になります。多くの場合、最後に間違いなく正しい解決策になるようにします)

int main()
{
    int n;//个数
    scanf("%d",&n);
    int temp,k,count=0;
    while(n--)
    {
        scanf("%d",&temp);
        if(temp==k)count++;
        else
        {
            count--;
            if(count<0){count=0;k=temp;}
        }
    }
    printf("%d\n",k);
}

 

 

トピック3:

N×M整数行列行列と整数Kが与えられると、行列の各行と各列がソートされます。Kが行列にあるかどうかを判別する関数を実装します。

例えば:

0 1 2 5

2 3 4 7

4 4 4 8

5 7 7 9

Kが7の場合はtrueを返し、Kが6の場合はfalseを返します

請求:

時間計算量はO(N + M)であり、余分な空間計算量はO(1)です。

アイデア:

1.マトリックスの右上隅で数値を探し始めます(row = 0、col = M-1)。

2.現在の数値行列[行] [列]とKの関係を比較します。

Kと等しい場合は、検出されたことを意味し、trueを直接返します。

行列の各列が並べ替えられているため、Kより大きい場合、現在の番号が配置されている列では、現在の番号の下の番号がKより大きいため、検索を続ける必要はありません。 colth列で、col = col -1とし、手順2を繰り返します。

行列の各行が並べ替えられているため、Kより小さい場合、現在の数値が配置されている行では、現在の数値の左側の数値がKより小さいため、続行する必要はありません。行番目の行を検索し、row = row + 1とし、手順2を繰り返します。

3.範囲外が検出され、Kに等しい数が検出されない場合、falseを返します。

または、マトリックスの左下隅(row = N-1、col = 0)から検索を開始できます。特定のプロセスは、同様です。

コード:
 

/**
 * 在行列都排好序的矩阵中找数
 */
public class IsContains {
    public boolean isContains(int[][] matrix, int K) {
        int row = 0;
        int col = matrix[0].length - 1;
        while (row < matrix.length && col > -1) {
            if (matrix[row][col] == K) {
                return true;
            } else if (matrix[row][col] > K) {
                col--;
            } else {
                row++;
            }
        }
        return false;
    }
}

トピック4:マトリックスを円で印刷する


トピック:

整数行列を指定して、円で印刷してください。例:1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16、印刷結果は次のとおりです:1,2,3,4,5、 12、16、15、14、13、9、5、6、7、11、10

請求:

余分なスペースの複雑さはO(1)です

 アイデア:

行列分割円処理。行列では、左上隅の座標(tR、tC)と右下隅の座標(dR、dC)で行列を表すことができます。たとえば、問題の行列は、(tR、tC)=の場合です。 (0,0)、(dR、dC)=(3,3)の場合、表されるサブ行列は行列全体であり、このサブ行列の最も外側の部分は次のようになります:

1 2 3 4

5 8

9 12

13 14 15 16

このサブマトリックスの外層を円で印刷できる場合、(tR、tC)=(0,0)、(dR、dC)=(3,3)の場合、印刷結果は次のようになります。1、2 、3、4、8、12、16、15、14、13、9、5。次に、tRとtC、つまり(tR、tC)=(1,1)に1を加算し、dRとdC、つまり(dR、dC)=(2,2)から1を減算し、 -現時点で表されているマトリックスは次のとおりです。

6 7

10 11

次に、このサブマトリックスを回転させて印刷すると、結果は6,7,11,10になります。tRとtCに1を加算します。つまり、(tR、tC)=(2,2)であり、dRとdCから1を減算します。つまり、(dR、dC)=(1,1)です。左上隅の座標が右下隅の座標よりも右または下に移動していることが判明した場合、プロセス全体が停止します。印刷されたすべての結果は、必要な印刷結果に関連付けられています。

コード:

/**
 * 转圈打印矩阵
 */
public class PrintMatrixSpiralOrder {
    public void spiralOrderPrint(int[][] matrix) {
        int tR = 0;
        int tC = 0;
        int dR = matrix.length - 1;
        int dC = matrix[0].length - 1;
        while (tR <= dR && tC <= dC) {
            printEdge(matrix, tR++, tC++, dR--, dC--);
        }
    }
 
    //转圈打印一个子矩阵的外层,左上角点(tR,tC),右下角点(dR,dC)
    private void printEdge(int[][] matrix, int tR, int tC, int dR, int dC) {
        if (tR == dR) {//子矩阵只有一行时
            for (int i = tC; i <= dC; i++) {
                System.out.print(matrix[tR][i] + " ");
            }
        } else if (tC == dC) {//子矩阵只有一列时
            for (int i = tR; i <= dR; i++) {
                System.out.print(matrix[i][tC] + " ");
            }
        } else {//一般情况
            int curCol = tC;
            int curRow = tR;
            while (curCol != dC) {//从左向右
                System.out.print(matrix[tR][curCol] + " ");
                curCol++;
            }
            while (curRow != dR) {//从上到下
                System.out.print(matrix[curRow][dC] + " ");
                curRow++;
            }
            while (curCol != tC) {//从右到左
                System.out.print(matrix[dR][curCol] + " ");
                curCol--;
            }
            while (curRow != tR) {//从下到上
                System.out.print(matrix[curRow][tC] + " ");
                curRow--;
            }
        }
    }
}


トピック5:正方行列を時計回りに90°回転します


トピック:

N×Nマトリックスが与えられた場合、このマトリックスを時計回りに90°回転するように調整します。

例えば:

1 2 3 4

5 6 7 8

9 10 11 12

13 14 15 16

時計回りに90°回転すると、次のようになります。

13 9 5 1

14 10 6 2

15 11 7 3

16 12 8 4

請求:

余分なスペースの複雑さはO(1)です。

アイデア:

引き続き円処理を分割する方法を使用し、

行列では、左上隅の座標(tR、tC)と右下隅の座標(dR、dC)で行列を表すことができます。たとえば、問題の行列は、(tR、tC)=の場合です。 (0,0)、(dR、dC)=(3,3)の場合、表されるサブ行列は行列全体であり、このサブ行列の最も外側の部分は次のようになります:

1 2 3 4

5 8

9 12

13 14 15 16

この外側の円では、1、4、16、13がグループであり、1が4の位置、4が16の位置、16が13の位置、13が1の位置を占め、グループが調整されます。 。次に、2、8、15、および9はグループであり、調整を行うプロセスを続行し、最後に3、12、14、および5は、調整を行うプロセスを続行するグループです。次に、(tR、tC)=(0,0)、(dR、dC)=(3,3)の部分行列の外層が調整され、tRとtCが1ずつ増加します。つまり、(tR 、tC)=(1,1)、dRとdCから1を引く、つまり(dR、dC)=(2,2)であり、この時点で表される部分行列は次のようになります。

6 7

10 11

この外層には6、7、11、10のセットが1つだけあり、占有後に調整できます。したがって、サブマトリックスのサイズがM×Mの場合、合計M-1グループが存在し、個別に調整できます。

コード:

/**
 * 将正方形矩阵顺时针旋转90°
 */
public class RotateMatrix {
    public static void rotate(int[][] matrix) {
        int tR = 0;
        int tC = 0;
        int dR = matrix.length - 1;
        int dC = matrix[0].length - 1;
        while (tR < dR) {
            rotateEdge(matrix, tR++, tC++, dR--, dC--);
        }
    }
 
    private static void rotateEdge(int[][] matrix, int tR, int tC, int dR, int dC) {
        int times = dC - tC;//times就是总的组数
        int temp = 0;
        for (int i = 0; i != times; i++) {//一次循环就是一组占据调整
            temp = matrix[tR][tC + i];
            matrix[tR][tC + i] = matrix[dR - i][tC];
            matrix[dR - i][tC] = matrix[dR][dC - i];
            matrix[dR][dC - i] = matrix[tR - i][dC];
            matrix[tR - i][dC] = temp;
        }
    }
}


トピック6:「ジグザグ」印刷マトリックス


トピック:

行列を指定して、次のようにジグザグに行列を印刷します。

1 2 3 4

5 6 7 8

9 10 11 12

「ジグザグ」印刷の結果は次のとおりです:1,2,5,9,6,3,4,7,10,11,8,12

請求:

余分なスペースの複雑さはO(1)です

アイデア;

上位座標(tR、tC)は(0,0)に初期化され、最初に行列の最初の行に沿って移動し(tC ++)、最初の行の右端の要素に到達したら、行列の最後の列に沿って移動します(tR ++ )。
下位座標(dR、dC)は最初は(0、0)で、最初に行列の最初の列に沿って移動し(dR ++)、最初の列の一番下の要素に到達すると、次に行列の最後の行に沿って移動します(dC ++ )。
上の座標と下の座標は同期して移動します。各移動後の上の座標と下の座標の間の接続は、マトリックスの対角線です。対角線に要素を印刷するだけです。
前回スラッシュを左下から右上に印刷した場合は、今回は右上から左下に、またはその逆に印刷する必要があります。つまり、印刷の方向はブール値で表すことができ、毎回逆にすることができます。
コード:

/**
 * 之字形打印矩阵
 */
public class PrintMatrixZigZag {
    public static void printMatrixZigZag(int[][] matrix) {
        int tR = 0;
        int tC = 0;
        int dR = 0;
        int dC = 0;
        int endRow = matrix.length - 1;
        int endCol = matrix[0].length - 1;
        boolean fromUp = false;
        while (tR != endRow + 1) {
            printLevel(matrix, tR, tC, dR, dC, fromUp);
            tR = tC == endCol ? tR + 1 : tR;
            tC = tC == endCol ? tC : tC + 1;
            dR = dR == endRow ? dR : dR + 1;
            dC = dR == endRow ? dC + 1 : dC;
            fromUp = !fromUp;
        }
        System.out.println();
    }
 
    private static void printLevel(int[][] matrix, int tR, int tC, int dR, int dC, boolean fromUp) {
        if (fromUp) {
            while (tR != dR + 1) {//从左下到右上
                System.out.print(matrix[tR++][tC--] + " ");
            }
        } else {
            while (dR != tR - 1) {//从右上到左下
                System.out.print(matrix[dR--][dC++] + " ");
            }
        }
    }
}

トピック7

長さNの整数配列arrが与えられると、N個の等しくない自然数1〜Nがあります。

arrのソートを実装してください

ただし、直接割り当てによって、添え字0〜N-1の値を1〜Nに置き換えないでください。

要件:時間計算量はO(N)であり、追加の空間計算量はO(1)です。

 

アイデア:左から右にチェックします。変更する必要があることを確認した後、移動する位置に直接配置します。変更した番号の位置が間違いなく正しい場所ではない場合は、同じ方法を繰り返します。そして最後にそれは間違いなく跳ね返り(理由はそれについて考えるのが面倒です)、それからチェックを続けます。

public static void sort1(int[] arr) {
		int tmp = 0;
		int next = 0;
		for (int i = 0; i != arr.length; i++) {
			tmp = arr[i];
			while (arr[i] != i + 1) {
				next = arr[tmp - 1];
				arr[tmp - 1] = tmp;
				tmp = next;
			}
		}
	}

トピック8

この質問の一般的な考え方:前後よりも小さい数を見つけるために検索するか、最小の数を選択する、それはローカルで最小でなければなりません;など。

しかし、これらはすべてO(n)メソッドであり、二分法を使用するとO(logn)を実現できます。

二分法:

  左端と右端の要素について考えてみます。arr[0] <arr [1]が0を返す場合; arr [N-1] <arr [N-2]がN-1を返す場合。

   真ん中の要素を考えてみましょう。真ん中の要素がその左側の要素よりも大きい場合、極小値は配列の左半分にある必要があります。

   中央の要素が右側の要素よりも小さい場合、極小値は配列の右半分にある必要があります

   中央の要素は、左側の値よりも右側の値よりも小さく、極小値です。
 

トピックナイン

 

整数配列arrを指定すると、この位置を含まない累積配列を返します。

たとえば、2 3 14は128 246を返します。

方法1:すべての数値の積を計算し、各位置をそれ自体で除算します。ピットに注意してください。配列に0がある場合、0の位置は他の数値の積であり、他の位置はすべて0です。0が複数ある場合、すべての位置は0です。

	public int[] product1(int[] arr) {
		if(arr==null || arr.length<2) {
			return null;
		}
		int count=0;//0的个数
		int all=1;//除0以外的数的乘积
		for(int i=0;i!=arr.length;i++) {
			if(arr[i]!=0) {
				all*=arr[i];
			}else {
				count++;
			}
		}
		int[] res=new int[arr.length];
		if(count==0) {
			for(int i=0;i!=arr.length;i++) {
				res[i]=all/res[i];
			}
		}else if(count==1) {
			for(int i=0;i!=arr.length;i++) {
				if(arr[i]==0) {
					res[i]=all;
				}
			}
		}
		return res;
	}

 

トピック10:サブ配列の最大累積合計

整数配列を入力し、合計を最大化するために配列内の連続するサブ配列を見つけます。たとえば、配列x

x [2..6] 187の合計を返す必要があります。

 

これらの4つのコードの機能は、最大のサブ配列を見つけることです(単語は正確であり、サブ配列は連続的であり、サブシーケンスは不連続である可能性があることに注意してください)。

1)

for(i = 1; i <= n; i++)
	scanf("%d", &num[i]);
ans = num[1]; 
for(i = 1; i <= n; i++)
{
	for(j = i; j <= n; j++) 
	{
		s = 0;
		for(k = i; k <= j; k++)
			s += num[k];
		if(s > ans)
			ans = s;
	}
}

各サブ配列の開始点と終了点、つまりiとjをそれぞれ列挙し、開始点と終了点ごとに、中央部分、つまりkループを合計します。明らかに、n個の開始点とn個の終了点(複雑さに影響を与えずに重複排除と半分化)があるため、サブ配列の数はO(N ^ 2)です。各サブ配列について、トラバースして合計する必要があります。 、サブ配列の長さは1-nです。待機せずに、平均O(N)をトラバースし、O(N ^ 3)を掛けます(時間の錯覚が大きくなる可能性があることに注意してください)。すべてのサブアレイの中で最大のものを見つけるだけです。

2)

for(i = 1; i <= n; i++)
	scanf("%d", &num[i]);
sum[0] = 0;
for(i = 1; i <= n; i++) {
	sum[i] = num[i] + sum[i - 1];
}
ans = num[1]; 
for(i = 1; i <= n; i++) {
	for(j = i; j <= n; j++) {
		s = sum[j] - sum[i - 1];
		if(s > ans) ans = s;
	}
}

最初の要素で始まり、i番目の要素で終わる各サブ配列の合計を前処理するか、各開始点と終了点を列挙しますが、合計するときに、トラバースせずに直接減算できます。各サブ配列の演算はO(1)であり、サブ配列の数はO(N ^ 2)であるため、合計時間はO(N ^ 2)になります。

3)

int solve(int left, int right)
{
	if(left == right)
		return num[left];
 
	mid = (left + right) / 2;
	lans = solve(left, mid);
	rans = solve(mid + 1, right);
 
	sum = 0, lmax = num[mid], rmax = num[mid + 1];
	for(i = mid; i >= left; i--) {
		sum += num[i];
		if(sum > lmax) lmax = sum;
	}
	sum = 0;
	for(i = mid + 1; i <= right; i++) {
		sum += num[i];
		if(sum > rmax) rmax = sum;
	}
 
	ans = lmax + rmax;
	if(lans > ans) ans = lans;
	if(rans > ans) ans = rans;
	return ans;
}
 
int main(void)
{
	scanf("%d", &n);
	for(i = 1; i <= n; i++)
		scanf("%d", &num[i]);
 
	printf("%d\n", solve(1, n));
	return 0;
}

分割し、左側と右側で最大のサブ配列を見つけて、最大のものを取ります。ただし、別の状況があります。ブレークポイントを含むサブ配列も考慮する必要があります。これら2つのループがこのように記述されている理由を考えてみてください。なぜ最終的にロジックが正しいのですか?

4)動的計画法の入門的なアイデア

列挙しない場合、num [i]の意味は、添え字iで終わるすべてのサブ配列の中で最大です。

配列をトラバースします。i番目の要素の場合、そのすべてのサブ配列の添え字範囲には[1、i]、[2、i] ..... [i-1、i]があり、それ自体を見てみましょう。 i -1要素、そのサブ配列は[1、i-1]、[2、i-1] ..... [i-1]です。num [i]の意味を考えてください。iの終わりを探す場合は、i-1の最大の終わりにiを追加するだけです。もちろん、i-1で終わる最大のサブ配列が負の場合、最大のiで終わるサブ配列はそれ自体です。

なぜO(N)?時間はどこで節約できますか?多くの不要な計算を省略しました。iを計算するとき、前の配列の合計はすでに計算されています。ナイーブアルゴリズムはそれを記録しませんでしたが、計算を繰り返したため、時間の無駄になりました。アルゴリズム最適化のプロセスは、繰り返される計算を削除するプロセスです。
 

for(i = 1; i <= n; i++)
	scanf("%d", &num[i]);
 
num[0] = 0;
ans = num[1];
for(i = 1; i <= n; i++) 
{
	if(num[i - 1] > 0) 
		num[i] += num[i - 1];
	else
		num[i] += 0;
	if(num[i] > ans) 
		ans = num[i];
}

トピック11、部分行列の最大累積合計

行列が与えられたら、行列の中で最大の部分行列を見つけてください。

 

 

 

前の質問の説明を理解している場合は、ヒントを示します。2番目のコードと4番目のコードを組み合わせて使用​​します。

 

 

説明:

1 2 3 4

-1 -2 1 2

1 3 -2 1

-1 -2 -1 -3

図に示すように、最初の3行は全体で最大です。

どうやるか

最初に2番目のコードのアイデアを使用して、前処理します

各数値は、この数値の位置である累積合計に対するこの列の終わりを表します。

1 2 3 4

0 0 4 6

1 3 2 7

0 1 1 4

次に、各列の開始点と終了点をそれぞれ行0、1、2、および3として列挙します。

次に、それを1つの次元に圧縮して実行します

たとえば、行1〜3の長方形を見つけるには、行0と3を減算します。

0-1,1-2,1-3,4-4 = -1、-1、-2,0は1〜3行の圧縮結果です

次に、1次元のdpを押すだけで実行できます
 

public class SubMatrixMaxSum {
	public static int maxSum(int[][] m) {
		if (m == null || m.length == 0 || m[0].length == 0) {
			return 0;
		}
		int max = Integer.MIN_VALUE;
		int cur = 0;
		int[] s = null; // 累加数组
		for (int i = 0; i != m.length; i++) {
			s = new int[m[0].length];
			for (int j = i; j != m.length; j++) {
				cur = 0;
				for (int k = 0; k != s.length; k++) {
					s[k] += m[j][k];
					cur += s[k];
					max = Math.max(max, cur);
					cur = cur < 0 ? 0 : cur;
				}
			}
		}
		return max;
	}

	public static void main(String[] args) {
		int[][] matrix = { { -90, 48, 78 }, { 64, -40, 64 }, { -81, -7, 66 } };
		System.out.println(maxSum(matrix));

	}

}

トピック12:サブ配列の最大累積積

トピック:

二重型配列arrが与えられた場合、要素は正、負、および0になります。サブ配列の累積の最大の積を返します。

アイデア:

arr [i-1]で終わる配列の最小累積積が最小で、最大累積積が最大であると仮定すると、arr [i]で終わる配列の最大累積積には3つのケースがあります。

  • max * arr [i] //それ自体で乗算する前の最大累積乗算
  • min * arr [i] //負の値になる可能性があるため、正の値が最大になります
  • arr [i] //それ自体である可能性があります。たとえば、前の最大値が1未満です。

public class SubArrayMaxProduct {

	public static double maxProduct(double[] arr) {
		if (arr == null || arr.length == 0) {
			return 0;
		}
		double max = arr[0];
		double min = arr[0];
		double res = arr[0];
		double maxEnd = 0;
		double minEnd = 0;
		for (int i = 1; i < arr.length; ++i) {
			maxEnd = max * arr[i];
			minEnd = min * arr[i];
			max = Math.max(Math.max(maxEnd, minEnd), arr[i]);
			min = Math.min(Math.min(maxEnd, minEnd), arr[i]);
			res = Math.max(res, max);
		}
		return res;
	}

	public static void main(String[] args) {
		double[] arr = { -2.5, 4, 0, 3, 0.5, 8, -1 };
		System.out.println(maxProduct(arr));

	}

}

トピック13:左半分が順序付けられて繰り返されないように、順序付けられたarr配列を調整します。また、右側が正しいことを保証するものではありません。

アイデア:
              u:左側の最後の位置、つまり0 --- uが答えです
              i:uから右にトラバースします
              。arr[i]とarr [u]が等しくない場合、それは次のことを意味します。これまでに遭遇した最大数。arr[u + 1]とarr [i]を入れ替えます。

	public static void leftUnique(int[] arr) {
		if (arr == null || arr.length < 2) {
			return;
		}
		int u = 0;
		int i = 1;
		while (i != arr.length) {
			if (arr[i++] != arr[u]) {
				swap(arr, ++u, i - 1);
			}
		}
	}
	public static void swap(int[] arr, int index1, int index2) {
		int tmp = arr[index1];
		arr[index1] = arr[index2];
		arr[index2] = tmp;
	}

トピック14:配列arrには3つの値しかありません:0、1、2、並べ替えてください

アイデア:オランダの旗の問題:https//blog.csdn.net/hebtu666/article/details/81772701

上記のWebサイトでは、アイデアとC ++の実装を紹介しています。この記事では、Javaの実装について説明します。

	public static void sort(int[] arr) {
		if (arr == null || arr.length < 2) {
			return;
		}
		int left = -1;
		int index = 0;
		int right = arr.length;
		while (index < right) {
			if (arr[index] == 0) {
				swap(arr, ++left, index++);
			} else if (arr[index] == 2) {
				swap(arr, index, --right);
			} else {
				index++;
			}
		}
	}

 

トピック15:配列arr、kが与えられた場合、kより小さい場合は左側に配置し、kと等しい場合は中央に配置し、kより大きい場合は右側に配置してください。

同じことが3つの領域に分けられますが、条件が変更されます、<k、= k、> k。

 

おすすめ

転載: blog.csdn.net/hebtu666/article/details/114960146