b站左老师课程对应链接:4.链表_哔哩哔哩_bilibili
排序的稳定性
相同值的相对位置保持与原来一致,就被视为有稳定性。
对于多属性排序有用,比如要在商品好评率排序的基础上对价格排序,输出在同价格下好评率顺序。
可以做到有稳定性的排序:冒泡排序(等于时不移动)、插入排序(等于时不移动)、归并排序(相等取左数组;小和问题中,相等先取右数组,无法做到稳定性)。
时间复杂度 | 空间排序 | 稳定性 | |
选择排序 | O(N**2) | O(1) | 无 |
冒泡排序 | O(N**2) | O(1) | 有 |
插入排序 | O(N**2) | O(1) | 有 |
归并排序 | O(N*LogN) | O(N) | 有 |
快排3.0 | O(N*LogN) | O(logN) | 无 |
堆排序 | O(N*LogN) | O(1) | 无 |
一般排序选择快排方法,数据量很多时才使用堆排序,要有稳定性就选择归并排序。目前基于比较的排序算法无法三个方面兼顾。
前三种方案都有对应的方法可以取代,实现还更简单。
第五个问题:使用快排的步骤中就可以处理0-1标准的问题,但是无法做到稳定性,所以很难做到。
工程上针对不同的样本量对应不同的方法有更快的速度,大样本量时使用快排,小样本量用插入排序,因为插入排序使用的常数操作少,而快排调度数组快,利用各自优点处理不同量级数据。
哈希表与有序表
python中类似于HashSet的结构是set,类似于HashMap的结构是dict。python中对于哈希表传递内容都是使用地址引用法,不会像Java与C++对不同类型数据,使用不同的内部传递方法。Python中的哈希表主要通过引用内存地址来获取和存储数据。简而言之,当你在Python中使用字典存储数据时,字典中的每个键都指向相应值的内存地址,而不是直接存储值的副本。
有序表与哈希表的区别在于,有序表会根据key有序的在内存上存在,而哈希表是无序的。因此有序表的key需要可以比较大小,key的大小关系也可以直接推导获得,哈希表则无法得到这些大小关系。有序表的各类操作,时间复杂度O(LogN)。
python库中并没有可以直接使用的有序表结构,只能通过自定义类等其他方式来实现有序表的建立。
单链表
单链表是由节点(Node)组成的每个节点包含数据和指向下一个节点的引用。单链表的优点是可以高效地插入和删除元素,但不支持反向遍历。
双链表是由节点组成,每个节点包含数据、指向下一个节点的引用(next
)及指向前一个节点的引用(prev
)。双链表不仅可以实现便捷的插入和删除,还支持双向遍历。
反转链表时,是有换头操作的,即头节点从第一个数换到了最后一个数。所以函数f应该是一个head=f(head)的函数。
法1:迭代法
过程描述:1.建立单链表节点类
2.对头节点进行推进,头节点先指向下一节点,获得下一节点。
3.将指针反转
4.将头节点的下一节点值换成前一节点值。
5.头节点变为下一节点,重复2、3、4步骤
时间复杂度:O(n)
空间复杂度:O(1)
class ListNode:
##传入不同类型的数据时,将它们变为节点
def __init__(self, val):
if isinstance(val, int):
self.val = val
self.next = None
if isinstance(val, list):
self.val = val[0]
self.next = None
head = self
for i in range(1, len(val)):
node = ListNode(val[i])
head.next = node
head = head.next
def ListReverse(head):
pre = None
cur = head
while cur != None:
#记录下一节点
next = cur.next
#当前节点指向上一节点
cur.next = pre
#上一节点更新为当前节点,为了下一轮迭代时,当前节点变为下一节点的前一节点。
pre = cur
#当前节点更新为下一节点,为了下一轮迭代能继续之前的操作。
cur = next
return pre
if __name__ == '__main__':
print('###################数组输入#########################')
arr = [1,2,3,4,5]
node = ListNode(arr)
reversed_arr = ListReverse(node)
while reversed_arr:
print(reversed_arr.val)
reversed_arr = reversed_arr.next
print('###################整数输入#########################')
head = ListNode(1)
p1 = ListNode(2)
p2 = ListNode(3)
p3 = ListNode(4)
head.next = p1
p1.next = p2
p2.next = p3
p = ListReverse(head)
while p:
print(p.val)
p = p.next
双链表
class ListNode:
#构建双链表
def __init__(self, val):
if isinstance(val, int):
self.val = val
self.next = None
self.prev = None
if isinstance(val, list):
self.val = val[0]
self.next = None
self.prev = None
head = self
for i in range(1,len(val)):
node = ListNode(val[i])
head.next = node
node.prev = head
head = head.next
def reverseLink(head):
cur = head
new_head = None
while cur != None:
cur.next, cur.prev = cur.prev, cur.next
new_head = cur
cur = cur.prev
return new_head
if __name__ == '__main__':
p0 = ListNode(1)
p1 = ListNode(2)
p2 = ListNode(3)
p3 = ListNode(4)
p0.next = p1
p1.next = p2
p2.next = p3
p1.prev = p0
p2.prev = p1
p3.prev = p2
reversedInt = reverseLink(p0)
while reversedInt:
print(reversedInt.val)
reversedInt = reversedInt.next
法2:头插法
过程描述:是逐个取出原链表的节点,将它们按顺序插入到新链表的头部。也就是原链表第一个值是新链表的最后一个值、原链表第二个值是新链表的倒二个值...
假设我们有一个原链表:1 -> 2 -> 3 -> 4 -> 5
执行头插法时的步骤如下:
-
初始状态:新链表为空。
-
处理节点 1:
- 创建新节点 1,链接为空。
- 新链表:1
-
处理节点 2:
- 创建新节点 2,链接到新链表的头(当前节点 1)。
- 新链表:2 -> 1
-
处理节点 3:
- 创建新节点 3,链接到新链表的头(当前节点 2)。
- 新链表:3 -> 2 -> 1
-
依此类推,直到处理完所有节点,结果为:
-
新链表:5 -> 4 -> 3 -> 2 -> 1
时间复杂度:O(n)
空间复杂度:O(n)
class ListNode:
##传入不同类型的数据时,将它们变为单链表
def __init__(self, val):
if isinstance(val, int):
self.val = val
self.next = None
if isinstance(val, list):
self.val = val[0]
self.next = None
head = self
for i in range(1, len(val)):
node = ListNode(val[i])
head.next = node
head = head.next
def ListReverse(head):
new_head = None
cur = head
#可以理解为不断将当前节点推到新链表的头部,新链表当前节点的指向始终指向新链表头部
while cur:
new_node = ListNode(cur.val)
new_node.next = new_head
new_head = new_node
cur = cur.next
return new_head
if __name__ == '__main__':
print('###################数组输入#########################')
arr = [1,2,3,4,5]
node = ListNode(arr)
reversed_arr = ListReverse(node)
while reversed_arr:
#print(reversed_arr.val)
reversed_arr = reversed_arr.next
print('###################整数输入#########################')
head = ListNode(1)
p1 = ListNode(2)
p2 = ListNode(3)
p3 = ListNode(4)
head.next = p1
p1.next = p2
p2.next = p3
p = ListReverse(head)
while p:
print(p.val)
p = p.next
过程描述: 在两个链表中布置初始指针,哪个value小就往下移动,当两个value相等时,打印这个key和对应value,再一起向后移动指针,继续之前的操作。直至有一个链表越界。
class ListNode:
#构建双链表
def __init__(self, val):
if isinstance(val, int):
self.val = val
self.next = None
self.prev = None
if isinstance(val, list):
self.val = val[0]
self.next = None
self.prev = None
head = self
for i in range(1,len(val)):
node = ListNode(val[i])
head.next = node
node.prev = head
head = head.next
def sameNum(head1, head2):
cur1 = head1
cur2 = head2
while cur1 != None and cur2 != None:
if cur1.val < cur2.val:
cur1 = cur1.next
elif cur2.val < cur1.val:
cur2 = cur2.next
else:
print(cur1.val)
cur2 = cur2.next
cur1 = cur1.next
if __name__ == '__main__':
p0 = ListNode(1)
p1 = ListNode(2)
p2 = ListNode(5)
p3 = ListNode(6)
p0.next = p1
p1.next = p2
p2.next = p3
p1.prev = p0
p2.prev = p1
p3.prev = p2
q0 = ListNode(2)
q1 = ListNode(3)
q2 = ListNode(4)
q0.next = q1
q1.next = q2
sameNum(p0, q0)
方法1:使用栈来处理,从左往右放入栈中,存放完整链表后,开始弹出值并再从左往右与原链表比较,如果都相等就是回文。
方法2:只将链表右侧部分放入栈中,其他与方法1一样。这种方法需要用快慢指针方法来找中间点,快慢指针就是慢指针一次只移动1位,快指针一次移2位,当快指针走完链表时,慢指针就在链表中间那个点。快慢指针方法还需要考虑链表元素个数是奇数还是偶数。
class ListNode:
# 构建双向链表
def __init__(self, val):
if isinstance(val, int):
self.val = val
self.next = None
self.prev = None
if isinstance(val, list):
self.val = val[0]
self.next = None
self.prev = None
head = self
for i in range(1, len(val)):
node = ListNode(val[i])
head.next = node
node.prev = head
head = head.next
class Palindromic:
def __init__(self, head):
self.head = head
def searchMid(self):
cur = self.head
slow = cur
fast = cur
while fast and fast.next:
slow = slow.next
fast = fast.next.next
return slow # 返回中点
def is_palindromic(self):
mid = self.searchMid()
# 使用栈来存储后半部分的元素
stack = []
cur = mid
# 将后半部分的元素存入栈中
while cur:
stack.append(cur.val)
cur = cur.next
# 逐个比较栈中的元素和前半部分的元素
cur = self.head
while cur != mid:
if cur.val != stack.pop():
return False # 如果不匹配,返回 False
cur = cur.next
return True # 如果所有的元素都匹配,返回 True
if __name__ == '__main__':
# 创建一个回文链表:1 -> 2 -> 3 -> 2 -> 1
head1 = ListNode(1)
head1.next = ListNode(2)
head1.next.next = ListNode(3)
head1.next.next.next = ListNode(2)
head1.next.next.next.next = ListNode(1)
# 创建一个非回文链表:1 -> 2 -> 3 -> 4
head2 = ListNode(1)
head2.next = ListNode(2)
head2.next.next = ListNode(3)
head2.next.next.next = ListNode(4)
# 利用 Palindromic 类判断回文性
palindromic1 = Palindromic(head1)
print("链表1是回文吗?", palindromic1.is_palindromic()) # 应输出 True
palindromic2 = Palindromic(head2)
print("链表2是回文吗?", palindromic2.is_palindromic()) # 应输出 False
对于快慢指针对于不同情况的定位需要熟练掌握,比如对于偶数数量的元素,有时要得到的慢定位为中点的前一个值,有时候可能需要中点的的后一个值,这些是根据题目情况来判断的。
方法3:要使额外空间复杂度达到O(1),即不使用额外空间。 使用快慢指针找到中点,将右半部分的链表逆序,中点指向None,最左与最右分别放置初始指针,两两比较,如果都相等就是回文,直到有一个指针指向None停止。
class ListNode:
def __init__(self, val):
if isinstance(val, int):
self.val = val
self.next = None
if isinstance(val, list):
self.val = val[0]
self.next = None
cur = self
for i in range(1, len(val)):
node = ListNode(val[i])
cur.next = node
cur = cur.next
class Palindromic:
def __init__(self, head):
self.head = head
def searchMid(self):
cur = self.head
slow = cur
fast = cur
while fast and fast.next:
slow = slow.next
fast = fast.next.next
return slow # 返回中点
def is_palindromic(self):
mid = self.searchMid()
cur = mid
pre = None
while cur:
next = cur.next
cur.next = pre
pre = cur
cur = next
#while循环后pre在原链表的最右侧
mid.next = None
n1 = pre
n2 = self.head
while n1 != None and n2 != None:
if n1.val != n2.val:
return False
n1 = n1.next
n2 = n2.next
cur = pre
pre = None
while cur:
next = cur.next
cur.next = pre
pre = cur
cur = next
return True
if __name__ == '__main__':
# 创建一个回文链表:1 -> 2 -> 3 -> 2 -> 1
head1 = ListNode(1)
head1.next = ListNode(2)
head1.next.next = ListNode(3)
head1.next.next.next = ListNode(2)
head1.next.next.next.next = ListNode(1)
# 创建一个非回文链表:1 -> 2 -> 3 -> 4
head2 = ListNode(1)
head2.next = ListNode(2)
head2.next.next = ListNode(3)
head2.next.next.next = ListNode(4)
# 利用 Palindromic 类判断回文性
palindromic1 = Palindromic(head1)
print("链表1是回文吗?", palindromic1.is_palindromic()) # 应输出 True
palindromic2 = Palindromic(head2)
print("链表2是回文吗?", palindromic2.is_palindromic()) # 应输出 False
链表课后练习按照面试笔试两套标准来做,即一套coding简单不注重空间复杂度,一套要实现更低的空间复杂度,练习coding能力。
代码简易方法,用快排处理node型的数组
面试方法:
过程描述:设置六个变量,小于头、尾,大于头、尾,等于头、尾。遍历链表,将第一个复合条件的节点设为相应组的头节点,最新的复合条件的节点设为尾节点。遍历完链表后,将三组的头尾用指向相连。
class ListNode:
def __init__(self, val):
if isinstance(val, int):
self.val = val
self.next = None
if isinstance(val, list):
self.val = val[0]
self.next = None
cur = self
for i in range(1, len(val)):
node = ListNode(val[i])
cur.next = node
cur = cur.next
def groupSort(head, val):
cur = head
sh = None
st = None
rh = None
rt = None
hh = None
ht = None
while cur:
next_node = cur.next
cur.next = None
if cur.val < val:
if sh == None:
sh = cur
st = cur
else:
st.next = cur
st = cur
if cur.val == val:
if rh == None:
rh = cur
rt = cur
else:
rt.next = cur
rt = cur
if cur.val > val:
if hh == None:
hh = cur
ht = cur
else:
ht.next = cur
ht = cur
cur = next_node
if st:
st.next = rh
else:
sh = rh
if rt:
rt.next = hh
result = sh if sh is not None else (rh if rh is not None else hh)
return result
if __name__ == '__main__':
head1 = ListNode(6)
head1.next = ListNode(2)
head1.next.next = ListNode(2)
head1.next.next.next = ListNode(4)
head1.next.next.next.next = ListNode(1)
val = 4
sortLinkHead = groupSort(head1, val)
while sortLinkHead:
print(sortLinkHead.val)
sortLinkHead =sortLinkHead.next
使用哈希表处理方法:
过程描述:
将使用节点类型的哈希表,哈希表内key储存原节点,value储存复制原节点得到的新节点, 遍历原链表获得以上内容的哈希表。
遍历原链表,查找节点对应哈希表的value,即该节点对应的复制节点。再根据原节点的next指针指向的节点,将新节点的指针指向对应节点的新节点,将rand指针指向应该指向的新节点。
class Listnode:
def __init__(self, val):
if isinstance(val, int):
self.val = val
self.next = None
self.rand = None
if isinstance(val, list):
self.val = val[0]
self.next = None
cur = self
for i in range(1, len(val)):
node = Listnode(val[i])
cur.next = node
cur = cur.next
def copy_rand(head):
cur = head
map = {}
while cur:
#哈希表内key储存原节点,value储存复制原节点得到的新节点。
map[cur] = Listnode(cur.val)
cur = cur.next
cur = head
while cur:
map[cur].next = map[cur.next] if cur.next else None
map[cur].rand = map[cur.rand] if cur.rand else None
cur = cur.next
return map[head]
head = Listnode(1)
p1 = Listnode(2)
p2 = Listnode(3)
p3 = Listnode(4)
head.next = p1
p1.next = p2
p2.next = p3
head.rand = p2
p1.rand = p3
p2.rand = p1
new_link = copy_rand(head)
while new_link:
print(new_link.val)
if new_link.rand:
print(new_link.rand.val)
else:
print("rand is None")
new_link = new_link.next
不使用哈希表的方法:
将原链表节点称为原节点,原链表复制的新节点称为新节点。将原链表变为原节点的next节点为新节点,即1-1'-2-2'-3-3'。原节点的next为新节点,原节点的rand为目标节点,目标节点的next为目标节点的新节点,所以新节点的rand目标节点为原节点rand目标节点next。做完这些操作后再把原链表与新链表next方向还原。
class Listnode:
def __init__(self, val):
self.val = val
self.next = None
self.rand = None
def copy_rand(head):
cur = head
#将原链表变为原节点的next节点为新节点
while cur:
cur_next = cur.next
cur_copy = Listnode(cur.val)
cur.next = cur_copy
cur.next.next = cur_next
cur = cur_next
#设置新节点rand
cur = head
cur_copy = None
while cur:
cur_next = cur.next.next
cur_copy = cur.next
cur_copy.rand = cur.rand.next if cur.rand else None
cur = cur_next
new_head = head.next
#还原链表
cur = head
while cur:
cur_next = cur.next.next
cur_copy = cur.next
cur.next = cur_next
cur_copy.next = cur_copy.next.next if cur_copy.next else None
cur = cur_next
return new_head
head = Listnode(1)
p1 = Listnode(2)
p2 = Listnode(3)
p3 = Listnode(4)
head.next = p1
p1.next = p2
p2.next = p3
head.rand = p2
p1.rand = p3
p2.rand = p1
new_link = copy_rand(head)
while new_link:
print(new_link.val)
if new_link.rand:
print(new_link.rand.val)
else:
print("rand is None")
new_link = new_link.next