Skip to content

006 — Move Zeroes

Source

  • Original source: Classic two-pointer (slow/fast) exercise
  • Platform: LeetCode — https://leetcode.com/problems/move-zeroes/
  • Topic: array
  • Difficulty: Easy
  • Pattern: two pointers (slow/fast, same direction)
  • Basic idea inherited from: remove-duplicates-এর skeleton — slow write-pointer + fast read-pointer

1. Problem in simple words

একটা integer array nums দেওয়া। সব 0 কে array-র শেষে পাঠাও, আর non-zero গুলোর relative order ঠিক রেখে। কাজটা in place — নতুন array না বানিয়ে।

nums = [0, 1, 0, 3, 12]  ->  [1, 3, 12, 0, 0]
(1, 3, 12 আগের order-এই; zero গুলো শেষে)

2. Which basic idea does this inherit from?

হুবহু problem 4 (remove duplicates)-এর slow/fast skeleton। সেখানে শর্ত ছিল "নতুন value কিনা"; এখানে শর্ত "non-zero কিনা"। slow বলে পরের non-zero কোথায় বসবে, fast পুরো array scan করে। একই কঙ্কাল, শুধু শর্ত বদল।

3. What is the hidden math or logic?

লুকানো logic একটা invariant: nums[0..slow-1] সবসময় এ পর্যন্ত পাওয়া সব non-zero, order ঠিক রেখে। fast যখন non-zero পায়, সেটাকে slow-এ এনে slow এক বাড়াই। শেষে slow থেকে array-র শেষ পর্যন্ত সব আপনাআপনি zero (swap version-এ zero গুলো পিছনে চলে যায়)।

4. Which data structure is needed?

কোনো extra structure লাগে না — array আছে, আর দুটো integer pointer (slow, fast)। O(1) extra space।

5. Why this data structure fits

array O(1) index access/swap দেয়, তাই non-zero element-কে সরাসরি সামনের সঠিক ঘরে swap করা যায়। order রক্ষা পায় কারণ আমরা বাঁ-থেকে-ডান একই ক্রমে non-zero গুলো বসাই — কোনো structure-এ আলাদা করে জমা রাখার দরকার নেই।

6. Real-life analogy

একটা ট্রে-তে কিছু পূর্ণ গ্লাস আর কিছু খালি গ্লাস এলোমেলো। তুমি চাও পূর্ণ গ্লাসগুলো (একই order-এ) সামনে চলে আসুক, খালিগুলো পেছনে জমুক। বাঁ থেকে হাঁটতে হাঁটতে পূর্ণ গ্লাস পেলেই সামনের পরের ফাঁকা জায়গায় এনে রাখো; খালি গ্লাস পেলে এড়িয়ে যাও।

7. Visual explanation

nums = [0, 1, 0, 3, 12] (swap version, slow = পরের non-zero যেখানে বসবে):

slow=0
f=0: nums[0]=0  -> skip
f=1: nums[1]=1  -> swap(0,1)  [1,0,0,3,12]  slow=1
f=2: nums[2]=0  -> skip
f=3: nums[3]=3  -> swap(1,3)  [1,3,0,0,12]  slow=2
f=4: nums[4]=12 -> swap(2,4)  [1,3,12,0,0]  slow=3

ফল: [1,3,12,0,0]

8. Example input and output

Input : [0, 1, 0, 3, 12]  -> [1, 3, 12, 0, 0]

Edge case 1 (সব zero):   [0, 0, 0] -> [0, 0, 0]
Edge case 2 (zero নেই):  [1, 2, 3] -> [1, 2, 3]
Edge case 3 (একটা):      [0]       -> [0]

9. Brute force thinking

প্রথম সরল চিন্তা: সব non-zero নতুন list-এ তুলে নাও, পেছনে যত zero দরকার ততগুলো জুড়ে দাও, তারপর nums-এ copy করো।

out = [x for x in nums if x != 0]
out += [0] * (len(nums) - len(out))
nums[:] = out

10. Why brute force is slow

এটা ঠিক কাজ করে কিন্তু O(n) extra space নেয় (নতুন list)। problem in place, O(1) space চায়। আলাদা list-এর দরকার নেই — একটা write-pointer দিয়ে array-র ভেতরেই non-zero সামনে আনা যায়।

11. Key observation

মূল observation: zero "সরানোর" দরকার নেই; শুধু non-zero গুলো order ঠিক রেখে সামনে packing করলেই zero গুলো এমনিতেই পেছনে চলে যায়। (swap করলে zero নিজে থেকে slow-এর ঘরে গিয়ে বসে।)

12. Optimized intuition

slow=0fast পুরো array-তে হাঁটে। nums[fast] non-zero হলে nums[slow]-এর সাথে swap করো আর slow এক বাড়াও। zero হলে কিছু না করে এগিয়ে যাও। শেষে non-zero গুলো সামনে (order সহ), zero গুলো পেছনে।

13. Thinking tweak

মোচড়: "zero গুলো শেষে ঠেলব" ভাবার বদলে ভাবো "non-zero গুলো সামনে টানব।" তখন remove-duplicates-এর সেই একই slow/fast packing কাজে লেগে যায় — শুধু শর্ত != 0

14. Step-by-step dry run

Input [0, 1, 0, 3, 12]:

  1. slow=0fast=0: nums[0]=0 → skip।
  2. fast=1: nums[1]=1 non-zero → swap nums[0],nums[1][1,0,0,3,12]; slow=1
  3. fast=2: nums[2]=0 → skip।
  4. fast=3: nums[3]=3 non-zero → swap nums[1],nums[3][1,3,0,0,12]; slow=2
  5. fast=4: nums[4]=12 non-zero → swap nums[2],nums[4][1,3,12,0,0]; slow=3। ফল [1,3,12,0,0]

15. Python solution

def move_zeroes(nums):
    slow = 0                              # পরের non-zero যেখানে বসবে
    for fast in range(len(nums)):
        if nums[fast] != 0:               # non-zero পেলাম
            nums[slow], nums[fast] = nums[fast], nums[slow]
            slow += 1
    return nums

# ---- tests ----
a = [0, 1, 0, 3, 12]
move_zeroes(a)
assert a == [1, 3, 12, 0, 0]

b = [0, 0, 0]
move_zeroes(b)
assert b == [0, 0, 0]                     # সব zero

c = [1, 2, 3]
move_zeroes(c)
assert c == [1, 2, 3]                     # zero নেই
print("সব test pass!")

16. Line-by-line code explanation

  • slow = 0 — পরের non-zero কোন ঘরে বসবে তার write-pointer।
  • for fast in range(len(nums)) — পুরো array একবার scan।
  • if nums[fast] != 0 — শুধু non-zero হলেই কাজ।
  • nums[slow], nums[fast] = nums[fast], nums[slow] — non-zero-কে সামনে আনি; zero (থাকলে) পেছনে চলে যায়।
  • slow += 1 — পরের non-zero-র জন্য জায়গা এক ঘর এগোলো।

17. Output walkthrough

[0,1,0,3,12]: 1, 3, 12 ক্রমে সামনে swap হয়ে [1,3,12,0,0] — assert pass। সব-zero array swap-এ অপরিবর্তিত; zero-নেই array-তে প্রতিটা element নিজের সাথে swap হয়ে অপরিবর্তিত। শেষে print: সব test pass!

18. Time complexity

O(n)fast array-তে একবার হাঁটে; প্রতিটা step O(1)।

19. Space complexity

O(1) — শুধু দুটো pointer; swap array-র ভেতরেই।

20. Common mistakes

  • order ভেঙে ফেলা — non-zero গুলো sort/অদলবদল করা; problem relative order রাখতে বলে।
  • শুধু non-zero সামনে লিখে বাকি ঘর zero করতে ভুলে যাওয়া (write-only version-এ) — swap version-এ এই সমস্যা নেই।
  • zero-element-এও slow বাড়িয়ে দেওয়া — তখন zero-ও "valid" ধরা পড়ে, packing ভুল হয়।

21. Which basic problem this inherits from

ভিত্তি: problem 4 (remove duplicates)-এর slow/fast packing; শর্ত বদলে "non-zero কিনা"। দুটোই Pattern 1-এর same-direction two pointers-এর রূপ। chapter-এর ../patterns.md-এর "Pattern 1 — Two Pointers" দেখো।

22. Similar harder problems

23. Pattern learned

Slow/fast partition (stable compaction): একটা শর্ত মানা element গুলো order ঠিক রেখে সামনে packing, বাকিরা পেছনে — এক pass, O(1) space।

24. Final summary

ভবিষ্যতের আমাকে: "move zeroes = non-zero সামনে pack করা (slow/fast), zero নিজে থেকে পেছনে।" "keep order, push some to the end" দেখলেই এই slow/fast partition মনে করবে।

25. JavaScript solution (with test cases)

slow/fast same-direction two pointers — non-zero পেলে slow-এ swap করে slow এগোই; zero এড়িয়ে যাই।

// throwing assert — fail করলে থেমে error দেবে
const assert = (cond, msg = "") => { if (!cond) throw new Error("FAIL " + msg); };

function moveZeroes(nums) {
  let slow = 0;                                   // পরের non-zero যেখানে বসবে
  for (let fast = 0; fast < nums.length; fast++) {
    if (nums[fast] !== 0) {
      [nums[slow], nums[fast]] = [nums[fast], nums[slow]];  // non-zero সামনে
      slow++;
    }
  }
  return nums;
}

// ---- test cases (file-এর example + edge case) ----
const eq = (a, b) => JSON.stringify(a) === JSON.stringify(b);
assert(eq(moveZeroes([0, 1, 0, 3, 12]), [1, 3, 12, 0, 0]), "basic");
assert(eq(moveZeroes([0, 0, 0]), [0, 0, 0]), "সব zero");
assert(eq(moveZeroes([1, 2, 3]), [1, 2, 3]), "zero নেই");
assert(eq(moveZeroes([0]), [0]), "একটা");
assert(eq(moveZeroes([1, 0, 2, 0, 3]), [1, 2, 3, 0, 0]), "mixed, order রক্ষা");
console.log("সব JS test pass!");

JS টীকা: swap [a, b] = [b, a] (destructuring) — temp লাগে না, in-place থাকে, তাই O(1) extra space। zero-element-এ slow বাড়িও না (তখন zero "valid" ধরা পড়ে packing ভাঙবে)। non-zero গুলো বাঁ-থেকে-ডান একই ক্রমে বসে বলে relative order অটুট।

26. TypeScript solution (with test cases)

function moveZeroes(nums: number[]): number[] {
  let slow = 0;
  for (let fast = 0; fast < nums.length; fast++) {
    if (nums[fast] !== 0) {
      [nums[slow], nums[fast]] = [nums[fast], nums[slow]];
      slow++;
    }
  }
  return nums;
}

function expectArr(actual: number[], expected: number[], msg = ""): void {
  if (JSON.stringify(actual) !== JSON.stringify(expected)) {
    throw new Error(`FAIL ${msg}: [${actual}]`);
  }
}

expectArr(moveZeroes([0, 1, 0, 3, 12]), [1, 3, 12, 0, 0], "basic");
expectArr(moveZeroes([0, 0, 0]), [0, 0, 0], "all zero");
expectArr(moveZeroes([1, 2, 3]), [1, 2, 3], "no zero");
console.log("সব TS test pass!");

TS টীকা: nums: number[] declare করায় in-place mutation-এর target স্পষ্ট; ভুলে readonly tuple বা অন্য টাইপ পাঠালে compile-time-এ ধরা পড়ে।

27. কোথায় কাজে লাগে (real-world use)

  • Stable filtering/compaction: কিছু element order রেখে সামনে, বাকি পেছনে — UI list-এ active item আগে, archived পরে।
  • Garbage/null packing: array থেকে null/empty slot পেছনে ঠেলে valid data সামনে compact করা (memory pool, free-list)।
  • Partition step: quicksort partition, Dutch national flag (Sort Colors) — same-direction two-pointer-এর জ্ঞাতি।
  • Stream cleanup: invalid/zero রেকর্ড সরিয়ে valid গুলো এক pass-এ সাজানো। মূল pattern — "শর্ত-মানা element order রেখে সামনে pack করা (slow write + fast read, O(1) space)" — filter-in-place, partition, remove-duplicates জুড়ে বড় ছবিতে ফেরে।

মনে রাখার নিয়ম (legal): সব নিজের ভাষায় লেখা; problem statement copy করা হয়নি; official link শুধু attribution-এর জন্য; "asked by Google/Amazon" দাবি নেই — এটা একটা common interview pattern।