An array is a data structure that stores a collection of items of the same type, such as integers or strings. These items are stored contiguously, much like a row of numbered lockers. Each item in an array can be quickly accessed using its index allowing for efficient retrieval and modification of data.
Arrays offer many operations that allow us to work with data efficiently. Because array elements are stored together in memory, some operations are very fast, while others require more work. Here is an overview of array operations:
Operations | Time | Space |
---|---|---|
Append | O(1) | O(1) |
Pop | O(1) | O(1) |
Access | O(1) | O(1) |
Insert | O(n) | O(1) |
Delete | O(n) | O(1) |
Search | O(n) | O(1) |
Resize | O(n) | O(n) |
Pointers are important because they can be used to directly access and manipulate array elements. This direct access is what makes arrays efficient, especially when it comes to iterating over elements or implementing data structures like stacks and queues. Let's take a look at an example of pointers:
def reverse(array: List[int]) -> List[int]:
left, right = 0, len(array) - 1
while left < right:
array[left], array[right] = array[right], array[left]
left += 1
right -= 1
return array
# Example usage
my_array = [1, 2, 3, 4, 5, 6]
print(reverse(my_array)) # Output: [6, 5, 4, 3, 2, 1]
The code above defines a function reverse()
that takes an array of integers and reverses its elements in place, meaning the original array is modified without needing extra space for another array. It starts with two pointers, left
at the beginning and right
at the end of the array. The pointers meet in the middle to swap the elements, which reverses the original array.
Static arrays have a fixed size, which is determined when the program is compiled. This makes them very quick for accessing elements. However, the fixed size has a drawback: you can't add more elements once the array is full. Trying to do so will cause an error or unexpected behavior. You don't need to worry about this in Python, but the concepts of static arrays are still important to know.
Unlike static arrays, dynamic arrays can change size while the program is running. However, the computer needs to do extra work to keep track of the array's size. When the array gets bigger, it might need to be copied to a new location in memory. This process can take more time and use more resources compared to static arrays. Thankfully, clever resizing strategies makes this operation amortized constant time.
class DynamicArray:
def __init__(self):
self.array = [None] # Start with an array of size 1
self.size = 0
def append(self, element):
if self.size == len(self.array):
# Array is full, time to resize
new_array = [None] * (2 * len(self.array))
# Copy elements to the new array
for i in range(self.size):
new_array[i] = self.array[i]
self.array = new_array
print(f"Array doubled. New capacity: {len(self.array)}")
# Add the new element
self.array[self.size] = element
self.size += 1
# Example usage
dynamic_array = DynamicArray()
for i in range(5):
dynamic_array.append(i)
print(f"Added {i}. Current size: {dynamic_array.size}, Capacity: {len(dynamic_array.array)}")
Many programming languages have dynamic arrays built-in. For example, Python uses List
, while Java has ArrayList
. To use dynamic arrays effectively, it's important to be familiar with the array functions available in your preferred programming language.
Stacks are data structures that follow the LIFO principle. While stacks are not arrays, they can be efficiently implemented using arrays. Stacks use arrays to their advantage by working with elements at the end. They have two key operations: push
to add an item on top, and pop
to remove the top item. This allows stacks to perform all operations in constant time.
Operations | Time | Space |
---|---|---|
Push | O(1) | O(1) |
Pop | O(1) | O(1) |
Peek | O(1) | O(1) |
Is Empty | O(1) | O(1) |
Size | O(1) | O(1) |
Clear | O(1) | O(1) |
The animation above shows how stacks work in practice. Just like a stack of plates where you can only add or remove from the top, each operation works with the most recently added element. This LIFO behavior makes stacks the perfect choice when you need to process elements that depend on the elements that came before.
Understanding arrays and stacks is necessary for coding interviews and optimal problem-solving. These best practices will help you understand key concepts more easily, avoid common pitfalls, and approach problems with confidence: