Skip to content

Commit d193bf2

Browse files
Merge pull request #2694 from sudhendrak04/issue-1876-fix
[MEDIUM] #1089 - Duplicate Zeros
2 parents 2bb0dfc + 7be788e commit d193bf2

1 file changed

Lines changed: 165 additions & 0 deletions

File tree

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
"""Program 1089: Duplicate Zeros.
2+
3+
Difficulty: Medium
4+
Category: Array
5+
6+
Task: Duplicate zeros in array.
7+
8+
Given a fixed-length integer array ``arr``, duplicate every ``0`` in place. The
9+
original elements must shift to the right, and the length of the array must stay
10+
the same, so any element pushed past the last index is dropped.
11+
12+
Input: arr
13+
Expected Output: Modified array
14+
15+
Example:
16+
duplicate_zeros([1, 0, 2, 3, 0, 4, 5, 0])
17+
-> [1, 0, 0, 2, 3, 0, 0, 4]
18+
19+
Approach
20+
--------
21+
A naive solution builds a second list of the expanded array and truncates it,
22+
but that needs O(n) extra space. The exercise asks for the in-place version, so
23+
we can only use a fixed number of local variables.
24+
25+
The trick is to walk the array from *right to left* and compute, for every
26+
source index, the position it will occupy after the duplication:
27+
28+
* A value only ever moves to the *right*, so a zero at index ``j`` shifts every
29+
element after it. The final position of ``arr[j]`` is therefore
30+
``j + (number of zeros strictly before j)``.
31+
* If ``arr[j]`` is itself a zero it claims two slots: that position and the next
32+
one.
33+
* Source elements whose position would be ``>= len(arr)`` have been pushed off
34+
the end, so they are simply skipped.
35+
36+
We track ``zeros_before`` as the running count of zeros in ``arr[0..j]``.
37+
Because we iterate backwards we can maintain it in O(1) per step: start it at
38+
the total number of zeros and decrement it after visiting each zero.
39+
40+
Walking backwards is what makes the in-place version safe -- the destination is
41+
always at or after the source, so we never overwrite an element we have not read
42+
yet.
43+
44+
Complexity: O(n) time, O(1) extra space.
45+
"""
46+
47+
48+
def duplicate_zeros(arr: list[int]) -> list[int]:
49+
"""Duplicate every zero in ``arr`` in place, keeping the array length.
50+
51+
Args:
52+
arr: The list of integers to modify. It is modified in place.
53+
54+
Returns:
55+
The same list object that was passed in, for convenience.
56+
57+
Raises:
58+
TypeError: If ``arr`` is not a list of integers.
59+
60+
Example:
61+
>>> duplicate_zeros([1, 0, 2, 3, 0, 4, 5, 0])
62+
[1, 0, 0, 2, 3, 0, 0, 4]
63+
"""
64+
# Guard the contract up front so a bad call fails loudly instead of silently
65+
# producing nonsense part-way through the loop.
66+
for index, value in enumerate(arr):
67+
if not isinstance(value, int) or isinstance(value, bool):
68+
raise TypeError(
69+
f"arr must contain only integers, but arr[{index}] is {value!r}"
70+
)
71+
72+
n = len(arr)
73+
if n == 0:
74+
return arr
75+
76+
# Running count of the zeros in arr[0..j]; walking backwards it starts as
77+
# the total number of zeros and shrinks as we pass each one.
78+
zeros_before = arr.count(0)
79+
written = 0 # number of slots filled so far, only used for the report
80+
81+
for j in range(n - 1, -1, -1):
82+
is_zero = arr[j] == 0
83+
84+
# Final resting place of arr[j] after every zero to its left pushed it
85+
# right. A zero also claims the following slot for its duplicate.
86+
destination = j + zeros_before - (1 if is_zero else 0)
87+
88+
if destination < n:
89+
arr[destination] = arr[j]
90+
written += 1
91+
if is_zero and destination + 1 < n:
92+
arr[destination + 1] = 0
93+
written += 1
94+
95+
if is_zero:
96+
zeros_before -= 1
97+
98+
return arr
99+
100+
101+
def _run_tests() -> None:
102+
"""Run the self-checks covering normal input, edge cases and failures."""
103+
# --- Normal / documented examples -------------------------------------
104+
assert duplicate_zeros([1, 0, 2, 3, 0, 4, 5, 0]) == [1, 0, 0, 2, 3, 0, 0, 4]
105+
assert duplicate_zeros([1, 0, 1]) == [1, 0, 0]
106+
assert duplicate_zeros([0, 0, 0]) == [0, 0, 0]
107+
108+
# --- Edge cases --------------------------------------------------------
109+
# Empty list.
110+
assert duplicate_zeros([]) == []
111+
# Single element, with and without a zero.
112+
assert duplicate_zeros([0]) == [0]
113+
assert duplicate_zeros([7]) == [7]
114+
# Every element is a zero: the result is all zeros, same length.
115+
assert duplicate_zeros([0, 0, 0, 0, 0]) == [0, 0, 0, 0, 0]
116+
# No zeros at all: the array must be untouched.
117+
assert duplicate_zeros([1, 2, 3]) == [1, 2, 3]
118+
# A trailing zero is duplicated and the last element is dropped.
119+
assert duplicate_zeros([1, 2, 0]) == [1, 2, 0]
120+
# A leading zero pushes everything one slot to the right.
121+
assert duplicate_zeros([0, 1, 2]) == [0, 0, 1]
122+
# Duplication overflows the end and truncates.
123+
assert duplicate_zeros([1, 0, 0, 0, 0]) == [1, 0, 0, 0, 0]
124+
# Negative values must be preserved.
125+
assert duplicate_zeros([-1, 0, -2]) == [-1, 0, 0]
126+
127+
# --- Length and identity are preserved (in place) ---------------------
128+
for sample in ([1, 0, 2, 3, 0, 4, 5, 0], [0, 0, 0], [4, 5, 6], []):
129+
original_length = len(sample)
130+
assert duplicate_zeros(sample) is sample, "must modify the list in place"
131+
assert len(sample) == original_length, "length must not change"
132+
133+
# --- Failure cases: invalid input raises TypeError ---------------------
134+
for bad_input in ([1, "0", 2], [None], [1.5], [True]):
135+
try:
136+
duplicate_zeros(bad_input) # type: ignore[arg-type]
137+
except TypeError:
138+
pass
139+
else:
140+
raise AssertionError(f"expected TypeError for {bad_input!r}")
141+
142+
# --- Brute-force cross-check on many random inputs ---------------------
143+
def reference(arr: list[int]) -> list[int]:
144+
"""Obvious O(n) extra-space version, used only to validate the above."""
145+
expanded: list[int] = []
146+
for value in arr:
147+
expanded.append(value)
148+
if value == 0:
149+
expanded.append(0)
150+
return expanded[: len(arr)]
151+
152+
checked = 0
153+
for first in range(-2, 3):
154+
for second in range(-2, 3):
155+
for third in range(-2, 3):
156+
candidate = [first, second, third]
157+
assert duplicate_zeros(list(candidate)) == reference(candidate)
158+
checked += 1
159+
assert checked == 125, f"expected 125 cross-checked cases, got {checked}"
160+
161+
print(f"All tests passed ({checked} brute-force cross-checks included).")
162+
163+
164+
if __name__ == "__main__":
165+
_run_tests()

0 commit comments

Comments
 (0)