前言
本人是一個長期的數據分析愛好者,最近半年的時間的在網上學習了很多關於python、數據分析、數據挖掘以及項目管理相關的課程和知識,但是在學習的過程中,過於追求課程數量的增長,長時間關注於學習了多少多少門課程。事實上,學完一門課之後真正掌握的知識並不多,主要的原因是自己沒有認真學習和理解溫故而知新的這句話的真正含義。因此,從現在開始,我在學習《數據結構與算法——基於python》的課程內容之後,抽出固定的時間對每天學習的內容進行總結和分享,一方面有助於個人更好的掌握課程的內容,另一方面能和大家一起分享個人的學習歷程和相應的學習知識。
第二節 數組和動態數組
## 基礎知識:
2.1 動態數組大小爲什麼可以改變:當原有的空間內存滿了之後,會在內存中新開一個原來空間兩倍大的空間,然後將原來的元素複製到新的空間,原來的空間會被清空回收。
2.2 append.() o(1),insert.() o(n)、deleate.() o(n),
對於無序的數組,查找的時間複雜度也是o(n);
2.4 數組在做切片操作的時候,是首先將數據複製過來,然後再操作,不會修改原始數據;
np.arange做切片操作的時候會修改原始數據,修改之後會改變原始數據;
Shallow Copy和Deep Copy,在修改被指定的元素的值之後,其他所有指向該地址元素的值均會被改變;
## 數組應用:
- 挖雷遊戲
問題描述:
程序接收三個參數,M,N和p,然後生成一個M * N的矩陣,然後每一個cell有p的概率是地雷。生成矩陣後,再計算出每一個cell周圍地雷的數量 。
思路:
對於每一個m和n,生成一個m+2*n+2的矩陣,將矩陣的最外一層的元素全部置空,用隨機數生成一個0到1的數,對於小於p的位置,就放上地雷;大於p的位置,就不放上地雷;然後重新生成一個矩陣計算每個位置周圍的地雷數;最後輸出地雷矩陣和每個點周圍的地雷數的矩陣;
代碼
import random
def minesweeper(m, n, p):
board = [[None] * (n+2) for i in range(m+2)]
for i in range(1, m + 1):
for j in range(1, n + 1): # 生成地雷舉證
r = random.random()
board[i][j] = -1 if r < p else 0
for i in range(1, m + 1): #輸出地雷舉證
for j in range(1, n + 1):
print("*", end=" ") if board[i][j] == -1 else print(".", end=" ")
print()
# 計算每個點周的地雷數;
for i in range(1, m + 1):
for j in range(1, n + 1):
if (board[i][j] != -1):
for ii in range(i-1, i+2):
for jj in range(j-1, j+2):
if (board[ii][jj] == -1):
board[i][j] += 1
print()
# 輸出地雷數的矩陣
for i in range(1, m + 1):
for j in range(1, n + 1):
print("*", end=" ") if board[i][j] == -1 else print(board[i][j], end=" ")
print()
minesweeper(5, 10, 0.2) # 初始化 m、n、p
輸出結果:
2. 矩陣0變換
問題描述:
給一個m×n的矩陣,如果有一個元素爲0,則把該元素對應的行與列所有元素全部變成0;
思路:
首先記錄矩陣中哪一行和哪一列出現了零;最後對出現零的行和列進行清零處理;新加兩個一位數組,一個長度爲m;另外一個的長度爲n;對於m*n矩陣中出現0的值就將m[i]和n[j]變爲1,最後對於m[i]或n[j]= 1的位置對應行和列全部變爲零;空間複雜度爲o(m+n)
代碼
# O(m+n) space complexity
def zero(matrix):
m = [None] * len(matrix)
n = [None] * len(matrix[0])
for i in range(len(matrix)):
for j in range(len(matrix[0])):
if (matrix[i][j] == 0):
m[i] = 1
n[j] = 1
for i in range(len(matrix)):
for j in range(len(matrix[0])):
if (m[i] == 1 or n[j] == 1):
matrix[i][j] = 0
matrix = [ [ 1, 1, 1, 1, 1, 0, 1, 1, 1, 1 ],
[ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ],
[ 1, 1, 0, 1, 1, 1, 1, 1, 1, 1 ],
[ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ],
[ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ] ]
for x in matrix:
print(x, sep=" ")zero(matrix)
for x in matrix:
print(x, sep=" ")
輸出結果
如圖所示,將原來行或者列存在0的元素全部變成了0;
3. 九宮圖
問題描述:
給一個n×n的矩陣,保證每行每列以及對角線的和都相等;
思路:
按照下圖所示的方法構建矩陣,將1放在最後一行的最中間,然後逆時針旋轉依次放入2、3、4等,遇到位置上已經有數字的位置,向上移動一行,再繼續按照逆時針旋轉即可;
代碼
def magic_square(n):
magic = [[0] * (n) for i in range(n)]
row = n - 1
col = n//2
magic[row][col] = 1 # 1 放在最後一行的中間
for i in range(2, n * n + 1):
try_row = (row + 1) % n
try_col = (col + 1) % n
if (magic[try_row][try_col] == 0):
row = try_row
col = try_col
else:
row = (row - 1 + n) % n
magic[row][col] = i
for x in magic:
print(x, sep=" ")
magic_square(5)
輸出結果如下所示;
4. 數獨驗證
問題描述:
給一個填好的數獨,驗證是否正確;
思路:
數獨的驗證條件是在99的矩陣裏面。每一行每一列都有1到9這9個數,並且在每個小的33的小矩陣裏面也要有1到9這9個數。如何將上述要求用代碼實現呢?這是一個問題!!!經過我上手計算,終於搞明白了老師的思路,現分享如下:
對每一行(列)的數據,建立一個9個元素的數組,對於該行出現1到9中的任意一個數i,就將該數組中的第i變成1,到最後如果所有的位置都變爲了1,即可確定爲滿足數獨的條件;
對於每個3*3的小矩陣,按照從左到右,從上到下的順序,按照上同樣的方法,出現i就將第i個元素變成1,到最後如果所有的位置都變爲了1,即可確定爲滿足數獨的條件;
第i個元素變成i的操作需要使用到二進制的位操作,具體如程序所示;
代碼
matrix = [
[5,3,4,6,7,8,9,1,2],
[6,7,2,1,9,5,3,4,8],
[1,9,8,3,4,2,5,6,7],
[8,5,9,7,6,1,4,2,3],
[4,2,6,8,5,3,7,9,1],
[7,1,3,9,2,4,8,5,6],
[9,6,1,5,3,7,2,8,4],
[2,8,7,4,1,9,6,3,5],
[3,4,5,2,8,6,1,7,9]
]
def sudoku(matrix):
n = len(matrix)
result_row = result_col = result_blk = 0
for i in range(n):
result_row = result_col = result_blk = 0
for j in range(n):
## check row
tmp = matrix[i][j]
if ((result_row & (1 << (tmp-1))) == 0):
result_row = result_row | (1<<(tmp-1))
else:
print("row: ", i, j)
return False
## check column
tmp = matrix[j][i]
if ((result_col & (1 << (tmp-1))) == 0):
result_col = result_col | (1<<(tmp-1))
else:
print("col: ", j, i)
return False
## check block
idx_row = (i//3) * 3 + j//3
idx_col = (i%3) * 3 + j%3
tmp = matrix[idx_row][idx_col]
if ((result_blk & (1 << (tmp-1))) == 0):
result_blk = result_blk | (1<<(tmp-1))
else:
print("block: ", idx_row, idx_col)
return False
return True
sudoku(matrix)
輸出結果如下所示;
5. 旋轉數組
問題描述:
給一個n×n的數組,旋轉90度;
思路:
思路一:可以新建一個數組,在新的數組裏面直接賦值,使得新數組的行和原數組的列相等。
思路二:將原始的矩陣中每個元素按照下圖的順序進行旋轉即可實現旋轉90度的要求;
代碼
思路1
def rotate(matrix):
n = len(matrix)
result = [[0] * (n) for i in range(n)]
for i in range(n):
for j in range(n):
result[j][n-1-i] = matrix[i][j]
for x in result:
print(x, sep=" ")
matrix = [[i*5+j for j in range(5)] for i in range(5)]
rotate(matrix)
這是原始矩陣;
這是新矩陣;
由圖可以看出,完成了矩陣的順時針90度的旋轉;
思路1
# in-place
def rotate_in_place(matrix):
n = len(matrix)
for layer in range(n//2):
first = layer
last = n - 1 - layer
for i in range(first, last):
offset = i - first
top = matrix[first][i] # save top
## left->top
matrix[first][i] = matrix[last-offset][first]
##bottom -> left
matrix[last-offset][first] = matrix[last][last - offset];
# right -> bottom
matrix[last][last - offset] = matrix[i][last];
# top -> right
matrix[i][last] = top; # right <- saved top
for x in matrix:
print(x, sep=" ")
rotate_in_place(matrix)
輸出結果;
如圖所示,完成了矩陣旋轉的功能。
6. 反轉字符串
問題描述:
hello => olleh;
思路:
思路一:直接用python的s[::-1]。
思路二:將原始的數值中每個元素按度進行首尾替換;
代碼
思路1:
def reverse(s):
return s[::-1]
s = "hello"
r = reverse(s) # O(n)
r
思路2:
def reverse2(s):
l = list(s)
for i in range(len(l)//2):
l[i], l[len(s)-1-i] = l[len(s)-1-i], l[i]
return ''.join(l)
s = "hello"
r = reverse2(s)
r
即可完成題目要求的操作;
7. 最長連續子串
問題描述:
給一個只包含0和1的數組,找出最長的全是1的子數組。
Example:
Input: [1,1,0,1,1,1]
Output: 3;
思路:
記錄出現1的個數,如果遇到0,個數清零,然後選出記錄1 的最大值即可。
代碼
def find_consecutive_ones(nums):
local = maximum = 0
for i in nums:
local = local + 1 if i == 1 else 0
maximum = max(maximum, local)
return maximum
nums = [1,1,0,1,1,1,1,0,0,0,0,0,1,1,1,0,0,1]
result = find_consecutive_ones(nums)
result
輸出結果如下圖所示,完成了題目的要求;
8. 最大數
問題描述:
給定一個數組,數組裏有一個數組有且只有一個最大數,判斷這個最大數是否是其他數的兩倍或更大。如果存在這個數,則返回其index,否則返回-1;
思路:
找到最大值和次最大值,然後判斷最大值是不是次大值的二倍即可。
代碼
def largest_twice(nums):
maximum = second = idx = 0
for i in range(len(nums)):
if (maximum < nums[i]):
second = maximum
maximum = nums[i]
idx = i
elif second < nums[i]:
second = nums[i]
return idx if (maximum >= second * 2) else -1
nums = [1, 2,3,8,3,2,1]
result = largest_twice(nums)
result
輸出結果;
找到了最大值的序號,第四個數,並且滿足是其他數的2倍;
- 查找數組中缺少的數字
問題描述:
給定一個整數數組,其中1≤a [i]≤n(n =數組大小),某些元素出現兩次,而另一些元素出現一次。
查找[1,n]包含的所有未出現在此數組中的元素。
您可以在沒有額外空間的情況下在O(n)運行時執行此操作嗎? 您可能會假定返回的列表不算作多餘的空間。
例:
輸入:[4,3,2,7,8,2,3,1]
輸出:[5,6]
思路:
思路1:直接從1到n逐個判斷,是否存在數組中;
思路2:對數組中的每個元素i,將該數組中的第i個元素變爲負數,最後只需要檢查新數組中非負數的序號,則輸出該序號就是缺失的數據;
代碼
思路1:
def findDisappearedNumbers1(nums):
result = []
for i in range(1, len(nums) + 1):
if (i not in nums):
result.append(i)
return result
nums = [4,3,2,7,8,2,3,1]
print(findDisappearedNumbers1(nums))
輸出結果
思路2:;
def findDisappearedNumbers2(nums):
# For each number i in nums,
# we mark the number that i points as negative.
# Then we filter the list, get all the indexes
# who points to a positive number
for i in range(len(nums)):
index = abs(nums[i]) - 1
nums[index] = - abs(nums[index])
return [i + 1 for i in range(len(nums)) if nums[i] > 0]
nums = [4,3,2,7,8,2,3,1]
print(findDisappearedNumbers2(nums))
輸出結果;
由上述兩個圖片可以看書,兩個程序都實現了要求的功能,但是二者在效率上有很大的區別;
思路1 和思路2 對比分析
思路1 的時間複雜度;
def findDisappearedNumbersTest1(nums):
start = time.time()
r = findDisappearedNumbers1(nums)
t = time.time() - start
return r, len(nums), t
import time
import matplotlib.pyplot as plt
import random
import math
%matplotlib inline
def random_list(l):
return [[i + 1 for i in range(l * n)] for n in range(1, 20)]
random_lists = random_list(100)
rst = [findDisappearedNumbersTest1(l) for l in random_lists]
len(rst)
x = list(zip(*rst))[1]
y = list(zip(*rst))[2]
plt.plot(x, y)
輸出結果;
由圖可以看出,時間複雜度是O(n^2),思路1 的效率並不高;
思路2的時間複雜度;
程序:
def findDisappearedNumbersTest2(nums):
start = time.time()
r = findDisappearedNumbers2(nums)
t = time.time() - start
return r, len(nums), t
random_lists = random_list(100)
rst = [findDisappearedNumbersTest2(l) for l in random_lists]
len(rst)
x = list(zip(*rst))[1]
y = list(zip(*rst))[2]
plt.plot(x, y)
輸出結果;
如圖所示,思路2的時間複雜度要小很多,爲# O(n),思路2的效率比思路1的效率好很多;