オットセイの経営日誌

データサイエンス系ベンチャーを経営してます。経営のこと、趣味のことつぶやきます。

LeetCode / Invert Binary Tree

初年度のベンチャーはどこもそうなんだと思いますが、特に平日は心身ともに擦り減るので、ベンチャーに休日はないと言えど強制的に休日を作らないとなかなかやっていけません。

と言っても、ただボーッとしてると仕事のことを考えてしまうので、LeetCodeでもやって脳を強制リセットします。

脳の中にコンテナをいくつか作ってうまくオーケストレーションする感じ。あ、また仕事のこと考えてる。。。

https://leetcode.com/problems/invert-binary-tree/

Invert a binary tree.

f:id:mhiro216:20200229203757p:plain

Binary Treeの左右を反転させる問題です。

解法1

Recursiveな解法。

コードにしてみると至って単純で、左の部分木を右に、右の部分木を左に入れ替える処理を再帰的に行います。

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def invertTree(self, root: TreeNode) -> TreeNode:
        if not root:
            return None
        right = self.invertTree(root.right)
        left = self.invertTree(root.left)
        root.left = right
        root.right = left
        return root

時間計算量、空間計算量ともにO(n)です。

解法2

Iterativeな解法。

stackという一時変数を用意して、左右の入れ替えを行う対象を格納していきます。

後は先ほどの解法と同様に、左の部分木を右に、右の部分木を左に入れ替えた上で、入れ替えた部分木をstackに格納し、同様の処理を行っていきます。

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def invertTree(self, root: TreeNode) -> TreeNode:
        stack = [root]
        while stack:
            node = stack.pop()
            if node:
                node.left, node.right = node.right, node.left
                stack += node.left, node.right
        return root

while loop中のpop前のstack, pop後のnodeの中身を見ていくと、以下のようになっています。

len(stack):  1
stack:  [TreeNode{val: 4, left: TreeNode{val: 2, left: TreeNode{val: 1, left: None, right: None}, right: TreeNode{val: 3, left: None, right: None}}, right: TreeNode{val: 7, left: TreeNode{val: 6, left: None, right: None}, right: TreeNode{val: 9, left: None, right: None}}}]
node: TreeNode{val: 4, left: TreeNode{val: 2, left: TreeNode{val: 1, left: None, right: None}, right: TreeNode{val: 3, left: None, right: None}}, right: TreeNode{val: 7, left: TreeNode{val: 6, left: None, right: None}, right: TreeNode{val: 9, left: None, right: None}}}

len(stack):  2
stack:  [TreeNode{val: 7, left: TreeNode{val: 6, left: None, right: None}, right: TreeNode{val: 9, left: None, right: None}}, TreeNode{val: 2, left: TreeNode{val: 1, left: None, right: None}, right: TreeNode{val: 3, left: None, right: None}}]
node: TreeNode{val: 2, left: TreeNode{val: 1, left: None, right: None}, right: TreeNode{val: 3, left: None, right: None}}

len(stack):  3
stack:  [TreeNode{val: 7, left: TreeNode{val: 6, left: None, right: None}, right: TreeNode{val: 9, left: None, right: None}}, TreeNode{val: 3, left: None, right: None}, TreeNode{val: 1, left: None, right: None}]
node: TreeNode{val: 1, left: None, right: None}

len(stack):  4
stack:  [TreeNode{val: 7, left: TreeNode{val: 6, left: None, right: None}, right: TreeNode{val: 9, left: None, right: None}}, TreeNode{val: 3, left: None, right: None}, None, None]
node: None

len(stack):  3
stack:  [TreeNode{val: 7, left: TreeNode{val: 6, left: None, right: None}, right: TreeNode{val: 9, left: None, right: None}}, TreeNode{val: 3, left: None, right: None}, None]
node: None

len(stack):  2
stack:  [TreeNode{val: 7, left: TreeNode{val: 6, left: None, right: None}, right: TreeNode{val: 9, left: None, right: None}}, TreeNode{val: 3, left: None, right: None}]
node: TreeNode{val: 3, left: None, right: None}

len(stack):  3
stack:  [TreeNode{val: 7, left: TreeNode{val: 6, left: None, right: None}, right: TreeNode{val: 9, left: None, right: None}}, None, None]
node: None

len(stack):  2
stack:  [TreeNode{val: 7, left: TreeNode{val: 6, left: None, right: None}, right: TreeNode{val: 9, left: None, right: None}}, None]
node: None

len(stack):  1
stack:  [TreeNode{val: 7, left: TreeNode{val: 6, left: None, right: None}, right: TreeNode{val: 9, left: None, right: None}}]
node: TreeNode{val: 7, left: TreeNode{val: 6, left: None, right: None}, right: TreeNode{val: 9, left: None, right: None}}

len(stack):  2
stack:  [TreeNode{val: 9, left: None, right: None}, TreeNode{val: 6, left: None, right: None}]
node: TreeNode{val: 6, left: None, right: None}

len(stack):  3
stack:  [TreeNode{val: 9, left: None, right: None}, None, None]
node: None

len(stack):  2
stack:  [TreeNode{val: 9, left: None, right: None}, None]
node: None

len(stack):  1
stack:  [TreeNode{val: 9, left: None, right: None}]
node: TreeNode{val: 9, left: None, right: None}

len(stack):  2
stack:  [None, None]
node: None

len(stack):  1
stack:  [None]
node: None

これも時間計算量、空間計算量ともにO(n)です。

LeetCode / Implement Stack using Queues, Implement Queue using Stacks

重要なデータ構造の1つにStack(スタック)とQueue(キュー)がありますが、これを実装しろという問題を取り上げます。

と言っても、こちらの解説記事にあるように、スタックもキューも配列で表現することができ、データ構造としては特別なものではありません。重要なのは以下の使われ方の違いです。

スタック データ構造に入っている要素のうち、最後に push した要素を取り出す
キュー データ構造に入っている要素のうち、最初に push した要素を取り出す

上記メソッドを有するStack class, Queue classを実装します。

Implement Stack using Queues

https://leetcode.com/problems/implement-stack-using-queues/

Implement the following operations of a stack using queues.

push(x) -- Push element x onto stack.
pop() -- Removes the element on top of the stack.
top() -- Get the top element.
empty() -- Return whether the stack is empty.

Example:

MyStack stack = new MyStack();

stack.push(1);
stack.push(2);
stack.top(); // returns 2
stack.pop(); // returns 2
stack.empty(); // returns false

Notes:

You must use only standard operations of a queue -- which means only push to back, peek/pop from front, size, and is empty operations are valid.
Depending on your language, queue may not be supported natively. You may simulate a queue by using a list or deque (double-ended queue), as long as you use only standard operations of a queue.
You may assume that all operations are valid (for example, no pop or top operations will be called on an empty stack).

まずはstackの実装から。問題タイトルにもあるように、queueを使ってstackを実装することが求められています。

queueを使うということは、可能な操作も限定されます。only push to back, peek/pop from front, size, and is empty operations are validとあるように、pushは後ろから、popは前からに限定されます。

誤答

一応やっておくと、問題の意図を無視してstackを使ってしまっても、テストは通ってしまいます。
ただ当然ながら、stackを使ってstackを作っても何の意味もないですね。
pythonではlistがstackにあたる挙動を示します。

class MyStack:

    def __init__(self):
        """
        Initialize your data structure here.
        """
        self.stack = []
        

    def push(self, x: int) -> None:
        """
        Push element x onto stack.
        """
        self.stack.append(x)
        

    def pop(self) -> int:
        """
        Removes the element on top of the stack and returns that element.
        """
        return self.stack.pop()
        

    def top(self) -> int:
        """
        Get the top element.
        """
        return self.stack[-1]
        

    def empty(self) -> bool:
        """
        Returns whether the stack is empty.
        """
        return len(self.stack) == 0

解法

この問題ではqueueを使ってstackを実装することを求められていますが、ポイントはpop。繰り返しになりますが、stackでは最後にpushされた要素を取り出しますが、queueでは最初にpushされた要素を取り出します。

pythonではqueue.popleft()で最初にpushされた要素を取り出せます。popleftを使って、pushを実行した際に、後にpushされた要素が先頭にくるようにqueueを並び替えるのが以下の解法です。

class MyStack:

    def __init__(self):
        self.queue = collections.deque()
        

    def push(self, x: int) -> None:
        q = self.queue
        q.append(x)
        for _ in range(len(q)-1):
            q.append(q.popleft())
        

    def pop(self) -> int:
        return self.queue.popleft()
        

    def top(self) -> int:
        return self.queue[0]
        

    def empty(self) -> bool:
        return len(self.queue) == 0

時間計算量がpushでO(n)、popではO(1)かかることになります。

ちなみに、本問題の要求のもとでは使えませんが、以下記事にあるようにcollections.deque()にはpopメソッドも存在し、時間計算量O(1)で後方から要素を取り出し削除することが可能です。

note.nkmk.me

Implement Queue using Stacks

https://leetcode.com/problems/implement-queue-using-stacks/

Implement the following operations of a queue using stacks.

push(x) -- Push element x to the back of queue.
pop() -- Removes the element from in front of queue.
peek() -- Get the front element.
empty() -- Return whether the queue is empty.

Example:

MyQueue queue = new MyQueue();

queue.push(1);
queue.push(2);
queue.peek(); // returns 1
queue.pop(); // returns 1
queue.empty(); // returns false

Notes:

You must use only standard operations of a stack -- which means only push to top, peek/pop from top, size, and is empty operations are valid.
Depending on your language, stack may not be supported natively. You may simulate a stack by using a list or deque (double-ended queue), as long as you use only standard operations of a stack.
You may assume that all operations are valid (for example, no pop or peek operations will be called on an empty queue).

次に、Queueの実装です。今度はstackを使ってqueueを実装することを求められています。

これも可能な操作が限定されます。only push to top, peek/pop from top, size, and is empty operations are validとなっています。つまりpush, peek, popについてはlist.append(x), list[-1], list.pop()のみが許容されていることになります。

解法

2つstackを用意し、pushの際はinStackに格納、peekやpopの際はinStackの要素を逆順にして格納したoutStackを用意し、処理を行うのが以下の解法です。

class MyQueue:

    def __init__(self):
        """
        Initialize your data structure here.
        """
        self.inStack, self.outStack = [], []
        

    def push(self, x: int) -> None:
        """
        Push element x to the back of queue.
        """
        self.inStack.append(x)
        

    def pop(self) -> int:
        """
        Removes the element from in front of queue and returns that element.
        """
        self.peek()
        return self.outStack.pop()
        

    def peek(self) -> int:
        """
        Get the front element.
        """
        if(self.outStack == []):
            while(self.inStack != []):
                self.outStack.append(self.inStack.pop())
        return self.outStack[-1]
        

    def empty(self) -> bool:
        """
        Returns whether the queue is empty.
        """
        return len(self.inStack) == 0 and len(self.outStack) == 0

LeetCode / Contains Duplicate, Contains Duplicate Ⅱ

明日から海外旅行で浮き足立っていますが、リストから重複判定する問題シリーズを解きます。

https://leetcode.com/problems/contains-duplicate/

Given an array of integers, find if the array contains any duplicates.

Your function should return true if any value appears at least twice in the array, and it should return false if every element is distinct.

Example 1:
Input: [1,2,3,1]
Output: true

Example 2:
Input: [1,2,3,4]
Output: false

Example 3:
Input: [1,1,1,3,3,4,3,2,4,2]
Output: true

リスト中に重複があればTrueを返す問題。これは簡単。

解法1

class Solution:
    def containsDuplicate(self, nums: List[int]) -> bool:
        return len(set(nums)) != len(nums)

これだけでは物足りないのでもう一問。

https://leetcode.com/problems/contains-duplicate-ii/

Given an array of integers and an integer k, find out whether there are two distinct indices i and j in the array such that nums[i] = nums[j] and the absolute difference between i and j is at most k.

Example 1:
Input: nums = [1,2,3,1], k = 3
Output: true

Example 2:
Input: nums = [1,0,1,1], k = 1
Output: true

Example 3:
Input: nums = [1,2,3,1,2,3], k = 2
Output: false

問題文の英語がわかるかどうかが勝負。インデックスi, jについて、nums[i] = nums[j]となり、かつiとjの差がk以下の要素があればTrueを返す問題。

解法1

class Solution:
    def containsNearbyDuplicate(self, nums: List[int], k: int) -> bool:
        dic = {}
        for i, n in enumerate(nums):
            if n in dic and i - dic[n] <= k:
                return True
            dic[n] = i
        return False

LeetCode / Reversed Linked List

https://leetcode.com/problems/reverse-linked-list/

Reverse a singly linked list.

Example:

Input: 1->2->3->4->5->NULL
Output: 5->4->3->2->1->NULL

1つのLinked Listをsortして逆順にする問題。

解法1

Iterativeな解法。

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution:
    def reverseList(self, head: ListNode) -> ListNode:
        prev = None
        while head:
            curr = head
            head = head.next
            curr.next = prev
            prev = curr
        return prev

head, curr, prevの値の移り変わりは以下の通り。

head ListNode{val: 2, next: ListNode{val: 3, next: ListNode{val: 4, next: ListNode{val: 5, next: None}}}}
curr ListNode{val: 1, next: None}
prev ListNode{val: 1, next: None}

head ListNode{val: 3, next: ListNode{val: 4, next: ListNode{val: 5, next: None}}}
curr ListNode{val: 2, next: ListNode{val: 1, next: None}}
prev ListNode{val: 2, next: ListNode{val: 1, next: None}}

head ListNode{val: 4, next: ListNode{val: 5, next: None}}
curr ListNode{val: 3, next: ListNode{val: 2, next: ListNode{val: 1, next: None}}}
prev ListNode{val: 3, next: ListNode{val: 2, next: ListNode{val: 1, next: None}}}

head ListNode{val: 5, next: None}
curr ListNode{val: 4, next: ListNode{val: 3, next: ListNode{val: 2, next: ListNode{val: 1, next: None}}}}
prev ListNode{val: 4, next: ListNode{val: 3, next: ListNode{val: 2, next: ListNode{val: 1, next: None}}}}

head None
curr ListNode{val: 5, next: ListNode{val: 4, next: ListNode{val: 3, next: ListNode{val: 2, next: ListNode{val: 1, next: None}}}}}
prev ListNode{val: 5, next: ListNode{val: 4, next: ListNode{val: 3, next: ListNode{val: 2, next: ListNode{val: 1, next: None}}}}}

headの各要素を走査する間に、以下の操作が走ります。

curr = head
head = head.next # headを1つ先に進める
curr.next = prev # currの2つ目以降の要素に前のループで格納しておいたprevの値を入れる
prev = curr # prevにcurrの値を入れる(新たな要素が1つ先頭に加わる)

解法2

Recursiveな解法。

class Solution:
    def reverseList(self, head):
        return self._reverse(head)

    def _reverse(self, node, prev=None):
        if not node:
            return prev
        n = node.next
        node.next = prev
        return self._reverse(n, node)

LeetCode / Isomorphic Strings

https://leetcode.com/problems/isomorphic-strings/

Given two strings s and t, determine if they are isomorphic.

Two strings are isomorphic if the characters in s can be replaced to get t.

All occurrences of a character must be replaced with another character while preserving the order of characters. No two characters may map to the same character but a character may map to itself.

Example 1:
Input: s = "egg", t = "add"
Output: true

Example 2:
Input: s = "foo", t = "bar"
Output: false

Example 3:
Input: s = "paper", t = "title"
Output: true

Note:
You may assume both s and t have the same length.

2つの同じ長さの文字列があって、文字iを文字jに置き換えると文字列sが文字列tに変わる、という対応関係が崩れていないことをisomorphicと言うそうです。

数学では同型写像を意味するそうです(聞いたこともない用語でしたが)。

解法がたくさんあって面白いですが、計算量オーダー的にはそこまで大きな差はありません。

解答・解説

解法1

sの各文字をkey, tの各文字をvalueに持つdictと、tの各文字をkey, sの各文字をvalueに持つdictの2つを用意し、対応関係をチェックします。

class Solution:
    def isIsomorphic(self, s: str, t: str) -> bool:
        s_d = {}; t_d = {}
        for i,j in zip(s,t):
            if i in s_d and s_d[i] != j:
                return False
            elif j in t_d and t_d[j] != i:
                return False
            else:
                s_d[i] = j
                t_d[j] = i
        return True

2つdictを用意するので、空間計算量はO(2n)とあまり効率的ではありませんが、分かりやすい解法かと思います。

解法2

同じ2つのdictを使う手法としては以下もあります。

class Solution:
    def isIsomorphic(self, s: str, t: str) -> bool:
        d1, d2 = {}, {}
        for i, val in enumerate(s):
            d1[val] = d1.get(val, []) + [i]
        for i, val in enumerate(t):
            d2[val] = d2.get(val, []) + [i]
        return sorted(d1.values()) == sorted(d2.values())

ただ、最後のsortに時間がかかるので時間計算量の面で劣ります。

解法3

文字列に対するfindメソッドで、文字列中の各文字が初めて出てくるインデックスを返させ、2つの文字列について同じ値が返ってくるかチェックします。

class Solution:
    def isIsomorphic(self, s: str, t: str) -> bool:
        return [s.find(i) for i in s] == [t.find(j) for j in t]

ただO(n)のfindメソッドをn回繰り返すことになるので、時間計算量の面で劣ります。

解法4

文字列sと文字列t中の文字を集合としたときの要素数と、文字列sとt中の文字のペアを集合としたときの要素数が同じかどうかをチェックするという解法もあります。

class Solution:
    def isIsomorphic(self, s: str, t: str) -> bool:
        return len(set(zip(s, t))) == len(set(s)) == len(set(t))

これが時間計算量/空間計算量的にも、コードの短さ的にも最も優れているかと思います。

解法5

最後に、アスキーコードを利用する解法。

アスキーコードの要素数である256個の要素からなるリストを用意します。
文字列sの各文字xをアスキーコードiに変換し、リストのi番目に、xのsにおけるインデックス+1を値として格納します。
文字列sが"abc"なら、ord("a")が97なので、リストの97番目の要素に0+1=1を格納します。
インデックス+1とするのは、リストの各要素の初期値を0としているためです。
このインデックスが文字列sとtでずれていないかをチェックします。

class Solution:
    def isIsomorphic(self, s: str, t: str) -> bool:
        l1, l2 = [0 for _ in range(256)], [0 for _ in range(256)]
        for i in range(len(s)):
            if l1[ord(s[i])] != l2[ord(t[i])]:
                return False
            l1[ord(s[i])] = i+1
            l2[ord(t[i])] = i+1
        return True

LeetCode / Count Primes

https://leetcode.com/problems/count-primes/

Count the number of prime numbers less than a non-negative number, n.

Example:

Input: 10
Output: 4
Explanation: There are 4 prime numbers less than 10, they are 2, 3, 5, 7.

与えられた値より小さい素数の数を返す問題。

解答・解説

誤答(TLE)

TLEですが、重要な考え方その1が含まれます。それは、

nが何らかの数pで割り切れる場合、n=pqであり、qがpより小さい場合には既にqもしくはqの約数で確かめた際に素因数が検出されているはずである。したがって、素因数候補として確かめるべきは{\displaystyle {\sqrt {n}}}までで十分

です。

以上の考え方を取り入れたのが下記コードで、偶数の判定を飛ばす(2以外素数でない)改善も加えています。

def is_prime(n):
    if n <= 1:
        return False
    if n == 2:
        return True
    if n % 2 == 0:
        return False
    for i in range(3, math.floor(math.sqrt(n))+1, 2):
        if n % i == 0:
            return False
    return True

class Solution:
    def countPrimes(self, n: int) -> int:
        return sum([is_prime(i) for i in range(n)])

が、これだとまだTLEになります。もう一段の高速化を考えないといけません。

解法1

さらに高速化するにあたり、エラトステネスの篩というアルゴリズムがあります。これは、

{\displaystyle x^{1/2}} 以下の素数が既知のとき、{\displaystyle x^{1/2}} 以上 x 以下の素数を決定するには、x 以下の整数で {\displaystyle x^{1/2}} 以下の素数の倍数を全て取り除けば(= 篩えば)よい

というものです。

class Solution:
    def countPrimes(self, n):
        if n < 3:
            return 0
        primes = [True] * n
        primes[0] = primes[1] = False
        for i in range(2, int(n ** 0.5) + 1):
            if primes[i]:
                primes[i * i: n: i] = [False] * len(primes[i * i: n: i])
        return sum(primes)

LeetCode / Remove Linked List Elements

https://leetcode.com/problems/remove-linked-list-elements/

Remove all elements from a linked list of integers that have value val.

Example:

Input: 1->2->6->3->4->5->6, val = 6
Output: 1->2->3->4->5

これがlistだったりsetだったら問題にもならないのですが、linked listならではのコードを書く必要があります。

解答・解説

誤答

各ノードを走査するための変数current_nodeを定義し、current_nodeを動かしながらvalを取り除いていくと考えます。

と、ここまでは良いのですが以下のようにやってしまうと誤答です。

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution:
    def removeElements(self, head: ListNode, val: int) -> ListNode:
        
        current_node = head
        while current_node.next != None:
            if current_node.next.val == val:
                current_node.next = current_node.next.next
            else:
                current_node = current_node.next
                
        return head

これだと、1つ目の要素をチェックできないので
Inputが[1,2,6,3,4,5,6], val=6のときは良いですが
Inputが[1,2,6,3,4,5,6], val=1のときは正しい戻り値を返せません。

解法1

シンプルに1つ目の要素をチェックする処理を追加する、のが案1です。

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution:
    def removeElements(self, head: ListNode, val: int) -> ListNode:
        
        while head is not None and head.val == val:
            head = head.next
        
        current = head
        while current is not None:
            if current.next is not None and current.next.val == val:
                current.next = current.next.next
            else:
                current = current.next
        
        return head
解法2

誤答のコードは1つ目の要素をチェックできないことが問題だったので、1つ目の要素の前にダミーのnodeを追加したlinked listを作ってやれば問題なくなる、と考えます。

そこで、dummy_headという変数を、ListNode(-1)とheadを結合させる形で作り、dummy_headの各nodeを走査します。

最後に返す値はdummy_head.nextである点に注意します(でないと、ダミーで追加したnodeも含めて返してしまう)。

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution:
    def removeElements(self, head: ListNode, val: int) -> ListNode:
        
        dummy_head = ListNode(-1)
        dummy_head.next = head
        
        current_node = dummy_head
        while current_node.next != None:
            if current_node.next.val == val:
                current_node.next = current_node.next.next
            else:
                current_node = current_node.next
                
        return dummy_head.next