Arrays - Lists and Matrices
Arrays - Lists and Matrices
And
Matrices
Pre-requisites
● Basic Python Knowledge
● Time and Space Complexity
● Willingness to learn
What are Data Structures?
Data Structures
● Particular way of organizing, and storing the data
● Tools to build efficient algorithms
● The right one depends on the task
● Common Data Structures:
○ Arrays ○ Trees
○ Linked Lists ○ Maps(dictionaries)
○ Stacks ○ Sets
○ Queues ○ Tries, etc…
Arrays
Arrays
● Collection of items stored at contiguous memory locations.
● Storing multiple items of the same type together.
● Easier to calculate the position of each element by simply adding an
offset to a base value.
19 10 8 17 9 15
Array indices 0 1 2 3 4 5
Static Arrays
Static Arrays
● A type of array in which the size or length is determined when the array is
created and/or allocated.
7 3 6
*0 *4 *8
Static Arrays
Value 1 3 5
1 3 5
Address *0 *4 *8
Value a b c
a b c
Address *0 *1 *2
Static Arrays - Reading
● Reading is Very fast : O(1)
● Why?
print(my_array[4])
Static Arrays - Reading
First Index 4th element
0 1 2 3 4 5 6 7 8 9
Array Length is 10
1400 + (3 * 4) = 1412
Static Arrays - Writing
● Writing is an instant operation
5
1 3 Value 1 3 0
Address *0 *4 *8
5
Static Arrays - Writing
4
● Overwriting is an instant operation
1 3 5 Value 1 3 5
Address *0 *4 *8
4
Static Arrays - Writing
● What happens when we want to add an element to our
array but don’t have any empty spots?
1 3 5 Value 1 3 5
Address *0 *4 *8
7
Static Arrays - Writing
● The operating system might be using this part of the
memory for some other purpose.
● So we are not necessarily allowed to put our seven there.
1 3 5 Value 1 3 5
Address *0 *4 *8
7
Static Arrays - Summary
Operation Big-O Time
0 1 2 3
[0xa3d25342, 0x635423fa, 0xff243546, 0x2545fade]
Dynamic Arrays
● Each element is a reference that "points" to the
respective objects in memory
*0 *1 *2 *3
*0 *1 *2 *3
5 6 7 8 9 1 2 3
Pushing
● After pushing 5, no place for 6
5 6 7 8 9 1 2 3
5
Operation count: 1
Pushing
● Copy the original array ([5]) to the new allocated memory which
is double the old size and push 6 onto it
6 7 8 9 1 2 3
5 6
Operation count: 1 + 2
Pushing
● Allocate a new array of size 4 and copy over all the old elements
and then we append 7
7 8 9 1 2 3
5 6 7
Operation count: 1 + 2 + 3
Pushing
● Push 8 onto the array. Now we have run out of space for the next
elements.
8 9 1 2 3
5 6 7 8
Operation count: 1 + 2 + 4
Pushing
● Allocate a new array of size 8 and copy over all the old elements
and then we append the element 9
9 1 2 3
5 6 7 8 9
Operation count: 1 + 2 + 4 + 5
Pushing
● Push/append 1 into the array
1 2 3
5 6 7 8 9 1
Operation count: 1 + 2 + 4 + 6
Pushing
● Push/append 2 into the array
2 3
5 6 7 8 9 1 2
Operation count: 1 + 2 + 4 + 7
Pushing
● Push/append 3 into the array
5 6 7 8 9 1 2 3
Operation count: 1 + 2 + 4 + 8
Pushing- Power Series
● For a length of 8, it took us 1 + 2 + 4 + 8 operations.
● The last summand always dominates the sum.
= +
= + +
≥ + + +
Pushing - Power Series
● For a length of 8, it took us 1 + 2 + 4 + 8 operations.
● The last summand always dominates the sum.
2≥1
4≥1+2
8≥1+2+4
.
.
.
2*N ≥ 1 + 2 + 4 + 8 + … + N
● Thus to push N elements, it will take us no more than 2N operations,
making the time complexity O(N).
Pushing - Power Series
1 3 7
Value 1 3 7
5 Address *0 *4 *8 *12
Dynamic Arrays - Insertion
● We need to shift all elements to the right of the new element, to
not lose data, and create the empty space needed.
1 3 7
Value 1 3 7
5 Address *0 *4 *8 *12
Dynamic Arrays - Insertion
● The shifting is what costs us time.
Value 5 1 3 7
5 1 3 7
Address *0 *4 *8 *12
Dynamic Arrays - Removal
● What about removing an element?
Value 5 1 3 7
5 1 3 7
Address *0 *4 *8 *12
Dynamic Arrays - Removal
● It is O(n) at worst.
Value 1 3 7
1 3 7
Address *0 *4 *8
Checkpoint
Traversing Lists
Traversing Lists - Reverse Traversal
Approach Implementation
● Start from the last index for index in range(len(array)-1, -1, -1):
print(array[index])
● Decrement by 1 until -1
Traversing Lists - Even/Odd Indices Traversal
Approach Implementation
● Start from the first step = 2 # incremental value
even/odd index # even indices traversal
● Increment by 2 size = len(array)
for index in range(0, size, step):
print(array[index])
Traversing Lists - Circular Traversal
Approach Implementation
● Start from first index to size = len(array)
size*2 for index in range(0, 2 * size):
● Incrementally go 1 step value = array[index % size]
but modulo index by size print(value)
Pair Programming
Find the winner of the circular
game
Swapping Elements
Swapping Elements
Approach Implementation
● Approach 1
# approach 1
● Store first element in a
variable temp = arr[i]
arr[i] = arr[j]
● Change first element
with second element arr[j] = temp
print(arrayDictionary)
# [2, 2, 2, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
# a b c d e f g h i j k l m n, o p q r s t u v w x y z
Read and Write Indices
Read and Write and Indices
Approach Implementation
(Question Link)
● Set your read index def moveZeroes(self, nums: List[int])
accordingly to your need write = 0
read = 0
● Set your write index
accordingly to your need while read < len(nums):
if nums[read] != 0:
● Your read should always temp = nums[read]
move nums[read] = nums[write]
nums[write] = temp
● Your write only moves after
write operation
write = write + 1
read = read + 1
Pair Programming
Apply operations to an array
Matrices
Matrices
● Two-dimensional arrays can be defined as arrays within an array.
● 2D arrays erected as matrices, which is a collection of rows and columns.
columns
rows
Matrices
● You should think two dimensional arrays as matrices.
● In reality, they are arrays holding references to other arrays
*0 *1
1 2 3
4 5 6
1 2 3 4 5 6
Common Approaches
Traversing Matrices
Traversing - from Top-left to Bottom-right
Approach Implementation
● Start from (0, 0) for row_idx in range(len(matrix)):
for col_idx in range(len(matrix[0])):
● Increment column index by
one for each row you visit print(matrix[row_idx][col_idx])
Traversing - From Bottom-right to Top-left
Approach Implementation
● Start from (last_row - 1,
last_col - 1) for row_idx in range(len(matrix) - 1, -1, -1):
for col_idx in range(len(matrix[0]) - 1, -1, -1):
● Decrement column index by print(matrix[row_idx][col_idx])
one for each row you visit.
Enumerating Cells/ 2D - 1D
Mapping
Enumerating Cells - Approach
0 1 2 3
0 0 1 2 3
1 4 5 6 7
2 8 9 10 11
Enumerating Cells - Approach
Consecutive elements in a column differ by the number of columns
0 1 2 3
0 0 1 2 3
1 4 5 6 7
2 8 9 10 11
Enumerating Cells - Approach
Consecutive elements in a row differ by 1
0 1 2 3
0 0 1 2 3
1 4 5 6 7
2 8 9 10 11
Enumerating Cells - Approach
Therefore the 1D number of a cell is row_number*n_columns + column_number
0 1 2 3
0 0 1 2 3
1 4 5 6 7
2 8 9 10 11
Enumerating Cells - Remainder Theorem
0 1 2 3
0 0 1 2 3
1 4 5 6 7
2 8 9 10 11
Diagonal Keys
What is common about the highlighted column and row numbers?
0 1 2 3
0 0 1 2 3
1 4 5 6 7
2 8 9 10 11
Common Pitfalls
Common Pitfalls - Index Out of Bound
● Trying to access elements using indices that are greater (or equal to)
than the size of our array will result in this exception.
Common Pitfalls - Negative Indexing
● Because we may use negative indices in Python, it is a typical
problem to use negative indices accidently, which can result in a
highly troublesome misunderstanding if no exception is triggered.
Negative indices: -4 -3 -2 -1
Positive indices: 0 1 2 3
Values: [1, 2, 3, 4]
Common Pitfalls - Copying lists
● A list is a reference. Take care when you want to you want to pass
only the content of the list.
nums[::]
nums.copy()
Common Pitfalls - Initializing with *
● The * operator can be used as [object]*n where n is the number
of elements in the array.
● In the case of 2D arrays, this will result in shallow lists
● Hence using list comprehensions is a safer way to create 2D lists.
row = len(matrix)
col = len(matrix[0])
Warren W. Wiersbe