定番面接問題のまとめ——Binary Searchとその変種


二分検索は技術面接でよく出てくる問題で、まずこのような問題を考察し、またコードが一般的に短いため--50行を超えない.技術筆記試験や面接などの問題が出るのに適しています.
前にいくつかのテーマをしたことがありますが、多くはBSアルゴリズムの変種で、私はここでいくつかの例をあげて、総括をしましょう.
1.従来のBinary Search
     1.1. 最も一般的なBSアルゴリズムは、配列の配列を指定し、配列内に数があるかどうかを検索し、下付き文字が与えられている場合、いなければ-1を返す. 
     
template<typename T>
int binarySearch(const T vData[], int nSize, const T &query)
{
	int l, u, m;

	l = 0;
	u = nSize - 1;

	while(l <= u)//             l  u,   u     size  ,        
	{
		m = (l + u)/2;

		if(vData[m] == query) //    
		{
			return m;
		}
		else if(vData[m] > query) //           
		{
			u = m - 1;
		}
		else
		{
			l = m + 1;
		}
	}
	return -1;
}

    1. 2通常のBSアルゴリズムより少し複雑なのは、辞書で指定された文字列を検索するはずです.
入力されたすべての単語について、ソートアルゴリズムを使用して、すべての単語を辞書順に並べ、BSアルゴリズムで所与の要素の下付き文字を見つけることができます.
コードは次のとおりです.
 
#define MAXCHARLEN 50
int binarySearch(const char directionary[][MAXCHARLEN], int nSize, const char* pQuery)
{
	int l, u;

	l = 0;
	u = nSize - 1;
	int m;
	int cmpResult;
	while(l <= u)
	{
		m = (l + u)/2;
		cmpResult = strcmp(directionary[m], pQuery);//     ,        

		if(cmpResult == 0)
		{
			return m;
		}
		else if(cmpResult == 1)
		{
			u = m - 1;
		}
		else
		{
			l = m + 1;
		}

	}
	return -1;
} 

    
2.空のBianry Searchを含む
この問題の1つの記述形式は以下のようになる可能性がある.
与えられた文字列のシーケンス(辞書順に並べられたもの)には、いくつかの空白列があります.与えられた文字列の位置を見つけて、-1を返さないでください.
例えばchar directionary[]={","a","ab",",",",",",",",",",",",",",",",",",",",",",",",",",",",",",",",",",",",",",",",",",",", 
ここではbinary searchの中心点が見つかった後、空の場合、現在のmを移動する処理を追加する必要があります.
#include<stdio.h>
#include <string.h>
#include<memory.h>

#define MAXCHARLEN 50
int binarySearch(const char directionary[][MAXCHARLEN], int nSize, const char* pQuery)
{
	int l, u;

	l = 0;
	u = nSize - 1;
	int m;
	int cmpResult;
	while(l <= u)
	{
		m = (l + u)/2;
		while(m > l && directionary[m][0] == '\0') //            bug ,        bug  。
		{
			m--;
		}
		cmpResult = strcmp(directionary[m], pQuery);

		if(cmpResult == 0)
		{
			return m;
		}
		else if(cmpResult == 1)
		{
			u = m - 1;
		}
		else
		{
			l = m + 1;
		}

	}
	return -1;
}  
int main()//test cases.
{

	const char directionary[][MAXCHARLEN] = 
	{
		"", "aa", "", "bb", "", "cc", "dd", "e", "", "", "ff"
	};

	const char query[][MAXCHARLEN] = 
	{
		"aa", "bb", "cc", "dd", "kk", "e"
	};

	for( int i = 0; i < 6; i++)
	{
		printf("%d ", binarySearch(directionary, 11,  query[i]));
	}
	return 0;
}

 
3. Rotated Sorted Array
この問題には,配列を並べ替えた上でrotate方式で生成された配列が知られているという記述がある.
例えば、3 4 5 1 2は上記の説に合致する配列である. 
次の2つのタスクがあります.
3.1ある要素(Search for a specific element)を検索する.
このような問題の解析の際,1つの核心的な考え方は,配列[l,u]が分離されても,[l,m−1],[m+1,u]のうち少なくとも1つが順序付けされており,この順序付けされた区間内のすべての要素と別の区間は互いに覆われないということである.
たとえば3 4 4 5 1 2を3 4,5 1 2に分けると,重なり合わない場合,すなわちそれぞれが元のratated sorted arrayの定義を満たしている.
だからbinary searchの方法で検索することができます.
しかし、これらのコードは特にバグを書きやすく、簡単に見えますが. 
#include<stdio.h>
#include <string.h>
#include<memory.h>

bool InRange(int l, int u, int query)
{
	return query >= l && query <= u;
}
int binarySearch(int rotatedArray[], int nSize, int query)
{
	int lower, upper;

	lower = 0, upper = nSize - 1;
	int m;

	while(lower <= upper)
	{
		m = (lower + upper)/2;

		if(rotatedArray[m] == query)
		{
			return m;
		}

		if(rotatedArray[m] >= rotatedArray[lower]) //            
		{
			if(InRange(rotatedArray[lower], rotatedArray[m], query))//            
			{
				upper = m - 1;
			}
			else//           
			{
				lower = m + 1;
			}
		}
		else//        
		{
			if(InRange(rotatedArray[m], rotatedArray[upper], query))//      
			{
				lower = m + 1;

			}
			else//       
			{
				upper = m - 1;
			}
		}

	}

	return -1;

}
int main()
{

	int a[] = {3, 5, 7, 9, 0, 2};

	for(int i = 0; i < 10; i++)
	{
		printf("i: %d is at %d
",i, binarySearch(a, 6, i)); } return 0; }

    
3.2最小要素を見つける(Search for the minumum or maximum)
もし私たちが探している区間の中でa[lower]もしlower==upperが要素が1つしかない場合は、直接a[lower]に戻ることもできます.
    
他の場合、binary searchの考えを使うことができます.
a[m]>=a[lower]の場合、a[lower]->a[m]からソートされていることを示し、a[lower]検索区間はa[m],a[lower]である. 
  
int findMin(int a[], int lower, int upper)
{
	if(a[lower] <= a[upper]) //  2 case, 1:        2:           ,          
	{
		return lower;
	}
	int m = (lower + upper)/2;

	if(a[m] >= a[lower] )//   lower ... m      ,              ,          m + 1, upper. 
	{                    //               6 1     ,       m == lower
		return findMin(a, m+1, upper);
	}
	else
	{
		return findMin(a, lower, m); //        
	}
}
int main() // generate test case
{
	int a[10];
	for( int i = 0; i < 6; i++)
	{
		for( int j = 0; j < 6; j++)
		{
			a[ (i + j)%6] = j;
		}
		for( int j = 0; j < 6; j++)
		{
			printf("%d ", a[j]);
		}
		printf("
Min at %d
", findMin(a, 0, 5)); } return 0; }

    
4.出現回数を集計する
このテーマの説明は次のとおりです.
並べ替えられた配列には、いくつかの要素が重複しています.与えられた数に対してこの数が現れる回数を返す関数を書きます.
例えば、入力データ1 2 2 3 3 5 5、2を入力すると、2が配列に2回現れるため、2を返します.
    
この問題は2つのサブ問題を導入し,Query(Q)が最初に出現した位置とQが最後に出現した位置を探すことができる.
私たちはlowerと呼ぶことができます.bound,upper_bound
    
int findLowerBound(int a[], int nSize, int query)
{
	int lower, upper;
	int m;
	lower = 0, upper = nSize - 1;

	while(lower <= upper)
	{
		m = (lower + upper)>>1;

		if(a[m] == query)
		{
			while(a[--m] == query);
			return m+1;
		}
		else if(a[m] > query)
		{
			upper = m - 1;
		}
		else
		{
			lower = m + 1;
		}
	}

	return -1;
}

int findUpperBound(int a[], int nSize, int query)
{
	int lower, upper, m;

	lower = 0, upper = nSize - 1;

	while(lower <= upper)
	{
		m = (lower + upper) >> 1;

		if(a[m] == query)
		{
			while(a[++m] == query);
			return m-1;
			
		}
		else if(a[m] > query)
		{
			upper = m - 1;

		}
		else
		{
			lower = m + 1;
		}
	}
	return -1;
}
int main() //generate test case
{
	int a[] = {1, 2, 2, 3, 3, 3, 5, 5, 5}; //size is 9
	int l, u;
	//int *p =  lower_bound(a, a + 9, 0);
 	for( int i = 0; i < 7; i++)
 	{
 		l = findLowerBound(a, 9, i);
 		u = findUpperBound(a, 9, i);
		if( l != -1)
		{
			printf("l:%d u:%d  (u-l):%d
", l, u, u - l + 1); } else { printf("Can't find
"); } } return 0; }

補足:(NickMouseに感謝)
しかし,このアルゴリズムは極端な場合,複雑度はO(n)に低下し,いくつかの改良されたコードを与える.
by NickMouse:
#include <stdio.h>

int bsearch_lowerbound(int a[],int n,int x)
{
	int l=0,r=n-1;
	while(l+1<r){
		int m=(l+r)/2;
		if(a[m]>=x)
			r=m;
		else
			l=m+1;
	}
	if(a[l]==x)
		return l;
	else if(a[r]==x)
		return r;
	else
		return -1;
}

int bsearch_upperbound(int a[],int n,int x)
{
	int l=0,r=n-1;
	while(l+1<r){
		int m=(l+r)/2;
		if(a[m]<=x)
			l=m;
		else
			r=m-1;
	}
	if(a[r]==x)
		return r;
	else if(a[l]==x)
		return l;
	else
		return -1;
}

int main()
{
	int a[] = {1,1, 2, 3, 3,3, 5};

	

	
	for(int i = 0; i < 6; i++)
	{
		printf("%d
", bsearch_lowerbound(a, 7, i)); } return 0; }

5.指定した数より大きい(または小さい)最初の数を探す必要があります.
ちょっとわかりにくいですが、例をあげてください.
例えば、昇順数グループ1 4 5 8は、7が入力されている場合、8が入力:7より大きい最初の数であるため、8を返すべきである.
実はこのアルゴリズムはO(Nlog(N))の最長増分子シーケンスの中で、毎回1つの数をスキャンして、私たちはこの数を知っていて、長さが何の増分子シーケンスの最後の要素とすることができます.
    
//1 4 5 8 ---> 7
int FindFirstLarger(int a[], int nSize, int query)
{
	int lower = 0; 
	int upper = nSize - 1;
	int m ;
	while(lower <= upper)
	{
		m = (lower + upper) / 2;
		
		if(a[m] < query)
		{
			lower = m + 1;
		}
		else
		{
			upper = m - 1;
		}
	}
	return lower;
}
int main()
{
	int a[] = {1, 4, 5, 8}; //size is 9
	
	for(int i = 0; i < 10; i++)
	{
		printf("i: %d  at: %d
", i, FindFirstLarger(a, 4, i)); } return 0; }

6.行列の行列の中にある要素を探す
たとえば、次のように入力します.
  1   5   7    10 
  2   6   8   15
  4   9  11  16
12 13 19  21
入力が満たされているのは行単位で、増分ソートであり、列単位でも増分ソートです.エレメントを見つけます.存在する場合は-1を出力します.
行単位と列単位は順序付けされているため、任意の要素にとって、左側のすべての要素がそれより大きく、その下の要素がそれより大きいので、この性質を利用して、二分検索を設計することができます.この検索は0行目の最後の要素から始まります.比較の結果、下にするか、左にするかで区間全体をカバーするかを決めます.
コードは次のとおりです.
bool SearchInRowColSortedMatrix(const int data[], int nRow, int nCol, int query, int &tRow, int &tCol)
{
     int iRow, iCol;
     
     iRow = 0;
     iCol = nCol - 1;
     int t; 
     while(iRow < nRow && iCol >= 0)
     {
         t = data[iRow * nCol + iCol];//get the current data. 
         
         if( t == query) //find it
         {
             tRow = iRow;
             tCol = iCol;
             return true;
         }
         else if( t < query) // eleminate the rest of the elements in the current row, who are less than t. 
         {
             iRow++;
         }
         else //eleminate all the rest elements in current col, who are greater than t.
         {
             iCol--;
         }
     }
     return false;
}

最後に書きます.
上のコードにはバグがあるかもしれませんが、すべての内部要素を網羅した正のテストや、配列を検索しない逆のテストなど、いくつかのテストをしました.これらのコードは確かにバグが発生しやすく、MSなどの大手企業がコードを重視しているため、バグフリーを要求している会社はよく試験問題として、現場のプログラミング能力を考察する可能性があります.しかし、自分はまだ水で、努力してbug-freeのcodeを書く必要があります.