🐙

LeetCode 283. Move Zeroes - 多重・分割・タプル代入の比較

に公開

283. Move Zeroes

https://leetcode.com/problems/move-zeroes/description/

Intuition

  • 与えられた配列の中にある0を末尾に移動する
  • 新しく配列を作らずにin-placeで処理すること
    • 0を見つけたタイミングで配列の一番うしろに移動すればいいのではないか?
    • sliceで移動できるか?
>>> arr = [0,1,2,3,4]
>>> arr[0:4] = arr[1:5]
>>> arr
[1, 2, 3, 4, 4]
>>> arr[4] = 0
>>> arr
[1, 2, 3, 4, 0]

以下のテストケースが通らない

# nums = [0,0,1]
# Output: [0,1,0]
# Expected: [1,0,0]
# ゼロを見つけるたびにスライス代入して配列全体を左にシフトしている。
# i=0で左シフトした後に`[0,1,0]`となるが、i=0のインデックスはすでに処理されなくなってしまっている。

class Solution:
    def moveZeroes(self, nums: List[int]) -> None:
        """
        Do not return anything, modify nums in-place instead.
        """

        zero_count = 0
        for i in range(len(nums)):
            if (nums[i] == 0):
                # nums = [0, 1, 0, 3, 12]
                # nums[2:4] => [0. 3]
                # nums[2:5] => [0, 3, 12]
                nums[i:len(nums)-1] = nums[i+1:len(nums)]
                nums[-1] = 0        

python

この2つのポインタを使って、非ゼロ要素をすべて配列の先頭に集めてから、残りの部分をゼロで埋めるようにする。

class Solution:
    def moveZeroes(self, nums: List[int]) -> None:
        """
        Do not return anything, modify nums in-place instead.
        """
        write_index = 0

        for read_index in range(len(nums)):
            if nums[read_index] != 0:
                nums[write_index] = nums[read_index]
                write_index += 1

        for i in range(write_index, len(nums)):
            nums[i] = 0

ruby

rubyっぽい(?)書き方だが、前提としてのin-placeを満たしてはおらず、効率も悪い。(テストケースは通る)

  • 元の配列と同じサイズの新しい配列を作成する可能性があるため、メモリを余分に使用
  • 2回の配列の走査(select と each)が必要。
def move_zeroes(nums)
   nonzeros = nums.select do |n| n != 0 end

   nums.length.times do |index|
     nums[index] = nonzeros[index] || 0
   end
end

Approach

2つのポイントを使って非ゼロを左に動して1ループで要素も交換する方式でエレガントでした。
https://leetcode.com/problems/move-zeroes/solutions/6743967/video-two-pointer-solution/

Input: nums = [0,1,0,3,12]
[0,1,0,3,12]
 R
 W

を例にステップで記載する。

  1. i = 0 (nums[0] は 0): 何もせずスキップ。write_index = 0。
[0,1,0,3,12]
 R
 W
  1. i = 1 (nums[1] は 1): nums[1] と nums[0] を交換。nums は [1, 0, 0, 3, 12] に。write_index = 1。
[1,0,0,3,12]
   R
   W
  1. i = 2 (nums[2] は 0): 何もせずスキップ。write_index = 1。
[1,0,0,3,12]
     R
   W
  1. i = 3 (nums[3] は 3): nums[3] と nums[1] を交換。nums は [1, 3, 0, 0, 12] に。write_index = 2。
[1,3,0,0,12]
       R
     W
  1. i = 4 (nums[4] は 12): nums[4] と nums[2] を交換。nums は [1, 3, 12, 0, 0] に。write_index = 3。
[1,3,12,0,0]
          R
        W

typescript

配列の分割代入(Destructuring Assignment)を利用する。

let a = 10;
let b = 20;

// 分割代入で値を交換
[a, b] = [b, a];

console.log(a); // 20
console.log(b); // 10
function moveZeroes(nums: number[]): void {
    let write_index: number = 0

    for(let read_index = 0; read_index < nums.length; read_index++){
        if(nums[read_index] != 0){
            [nums[read_index], nums[write_index]] = [nums[write_index], nums[read_index]]
            write_index++
        }
    }
};

golang

多重代入を利用する。

a, b := 10, 20
a, b = b, a  // aとbの値を交換
// aは20、bは10になる
func moveZeroes(nums []int) {
	var write_index int = 0

	for read_index := 0; read_index < len(nums); read_index++ {
		if nums[read_index] != 0 {
			nums[read_index], nums[write_index] = nums[write_index], nums[read_index]
			write_index++
		}
	}
}

学習のポイント

  • 書き込みと読み込みの2つのポインタを使って、ループ処理と配列の要素の移動を切り離す
  • in-placeで処理するためには新しく配列をつくらないこと
  • 多重代入、タプル代入、分割代入などを使って値を入れ替えることができる
Golang Python JavaScript
処理の名称 多重代入 (multiple assignment) タプル代入 (tuple assignment) 分割代入 (Destructuring assignment)
コード例 a, b = b, a a, b = b, a [a, b] = [b, a]
コンセプト 複数の変数に同時に値を代入する タプルというデータ構造の要素を分解して複数の変数に代入する。 配列やオブジェクトの要素・プロパティを分解して複数の変数に代入する。

Discussion