Skip to content

Latest commit

 

History

History
370 lines (315 loc) · 13.1 KB

File metadata and controls

370 lines (315 loc) · 13.1 KB

1. LeetCode勉強記録(Python_LinkedList編)_Easy

  • LeetCodeの勉強記録
    • LinkedListの問題
    • Easy編
  • その他詳細・雑感はsolutions[Python].mdの冒頭参照

1.1. Easy

  • リストであれば
    • 両方のリストから先頭から順に数をpop
    • 数同士を比較し、小さい方を新しいリストに追加
    • 大きい方は残したまま小さかった方のリストから次の数をpop
    • どちらかのリストが空になるまで繰り返す
    • 残ったリストの要素を新しいリストに結合
    • この機能を2つのcntを使用することで実現
  • しかしリストではなくListNodeというClassを使用しているため、len()やスライシングが使えない
  • ▲Discussionを見ると再帰的なコードが書けるっぽい(要復習)
  • 再帰ではなく、上述の元アイデアをListNodeに落とし込んだ解法が上がっていたため、ListNodeの挙動を参考にして実装
# リストならうまく動きそうなバージョン(自力実装)
class Solution:
    def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:
        cnt1=0
        cnt2=0
        l = []
        while cnt1<len(l1) and cnt2<len(l2):
            # 数を比較
            if l1[cnt1]<=l2[cnt2]:
                l.append(l1[cnt1])
                cnt1 += 1
            else:
                l.append(l2[cnt2])
                cnt2 += 1
        # 残った要素を結合
        if cnt1==len(l1):
            l.extend(l2[cnt2:])
        else:
            l.extend(l1[cnt1:])

        return l
# ListNodeで動いたバージョン(Discussionを参考に実装)

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

class Solution:
    def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:
        l = ListNode(0)
        # 最初のインデックスを覚えておく
        head = l
        while l1 and l2:
            # 小さい値を持つ方のリストを連結する
            # 連結の後、連結されたリストのインデックスを1つ送る
            if l1.val<=l2.val:
                l.next = l1
                l1 = l1.next
            else:
                l.next = l2
                l2 = l2.next
                
            # 作成リストのインデックスを次へ送る
            # 値を残して次以降が上書きされるように
            l = l.next

        # 最後に残ったリストを連結する
        if l1:
            l.next = l1
        else:
            l.next = l2

        # 覚えておいた最初のインデックスの次以降を出力
        # 最初のインデックスは0を指すため
        return head.next
  • リストノードの扱いに納得がいっていないため、うまく実装できない
  • ▲リストノードの扱い能力を測る基本的な問題とのこと
    • Solutionを見ると、最初に考えた方針はよかった
# Solutionを参考に実装
class Solution:
    def deleteDuplicates(self, head: ListNode) -> ListNode:
        ans = head
        while ans!=None and ans.next!=None:
            if ans.val==ans.next.val:
                ans.next = ans.next.next
            else:
                ans = ans.next
        return head
  • 値の一致を基準にすると、無限ループかたまたま同じ数列が繰り返されているだけなのかの判別ができない
  • 1度通った値を変更しておくことで、次にその値に当たった場合はループしているということが分かる
    • たまたまリスト内にその値が存在しないように注意しなければならない
    • 今回は数値がvalに含まれるため、文字列ならたまたま存在することはない、しかも2つ連続では存在しないだろうという判断
  • Solutionではメモリアドレスを保存する方法、2つのポインタを使用する方法が紹介されている
# 自力実装

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

class Solution(object):
    def hasCycle(self, head):
        """
        :type head: ListNode
        :rtype: bool
        """
        if not head:
            return False
        while True:
            if head.val=="cycle" and head.next.val=="cycle":
                return True
            if head.next:
                head.val = "cycle"
                head = head.next
            else:
                return False
# Solutionを参考に実装
class Solution(object):
    def hasCycle(self, head):
        """
        :type head: ListNode
        :rtype: bool
        """
        if not head or not head.next:
            return False
        slow = head
        fast = head.next
        while True:
            if slow==fast:
                return True
            if not fast.next or not fast.next.next:
                return False
            slow = slow.next
            fast = fast.next.next
  • 元のインプットを破壊しないように、といった指示がある
  • 解法を思いつかなかった
  • Solutionではブルートフォース、メモリアドレス、2つのポインタを使った解法が紹介されていた
    • ▲2つのポインタでの解法、本当にこれでコーナーケース含めてうまく処理できるのかどうか納得いっていないので復習が必要
# Two Pointers(Solutionを参考に実装)

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

class Solution(object):
    def getIntersectionNode(self, headA, headB):
        """
        :type head1, head1: ListNode
        :rtype: ListNode
        """
        pa = headA
        pb = headB
        while pa!=pb:
            pa = headB if pa==None else pa.next
            pb = headA if pb==None else pb.next
        return pa
  • 試行錯誤の末、正解にたどり着いた
    • head = head.next.nextではうまくいかないと気づくまで時間がかかった
    • 条件分岐が多い解法になってしまった
  • Discussionの記事では先頭にダミーを作成することでシンプルなコードを実現していた
    • elegant!
  • その他のDiscussionでは自力で実装したコードと似た発想での解法が紹介されていた
# 自力実装

# 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:
        # リストの長さが0, 1の場合
        if not head:
            return head
        if not head.next:
            if head.val==val:
                return None
            else:
                return head

        # 先頭からvalが続く場合
        while head.val==val:
            head = head.next
            if not head:
                return None

        # 解答のために先頭を記録
        ans = head

        while head:
            # 次の値がvalの場合、飛ばして次の次に接続する
            while head.next and head.next.val==val:
                head.next = head.next.next
            head = head.next

        return ans
  • ListNodeを前から順にスキャンして値をリストに保存していく
    • リストの逆順に新しいListNodeを作成していく
  • Solutionではループな方法と再帰的な方法が解説されていた
    • ▲ループな方法、紙に書いてなんとか理解できるけど、これを思いつくのは今の自分では無理そう...
# 自力実装

# 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:
        if not head:
            return None
        lis = []
        while head:
            lis.append(head.val)
            head = head.next
        ans = ListNode(lis[-1])
        cur = ans
        for t in lis[::-1][1:]:
            cur.next = ListNode(t)
            cur = cur.next
        return ans
# Solutionを参考に実装
class Solution:
    def reverseList(self, head: ListNode) -> ListNode:
        p = None
        c = head
        while c:
            c.next, p, c = p, c, c.next
        return p
  • ひとまずは上の条件を無視して、リストを作成して逆順と同一になるかどうか判定して実装
  • 処理速度O(n), 使用メモリO(1)での実装を要求されている
    • 解法思いつかず
  • Discussionではfast(1個飛ばし), slow(順番)の2つのカーソルを使用
    • 使用メモリはO(1)ではなくO(n)?
    • Linked Listの最後から作っていくrevをslowに合わせて作成し、fastが最後まで行った段階で前半半分を逆順にしたrevも完成する
    • あとは作成したrevと、後半の頭にあるslowとを順に比較していき、revが無事最後まで行けばTrue
    • ▲Linked Listが後ろからも作成できることは覚えておくべき
# 自力実装(条件無視)

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

class Solution:
    def isPalindrome(self, head: ListNode) -> bool:
        lis = []
        while head:
            lis.append(head.val)
            head = head.next
        return lis==lis[::-1]
# Discussionを参考に実装
class Solution:
    def isPalindrome(self, head: ListNode) -> bool:
        rev = None
        s = f = head
        while f and f.next:
            f = f.next.next
            rev, rev.next, s = s, rev, s.next
        if f:
            s = s.next
        while rev and rev.val==s.val:
            rev = rev.next
            s = s.next
        return not rev
  • headとnodeの2の引数が必要そうな設問なのに、コードでは引数がnodeのみ
  • Solutionを見ると、削除すべきノードとしてnodeが与えられている
    • コード上でnode.val, node.next.valの値を確認することで確かめられた
    • Down voteが大量についている
# Solutionを参考に実装

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

class Solution:
    def deleteNode(self, node):
        """
        :type node: ListNode
        :rtype: void Do not return anything, modify node in-place instead.
        """
        node.val = node.next.val
        node.next = node.next.next