Googleで働きたい! 電話インタビュー(パート3、栄養)

前の記事のコメントから、多くの有用な情報、コードの欠点の議論に加えて、次のインタビューでC / C ++プログラミングを避けるためにフックまたは詐欺師によって戦略的な決定を下しました。 プログラム作成の練習不足に影響します。 4年以上も彼は触れられておらず、統計計算やデータの視覚化にはpythonで十分でした。 しかし、来週は間違いなく古典的な教科書に戻ります。 仲間のTheHorse0leGGは2番目の記事で恥をかきAxisPodは最後のクローブを、古い知識に行くことが判明するという私の希望のcoに打ち込んだ。 したがって、最愛のPythonに重点を移して、可能なタスクを見てみましょう。

そして、前線からのさらなるニュース: danmiruが提起し問題と、Habré(世界はまだ小さい)でこれらの出版物について学んだリクルーターからの電話について話し合った後、私は自分が持っていた特定のタスクを出版しないという強い意志の決定をしましたインタビュー。 準備のためのトピックのリストと公的な情報源から取られた標準タスクは、私の将来の雇用主にとって大きな問題を引き起こさないことを願っています。
何度も繰り返しますが、これらのトピックに関する活発な議論をしてくれた聴衆に非常に感謝しています。 「著者を壁に置いておく」というコメントを除外し、コードを議論する提案にまっすぐ進むと、自分にとって非常に有用なことをたくさん学びました。 どうもありがとう!

さて、Pythonで実装されているデータ構造とやや複雑なアルゴリズムを見てみましょう。 ここで考慮するタスクは、 Googleが尋ねるコーディングの質問の種類の本に非常に影響を受けています。 次の仕事を上陸させる秘Secret(プログラマーからプログラマーへ) '' John Mongan、Noah Suojanen、Eric Giguere。 しかし、私は著作権を侵害しないことを望みます。なぜなら、 これらは一般的なプログラミングの問題であり、他の場所で議論されたソースを提供します。 好きなものだけを選びます。 プラス本のタスクはCで解決されますが、私はそれらをPythonで書こうとしています。

リンクリスト


驚くべきことに、このデータ構造はCで実装されています。
typedef struct IntElement {
struct IntElement * next ;
intデータ;
} IntElement ;

前回、私はしません! Pythonにはそれ自体のポインターはありませんが、任意のオブジェクトを変数に割り当てることができます。 したがって、チェーンから次のオブジェクトを前のリンクのプロパティに割り当てることにより、オブジェクトのチェーンを作成できます。 この設計には特に実用的な価値はありませんが、練習のために...(正当化すべきことがたくさんあります)。
つまり、このような構造を構築します。
クラス要素:
def __init__ self 、data = None
自己next = なし
自己データ =データ

シーケンス自体のクラスを作成する方が正確です。 チェーンの先頭に新しい要素を追加するには、必要なリンクを見つけます:
クラス LinkedList:
def __init__ self 、data = None
自己head = なし
データの場合:
#シートを初期化して、すぐにデータをプッシュしたい
newElement = Element data
自己head = newElement

def insertFront self 、data
newElement = Element data
newElement。 next = self
自己head = newElement

def insertBack self 、data
pos = self
while pos。nextではありません :pos = pos。 次の
pos。 next =要素データ

def find self 、data
next = self
while next and data = next。data :next = next。 次の
次に戻る

def delete self 、delElement
#シーケンスの先頭の特別な場合
delElement == selfの 場合
自己head = self 次の
#Pythonのgcは、誰も参照していない場合にオブジェクトを削除します
#(参照カウント)
#または:del delElement-専門家の意見
真を 返す
pos = self
while pos。nextではありません
#最後のアイテムできれいに。 pos-に変更されます
#最後から2番目
delElement == posの場合
pos。 next = delElement。 次の
#最後の要素を削除した場合、delElement.next == None
#繰り返しますが、del delElementはここで必要ですか?
真を 返す
pos = pos。 次の
偽を返す

def deleteAll self
一方 自己で はなく、
delElement = self
自己head = self 次の
del delElement #または十分なdelElement.next =なし
自己head = なし

Pythonでは、ガベージコレクターは、このオブジェクトへのリンクの数によってオブジェクトの状態を追跡します。 すべてのリンクが削除または再割り当てされた場合、オブジェクトは削除されます。 ここでは、Pythonのバージョン2.0より前では、オブジェクトが自分自身を参照している場合、潜在的な問題がありました。削除されなかったため、ループの定期的なチェックを導入しました。
クラス Untouchable:
def __init__ self
自己 = 自己

しかし、これはとても叙情的な余談です。 一般的に、この種の設計は非常に人工的です。 Pythonでは、このタイプのデータは通常のシート(リスト、タプル)に簡単に置き換えられます。
しかし、タスクがこの種のデータ構成を記述することである場合、 ここで行われように、一般にラムダ関数を使用して多くのことを実装できます
w = sys 標準 書きます
cons = lambda el、lst: el、lst
mklist = lambda * args: reduce lambda lst、el:cons el、lst 、reverse args None
car = lambda lst:lst [ 0 ] if lst else lst
cdr = lambda lst:lst [ 1 ] if lst else lst
nth = lambda n、lst:nth n- 1 、cdr lst if n > 0 else car lst
length = lambda lst、count = 0 :length cdr lst 、count + 1 if lst else count
begin = lambda * args:args [ -1 ]
display = lambda lst:begin w "%s" car lst 、display cdr lst if lst else w "niln"

機能は少し混乱していますが、機能を調べることで理解できます。
次に、この構造に与えることができるタスクを見てみましょう

オブジェクトのリンクされたシーケンスを使用してスタックを書く


前に説明した2つのクラスに、2つのメソッドpush, popを追加する必要があります
クラス Stack LinkedList
def push self 、data :insertFront data

def pop self
tmp = self データ
削除 self。head
tmpを返す

残念なことに、前回のようになりました-最初にテキストの20%を書くのに多くの時間を費やし、それほど時間がないことに気付き、残りの80を真っ向から終えます。しかし、これはすでに伝統です。 さあ、行こう、私の高速80%が始まった。

シーケンスの最後からm番目の要素を返します(m = 0-最後の要素)


関連するシーケンスでトピック全体を使用します。 このタスクは、Cプログラミングスキルをよくテストします。 ポインターを操作し、特にコード実行エラー(メモリ不足、不正な入力データ)を監視する必要があります。 しかし、Pythonの場合、このようなタスクで十分です。
def mFromTheEnd self 、m
cur = self
curでない 場合Noneを 返す #空のシーケンスをチェックする
範囲内の i m
curの場合 :cur = cur。 次の
elseなしを 返し ます
mcur = self
while cur。next
mcur = mcur。 次の
cur = cur。 次の
mcurを返す

考えは、シーケンス全体に関するデータを保存し、最初のm要素に沿って実行するのではなく、可能であれば(そうでない場合はNoneを返しますが、例外を発生させることもできます)。 そして、2番目のポインターと同時にシートの最後に到達します。これは、最初のポインターからm要素だけオフセットされています。 このメソッドはメモリを節約します-メモリに2つのポインタを保持するだけです。 そして、これは最悪の場合のO(n)アルゴリズムであり、これは一方向に接続されたシーケンスで達成できる最高のアルゴリズムです。

子を持つ二重リンクシーケンスを広げる


フィクションの瀬戸際にある不活発な舌。 この例は、再帰的な方法を使用して、たとえばここで説明しました。 すべてを反復処理で実装できます。 メソッドのアイデアは次のとおりです。

空の要素(シーケンスの最後)に出会うまで、最初にシーケンスを実行します
現在の要素に子があることがわかっている場合-シーケンスの最後にそれをスローし、テールを子と結び、逆も同様です
サイクルがある場合、2つのポインターで実行することで新しいテールを見つけます(テールはサイクルチェックの2倍の速度で実行されます)-ポインターは一致します。

このデータ構造のコンストラクターは非常に単純ですが、単純にそのようなデータ構造を作成するために行われました。 このクラスは、単純に二重リンクシーケンスを作成します。 また、スタンドアロン関数は、親likeのような2つの要素を接続します。 邪悪な言語を避けるために、私は事前に言った、これは意図的に行われた。 本番では、手を引きちぎる必要があります。
#!/ usr / bin / env python
#-*-コーディング:utf-8-*-

インポートシステム

クラス要素:
def __init__ self 、data = None
自己next = なし
自己prev = なし
自己データ =データ
自己 = なし

クラス DoubleLinkedList:
def __init__ self 、data = None
自己head = なし
自己 = なし
データの場合:
データ内のデータの場合:
newEl =要素 dat
自己の 場合自己尾の next = newEl
newEl。 prev = self しっぽ
自己で なければ自己head = newEl
自己 = newEl

def addChild 親、子
=子
#ループのチェックなし

def flat lst
cur = lst。
while cur
curの場合
一番 尾の next = cur。
cur。 prev = lst。 しっぽ
cyclecheck = lst。 しっぽ
while lst。tail。next および lst。tail。next。next
cyclecheck = cyclecheck。 次の
一番 = lst。 尾の 次へ次の
#本には、このアルゴリズムのエラーが含まれています。 最初のステップは取られません
#および初期条件は同じです。 常に実行される場合
cyclecheck == lstの場合
印刷 「データ構造のループ」
sys 終了
場合 尾の :lst。 = lst。 尾の 次の
cur = cur。 次の

def main
st1 = DoubleLinkedList 範囲 6
st2 = DoubleLinkedList 範囲 4
st3 = DoubleLinkedList 範囲 3
addChild st1。head、st2。head
addChild st1。tail、st3。head
#addChild(st2.tail、st3.head)-ループ。

フラット st1

cur = st1。
while cur
印刷 cur。 データ
cur = cur。 次の

__name__ == '__main__'の場合
メイン

実際には、データ構造を復元できます(deFlatten)。 なぜなら 子に関する情報を削除しなかったので、順番に実行でき、子へのリンクが見つかったら、テールと子、およびテールを持つ子との接続を削除します。 子の関数を再帰的に呼び出します。
パーティション分割を繰り返し行うことができます。 ただし、最後から順番に実行する方が適切です。 子が見つかったら、前の要素から切り取り(これは新しいテールになります)、新しいテールから子へのリンクを削除します。 さらに順を追って。 構造内にサイクルがなかった場合、すべてが正常に機能します(スペシャリスト-アルゴリズム論者にコメントがありますか?) ただし、フラット構造を作成するときにこの条件を確認しました。

二分木の反復走査


トピックのリストにある質問の1つ(それらを公開することを許可されたのは良いことです)は、バイナリツリートラバースのタイプです。 覚えていない場合は、予約注文(KLP)、注文(LKP)、および注文後(KLP)についてお話します。 これは、ルート(K)、左(L)ブランチ、右(P)ブランチをどの順序で見るかを示しています。 この質問は非常に一般的であり、すでにここstackoverflowで書かれています。
#!/ usr / bin / env python
#-*-コーディング:utf-8-*-

クラス N:
def __init__ self 、data、left = None 、right = None
自己データ =データ
自己 =左
自己 =右

def __unicode__ self
'%s' selfを 返します。 データ
#ルート値を推測する素晴らしい方法

def inorder t
tの場合
x for inorder t。left
収量 x
収量 t データ
x in inorder t。right )の場合
収量 x

def iterate_preorder root
スタック= [ ]
スタック。 追加 root
スタック中:
cur =スタック。 ポップ
利回りデータ
curの場合 :スタック。 追加 cur。right
curの場合 :スタック。 追加 cur。left

def iterate_inorder root
スタック= [ ]
cur =ルート
スタックまたは curの場合:
curの場合
スタック。 追加 cur
cur = cur。
その他
スタックの場合
cur =スタック。 ポップ
利回りデータ
cur = cur。 そうだね


def main
bt = N 4 、N 2 、N 1 、N 3 、N 6 、N 5 、N 7None 、N 8
印刷 リスト iterate_preorder bt
print リスト iterate_inorder bt
0を 返す

__name__ == '__main__'の場合
メイン

ツリーを反復処理するには、訪問する必要があるブランチに関する情報を保存する必要があります。 スタックは事前注文クロールに適しています-これはディープクロールです。 反復的な順序のバイパスに長く座らなければなりませんでした。 しかし、前回と同じ考え。 ツリーの左端の要素を取得するために、空のシートに到達するまで下に行きます。 スタックから前の値を取り出します。これはシートそのものであり、その値を表示して右に見えます。 そこに何もない場合、スタックから既に適切な要素を持っている親を取得します。 などなど。 クロールは、左端、ルート、右端の要素の順序です。

文字列内の重複していない最初の文字を見つける


Pythonでは、辞書とセットはハッシュテーブルを使用して値/キーを保存および検索します。 以下に示すアルゴリズムは、新しい文字を辞書に追加するのにさらに時間がかかるため、少なくともO(n)(行全体に沿って渡す)になります。
#!/ usr / bin / env python
#-*-コーディング:utf-8-*-

def nonRepeated st
繰り返し= dict
stのlの場合:
l 繰り返される場合 :繰り返し[ l ] = 0
その他 :繰り返し[ l ] = 1
stのlの場合:
繰り返される場合 [ l ] :lを返します

def main
print nonRepeated 'test'
0を 返す

__name__ == '__main__'の場合
メイン

速度を上げる方法はまだありますか? ここでは、すべてが通常のシーケンスで行われました。 辞書やセットのように、文字がシーケンス内にあるかどうかのチェックは一定時間O(1)で行われません。 ですから、辞書の使用はわずかに優れていると思います。

さて、今日の最後:

文の語順を反転する


reverseOrder = lambda str''join x [ ::- 1 ] for x in str [ ::- 1 ] 。split
rev_order = lambda s: ''join s。split [ ::- 1 ] #信頼できるprintfの短いバージョン

2番目の方法は短いだけでなく、より高速です。
>>> timeit インポートタイマーから
>>> Timer "reverseOrder( '1 2 3 45')""from __main__ import reverseOrder" timeit
3.4366888999938965
>>>タイマー "rev_order( '1 2 3 45')""from __main__ import rev_order" timeit
0.9511728286743164

私のインタビューで問題はありません。 私はそれらを公開しないようにとても頼まれました。 ごめんなさい
すべて、蹴り始めましょう!

パート1
パート2

Source: https://habr.com/ru/post/J285456/


All Articles