Pythonバージョン-LeetCode学習:752。ターンテーブルロックをオンにします

4つの丸いダイヤルが付いたターンテーブルロックがあります。各ダイヤルには、「0」、「1」、「2」、「3」、「4」、「5」、「6」、「7」、「8」、「9」の10個の番号があります。各ダイヤルは自由に回転できます。たとえば、「9」を「0」に、「0」を「9」に変更します。各回転は、ダイヤルの1桁しか回転できません。

ロックの初期番号は「0000」で、4つのダイヤルの番号を表す文字列です。

行き止まりリストには、一連の死亡番号が含まれています。ホイールの番号がリスト内の要素と同じになると、ロックは永続的にロックされ、回転できなくなります。

文字列ターゲットは、ロックを解除できる数を表します。最小回転数を指定する必要があります。とにかくロックを解除できない場合は、-1を返します。

ソース:LeetCode
リンク:https ://leetcode-cn.com/problems/open-the-lock

方法1:双方向BFSアルゴリズム

従来のBFSフレームワークは、開始点から始まり、終点に到達すると広がり、停止します。一方、双方向BFSは、始点と終点から同時に広がり始め、2つの側面が交差するときに停止します従来のBFSアルゴリズムの戦略に従って、「エンドポイント」(ターゲット値)がバイナリツリーの最下部にある場合、ツリー全体のノードが検索され、最終的に検出されtargetます。一方、双方向BFSは、実際には、交差が表示される前にツリーの半分のみを通過します。つまり、最短距離が見つかりました。直感的には、双方向BFSは従来のBFSよりもはるかに効率的です。ただし、双方向BFSにも制限があります。つまり、「エンドポイント」(目標値)がどこにあるかを知る必要があります。終点がどこから始まっているかわからないため、双方向BFSを使用できません。ただし、パスワードロックの2番目の問題は、双方向BFSアルゴリズムを使用して効率を向上できることです。

class Solution:
    def openLock(self, deadends: List[str], target: str) -> int:
        def onePlus(s:str,j: int)-> str:
            # 将s[i]向上拨动一次
            strList=list(s)
            if strList[j]=='9':
                strList[j]='0'
            else:
                strList[j]=str(int(strList[j])+1)
            return ''.join(strList)
        
        def oneMinus(s,j):
            # 将s[i]向下拨动一次
            strList=list(s)
            if strList[j]=='0':
                strList[j]='9'
            else:
                strList[j]=str(int(strList[j])-1)
            return ''.join(strList)
        # 利用set提高查询速度
        deadendSet=set(deadends)
        visited=set()
        queue=set()
        # 从0000开始
        queue.add('0000')
        visited.add('0000')
        
        # 从目标值开始
        queue2=set()
        queue2.add(target)
        step=0
        while queue and queue2 :
            tmp=set()
            
            if (len(queue) > len(queue2)):
                #按照 BFS 的逻辑,队列(集合)中的元素越多,扩散之后新的队列(集合)中的元素就越多;
                # 在双向 BFS 算法中,如果我们每次都选择一个较小的集合进行扩散,那么占用的空间增长速度就会慢一些,效率就会高一些。所以这里进行一次交换
                queue,queue2 = queue2,queue

            for cur in queue:
                if cur in deadendSet:
                    continue
                if cur in queue2:
                    return step
                # 记录已经使用过的组合
                visited.add(cur)
                # 上下拨动的可能值放入循环队列
                for j in range(4):
                    up=onePlus(cur,j)
                    if (up not in visited):
                        tmp.add(up)
                    down=oneMinus(cur,j)
                    if down not in visited:
                        tmp.add(down)
            # 步数加一
            step+=1
            # temp 相当于 queue,交换queue,queue2,下一轮 while 就是扩散 q2
            queue=queue2
            queue2=tmp
        return -1

方法2:

class Solution:
    def openLock(self, deadends: List[str], target: str) -> int:

        # 定义一个距离函数,计算一个code直接拨需要多少下,比如0004就是4下,0009就是1下。
        def dist(code):
            return sum([min(int(c), 10-int(c)) for c in code])
        
        if "0000" in deadends or target in deadends :
            return -1

        # 由target反推能够到达target的所有前一个节点,放在 new_codes 里返回。
        new_codes = []
        for i in range(4):
            pre, x, sur = target[:i], int(target[i]), target[i+1:]
            new_codes.append(pre + str((x+1)%10) + sur)
            new_codes.append(pre + str((x-1)%10) + sur)
        
        # 利用集合(set)是可以做减法,得出不在deadends里面的new_codes,放在 moves 里。
        # set的减法:例如{'0001','0003','0015'} - {'0003','0007'} = {'0001', '0015'}
        moves = set(new_codes) - set(deadends)
        # 如果moves为空,那就说明没有路径可以到达target,那就返回 -1
        if not moves:
            return -1
        
        # 计算直接播到目标值需要多少次
        result = dist(target)  # 例如 result = dist('0088') ,那么 result就等于4
        # 遍历所有排除掉deadends的选项,计算这里面的值
        # 判断是否有大于直接到达目标值的选项
        for move in moves:
            if dist(move) < result: # 如果存在任一个到达target的前一节点,则直接到达target就是可能的
                return result       

        return result + 2           # 如果所有target的前一节点的到达距离都比直接到达target大,则就返回 result + 2  

方法3:この方法は方法2に似ていますが、方法2よりもスペースの複雑さが少なくなります。

class Solution:
    def openLock(self, deadends: List[str], target: str) -> int:
        def distance(string):
            return sum(min(int(num), 10 - int(num)) for num in string)        

        if '0000' in deadends or target in deadends:
            return -1

        moves = {str(i): (str((i - 1) % 10), str((i + 1) % 10)) for i in range(10)}
        adjacent = set()

        for i in range(len(target)):
            start = target[:i]
            end = target[i + 1:]
            for j in moves[target[i]]:
                adjacent.add(start + j + end)

        possibleMoves = adjacent - set(deadends)

        if not possibleMoves:
            return -1

        return min(distance(move) for move in possibleMoves) + 1

 

 

 

おすすめ

転載: blog.csdn.net/guyu1003/article/details/107296073