x
一 数据结构
常见的数据结构
数组,栈,队列,链表,树,哈希表,堆,图
一 数组
数组是可以在内存当中连续存储多个元素的结构,在内存中分配也是连续的,数组中的元素通过数组下标进行访问,数组小下标从0开始
特点:顺序存储,通过下标访问,array[i],支持随机的下标访问.
缺点:数组的大小固定就无法扩容,数组只能存储一种类型的数据,添加和删除要移动其他的元素
使用场景:频繁查询,对存储空间要求不大,很少增加和删除的情况(性能低).
二 栈
栈是一种特殊的线性表,仅能在一端操作,栈顶允许操作,栈底不允许操作
特点:先进后出,从栈顶放入元素叫入栈或压栈,取出元素叫出栈或弹栈
使用场景:常用于实现递归功能方面的场景,例如斐波那契数列
# 用python的列表实现一个栈的操作
class Stack(object):
def __init__(self):
self.items = []
def isEmpty(self):
return self.items == []
def push(self,item):
self.items.append(item)
def pop(self):
self.items.pop()
def peek(self):
return self.items.pop()
def size(self):
return len(self.items)
三 队列
1 队列
队列与栈一样,也是一种线性表,队列可以在一端添加元素,在另一端取出元素,
特点:先进先出,从一端放入元素的操作叫做入队,从另一端取出元素叫做出队
使用场景:在多线程阻塞队列管理中非常适用
class Queue(object):
def __init__(self):
self.items = []
def isEmpty(self):
return self.items == []
def enqueue(self,items):
self.items.insert(0,item)
def dequeue(self,items):
self.items.pop()
def size(self):
return len(self.items)
2 双端队列
双端队列与普通队列类似,区别在于双端队列的队头和队尾都可以插入和删除元素
class deque(object):
def __init__(self):
self.items = []
def isEmpty(self):
return self.items == []
def addFront(self,item):
self.items.insert(0,item)
def addRear(self,item):
self.items.append(item)
def removeFront(self):
return self.items.pop(0)
def removeRear(self):
return self.items.pop()
def size(self);
return len(self.items)
四 链表
链表是物理存储单元上非连续的,非顺序的存储结构.根据指针的指向,链表能形成不同的结构,例如可分为单链表,循环链表,双向链表(下一个元素还具有指向上一个元素的指针)
数据元素的逻辑顺序是通过链表的指针地址来实现的,每个元素包含两个节点,一个是存储元素的数据域(内存空间),另一个是指向下一个节点地址的指针域
特点:一般指单链表,节点有两个属性,值(data),next指针(存放下一个元素的地址),在内存里面链表的节点存放的地址是不连续的
使用场景:适合顺序访问,不适合随机访问(此时必须从头开始访问),适合频繁的随机插入和删除(值需要一个data和指向下一个节点的next指针),其内部的节点的地址不连续
# 节点
class Node(object)
# 值,指针
def __init__(self,val=None,nxt=None):
self.value = val
slef.next = nxt
def __str__(self):
return str(self.value)
# 链表
class LinkedList(object):
"""
Linked list:
node_1 -> node_2 -> node_3
"""
def __init__(self,iterable=())
self.header = None
if iterable:
self.init(iterable)
def __str__(self):
def _traversal(self):
node = self.header
while node and node.next:
yield node
node = node.next
yield node
return '->'.join(map(lambda x:str(x),_traversal(sefl)))
def init(self,iterable=())
# Note:use empty tuple rather than list to init iterable
if not iterable:
return
self.header = Node(iterable[0]) #header value
node = self.header
for i in iterable[1:] # add all node
node.next = Node(i)
node = node.next
def show(self):
print(self)
@property
def length(self):
if self.header is None:
return 0
node = self.header # node pointer points to header
i = 1
while node.next:
node = node.next # node pointer move to next
i += 1
return i
@property
def is_empty(self):
return self.header is None
def clear(self):
self.__init__()
# self.header = None
def append(self, item):
self.insert(item, self.length)
def find(self, item):
node = self.header
while node.next and node.value != item:
node = node.next
if node.value == item:
return node
return None
def find_previous(self, item):
node = self.header
while node.next and node.next.value != item:
node = node.next
if node.next and node.next.value == item:
return node
return None
def delete(self, item):
'''
node_1 -- X --> node_2 -----> node_3
/
/
------------------
'''
prev = self.find_previous(item)
if prev:
prev.next = prev.next.next
def insert(self, item, index):
'''
----> node_2 ---
/
/
node_1 ------- X ---------> node_3
'''
if abs(index) > self.length:
return
if index < 0:
self.insert(item, self.length + index + 1)
return
elif index == 0:
self.insert(self.header.value, 1)
self.header.value = item
return
node = self.header
i = 0
while i < index - 1:
node = node.next
i += 1
n = node.next
node.next = Node(item, n)
五 树
1 树
树是由n(n>=1)个有限节,组成一个具有层次关系的集合,它的外形像一个倒挂的树,根朝上,而叶朝下
2 二叉树
- 每个节点最多有两颗子树,节点的度最大为2;
- 左子树和右子树是有顺序的,次序不能颠倒;
- 即时某节点只有一个子树,也要左右子树;
- 分叉,没有环
3 使用查询:查询很快,二分查找
六 散列表/哈希表
散列表也叫哈希表,是根据关键码和值直接进行访问的数据结构
每一个存储位置称为槽,用来保存数据项,槽号通过[求余]来实现,当一个槽有多个数据项时,就会产生哈希冲突
特点:本质是取模,使分布不均匀的数据均匀
七 堆
堆是一种比较特殊的数据结构,可以被看作是一棵树的数组对象,实际是一颗二叉树,可分为大顶堆和小顶堆
大顶堆和小顶堆分别用最大的或最小的元素作为根,他们的共同点节点的左孩子节点的数值比右孩子节点小
- 堆中某个节点的值总是不大于或不小于其父节点的值
- 堆总是一颗完全二叉树
八 图
图是由节点的有穷集合V和边的集合E组成.为了与树形结构加以区别,在图结构中常常将节点称为顶点,边是顶点的有序偶对
若两个顶点之间存在一条边,就表示这两个顶点具有相邻关系;当图的边具有相邻关系,则称为有向图,否则,称为无向图
九 面试题
单链表反转
A->B->C->D 反转之后 D->C->B->A
- 方法一:借助两个临时数组
对于一个长度为n的单链表head,用一个大小为n的数组arr存储从单链表从头到尾遍历的所有元素,在从arr尾到头读取每一个元素形成一个新的单链表;时间消耗O(n),空间消耗O(n) - 方法二:借助两个临时变量
开始以单链表的第二个元素为循环变量,用两个变量循环向后操作,并设置1个辅助变量tmp,保存数据;时间消耗O(n),空间消耗O(1)
class ListNode(object):
def __init__(self,val):
self.val = val
self.next = None
def reverse_linkedlist1(head):
if head == None or head.next == None: #边界条件
return head
arr = []
while head:
arr.append(head.val)
head = head.next
newhead = ListNode(0)
tmp = newhead
for i in arr[::-1]:
tmp.next = ListNode(i)
tmp = tmp.next
return newhead.next
def reverse_linkedlist2(head):
if head == None or head.next == None: #边界条件
return head
p1 = head #循环变量1
p2 = head #循环变量2
tmp = None # 保存数据的临时变量
while p2:
tmp = p2.next
p2.next = p1
p2 = p1
tmp = p2
head.next = None
return p1
def create_linkedlist(arr):
pre = ListNode(0)
tmp = pre
for i in arr:
tmp.next = ListNode(i)
tmp = tmp.next
return pre.next
def print_linkedlist(head):
tmp = head
result = []
while tmp:
result.append(tmp.val)
tmp = tmp.next
print(result)
a = create_linkedlist(range(5))
print_linkedlist(a) # 0 1 2 3 4
a = reverse_linkedlist1(a)
print_linkedlist(a) # 4 3 2 1 0
a = reverse_linkedlist2(a)
print_linkedlist(a) # 0 1 2 3 4
链表是否相交
1.方法一:借助两个临时数组
将两个链表分别转换为两个列表,从列表的尾部开始对比两个列表的节点是否相等,并进行相应操作,时间复制度O(n),额外申请空间O(n)
详细步骤:
- 定义遍历函数,用于遍历链表
- 分别遍历两个链表,记录下步数即为链表长度,最后的节点即链表尾节点
- 判断尾节点是否相同,若不同则不相交
- 若相同则根据长度判断,让长链表先前进长度差的步数
- 随后同时前进两个链表,找到第一个相遇点即为相交节点
2.方法二:转变为链表是否有环的问题: 操场跑步追尾现象, 两个指针遍历链表(一快一慢,若再相遇则链表有环)
先遍历第一个链表到他的尾部,然后将尾部的next指针指向第二个链表(尾部指针next本来指向的是null),这样两个链表就合成了一个链表,判断原来的两个链表是否相交也就转变成了判断新的链表是否有环的问题了;时间消耗O(n),空间消耗O(1)
详细步骤:
- 定义遍历函数,遍历其中一个链表并找到尾节点
- 首尾相接形成一个环
- 判断另一个链表是否有环,并获取结果信息
- 解除前面的链表环,还原链表,并返回结果
# coding=utf-8
"""
author = jamon
"""
from algo.node import LinkedList
def check_loop(chain):
has_loop, entry_node, loop_length, chain_length = False, None, 0, 0
# Get header for fast and slow
step = 0
fast = slow = head = chain.header
while fast and fast.next:
fast = fast.next.next
slow = slow.next
step += 1
# Note:
# Do remember to use 'is' rather than '==' here (assure the id is same).
if fast is slow:
break
has_loop = not(fast is None or fast.next is None)
pass_length = (step * 2) if fast is None else (step * 2 + 1)
if has_loop:
step = 0
while True:
if head is slow:
entry_node = slow
pass_length = step
if not entry_node:
head = head.next
fast = fast.next.next
slow = slow.next
step += 1
if fast is slow:
break
loop_length = step
chain_length = pass_length + loop_length
return has_loop, entry_node, loop_length, chain_length
def check_intersect_one(c_1, c_2):
def _traversal(c):
node = c.header
while node and node.next:
yield node
node = node.next
yield node
is_intersect, intersect_node = False, None
# Get tail node and length
step_1 = step_2 = 0
for node_1 in _traversal(c_1):
step_1 += 1
for node_2 in _traversal(c_2):
step_2 += 1
tail_1, length_1 = node_1, step_1
tail_2, length_2 = node_2, step_2
if tail_1 is tail_2:
# Intersected, fetch the first same node encountered as intersect node.
is_intersect = True
offset = length_1 - length_2
long, short = (_traversal(c_1), _traversal(c_2)) if offset >= 0 else (_traversal(c_2), _traversal(c_1))
for i in range(offset):
next(long)
for node_1, node_2 in zip(long, short):
if node_1 is node_2:
break
intersect_node = node_1
return is_intersect, intersect_node
def check_intersect_two(c_1, c_2):
def _traversal(c):
node = c.header
while node and node.next:
yield node
node = node.next
yield node
# Create a loop for one of linked lists.
for node in _traversal(c_1): pass
node.next = c_1.header
is_intersect, intersect_node = check_loop(c_2)[:2]
# Un-loop
node.next = None
return is_intersect, intersect_node
if __name__ == '__main__':
print('------------ intersect check ------------------')
print('''
chain_1: 0 -> 1 -> 2 -> 3 -> 4 -> 5 -> 6
chain_2: 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9 -> 10 -> 11 -> 12 -> 13
''')
chain_1 = LinkedList(range(7))
chain_2 = LinkedList(range(3, 14))
print('Linked lists are intersected: %s, intersected node is: %s' % check_intersect_one(chain_1, chain_2))
print('Linked lists are intersected: %s, intersected node is: %s' % check_intersect_two(chain_1, chain_2))
# Merge two linked lists
print('''Merge two linked lists:
chain_1: 0 -> 1 -> 2 -> 3 -> 4 -> 5 -> 6 _
chain_2: 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9 -> 10 -> 11 -> 12 -> 13
''')
node_6 = chain_1.find(6)
node_7 = chain_2.find(7)
node_6.next = node_7
# Method one:
print('Linked lists are intersected: %s, intersected node is: %s' % check_intersect_one(chain_1, chain_2))
# Method two:
print('Linked lists are intersected: %s, intersected node is: %s' % check_intersect_two(chain_1, chain_2))
二 算法
一 算法是什么?
算法是指解题方案的准确而完整的描述,是一系列解决问题的清晰指令,算法代表着用系统的方法描述解决问题的策略机制.也就是说,能够对一定规范的输入,在有限的时间内获得所要求的输出.如果一个算法有缺陷,或不适合于某个问题,执行中算法将不会解决这个问题.不同的算法可能用不同的时间,空间或效率来完成同样的任务.一个算法的优劣可以用空间复杂度与时间复杂度来衡量
二 时间/空间复杂度
时间复杂度是用来估计算法运行时间的一个单位,一般来说,时间复杂度越高,算法的执行速度越慢
常见的时间复杂度,效率从上到下变低.几次循环就是n的几次方的复杂度
O(1) 简单的一次运算
O(logn) 循环减半
O(n) 一次循环
O(nlogn) 一次循环加一个循环减半
O(n^2) 两个循环
常见的空间复杂度,是用来评估算法内存占用大小的单位
空间换时间:如果需要增快算法的速度,需要的空间会更大
三 python常见的算法
1 冒泡(交换)排序
原理:列表中两个相邻的数,如果前一个数比后一个数大,就做交换.一共需要遍历的列表的次数是len(lst)-1,直到没有任何一对数字需要比较
def bubble_sort(lst):
for i in range(len(lst)-1): # 循环遍历的次数
for j in range(len(lst)-i-1): # 每次数组的无序区
if lst[j] > lst[j+1]:
lst[j],lst[j+1] = lst[j+1],lst[j]
lst[1,2,44,36,9]
bubble_sort(lst)
print(lst)
优化:如果在循环的时候,有一次没有进行交换,就表示数列中的数据已经是有序的
时间复杂度:最好的情况是O(n),值遍历一次,一般情况和最坏都是O(n^2)
def bubble_sort(lst):
for i in range(len(lst)-1): # 这是需要循环遍历多少次
change = False # 做一个标志变量
for j in range(len(lst)-i-1): # 每次数组中的无序区
if lst[j] >lst[j+1]:
lst[j],lst[j+1] = lst[j+1],lst[j]
change = True # 每次遍历,如果进来排序的话,就会改变change的值
if not change: # 如果change没有改变,那就表示当前的序列是有序的,直接跳出循环即可
return
lst[1,2,44,36,9]
bubble_sort(lst)
print(lst)
2 选择排序
原理:首先在序列中找到最小或最大元素,存放到排序序列的前或后.然后,再从剩余元素中继续寻找最小或最大元素,放到已排序列的末尾,直到所有元素排序完毕.每次遍历找到当下数组最小的数,并把它放到第一个位置,下次遍历剩下的无序区
def select_sort(lst):
for i in range(len(lst)): # 当前需要遍历的次数
min_index = i # 当前最小数的位置
for j in range(i+1,len(lst)): # 无序区
if lst[j] < lst[min_index]: # 如果有更小的数
min_index = j # 最小的位置改变
if min_index != i;
lst[i],lst[min_index] = lst[min_index],lst[i] # 把最小数和无序区第一个数交换
lst[1,2,44,36,9]
bubble_sort(lst)
print(lst)
3 插入排序
原理:列表分为有序区和无序区,有序区是一个相对有序的序列,认为一开始的时候有序区有一值,每次从无序区选择一个值,放到有序区,直到无序区为空
def insert_sort(lst):
for i in raneg(1,len(lst)): # 从1开始遍历表示无序区从1开始,有序区初始有一个值
tmp = lst[i] # tmp表示拿到的无序区的第一张牌
j = i-1 # j表示有序区的最后一个值
while j >= 0 and lst[j] > tmp: # 当有序区有值,并且有序区的值比无序区拿到的值大就一直循环
lst[j+1] = lst[j] # 有序区的值往后移
j -= 1 # 找到上一个有序区的值,然后再循环
lst[j+1] = tmp # 跳出循环之后,只有j+1的位置是空的,要把当下无序区的值放到j+1的位置
lst[1,2,44,36,9]
bubble_sort(lst)
print(lst)
4 二分插入:实际并没有优化的效果
def insert_sort(lst):
for i in range(1, len(lst)):
left = 0
right = i - 1
tmp = lst[i]
while left <= right:
mid = (left + right) / 2
if tmp >= lst[mid]:
left = mid + 1
if tmp < lst[mid]:
right = mid - 1
for j in range(i - 1, left - 1, -1): # [i-1,left]
lst[j + 1] = lst[j]
lst[left] = tmp
return lst
5 快速排序
思路:取第一个元素,让它归位,就是放到一个位置,使它左边的都比它小,右边的都比它大,然后递归,
先归位,再递归
时间复杂度:O(nlogn)
最坏情况:最坏情况下的事件复杂度是O(n2),标志数的左边或者右边只有一个数
解决方法:不要找第一个元素,随机找一个元素
def part(li, left, right): # 列表,最左索引,最右索引
tmp = li[left] # 先找个临时变量把第一个元素存起来
while left < right: # 当最左小于最右
while left < right and li[right] >= tmp: # 当最左<最右 且 最右边的值大于等于临时变量
right -= 1 # 最右 往左 挪 1 个单位长度
li[left] = li[right] # 都不满足:把挪完之后的最右的值 赋值给 最左的值(即最右的值小于临时变量时,这个值挪到当前最左的值)
while left < right and li[left] <= tmp: # 当最左<最右 且 最左边的值小于等于临时变量
left += 1 # 最左 往右 挪 1 个单位长度
li[right] = li[left] # 都不满足:把挪完之后的最左的值 赋值给 最右的值(即最左的值大于临时变量时,这个值挪到当前最右的值)
li[left] = tmp # 当前最左最右的值相等时,把这个值赋给临时变量
return left # 返回当前临时变量的索引
def quick(li, left, right):
if left < right: # 如果左索引<右索引
mid = part(li, left, right) # 调用part进行分区 返回一个索引赋给mid
quick(li, left, mid - 1) # 递归调用quick 直到left=mid-1
quick(li, mid + 1, right) # 递归调用quick 直到mid+1=right
li = list(range(1000))
import random
random.shuffle(li)
print(li)
quick(li, 0, len(li) - 1)
print(li)
6 归并排序
思路:将数组分解最小之后,然后合并两个有序数组;比较两个数组的最前面的数,谁小就先取谁,取了后相应的指针就往后移一位,然后再比较,直至一个数组为空,最后把另一个数组的剩余部分复制过来即可.
def merge(li, low, mid, high):
i = low
j = mid + 1
ltmp = []
while i <= mid and j <= high:
if li[i] < li[j]:
ltmp.append(li[i])
i += 1
else:
ltmp.append(li[j])
j += 1
while i <= mid:
ltmp.append(li[i])
i += 1
while j <= high:
ltmp.append(li[j])
j += 1
# for k in range(low, high+1):
# li[k] = ltmp[k-low]
li[low:high+1] = ltmp
def merge_sort(li, low, high):
if low < high:
mid = (low + high) // 2
merge_sort(li, low, mid)
merge_sort(li, mid+1, high)
merge(li, low, mid, high)
# li = list(range(10000))
# random.shuffle(li)
# merge_sort(li, 0, len(li)-1)
# print(li)
li = [10,4,6,3,8,2,5,7]
merge_sort(li, 0, len(li)-1)
7 堆排序........最难
def sift(li, low, high):
tmp = li[low]
i = low
j = 2 * i + 1
while j <= high: # 退出条件2:当前i位置是叶子结点,j位置超过了high
# j 指向更大的孩子
if j + 1 <= high and li[j+1] > li[j]:
j = j + 1 # 如果右孩子存在并且更大,j指向右孩子
if tmp < li[j]:
li[i] = li[j]
i = j
j = 2 * i + 1
else: # 退出条件1:tmp的值大于两个孩子的值
break
li[i] = tmp
@cal_time
def heap_sort(li):
# 1. 建堆
n = len(li)
for i in range(n//2-1, -1, -1):
# i 是建堆时要调整的子树的根的下标
sift(li, i, n-1)
# 2.挨个出数
for i in range(n-1, -1, -1): #i表示当前的high值 也表示棋子的位置
li[i], li[0] = li[0], li[i]
# 现在堆的范围 0~i-1
sift(li, 0, i-1)
8 面试题
1 子集和
递归 @functools.lru_cache
给定一个list,求出从列表当中取出两项的和等于一个固定的值
import time
import functools
def calSubSet(sum_value,digit_list=[]):
"""
求一个指定数组中等于和的所有子集
(此处主要用到递归思路)
:param sum_value: int
:param digit_list: []
:return: [[], [], [], ...]
"""
result = []
for i,d in enumrate(digit_list):
if d == sum_value:
result.append([d])
continue
if i == len(digit_list)-1:
break
# 开始递归
new_list = digit_list[i+1:]
sub_result = calSubSet(sum_value-d,new_list)
if sun_result:
temp = []
# 返回格式调整
for t in sub_result:
if t:
s = [d]
s.extend(t)
temp.append(s)
result.extend(temp)
return result
@functools.lru_cache(maxsize=512):
def calSubSet(sum_value,digit_list=[]):
"""
求一个指定数组中等于和的所有子集
(此处主要用到递归思路)
:param sum_value: int
:param digit_list: []
:return: [[], [], [], ...]
"""
result = []
for i,d in enumrate(digit_list):
if d == sun_value:
result.append([d])
continue
if i == len(digit_list)-1:
break
# 开始递归
new_list = digit_list[i+1:]
sub_result = calSubSet(sum_value-d, tuple(new_list))
if sub_result:
temp = []
# 返回格式调整
for t in sub_result:
if t:
s = [d]
s.extend(t)
temp.append(s)
result.extend(temp)
return result
if __name__ == "__main__":
sum_value = 10
# 正整数测试案例
digit_list = [2, 3, 5, 7, 6, 8, 9, 4, 10]
s1 = time.time()
for i in range(1000):
ret = calSubSet(sum_value, digit_list)
s2 = time.time()
for i in range(1000):
ret2 = calSubSet2(sum_value, tuple(digit_list))
s3 = time.time()
print("origin took {}s, add lru_cache took {}s".format(s2-s1, s3-s2))
# 带负数测试
digit_list = [2, 3, 5, 7, 6, 8, 9, 4, 10, -7]
ret = calSubSet(sum_value, digit_list)
print(ret)
2 硬币组合问题
奇偶性: n元为偶数: 先10元, 再5元,再2元 n元为奇数: 代表着一定有一张5元, y-1, n=n-5 变成了偶数
泛化后: A1张x类型的纸币,A2张y类型的纸币, AN张Zn类型纸币 解题思路:
- 最小公倍数
- 余数组合,比如 余数(对最小公倍数取模)1 51+23
- 逐一判断各类型纸币不足的情况,找出一种符合的结果
import copy
import time
def _cal_min_common_multiple(x, y):
"""
求最小公倍数
:param x: int
:param y: int
:return: int
"""
greater = x if x > y else y
while 1:
if (greater % x == 0) and (greater % y == 0):
result = greater
break
greater += 1
return result
def cal_min_common_multiple(digit_list=[]):
"""
求最小公倍数
:param digit_list: [int, int, ...]
:return: int
"""
if 2 > len(digit_list):
return 0
temp = digit_list[0]
for i in range(len(digit_list)-1):
temp = _cal_min_common_multiple(temp, digit_list[i+1])
return temp
def _get_a_combination(min_multiple, target_num, origin_coins={}):
"""
根据指定的硬币种类和个数推导判断是否能够组成指定的硬币数字总价
:param min_multiple: int, 硬币种类的最小公倍数
:param target_num: int, 待组合的目标硬币总额,需为min_multiple倍数, 如八块七,则算为87
:param origin_coins: dictionary, {key: num, key2:num2, ...} 即 {一分:一分硬币个数,二分:二分硬币个数,...}
:return: {}, 返回一个成功的组合
"""
if 0 != target_num % min_multiple or 0 > target_num:
return {}
# print("_get_a:", target_num, origin_coins)
temp1 = int(target_num/min_multiple)
result = {}
for v in origin_coins.values():
if 0 > v:
return {}
for k, v in origin_coins.items():
temp2 = int(v/(min_multiple/k))
if temp2 >= temp1:
result[k] = temp1
return result
else:
result[k] = temp2
temp1 = temp1 - temp2
return {}
def compute_array_left(min_multiple, digit_list=[]):
"""
计算数组内最小公倍数下的余数
:param min_multiple: 最小公倍数
:param digit_list: [int, int, ...]
:return: {和1:[{int: num, int2: num2}, {}, ...]}
"""
if not digit_list:
return {}
temp_dict = {} # {87: [{2:10, 5:6, ...}, ...]}
n = 0
for k in digit_list:
for _ in range(int(min_multiple / k) - 1):
if not temp_dict:
temp_dict[k] = [{k: 1}] # 控制后第一次遍历初始化
temp_dict2 = copy.deepcopy(temp_dict)
# print("bbbbbb:", n)
n += 1
for x, y in temp_dict.items():
temp = copy.deepcopy(y) # temp=[{2:5}, ...]
# 原有的值内容个数累加1,没有则新键内容个数置为1
for i, t in enumerate(temp):
temp[i][k] = 1 if k not in temp[i].keys() else temp[i][k] + 1
if x + k not in temp_dict.keys(): # 判断新和key是否已存在,不存在则新建,存在则添加到列表末尾
temp_dict2[x + k] = temp
else:
for t in temp:
if t not in temp_dict2[x + k]:
temp_dict2[x + k].append(t)
temp_dict = temp_dict2
return temp_dict
def find_coin_combination(target_num, origin_coins={}):
"""
根据指定的硬币种类和个数推导判断是否能够组成指定的硬币数字总价
算法思路:该类问题实际上可以转化为三个问题:
1. 求指定硬币种类的最小公倍数;
2. 在硬币个数充足的情况下不同类型硬币相加的余数的组合;
3. 逐步考虑部分硬币类型个数不足情况下组合;
:param target_num: int, 待组合的目标硬币总额,如八块七,则算为87
:param origin_coins: dictionary, {key: num, key2:num2, ...} 即 {一分:一分硬币个数,二分:二分硬币个数,...}
:return: dictionary, 返回组合的情况,{key: use_num, key2: use_num2, ...}
"""
min_multiple = cal_min_common_multiple(list(origin_coins.keys()))
# print("最小公倍数为:", min_multiple)
left_num = target_num % min_multiple
# 计算各硬币组成的最小公倍数之外的余数数值(余数组合范围在0-硬币类型数*最小公倍数之间)
# 此部分一但硬币类型确定后,该部分可以预处理,一次计算,多次复用该结果,所以在计算复杂度时可忽略
left_key = ",".join([str(i) for i in origin_coins.keys()])
if not hasattr(find_coin_combination, "left_cache"):
find_coin_combination.left_cache = {}
if left_key not in find_coin_combination.left_cache.keys():
find_coin_combination.left_cache[left_key] = compute_array_left(min_multiple, list(origin_coins.keys()))
# print("预处理的余数数值为:")
# for k, v in find_coin_combination.left_cache[left_key].items():
# print(" ", k, v)
temp_dict = find_coin_combination.left_cache[left_key]
# 判断余数是否在预计算的数值集合中(余数组合范围在0-硬币类型数*最小公倍数之间)
# 余数相加的范围是固定的,不随着target的变化而变化,所以时间复杂度还是O(1)
# print("硬币数值类型为{0}, 相应类型的原始硬币个数为{1}, 欲拼凑的数字总额为 {2}".format(left_key, str(origin_coins), target_num))
result = {}
for i in range(len(origin_coins)):
left = left_num + min_multiple * i
if left in temp_dict.keys():
left_result = temp_dict[left] # left_result: [{2:10, 5:6, ...}, ...]
for t in left_result:
temp_coins = copy.deepcopy(origin_coins)
for k, v in t.items():
temp_coins[k] = temp_coins[k] - v
temp = _get_a_combination(min_multiple, target_num-left, temp_coins)
if not temp:
# print("bbbbbb:", temp, min_multiple, target_num, left, temp_coins)
continue
# print("aaaaaa:", temp, min_multiple, target_num, left, temp_coins)
# 找到一个组合,返回
result.update(temp)
for k, v in t.items():
result[k] = v if k not in result.keys() else result[k]+v
print("答案为:{0}n".format(result))
return result
print("未能计算得到答案n")
return {}
if __name__ == "__main__":
find_coin_combination(87, origin_coins={10: 9, 5: 3, 2: 6})
find_coin_combination(93, origin_coins={10: 9, 5: 3, 2: 6})
#find_coin_combination(111, origin_coins={10: 9, 5: 3, 2: 6})
find_coin_combination(29, origin_coins={7: 19, 5: 3, 3: 6})
find_coin_combination(83, origin_coins={7: 19, 5: 3, 3: 6})
3 洗牌算法
3.1 方法一: 时间复杂度O(N), 空间复杂度O(1) 第一次: 从2到54张牌中随机取一张和第一张牌交换,如 随机到26 , 交换后[26, 2, 3, ...., 25, 1, 27, 54] 第二次: 从第3张到54张牌中随机取一张和第2张牌交换,如随机到16 交换后[26, 16, 3, ..., 15, 2, 17, ..25, 1, 27, 54]
思路:1.从还没处理的数组(假如还剩k个)中,随机产生一个[0,k]之间的数字p(假设数组从0开始)
2.从剩下的k个数中把第p个数取出
- 重复步骤2和3直到睡着全部取完 4. 从步骤3取出的数字序列便是一个打乱了的序列
def shuffle_card1(card_array=[]):
result = []
while card_array:
p = randrange(0,len(card_array))
card_array.pop(p)
return result
4 数组旋转
四种情况:
1. 90度,依次去每列第i个元素(降序),如第行数为m, 列数为n,则取第n列第i个元素,第n-1列第i个元素…;
2. 180度,依次去每行第i个元素(降序),如第行数为m, 列数为n,则取第i行第n个元素,第i行第n-1个元素…;
3. 270度,依次去每列第i个元素(升序),如第行数为m, 列数为n,则取第i列第k个元素,第i+1列第k个元素…;
4. 360度,原数组不变
4.1 思路一:
翻转数组的行列,行做列,列做行
思路二:将原始的数组的行做列,列做行产生一个新数组缓存起来,四次旋转的四个结果即为原始数组,缓存的行列转换的数组,原始数组每行逆转,缓存的数组每行逆转
思路三:先求出90°的结果,180°=翻转两次90,270°等于翻转三次90°
import time
num = 3
data = [[i+j*num+1 for i in range(0,num)] for j in range(0,num)]
def reverse_row_col(ori_data):
# 翻转数组的行列,行做列,列做行
row_len = len(data) # 原始行数
col_len = len(data[0]) # 原始列数
new_data = [_ for _ in range(row_len) for _ in range(col_len)]
for i in range(col_len):
for j in range(row_len):
new_data[i][j] = data[j][i]
return new_data
def reversed(a=[]):
return [a[i] for i in range(len(a)-1,-1,-1)]
def rotate_array(angle,data):
"""
核心思想:将原始数组的行做列,列做行产生一个新数组缓存起来,四次旋转的四个结果即为原始数组、缓存的行列转换的数组,
原始数组每行逆转,缓存的数组每行逆转
:param angle: int, 旋转角度,取值范围为[90, 180, 270, 360]
:param data: list, [[], [], ...]
:return: list, [[], [], []]
"""
lis = list()
lis1 = reverse_row_col(data)
angle = angle % 360
if 90 == angle:
for row_index,row in enumrate(lis1):
lis.append([i for i in reversed(row)])
elif 180 == angle:
lis2 = [i for i in reversed(data)]
for row_index,row in enumrate(lis2):
lis.append([i for i in reversed(row)])
elif 270 == angle:
lis = [i for i in reversed(lis1)]
else:
lis = data
return lis
def rotate_array2(angle, data):
"""
四种情况:
1. 90度,依次去每列第i个元素(降序),如第行数为m, 列数为n,则取第n列第i个元素,第n-1列第i个元素…;
2. 180度,依次去每行第i个元素(降序),如第行数为m, 列数为n,则取第i行第n个元素,第i行第n-1个元素…;
3. 270度,依次去每列第i个元素(升序),如第行数为m, 列数为n,则取第i列第k个元素,第i+1列第k个元素…;
4. 360度,原数组不变
:param angle: int, 旋转角度,取值范围为[90, 180, 270, 360]
:param data: list, [[], [], ...]
:return: list, [[], [], []]
"""
row_len = len(data) # 原始行数
col_len = len(data[0]) # 原始列数
temp = col_len - 1
temp2 = row_len - 1
angle = angle % 360 # 数组每旋转360度回到原地
if 90 == angle:
result = [[_ for _ in range(row_len)] for _ in range(col_len)]
for i in range(0, col_len):
for j in range(temp2, -1, -1):
result[i][j] = data[temp2-j][i]
elif 180 == angle:
result = [[_ for _ in range(col_len)] for _ in range(row_len)]
for i in range(0, row_len):
for j in range(temp, -1, -1):
result[i][j] = data[temp2-i][temp-j]
elif 270 == angle:
result = [[_ for _ in range(row_len)] for _ in range(col_len)]
for i in range(0, col_len):
for j in range(temp2, -1, -1):
result[i][j] = data[j][temp-i]
else:
result = data
return result
if __name__ == '__main__':
data = [[2, 3, 4, 5], [5, 6, 7, 8], [9, 10, 11, 12]]
s1 = time.time()
loop_num = 1 # 101 % 4 = 1,最终结果相当于翻转一次
for angle in [90, 180, 270, 360]:
s1 = time.time()
for i in range(loop_num):
ret = rotate_array(angle, data)
s2 = time.time()
# print("x=", x)
print("n{} angle rotate_array {} loop took {}s".format(angle, loop_num, s2 - s1))
s1 = time.time()
for i in range(loop_num):
ret2 = rotate_array2(angle, data)
s2 = time.time()
print("{} angle rotate_array2 {} loop took {}s".format(angle, loop_num, s2 - s1))
for r in ret:
print(r)
print("")
for r in ret2:
print(r)