今是昨非

今是昨非

日出江花红胜火,春来江水绿如蓝

アルゴリズム_連結リストの二つのポインタ

Algorithem_TwoPointersOfLinked List#

876. リンクリストの中央#

単方向リンクリストのヘッドが与えられたとき、リンクリストの中央ノードを返します。

中央ノードが 2 つある場合は、2 番目の中央ノードを返します。

例 1:

入力: head = [1,2,3,4,5]
出力: [3,4,5]
説明: リストの中央ノードはノード3です。

例 2:

入力: head = [1,2,3,4,5,6]
出力: [4,5,6]
説明: リストには値が3と4の2つの中央ノードがあるため、2番目のものを返します。

解法#

単に例を見ると、配列の中央位置から末尾を取得するように思えますが、これは TwoPointer とどのように関係しているのでしょうか?与えられたコードを見ると、配列ではなく、ListNode が与えられていることがわかりました。

定義されたコードは以下の通りです:


/**
 * 単方向リンクリストの定義。
 * public class ListNode {
 *     public var val: Int
 *     public var next: ListNode?
 *     public init() { self.val = 0; self.next = nil; }
 *     public init(_ val: Int) { self.val = val; self.next = nil; }
 *     public init(_ val: Int, _ next: ListNode?) { self.val = val; self.next = next; }
 * }
 */
class Solution {
    func middleNode(_ head: ListNode?) -> ListNode? {

    }
}

ここでは、与えられた ListNode と上記の例の head がどのように対応しているのか理解できませんでした。

しばらく考えた後、理解しました。

リンクリスト
1 -> 2 -> 3 -> 4 -> 5

val     1
next    2

val     2
next    3

val     3
next    4

val     4
next    5

val     5
next    nil

listNode を理解した後、中央の ListNode をどのように取得するかを考えます。

2 つの変数 s と f を定義し、毎回ループで s が次の要素を指し、f が次の次の要素を指すようにします。こうすることで、最終的に f が終了したとき、s が指しているのが中央になります。


/*
最初
f
1 -> 2 -> 3 -> 4 -> 5
s

最初のループ
		  f
1 -> 2 -> 3 -> 4 -> 5
     s
	 
2回目のループ
		            f
1 -> 2 -> 3 -> 4 -> 5
          s

3回目のループ
fのnextがnilのとき終了し、この時sが指しているのが中央です。

f = ファストポインタ
s = スローポインタ
*/

最終的なコードは以下の通りです:


/**
 * 単方向リンクリストの定義。
 * public class ListNode {
 *     public var val: Int
 *     public var next: ListNode?
 *     public init() { self.val = 0; self.next = nil; }
 *     public init(_ val: Int) { self.val = val; self.next = nil; }
 *     public init(_ val: Int, _ next: ListNode?) { self.val = val; self.next = next; }
 * }
 */
class Solution {
    func middleNode(_ head: ListNode?) -> ListNode? {
        var slow = head
        var fast = head
        while (fast != nil) && (fast?.next != nil) {
            slow = slow?.next
            fast = fast?.next?.next
        }
        return slow
    }
}

19. リストの末尾から N 番目のノードを削除#

リンクリストのヘッドが与えられたとき、リストの末尾から n 番目のノードを削除し、そのヘッドを返します。

例 1:

入力: head = [1,2,3,4,5], n = 2
出力: [1,2,3,5]

例 2:

入力: head = [1], n = 1
出力: []

例 3:

入力: head = [1,2], n = 1
出力: [1]

解法#

ここで注意が必要なのは、nth node from the end of the list は末尾から数えた第 n 番目のノードであるということです。

考え方は、まず全体の長さを取得し、その長さから (n - 1) を引くことで、正方向から数えた第 n 番目のノードを求めます。1 から始まる数え方です。そして、頭から順にノードを数えます。ちょうどその位置に来たら、スキップします。
しかし、TwoPoint のアルゴリズムを使用する場合、slow と fast を定義し、最初に fast を n ステップ前に進め、その後 slow と fast を一緒に前に進めます。こうすることで、fast が末尾に到達したとき、slow がちょうど削除すべき位置にいます。その要素をスキップして値を設定します。

コードは以下の通りです:


/**
 * 単方向リンクリストの定義。
 * public class ListNode {
 *     public var val: Int
 *     public var next: ListNode?
 *     public init() { self.val = 0; self.next = nil; }
 *     public init(_ val: Int) { self.val = val; self.next = nil; }
 *     public init(_ val: Int, _ next: ListNode?) { self.val = val; self.next = next; }
 * }
 */
class Solution {
    func removeNthFromEnd(_ head: ListNode?, _ n: Int) -> ListNode? {
        let node = ListNode(0, head)
        
        var slow: ListNode? = node
        var fast: ListNode? = node
        
        var m = n
        while (m > 0) {
            fast = fast?.next
            m -= 1
        }
        
        while fast?.next != nil {
            slow = slow?.next
            fast = fast?.next
        }
        
        slow?.next = slow?.next?.next
        
        return node.next
    }
}

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。