プロジェクト紹介
このプロジェクトは、主要な工場の一般的な筆記面接の質問を分解し、データ構造とアルゴリズムの基礎となる実装原理までソースをたどり、それらが何であるかを知っています。
簡単な検索のための知識構造システムを確立し、志を同じくする友人をプロジェクト AlgorithmPractice に参加するように歓迎します (問題とプルリクエストは歓迎されます)。
最も長く増加しているサブシーケンスは何ですか
特定の数値シーケンスで、このサブシーケンスの要素の値が順次増加し、このサブシーケンスの長さが可能な限り長くなるように、サブシーケンスを見つけます。 最も長く増加するサブシーケンスの要素は、元のシーケンスでは必ずしも連続していません。
この質問に対する6つの解決策を学習した後、面接担当者を見せてください。
6つのソリューション
テキストの始まり
1.暴力法
for ( int beginLocation = 0 ; beginLocation < sequence. length ( ) - 1 ; beginLocation++ ) {
for ( int subLength = 1 ; subLength < sequence. length ( ) - beginLocation; subLength++ ) {
sb = new StringBuffer ( ) ;
sb. append ( sequence. charAt ( beginLocation) ) ;
dealString ( sb, sequence, beginLocation, subLength) ;
}
}
public void dealString ( StringBuffer sb, String s, int beginPosition, int strdepth) {
if ( strdepth == 0 && judge ( sb. toString ( ) ) ) {
if ( sb. length ( ) > best_length) {
best_length = sb. length ( ) ;
}
return ;
}
for ( int i = beginPosition + 1 ; i < s. length ( ) ; i++ ) {
sb. append ( s. charAt ( i) ) ;
dealString ( sb, s, i, strdepth - 1 ) ;
sb. deleteCharAt ( sb. length ( ) - 1 ) ;
}
}
注 :
統計に使用される一時配列または一時リストは、次の操作が実行されるときにクリアされる必要があるたびに。
走査文字列のすべてのコンポーネントは再帰によって行われます。再帰の前後の選択、特にStringBuffer.deleteCharAt()メソッドに注意する必要があります。内部のパラメーターは現在のループ値を使用できませんが、StringBuffer.length()-1を使用できます。
2.動的計画法
コードの実装 : LIS_Dynamic 、テストケース: TestLIS_Dynamic
デザインのアイデア :
文字列の任意の点でのJの最大部分文字列の場合、それは、その前の部分文字列の最大数に1を足したものに等しくなります。
状態遷移方程式:longest [i] =(longest [j] + 1)> longest [i]?(Longest [j] + 1):longest [i];
メインコード :
for ( int i = 0 ; i < length; i++ ) {
for ( int j = 0 ; j < i; j++ ) {
if ( ( intArray[ j] < intArray[ i] ) ) {
longest[ i] = ( longest[ j] + 1 ) > longest[ i] ? ( longest[ j] + 1 ) : longest[ i] ;
}
}
if ( longest[ i] > best) {
best = longest[ i] ;
point = i;
}
}
3.分割統治[連続する部分文字列に限定、この質問は参照用のみ]
コードの実装 : LIS_Divide 、テストケース: TestLIS_Divide
デザインのアイデア :
指定された文字列には、特定の最大長増加サブシーケンスが必要です。指定された文字列を左側と右側に分割すると、この最大増加サブシーケンスは、左側のサブストリング、右側のサブストリング、または水平のいずれかに存在します。左と右をクロスします(これはナンセンスではありません)。
両側にまたがる部分文字列の場合、左に拡張して、それよりも小さい、最も長く増加する部分列を見つけます。同じことが右にも当てはまりますが、この問題を行う場合、サイズの判断しか行わないため、この方法で解決できるのは連続インクリメントと非連続サブストリングのインクリメントの問題は、動的プログラミングが必要になる場合があります。
メインコード :
public int divide ( int [ ] stringArr, int left, int right) {
if ( left < right) {
int mid = ( left + right) / 2 ;
int leftValue = divide ( stringArr, left, mid) ;
int rightValue = divide ( stringArr, mid + 1 , right) ;
int midValue = middleHandle ( stringArr, left, right) ;
return Math. max ( Math. max ( leftValue, rightValue) , midValue) ;
}
return 0 ;
}
while ( leftPoint - 1 >= left && stringArr[ leftPoint] > stringArr[ leftPoint - 1 ] ) {
count++ ;
leftPoint-- ;
}
while ( rightPoint + 1 <= right && stringArr[ rightPoint] < stringArr[ rightPoint + 1 ] ) {
count++ ;
rightPoint++ ;
}
注 :
このソリューションは、最も長く増加するサブシーケンスを解決するために使用するのではなく、最も長く連続的に増加するサブシーケンスを計算するためにのみ使用する必要があります。
4.文字列比較メソッド
コードの実装 : LIS_Lcs 、テストケース: TestLIS_Lcs
デザインのアイデア :
文字列を配列から外して並べ替えます
ソートされた配列は重複排除されます(増分が単調ではなく、重複データがないことを考慮して)
重複排除された配列を文字列に変換し、最も長い共通のサブシーケンスを元の文字列と比較します(本質は依然として動的プログラミングのアイデアです)。
メインコード :
QuickSortDuplexing q = new QuickSortDuplexing ( ) ;
q. sortMethod ( ints) ;
HashMap hashMap = new HashMap ( ) ;
for ( int i = 0 ; i < c. length; i++ ) {
hashMap. put ( ints[ i] , 1 ) ;
}
String temp = hashMap. keySet ( ) . toString ( ) . replace ( "," , "" ) . replace ( "[" , "" ) . replace ( "]" , "" ) . replace ( " " , "" ) ;
LCS lcs = new LCS ( ) ;
int length = lcs. count ( temp, sequence) . getCommondLength ( ) ;
注 :
重複排除はハッシュマップのキーを選択します
ハッシュマップを文字列に変換するために、多くの置き換えがあり、現時点ではこれ以上の方法は見つかりませんでした。
5.ブランチとバインドされたメソッド
コードの実装 : LIS_Branch 、テストケース: TestLIS_Branch
デザインのアイデア :
ブランチアンドバウンド方式は、暴力法を改善したものであり、いくつかの明らかな条件を取り除きます。
といった:
現在のtemp値とトラバースする残りの距離の合計が最適値以下の場合、続行する必要はありません。
トラバースする残りの距離が現在の最適解よりも短いため、続行する必要はありません。
メインコード :
for ( int i = 1 ; count_best <= length - i; i++ ) {
list_temp = new ArrayList ( ) ;
list_temp. add ( StringArray[ i - 1 ] ) ;
count_temp = 1 ;
count ( i) ;
}
if ( ( length - 1 ) - depth + ( count_temp + 1 ) <= count_best) {
return ;
}
if ( count_temp > count_best || depth == length - 1 ) {
if ( count_temp > count_best) {
list_best = new ArrayList < > ( list_temp) ;
count_best = count_temp;
}
if ( depth == length - 1 ) {
return ;
}
}
for ( int i = depth; i < length; i++ ) {
if ( list_temp. get ( count_temp - 1 ) < StringArray[ i] ) {
count_temp++ ;
list_temp. add ( StringArray[ i] ) ;
count ( i + 1 ) ;
list_temp. remove ( list_temp. get ( -- count_temp) ) ;
}
}
注 :
リストが割り当てられている場合、これによって参照が従うことはありません。list_best = new ArrayList <>(list_temp);
6.ポーカー法[ポーカー法の本質は分割統治の考え方です]
コードの実装 : LIS_Poker 、 TestLIS_Poker
デザインのアイデア :
トランプのプレイ方法によると、最初のカードは自己開放型です。
2つ目は1つ目よりも小さいため、1つ目の上にあります
2番目のスタックは最初のスタックよりも大きく、別のスタックが開かれます。
3番目は2番目よりも小さいので、2番目の上にあります
3番目のものは2番目のものより大きく、別のスタックが再生されます。
スタックの最終的な数は、必要なサブシーケンスの最も長く増加する数です。異なるスタックに配置され、厳密に増加している組み合わせを確実に見つけることができます。数学的な証明は省略されています。
メインコード :
for ( int i = 0 ; i < count; i++ ) {
left = 0 ;
right = piles;
poker = intArray[ i] ;
while ( left < right) {
mid = ( left + right) / 2 ;
if ( poker < top[ mid] ) {
right = mid;
} else if ( poker > top[ mid] ) {
left = mid + 1 ;
} else {
right = mid;
}
}
if ( left == piles) {
piles++ ;
}
top[ left] = poker;
}