[プログラマー]不良ユーザー:フルナビゲーション
質問する
https://programmers.co.kr/learn/courses/30/lessons/64064
概要
「*」処理を受けた不良ユーザーと実際のユーザーのIDリストを入力します.
次に、不良ユーザとなる可能性のあるユーザのIDを照合し、異なる可能な組み合わせの個数を算出する問題である.
たとえば、[a*cd、ab*d]、[abcd、abxd、abvd]と入力した場合、可能な組み合わせの数は次のようになります.
(abcd,abxd)(abcd,abvd)(abxd,abvd)の3つ.
問題を解く
最初から逆追跡の解決策を思いついた.△ここまで来ればよかった.
しかし,解くと問題点(ABC,XYZ)と(XYZ,ABC)は同じデータであるため,重複を解消する必要がある.
これはDFSプールでよく見られるタイプです.
順序が逆転しているが、内容が同じである場合は、ビットマスクとバイナリデータを使用します. 解決しました.
例えば、user idの長さは3であり、user id[0]およびuser id[1]が不良ユーザ候補となる場合は「011」と表す.順序に関係なく、同じIDを入力すれば結果値の重複は解消されます.
しかし、私のソースには重複除外結果値よりも大きな問題があります.
テストケース5ではTLEを受信し続け,コードを長時間修正した.
逆トレース機能があり、重複結果値を消去する機能もありますが、逆トレース再帰関数内部論理では不要な場合の数が多すぎます.
バックグラウンドトラッキングロジックでは,コールスタックごとに二重砲口を用い,すべてのブロックID->すべてのユーザIDの順序で探索した.
しかし、このようにすると、callスタックごとに不要な探索が繰り返されます.
あるcallスタックで、不良ユーザとユーザのアイデンティティが一致し、次のcallスタックに移動すると、callスタックは最後まで無条件にアイデンティティ[0]を再試行します.ここで条件文をうまく処理しても,既に使用されているブロックIDの探索に時間を費やす.
では、二重砲口で、ブロックコード砲口のrange開始インデックス+1を、次のコールスタックで無条件にインデックス上に、ブロックコードから検索してもいいですか?
この問題とは異なり、簡単な例を挙げて説明します.
[a,b,c,d,e,f]にはリストXがある.
検索Xのfor文をbacktrackingロジックに書くと、呼び出しスタックごとにa~zを検索する必要があります.
深さ優先ナビゲーションであるため、インデックスを調整しました. abcdefは最初に戻り、次いでe->fとなり、次のcallスタックに移動します. abcdffを 回復します.
ここで問題が発生しました.
1.すべての遮蔽されたアイデンティティが一致しなければならない(eの喪失)
2.インデックスを調整したが、最終的には検索範囲を調整したので、1回のコールスタックで不要な複数のIDを検索する.
私が欲しいのは、Call StackでブロックIDを受け取ると、ブロックIDはそのCall Stackでしか管理されず、他のCall Stackでは別のブロックIDしか管理されない方式で、rangeの開始インデックスを調整しても最終的にはforゲートなので、1つのCall Stackで複数のブロックIDを検索します.
したがって、この問題は、最初から二重砲口を使用する問題ではなく、idリストを無効にするインデックスとしてcallスタックの深さを利用して、1つのcallスタックで1つのブロックIDしか閲覧できないことを実現する.(再帰関数では、callスタックは深さで区切られているため、callスタックの深さはunique valueです.)
また,この方式は組合せの問題と考えられるが,実際には並べ替え問題であり,並べ替えをDFSとして実現する.
したがって、itertoolsライブラリの 置換で解いても、この問題は解決できます.
整理すると、
1つの不良ユーザ情報は、複数のユーザIDに一致することができる.
順序を考慮する必要があるという意味では、ソートではなく、1つの不良ユーザ情報にすべてのユーザIDが一致するのがソートの問題である.
これは,ユーザのアカウントがすべての位置に一度入ることができれば,数を考慮し,いずれの場合もすべてのユーザのIDが不良ユーザと一致するか否かを判断することを意味する.
「位置」は「不良ユーザ」であり、「ユーザID」はどの「位置」に入ることができるか分からないので、全ての「位置」にユーザIDを置いて探索する.
今後DFS/backtracking問題でCall Stackでfor文を使用する場合は、新しいCall Stackでは常に新しいfor文で最初から始め、問題を解決することをよく考えなければなりません.
そしてソートを順番のある順序と見なすよりも.
配列自体は,ある値がすべての位置に1回入ることができると考えられる数である.
最後に,DFSでシーケンスを求めると,callスタックの深さをプレースホルダとする.
そして,入力値のみを返すfor文は,入力した値を例外的に処理し,最後に基本条件から返すことを実現する.
コード#コード#
from collections import defaultdict
def find(banned,user):
if len(banned) != len(user):
return False
for i in range(len(banned)):
if banned[i]=="*":continue
elif user[i]!=banned[i]:
return False
return True
def backtrk(d, bidx, user_id, banned_id, vistB, vistU, avoidDup, avoidDupDict):
global ans
if d == len(banned_id):
if avoidDupDict[avoidDup]==0:
avoidDupDict[avoidDup]=1
ans+=1
return
# 순열 : 한명의 불량 사용자당 가능한 아이디를 탐색, 깊이가 불량사용자의 인원수만큼 들어가게 되면 모든 불량 사용자의 아이디 매칭 완료 됐다는 의미
# 백트래킹으로 다시 빠져 나와서 한명의 불량 사용자에 대한 또 다른 가능한 유저 아이디 추출함.
# 하나의 콜스택에서는 하나의 불량 사용자만 다뤄야 함. 하나의 콜스택에서 여러명의 불량사용자를 다루면 안됨. 이러면 거듭제곱꼴 형태가 됨.
bb=banned_id[bidx]
for u in range(len(user_id)):
if not vistU[u] and find(bb, user_id[u]): # 이미 사용된 아이디, 아이디 형식 확인
vistU[u]=1
backtrk(d+1, bidx+1, user_id, banned_id, vistB, vistU, avoidDup|(1<<u), avoidDupDict)
vistU[u]=0
def solution(user_id, banned_id):
global ans
ans = 0
vistU=[0]*len(user_id)
vistB=[0]*len(banned_id)
avoidDupDict = defaultdict(int)
backtrk(0, 0, user_id, banned_id, vistB, vistU, 1<<len(user_id), avoidDupDict)
return ans
print(solution(["aaaaaaaa", "bbbbbbbb", "cccccccc", "dddddddd", "eeeeeeee", "ffffffff", "gggggggg", "hhhhhhhh"],
["********","********","********","********","********","********","********","********"]))
print(solution(["frodo", "fradi", "crodo", "abc123", "frodoc"], ["fr*d*", "abc1**"]))
print(solution(["frodo", "fradi", "crodo", "abc123", "frodoc"], ["*rodo", "*rodo", "******"]))
print(solution(["frodo", "fradi", "crodo", "abc123", "frodoc"], ["fr*d*", "*rodo", "******", "******"]))
Reference
この問題について([プログラマー]不良ユーザー:フルナビゲーション), 我々は、より多くの情報をここで見つけました https://velog.io/@seilk/프로그래머스-불량-사용자-완전탐색テキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol