本文主要介绍了面试中常见的算法,也是基于一本书籍上的内容--漫画算法(小灰的算法之旅)还是很不错的书籍,至少漫画可以看得下去。
主要内容有经典问题关于判断链表有环,最小栈的实现,最大公约数的几种算法思想,2的幂(这个我学习的时候很是惊讶可以有那么简单的算法!一行搞定,牛逼!)用栈实现队列的操作(这个我感觉有点小bug)... ...
期待大家的交流,互相学习!!!点赞收藏+关注~~~拜托拜托哞哞
目录
5.1.1 有一个单向链表,链表中可能出现“环”,那么如何判断该列表是否为有环链表呢?
5.1如何判断链表有环
5.1.1 有一个单向链表,链表中可能出现“环”,那么如何判断该列表是否为有环链表呢?
算法思想:
首先创建两个指针p1、p2(在Python中就是两个对象引用),让它们同时指向这个链表的头结点。然后开始一个大循环,在循环体中,让指针p1每次向后移动1个节点,让指针p2每次向后移动2个节点,然后比较两个指针指向的节点是否相同。如果相同,则可以判断出链表有环,如果不同,则继续进行下一次循环。
节点数量为n,则时间复杂度为O(n),空间复杂度为O(1)。
代码表示:
# 判断链表是否有环问题
class Node:
def __init__(self,data):
self.data = data
self.next = None
def is_cycle(head):
p1 = head
p2 = head
while p2 is not None and p2.next is not None:
p1 = p1.next
p2 = p2.next.next
if p1 == p2:
return True
return False
node1 = Node(5)
node2 = Node(3)
node3 = Node(7)
node4 = Node(2)
node5 = Node(6)
node1.next = node2
node2.next = node3
node3.next = node4
node4.next = node5
node5.next = node2
print(is_cycle(node1))
5.1.2 如果链表有环,如何求出环的长度
两个指针首次相遇,证明链表有环,让两个指针从相遇点继续前进,并统计循环的次数,直到两个指针第二次相遇。此时,统计出来的前进的次数就是环长。
P1走一步,P2走两步,所以再次相遇的时候,P2走的路程是P1的两倍,多走了整整一圈。
因此,环长 = 每一次速度差 x 前进次数 = 前进次数
5.1.3 如果链表有环,如何求出入环节点
当两个指针首次相遇时,各自走的路程是多少呢?
指针P1一次走一步,所走的距离是D+S1
指针P2一次走两步,多走了n(n>=1)圈,所走的距离是D+S1+n(S1+S2)
因为P2的速度是P1的两倍,所以走的路程也应该是P1的两倍:
2(D+S1) = D+S1+n(S1+S2) ——> D = (n-1)(S1+S2)+S2
方法就是,把其中一个指针放回到头结点位置,另一个指针保持在首次相遇点,两个指针都每次向前走一步。那么,他们最终相遇的节点,就是入环节点。
5.2 最小栈的实现
5.2.1 算法实现思想
题目:实现一个栈,该栈带有出栈(pop)、入栈(push)、取最小元素(get_min)3个方法。要保证这三个方法的时间复杂度都是O(1)。
5.2.2 代码实现
最小栈
class MinStack:
def __init__(self):
"""
初始化栈对象
"""
self.stack = [] # 主栈,用于存储所有元素
self.min_stack = [] # 最小栈,用于存储当前栈中的最小值
def push(self, x: int) -> None:
"""
将元素x压入栈中
"""
self.stack.append(x)
# 如果最小栈为空,或者x小于等于最小栈的栈顶元素,则将x也压入最小栈
if not self.min_stack or x <= self.min_stack[-1]:
self.min_stack.append(x)
def pop(self) -> None:
"""
弹出栈顶元素
"""
if not self.stack:
return None # 或者抛出异常,视具体需求而定
# 如果弹出的元素等于最小栈的栈顶元素,则最小栈也需要弹出
if self.stack[-1] == self.min_stack[-1]:
self.min_stack.pop()
self.stack.pop()
def top(self) -> int:
"""
获取栈顶元素,题目未直接要求,但通常栈会提供此方法
"""
if not self.stack:
return None # 或者抛出异常
return self.stack[-1]
def get_min(self) -> int:
"""
获取栈中的最小元素
"""
if not self.min_stack:
return None # 或者抛出异常,表示栈为空
return self.min_stack[-1]
# 测试代码
if __name__ == "__main__":
minStack = MinStack()
minStack.push(-2)
minStack.push(0)
minStack.push(-3)
print(minStack.get_min()) # 输出: -3
minStack.pop()
print(minStack.top()) # 输出: 0
print(minStack.get_min()) # 输出: -2
5.3 最大公约数
欧几里得算法
# 欧几里得算法 ''' def func1(x,y): while y > 0: (x,y) = (y,x%y) return x print(func1(12,8)) '''
辗转相减法
# 辗转相减法 ''' def func2(x,y): if x == y: return x big = max(x,y) small = min(x,y) return func2(big-small,small) print(func2(12,8)) '''
5.4 2的幂
如何判断一个数是否为2的整数次幂
给定一个非负整数n,请问是否存在一个x满足2^x=n,
如果有,则返回True,否则返回false
# 方法一 利用按位与运算来解决
# 0100(4) 1000(8)
# 0011(3) 0111(7)
'''
def func(n):
return n>0 and n & (n-1) == 0
print(func(32))
print(func(31))
'''
# 方法二 除以2
'''
def func(n):
while n>0 and n%2 != 1:
n /= 2
return True
return False
print(func(32))
print(func(31))
'''
5.5 无序数组排序后的最大相邻差
5.5.1 算法思想:
运用计数排序的思想
区间长度K = max - min + 1
偏移量D = min
建立一个长度为K的数组Array,其值全部为0
还有一种优化思想桶排序,但是我没看懂... ...
5.5.2 代码表示
def maximumGap(nums):
if len(nums) < 2:
return 0
# 找到数组中的最大值和最小值
min_val = min(nums)
max_val = max(nums)
# 如果最大值和最小值相同,则没有差值
if min_val == max_val:
return 0
# 初始化计数数组,长度为最大值和最小值之间的差值加1
# 并将所有元素初始化为0
bucket_size = max(1, (max_val - min_val) // (len(nums) - 1)) # 保证至少有一个元素在每个桶中
bucket_count = (max_val - min_val) // bucket_size + 1
bucket_min = [float('inf')] * bucket_count
bucket_max = [float('-inf')] * bucket_count
# 填充计数数组
for num in nums:
idx = (num - min_val) // bucket_size
bucket_min[idx] = min(bucket_min[idx], num)
bucket_max[idx] = max(bucket_max[idx], num)
# 找到非空桶之间的最大差值
previous_max = bucket_max[0] # 第一个桶的最大值(已排序数组的第一个元素)
max_gap = 0
for i in range(1, bucket_count):
if bucket_min[i] == float('inf'): # 如果桶是空的,则跳过
continue
max_gap = max(max_gap, bucket_min[i] - previous_max)
previous_max = bucket_max[i]
return max_gap
# 示例
nums = [3, 6, 9, 1]
print(maximumGap(nums)) # 输出应为3,因为排序后的数组为[1, 3, 6, 9],最大相邻差为6-3=3
5.6 如何利用栈实现队列
5.6.1 算法思想
栈是一种线性数据结构,先进后出(FILO)。栈底(bottom)栈顶(top)
队列是一种线性数据结构,先入先出(FIFO)。队头(front)队尾(rear)
1.入队(enqueue)注:队尾位置规定为最后入队元素的下一位置,只允许在队尾的位置放入元素,新元素的下一位置将会成为新的队尾。
2.出栈(dequeue)只允许在队头一侧移出元素,出队元素的最后一个元素将成为新的队头。
我们创建两个栈分别用来存放原始栈、即原始队列(A)和原始栈的pop操作进入下一个栈(B),即实现原始栈的出栈,让元素进行进栈操作完毕之后进行出站进入到栈B,可以实现栈B的出栈,从而实现了队列先入先出的特点。
5.6.2 代码实现(图绘)
# 栈实现队列
class StackQueue:
def __init__(self):
self.stackA = []
self.stackB = []
def en_queue(self,e):
self.stackA.append(e)
def de_queue(self):
if len(self.stackB) == 0:
if len(self.stackA) == 0:
print('栈已空')
self.transfer()
return self.stackB.pop()
def transfer(self):
while len(self.stackA) > 0:
self.stackB.append(self.stackA.pop())
stack_queue = StackQueue()
stack_queue.en_queue(1)
stack_queue.en_queue(2)
stack_queue.en_queue(3)
print(stack_queue.de_queue())
print(stack_queue.de_queue())
stack_queue.en_queue(4)
print(stack_queue.de_queue())
print(stack_queue.de_queue())
5.7 寻找全排列的下一个数
5.7.1算法思想
题目:给出一个正整数,找出这个正整数所有数字全排列的下一个数。
在一个整数所包含数字的全部组合中,找到一个大于且仅大于原数的新整数。
例:输入12345,返回12354
输入12354,返回12435
输入12435,返回12453
尽量保持高位不变,低位在最小范围内变换顺序。至于变换顺序的范围大小,则取决于当前整数的逆序区域。
步骤:
从后向前查看逆序区域,找到逆序区域的前一位,也就是数字置换的边界。
让逆序区域的前一位和逆序区域中大于它的最小数字交换位置。
把原来的逆序区域转换为顺序状态。
(书上这样写的但是我感觉有bug,132546->132564,但照着它思想就是162543)
5.7.2 代码实现
def next_permutation(num):
# 将整数转换为列表以便操作
digits = list(str(num))
n = len(digits)
# 步骤1: 从后向前找到逆序区域的开始
i = n - 2
while i >= 0 and digits[i] >= digits[i + 1]:
i -= 1
# 如果找不到逆序区域(即整个序列是降序的),则无法生成更大的排列
if i < 0:
return -1 # 或者可以根据需要抛出异常或返回原数
# 步骤2: 在逆序区域中找到比digits[i]大的最小数字
j = n - 1
while digits[j] <= digits[i]:
j -= 1
# 交换digits[i]和digits[j]
digits[i], digits[j] = digits[j], digits[i]
# 步骤3: 反转逆序区域(i+1到末尾)以形成升序
left, right = i + 1, n - 1
while left < right:
digits[left], digits[right] = digits[right], digits[left]
left += 1
right -= 1
# 将列表转换回整数并返回
return int(''.join(digits))
# 测试代码
print(next_permutation(132546)) # 输出: 12354
print(next_permutation(12354)) # 输出: 12435
print(next_permutation(12435)) # 输出: 12453
print(next_permutation(98765)) # 输出: -1 或者抛出异常,因为没有更大的排列