冒泡排序是一种简单的排序算法,它重复地走访过要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。
[5, 3, 8, 4, 2]
,经过冒泡排序后变为 [2, 3, 4, 5, 8]
。def bubble_sort(arr):
n = len(arr)
for i in range(n):
for j in range(0, n - i - 1):
if arr[j] > arr[j + 1]:
arr[j], arr[j + 1] = arr[j + 1], arr[j]
return arr
arr = [5, 3, 8, 4, 2]
print(bubble_sort(arr)) # 输出: [2, 3, 4, 5, 8]
选择排序是一种简单直观的排序算法。它的工作原理是每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到全部待排序的数据元素排完。
def selection_sort(arr):
n = len(arr)
for i in range(n):
min_idx = i
for j in range(i + 1, n):
if arr[j] < arr[min_idx]:
min_idx = j
arr[i], arr[min_idx] = arr[min_idx], arr[i]
return arr
arr = [5, 3, 8, 4, 2]
print(selection_sort(arr)) # 输出: [2, 3, 4, 5, 8]
插入排序是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
def insertion_sort(arr):
for i in range(1, len(arr)):
key = arr[i]
j = i - 1
while j >= 0 and key < arr[j]:
arr[j + 1] = arr[j]
j -= 1
arr[j + 1] = key
return arr
arr = [5, 3, 8, 4, 2]
print(insertion_sort(arr)) # 输出: [2, 3, 4, 5, 8]
快速排序使用分治法(Divide and conquer)策略来把一个串行(list)分为两个子串行(sub-lists)。
def quick_sort(arr):
if len(arr) <= 1:
return arr
else:
pivot = arr[0]
left = [x for x in arr[1:] if x <= pivot]
right = [x for x in arr[1:] if x > pivot]
return quick_sort(left) + [pivot] + quick_sort(right)
arr = [5, 3, 8, 4, 2]
print(quick_sort(arr)) # 输出: [2, 3, 4, 5, 8]
归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。
def merge_sort(arr):
if len(arr) <= 1:
return arr
mid = len(arr) // 2
left = merge_sort(arr[:mid])
right = merge_sort(arr[mid:])
return merge(left, right)
def merge(left, right):
result = []
i = j = 0
while i < len(left) and j < len(right):
if left[i] < right[j]:
result.append(left[i])
i += 1
else:
result.append(right[j])
j += 1
result.extend(left[i:])
result.extend(right[j:])
return result
arr = [5, 3, 8, 4, 2]
print(merge_sort(arr)) # 输出: [2, 3, 4, 5, 8]
堆排序是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。
def heapify(arr, n, i):
largest = i
l = 2 * i + 1
r = 2 * i + 2
if l < n and arr[i] < arr[l]:
largest = l
if r < n and arr[largest] < arr[r]:
largest = r
if largest != i:
arr[i], arr[largest] = arr[largest], arr[i]
heapify(arr, n, largest)
def heap_sort(arr):
n = len(arr)
for i in range(n // 2 - 1, -1, -1):
heapify(arr, n, i)
for i in range(n - 1, 0, -1):
arr[i], arr[0] = arr[0], arr[i]
heapify(arr, i, 0)
return arr
arr = [5, 3, 8, 4, 2]
print(heap_sort(arr)) # 输出: [2, 3, 4, 5, 8]
线性搜索是一种在数组中查找特定元素的简单方法。它从数组的第一个元素开始,逐个检查每个元素,直到找到目标元素或遍历完整个数组。
def linear_search(arr, target):
for i in range(len(arr)):
if arr[i] == target:
return i
return -1
arr = [5, 3, 8, 4, 2]
target = 8
print(linear_search(arr, target)) # 输出: 2
二分搜索是一种在有序数组中查找特定元素的高效算法。它每次将搜索范围缩小一半,直到找到目标元素或确定目标元素不存在。
def binary_search(arr, target):
left, right = 0, len(arr) - 1
while left <= right:
mid = (left + right) // 2
if arr[mid] == target:
return mid
elif arr[mid] < target:
left = mid + 1
else:
right = mid - 1
return -1
arr = [2, 3, 4, 5, 8]
target = 5
print(binary_search(arr, target)) # 输出: 3
广度优先搜索是一种用于遍历或搜索树或图的算法。它从根节点开始,逐层地访问节点,直到找到目标节点或遍历完整个图。
from collections import deque
graph = {
'A': ['B', 'C'],
'B': ['A', 'D', 'E'],
'C': ['A', 'F'],
'D': ['B'],
'E': ['B', 'F'],
'F': ['C', 'E']
}
def bfs(graph, start):
visited = set()
queue = deque([start])
visited.add(start)
while queue:
vertex = queue.popleft()
print(vertex, end=" ")
for neighbor in graph[vertex]:
if neighbor not in visited:
queue.append(neighbor)
visited.add(neighbor)
bfs(graph, 'A') # 输出: A B C D E F
深度优先搜索是一种用于遍历或搜索树或图的算法。它沿着树的深度遍历树的节点,尽可能深的搜索树的分支。当节点v的所在边都己被探寻过,搜索将回溯到发现节点v的那条边的起始节点。这一过程一直进行到已发现从源节点可达的所有节点为止。
graph = {
'A': ['B', 'C'],
'B': ['A', 'D', 'E'],
'C': ['A', 'F'],
'D': ['B'],
'E': ['B', 'F'],
'F': ['C', 'E']
}
def dfs(graph, start, visited=None):
if visited is None:
visited = set()
if start not in visited:
print(start, end=" ")
visited.add(start)
for neighbor in graph[start]:
dfs(graph, neighbor, visited)
dfs(graph, 'A') # 输出: A B D E F C
哈希表是根据键(Key)而直接访问在内存存储位置的数据结构。它通过哈希函数将键映射到存储桶中,从而实现快速的插入、查找和删除操作。
arr = [1, 2, 3, 2, 4, 3, 5]
frequency = {}
for num in arr:
if num in frequency:
frequency[num] += 1
else:
frequency[num] = 1
print(frequency) # 输出: {1: 1, 2: 2, 3: 2, 4: 1, 5: 1}
哈希表可以高效地解决重复元素问题,因为它可以快速判断一个元素是否已经存在。
arr = [1, 2, 3, 2, 4, 3, 5]
unique_arr = []
seen = set()
for num in arr:
if num not in seen:
unique_arr.append(num)
seen.add(num)
print(unique_arr) # 输出: [1, 2, 3, 4, 5]
哈希表可以与其他数据结构(如链表、栈、队列等)结合使用,以解决更复杂的问题。
对撞指针是指在数组或链表中,使用两个指针分别从两端向中间移动,直到两个指针相遇。
arr = [2, 3, 4, 5, 8]
target = 7
left, right = 0, len(arr) - 1
while left < right:
current_sum = arr[left] + arr[right]
if current_sum == target:
print(left, right) # 输出: 0 3
break
elif current_sum < target:
left += 1
else:
right -= 1
快慢指针是指在数组或链表中,使用两个指针,一个指针移动速度快,一个指针移动速度慢。
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
# 创建一个有环的链表
head = ListNode(1)
node2 = ListNode(2)
node3 = ListNode(3)
node4 = ListNode(4)
head.next = node2
node2.next = node3
node3.next = node4
node4.next = node2 # 形成环
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow == fast:
print("链表中存在环")
break
滑动窗口是一种常用的算法技巧,用于解决数组或字符串中的子数组或子串问题。
arr = [-2, 1, -3, 4, -1, 2, 1, -5, 4]
max_sum = float('-inf')
current_sum = 0
for num in arr:
current_sum = max(num, current_sum + num)
max_sum = max(max_sum, current_sum)
print(max_sum) # 输出: 6
数组是一种基本的数据结构,它由相同类型的元素组成,并且这些元素在内存中是连续存储的。数组的基础操作题主要涉及以下几个方面:
int[] arr = {1, 2, 3, 4, 5};
arr = [0] * 5 # 创建一个长度为 5 的数组,初始值都为 0
#include
int main() {
int arr[5] = {1, 2, 3, 4, 5};
std::cout << arr[2] << std::endl; // 输出第 3 个元素,结果为 3
return 0;
}
let arr = [1, 2, 3, 4, 5];
arr[2] = 10; // 将第 3 个元素修改为 10
console.log(arr);
int[] arr = {1, 2, 3, 4, 5};
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
int[] arr = {1, 2, 3, 4, 5};
for (int num : arr) {
System.out.print(num + " ");
}
二维数组可以看作是数组的数组,它在处理矩阵、表格等数据时非常有用。二维数组相关题主要有以下几种:
int[][] arr = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
rows = 3
cols = 3
arr = [[0 for _ in range(cols)] for _ in range(rows)]
#include
int main() {
int arr[3][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
std::cout << arr[1][2] << std::endl; // 输出第 2 行第 3 列的元素,结果为 6
return 0;
}
let arr = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];
arr[1][2] = 10; // 将第 2 行第 3 列的元素修改为 10
console.log(arr);
int[][] arr = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
for (int i = 0; i < arr.length; i++) {
for (int j = 0; j < arr[i].length; j++) {
System.out.print(arr[i][j] + " ");
}
System.out.println();
}
字符串匹配题主要是在一个主字符串中查找一个子字符串是否存在,以及它的位置。常见的算法有以下几种:
def brute_force_search(text, pattern):
n = len(text)
m = len(pattern)
for i in range(n - m + 1):
j = 0
while j < m:
if text[i + j] != pattern[j]:
break
j += 1
if j == m:
return i
return -1
text = "abcabcabd"
pattern = "abcabd"
result = brute_force_search(text, pattern)
print(result)
字符串编码与解码题主要涉及将字符串按照一定的规则进行编码,以及将编码后的字符串解码还原成原始字符串。常见的编码方式有以下几种:
def encode_string(s):
encoded = ""
for char in s:
encoded += chr(ord(char) + 1)
return encoded
def decode_string(s):
decoded = ""
for char in s:
decoded += chr(ord(char) - 1)
return decoded
s = "abc"
encoded = encode_string(s)
decoded = decode_string(encoded)
print(encoded)
print(decoded)
base64
模块进行编码和解码:import base64
s = "abc"
encoded = base64.b64encode(s.encode())
decoded = base64.b64decode(encoded).decode()
print(encoded)
print(decoded)
单链表是一种常见的数据结构,它由一个个节点组成,每个节点包含一个数据域和一个指向下一个节点的指针。单链表的操作题主要有以下几种:
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
# 创建一个单链表 1 -> 2 -> 3
head = ListNode(1)
head.next = ListNode(2)
head.next.next = ListNode(3)
def insert_at_head(head, val):
new_node = ListNode(val)
new_node.next = head
return new_node
def insert_at_tail(head, val):
new_node = ListNode(val)
if not head:
return new_node
current = head
while current.next:
current = current.next
current.next = new_node
return head
def delete_at_head(head):
if not head:
return None
return head.next
def delete_node(head, val):
dummy = ListNode(0)
dummy.next = head
current = dummy
while current.next:
if current.next.val == val:
current.next = current.next.next
else:
current = current.next
return dummy.next
双链表与单链表类似,但每个节点除了指向下一个节点的指针外,还包含一个指向前一个节点的指针。双链表的操作题主要有以下几种:
class DoublyListNode:
def __init__(self, val=0, prev=None, next=None):
self.val = val
self.prev = prev
self.next = next
# 创建一个双链表 1 <-> 2 <-> 3
head = DoublyListNode(1)
node2 = DoublyListNode(2)
node3 = DoublyListNode(3)
head.next = node2
node2.prev = head
node2.next = node3
node3.prev = node2
def insert_at_head(head, val):
new_node = DoublyListNode(val)
if not head:
return new_node
new_node.next = head
head.prev = new_node
return new_node
def insert_at_tail(head, val):
new_node = DoublyListNode(val)
if not head:
return new_node
current = head
while current.next:
current = current.next
current.next = new_node
new_node.prev = current
return head
def delete_at_head(head):
if not head:
return None
if head.next:
head.next.prev = None
return head.next
def delete_node(head, val):
current = head
while current:
if current.val == val:
if current.prev:
current.prev.next = current.next
if current.next:
current.next.prev = current.prev
if current == head:
head = current.next
break
current = current.next
return head
环形链表是指链表的尾节点指向链表中的某个节点,形成一个环。环形链表相关题主要有以下几种:
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def has_cycle(head):
slow = head
fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow == fast:
return True
return False
def detect_cycle(head):
slow = head
fast = head
has_cycle = False
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow == fast:
has_cycle = True
break
if not has_cycle:
return None
slow = head
while slow != fast:
slow = slow.next
fast = fast.next
return slow
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def merge_sort(head):
if not head or not head.next:
return head
# 找到链表的中间节点
slow = head
fast = head.next
while fast and fast.next:
slow = slow.next
fast = fast.next.next
mid = slow.next
slow.next = None
# 递归地对左右两部分进行排序
left = merge_sort(head)
right = merge_sort(mid)
# 合并两个有序链表
return merge(left, right)
def merge(left, right):
dummy = ListNode(0)
current = dummy
while left and right:
if left.val < right.val:
current.next = left
left = left.next
else:
current.next = right
right = right.next
current = current.next
if left:
current.next = left
if right:
current.next = right
return dummy.next
def merge_two_lists(l1, l2):
dummy = ListNode(0)
current = dummy
while l1 and l2:
if l1.val < l2.val:
current.next = l1
l1 = l1.next
else:
current.next = l2
l2 = l2.next
current = current.next
if l1:
current.next = l1
if l2:
current.next = l2
return dummy.next
栈是一种后进先出(LIFO)的数据结构,它的基本操作有入栈(push)和出栈(pop)。栈的基本应用题主要有以下几种:
def is_valid(s):
stack = []
mapping = {")": "(", "]": "[", "}": "{"}
for char in s:
if char in mapping:
top_element = stack.pop() if stack else '#'
if mapping[char] != top_element:
return False
else:
stack.append(char)
return not stack
def eval_rpn(tokens):
stack = []
for token in tokens:
if token in ['+', '-', '*', '/']:
right_operand = stack.pop()
left_operand = stack.pop()
if token == '+':
result = left_operand + right_operand
elif token == '-':
result = left_operand - right_operand
elif token == '*':
result = left_operand * right_operand
else:
result = int(left_operand / right_operand)
stack.append(result)
else:
stack.append(int(token))
return stack.pop()
队列是一种先进先出(FIFO)的数据结构,它的基本操作有入队(enqueue)和出队(dequeue)。队列的基本应用题主要有以下几种:
from collections import deque
def bfs(graph, start):
visited = set()
queue = deque([start])
visited.add(start)
while queue:
vertex = queue.popleft()
print(vertex, end=" ")
for neighbor in graph[vertex]:
# 第三章 动态规划题型
动态规划(Dynamic Programming,简称 DP)是一种通过把原问题分解为相对简单的子问题,并保存子问题的解来避免重复计算,从而解决复杂问题的方法。下面我们来详细介绍不同类型的动态规划题型。
## 3.1 基础动态规划题
### 3.1.1 斐波那契数列相关题
斐波那契数列是一个经典的数列,其定义为:$F(0) = 0$,$F(1) = 1$,$F(n) = F(n - 1) + F(n - 2)$($n \geq 2$)。也就是说,从第三项开始,每一项都等于前两项之和。数列形式为:$0, 1, 1, 2, 3, 5, 8, 13, 21, 34, \cdots$
**问题描述**:通常会要求计算斐波那契数列的第 $n$ 项的值。
**解题思路**:
- **递归方法**:根据斐波那契数列的定义,直接使用递归函数来计算。但这种方法会有大量的重复计算,时间复杂度为 $O(2^n)$,效率较低。示例代码(Python)如下:
```python
def fibonacci_recursive(n):
if n == 0:
return 0
elif n == 1:
return 1
else:
return fibonacci_recursive(n - 1) + fibonacci_recursive(n - 2)
def fibonacci_dp(n):
if n == 0:
return 0
elif n == 1:
return 1
dp = [0] * (n + 1)
dp[0] = 0
dp[1] = 1
for i in range(2, n + 1):
dp[i] = dp[i - 1] + dp[i - 2]
return dp[n]
问题描述:假设你正在爬楼梯,需要 n n n 阶你才能到达楼顶。每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
解题思路:
示例代码(Python)如下:
def climb_stairs(n):
if n == 1:
return 1
elif n == 2:
return 2
dp = [0] * (n + 1)
dp[1] = 1
dp[2] = 2
for i in range(3, n + 1):
dp[i] = dp[i - 1] + dp[i - 2]
return dp[n]
问题描述:有 n n n 个物品和一个容量为 W W W 的背包,每个物品有自己的重量 w i w_i wi 和价值 v i v_i vi。对于每个物品,你只能选择放入背包或者不放入(即 0 - 1 选择),问如何选择物品,使得背包中物品的总价值最大。
解题思路:
示例代码(Python)如下:
def knapsack_01(weights, values, capacity):
n = len(weights)
dp = [[0] * (capacity + 1) for _ in range(n + 1)]
for i in range(1, n + 1):
for j in range(1, capacity + 1):
if j < weights[i - 1]:
dp[i][j] = dp[i - 1][j]
else:
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weights[i - 1]] + values[i - 1])
return dp[n][capacity]
问题描述:与 0 - 1 背包问题类似,不同之处在于每个物品可以选择无限次放入背包。
解题思路:
示例代码(Python)如下:
def knapsack_complete(weights, values, capacity):
n = len(weights)
dp = [[0] * (capacity + 1) for _ in range(n + 1)]
for i in range(1, n + 1):
for j in range(1, capacity + 1):
if j < weights[i - 1]:
dp[i][j] = dp[i - 1][j]
else:
dp[i][j] = max(dp[i - 1][j], dp[i][j - weights[i - 1]] + values[i - 1])
return dp[n][capacity]
问题描述:在一个 m × n m \times n m×n 的棋盘上,每个格子有一个非负整数的权值。从棋盘的左上角 ( 0 , 0 ) (0, 0) (0,0) 出发,每次只能向下或向右移动一步,到达棋盘的右下角 ( m − 1 , n − 1 ) (m - 1, n - 1) (m−1,n−1)。问路径上经过的格子的权值之和的最小值是多少。
解题思路:
可以使用状态压缩的方法,将二维数组 d p dp dp 压缩为一维数组,因为在计算 d p [ i ] [ j ] dp[i][j] dp[i][j] 时,只需要用到 d p [ i − 1 ] [ j ] dp[i - 1][j] dp[i−1][j] 和 d p [ i ] [ j − 1 ] dp[i][j - 1] dp[i][j−1] 的值。
示例代码(Python)如下:
def min_path_sum(grid):
m = len(grid)
n = len(grid[0])
dp = [0] * n
dp[0] = grid[0][0]
for j in range(1, n):
dp[j] = dp[j - 1] + grid[0][j]
for i in range(1, m):
dp[0] += grid[i][0]
for j in range(1, n):
dp[j] = min(dp[j], dp[j - 1]) + grid[i][j]
return dp[n - 1]
问题描述:给定一个整数数组 n u m s nums nums,问是否存在一个子集,使得子集中元素的和等于给定的目标值 t a r g e t target target。
解题思路:
同样可以使用状态压缩的方法,将二维数组 d p dp dp 压缩为一维数组。
示例代码(Python)如下:
def subset_sum(nums, target):
n = len(nums)
dp = [False] * (target + 1)
dp[0] = True
for num in nums:
for j in range(target, num - 1, -1):
dp[j] = dp[j] or dp[j - num]
return dp[target]
问题描述:给定一个字符串 s s s,找出 s s s 中最长的回文子串。
解题思路:
在遍历过程中,记录最长回文子串的长度和起始位置。
示例代码(Python)如下:
def longest_palindrome(s):
n = len(s)
if n < 2:
return s
dp = [[False] * n for _ in range(n)]
start = 0
max_len = 1
for j in range(n):
for i in range(j + 1):
if j - i < 2:
dp[i][j] = (s[i] == s[j])
else:
dp[i][j] = (s[i] == s[j]) and dp[i + 1][j - 1]
if dp[i][j] and j - i + 1 > max_len:
max_len = j - i + 1
start = i
return s[start:start + max_len]
问题描述:给定 n n n 个矩阵 A 1 , A 2 , ⋯ , A n A_1, A_2, \cdots, A_n A1,A2,⋯,An,其中 A i A_i Ai 的维度为 p i − 1 × p i p_{i - 1} \times p_i pi−1×pi。问如何给矩阵链加上括号,使得矩阵乘法的总标量乘法次数最少。
解题思路:
示例代码(Python)如下:
def matrix_chain_order(p):
n = len(p) - 1
dp = [[0] * n for _ in range(n)]
for l in range(2, n + 1):
for i in range(n - l + 1):
j = i + l - 1
dp[i][j] = float('inf')
for k in range(i, j):
q = dp[i][k] + dp[k + 1][j] + p[i] * p[k + 1] * p[j + 1]
if q < dp[i][j]:
dp[i][j] = q
return dp[0][n - 1]
问题描述:给定一棵无向树,求树的最大独立集的大小。独立集是指图中一组两两不相邻的顶点。
解题思路:
最终答案为 max ( d p [ r o o t ] [ 0 ] , d p [ r o o t ] [ 1 ] ) \max(dp[root][0], dp[root][1]) max(dp[root][0],dp[root][1])。
问题描述:给定一棵无向树,求树的最小顶点覆盖的大小。顶点覆盖是指图中的一个顶点子集,使得图中的每条边至少有一个端点在该子集中。
解题思路:
最终答案为 min ( d p [ r o o t ] [ 0 ] , d p [ r o o t ] [ 1 ] ) \min(dp[root][0], dp[root][1]) min(dp[root][0],dp[root][1])。
通过对以上不同类型动态规划题型的学习,相信你对动态规划有了更深入的理解和掌握。加油,继续挑战更多的算法问题吧!
质数又称素数,是指在大于 1 的自然数中,除了 1 和它本身以外不再有其他因数的自然数。例如,2、3、5、7、11 等都是质数,而 4 不是质数,因为 4 除了 1 和 4 之外,还有因数 2。
import math
def is_prime(n):
if n < 2:
return False
for i in range(2, int(math.sqrt(n)) + 1):
if n % i == 0:
return False
return True
print(is_prime(7)) # 输出: True
print(is_prime(4)) # 输出: False
def gcd(a, b):
while b:
a, b = b, a % b
return a
print(gcd(12, 18)) # 输出: 6
def lcm(a, b):
return a * b // gcd(a, b)
print(lcm(12, 18)) # 输出: 36
import math
def permutation(n, m):
return math.factorial(n) // math.factorial(n - m)
print(permutation(3, 2)) # 输出: 6
import math
def combination(n, m):
return math.factorial(n) // (math.factorial(m) * math.factorial(n - m))
print(combination(3, 2)) # 输出: 3
a = 5
b = 3
a = a ^ b
b = a ^ b
a = a ^ b
print(a, b) # 输出: 3 5
&
,将数字与 1 进行按位与运算。如果结果为 0,则该数字为偶数;如果结果为 1,则该数字为奇数。def is_even(n):
return (n & 1) == 0
print(is_even(4)) # 输出: True
print(is_even(5)) # 输出: False
def separate_odd_even(arr):
left, right = 0, len(arr) - 1
while left < right:
while left < right and arr[left] % 2 == 1:
left += 1
while left < right and arr[right] % 2 == 0:
right -= 1
if left < right:
arr[left], arr[right] = arr[right], arr[left]
return arr
arr = [1, 2, 3, 4, 5, 6]
print(separate_odd_even(arr)) # 输出: [1, 5, 3, 4, 2, 6]
^
实现不进位加法,使用按位与运算 &
和左移运算 <<
计算进位,然后将不进位加法的结果和进位相加,直到没有进位为止。def add(a, b):
while b:
carry = (a & b) << 1
a = a ^ b
b = carry
return a
print(add(5, 3)) # 输出: 8
def subtract(a, b):
# 求 -b 的补码
b = add(~b, 1)
return add(a, b)
print(subtract(5, 3)) # 输出: 2
总结:数学与位运算在算法和编程中有着广泛的应用,掌握这些基础知识可以帮助我们更好地解决各种问题。通过不断练习相关的题目,可以提高我们的编程能力和思维能力。
贪心算法是一种在每一步选择中都采取在当前状态下最好或最优(即最有利)的选择,从而希望导致结果是全局最好或最优的算法。接下来我们看看贪心算法的具体题型。
假设有一个礼堂,有多个活动想要使用这个礼堂,每个活动都有开始时间和结束时间。我们的目标是在这个礼堂安排尽可能多的活动,使得这些活动的时间不会相互冲突。
例如,有以下活动:
活动编号 | 开始时间 | 结束时间 |
---|---|---|
1 | 1 | 4 |
2 | 3 | 5 |
3 | 0 | 6 |
4 | 5 | 7 |
5 | 3 | 9 |
我们需要找出一种安排方式,让礼堂能举办最多的活动。
我们采用按照活动结束时间进行排序的贪心策略。优先选择结束时间早的活动,这样可以为后续活动留出更多的时间。
对于上面的例子,排序后为:
活动编号 | 开始时间 | 结束时间 |
---|---|---|
1 | 1 | 4 |
2 | 3 | 5 |
4 | 5 | 7 |
3 | 0 | 6 |
5 | 3 | 9 |
首先选择活动 1,其结束时间是 4。然后看活动 2,开始时间 3 早于 4,不选;活动 4,开始时间 5 晚于 4,选择;活动 3,开始时间 0 早于 5,不选;活动 5,开始时间 3 早于 5,不选。所以最终选择的活动是 1 和 4。
在日常生活中,我们去商店买东西,付款后商家需要找零。例如,我们买了价值 37 元的商品,给了商家 100 元,商家需要找给我们 63 元。现在的问题是,如何用最少数量的纸币和硬币来完成找零。
假设我们有以下几种面额的货币:100 元、50 元、20 元、10 元、5 元、1 元。
每次都选择面额最大的货币,直到找零金额为 0。
所以最终找零方案是 1 张 50 元、1 张 10 元、3 张 1 元。
给定一个大区间和若干个小区间,每个小区间都有自己的起始和结束位置。我们的目标是用最少的小区间完全覆盖大区间。
例如,大区间是 [1, 10],有以下小区间:
小区间编号 | 起始位置 | 结束位置 |
---|---|---|
1 | 1 | 3 |
2 | 2 | 5 |
3 | 4 | 7 |
4 | 6 | 9 |
5 | 8 | 10 |
首先,将所有小区间按照起始位置从小到大排序。然后,在每一步中,选择所有起始位置能衔接上当前已覆盖区间末尾的小区间中,结束位置最远的那个小区间。
所以最少需要 4 个小区间(1、3、4、5)来覆盖大区间。
给定一组区间,每个区间由起始位置和结束位置表示。我们的任务是将所有重叠的区间合并成一个大区间,最终得到一组不重叠的区间。
例如,给定以下区间:
区间编号 | 起始位置 | 结束位置 |
---|---|---|
1 | 1 | 3 |
2 | 2 | 6 |
3 | 8 | 10 |
4 | 15 | 18 |
先将所有区间按照起始位置从小到大进行排序。然后,依次遍历这些区间,对于相邻的区间,如果它们有重叠部分,就将它们合并成一个更大的区间。
所以最终合并后的区间是 [1, 6]、[8, 10]、[15, 18]。
栈是一种遵循后进先出(LIFO - Last In First Out)原则的数据结构,就像一摞盘子,最后放上去的盘子总是最先被拿走。
push(element)
:将元素压入栈顶。pop()
:移除并返回栈顶元素。peek()
:返回栈顶元素,但不移除。isEmpty()
:判断栈是否为空。size()
:返回栈中元素的数量。class Stack:
def __init__(self):
self.items = []
def push(self, element):
self.items.append(element)
def pop(self):
if not self.isEmpty():
return self.items.pop()
return None
def peek(self):
if not self.isEmpty():
return self.items[-1]
return None
def isEmpty(self):
return len(self.items) == 0
def size(self):
return len(self.items)
队列是一种遵循先进先出(FIFO - First In First Out)原则的数据结构,就像排队买票,先到的人先买到票。
enqueue(element)
:将元素添加到队尾。dequeue()
:移除并返回队头元素。peek()
:返回队头元素,但不移除。isEmpty()
:判断队列是否为空。size()
:返回队列中元素的数量。class Queue:
def __init__(self):
self.items = []
def enqueue(self, element):
self.items.append(element)
def dequeue(self):
if not self.isEmpty():
return self.items.pop(0)
return None
def peek(self):
if not self.isEmpty():
return self.items[0]
return None
def isEmpty(self):
return len(self.items) == 0
def size(self):
return len(self.items)
哈希表(Hash Table)是一种根据键(Key)直接访问内存存储位置的数据结构,它通过哈希函数将键映射到存储桶(Bucket)中,以实现快速的查找、插入和删除操作。
put(key, value)
:将键值对插入哈希表。get(key)
:根据键获取对应的值。remove(key)
:根据键移除对应的键值对。class HashTable:
def __init__(self, size):
self.size = size
self.table = [[] for _ in range(size)]
def _hash(self, key):
return hash(key) % self.size
def put(self, key, value):
index = self._hash(key)
for pair in self.table[index]:
if pair[0] == key:
pair[1] = value
return
self.table[index].append([key, value])
def get(self, key):
index = self._hash(key)
for pair in self.table[index]:
if pair[0] == key:
return pair[1]
return None
def remove(self, key):
index = self._hash(key)
for i, pair in enumerate(self.table[index]):
if pair[0] == key:
del self.table[index][i]
return
LRU(Least Recently Used)缓存是一种缓存淘汰策略,当缓存满时,会优先淘汰最近最少使用的数据。它结合了哈希表和双向链表,以实现快速的查找和插入操作,同时能够维护数据的访问顺序。
get(key)
:根据键获取对应的值,并将该数据移到链表头部。put(key, value)
:插入或更新键值对,如果缓存已满,淘汰链表尾部的数据。class DLinkedNode:
def __init__(self, key=0, value=0):
self.key = key
self.value = value
self.prev = None
self.next = None
class LRUCache:
def __init__(self, capacity):
self.capacity = capacity
self.cache = {}
self.size = 0
self.head = DLinkedNode()
self.tail = DLinkedNode()
self.head.next = self.tail
self.tail.prev = self.head
def _add_to_head(self, node):
node.prev = self.head
node.next = self.head.next
self.head.next.prev = node
self.head.next = node
def _remove_node(self, node):
node.prev.next = node.next
node.next.prev = node.prev
def _move_to_head(self, node):
self._remove_node(node)
self._add_to_head(node)
def _remove_tail(self):
node = self.tail.prev
self._remove_node(node)
return node
def get(self, key):
if key not in self.cache:
return -1
node = self.cache[key]
self._move_to_head(node)
return node.value
def put(self, key, value):
if key in self.cache:
node = self.cache[key]
node.value = value
self._move_to_head(node)
else:
node = DLinkedNode(key, value)
self.cache[key] = node
self._add_to_head(node)
self.size += 1
if self.size > self.capacity:
removed = self._remove_tail()
self.cache.pop(removed.key)
self.size -= 1
短网址系统的主要功能是将长网址转换为短网址,方便用户分享和传播。用户输入长网址,系统生成对应的短网址,当用户访问短网址时,系统将其重定向到原始的长网址。
id
、long_url
、short_url
等字段。分布式缓存系统用于在分布式环境中缓存数据,以提高系统的性能和响应速度。多个应用服务器可以共享缓存数据,减少对后端数据源(如数据库)的访问压力。
通过以上设计,可以构建一个高效、可靠的分布式缓存系统,为分布式应用提供强大的缓存支持。