探索经典算法:贪心、分治、动态规划等

1.贪心算法

贪心算法是一种常见的算法范式,通常在解决最优化问题中使用。
贪心算法是一种在每一步选择中都采取当前状态下最优决策的算法范式。其核心思想是选择每一步的最佳解决方案,以期望达到最终的全局最优解。这种算法特点在于只考虑局部最优解,而不会回溯或重新考虑已经做出的决策,也不保证能够解决所有问题。尽管贪心算法并非适用于所有问题,但对于某些特定类型的问题,贪心算法的思路简单、高效。

1.区间调度

题目描述:

作业j从sj开始,在fj结束

如果两个作业不重叠,则它们是兼容的。

目标:找到相互兼容作业的最大子集。

解题思路分析:

要使用贪心算法解决这个问题,我们可以按照以下步骤进行:

  1. 按照作业的结束时间对作业列表进行排序,确保作业按照结束时间的升序排列。
  2. 初始化一个空的最大互相兼容作业子集max_jobs_subset。
  3. 从排序后的作业列表中选择第一个作业,并将其添加到max_jobs_subset中。
  4. 遍历剩余的作业,对于每个作业:
    • 检查该作业的起始时间是否晚于max_jobs_subset中最后一个作业的结束时间。
    • 如果是,则将该作业添加到max_jobs_subset中。
  5. 返回max_jobs_subset作为最大互相兼容作业子集。

python代码:

# 定义一个job类
class Job:def __init__(self, start_time, finsh_time):self.start_time = start_timeself.finsh_time = finsh_timedef find_max_compatible_jobs(jobs):# 使用sort函数对jobs列表进行排序 这里按照结束时间升序排列jobs.sort(key=lambda x: x.finsh_time)max_jobs_subset = []last_time = float('-inf')for job in jobs:if job.start_time >=last_time:max_jobs_subset.append(job)last_time = job.finsh_time# 这里直接返回最大公共子集return max_jobs_subset
# 编写测试函数
# 用户首先输入作业的数量
while True:try:job_count = int(input('请输入作业总数:'))if job_count < 1:raise ValueErrorbreakexcept ValueError:print('作业总数必须是一个正整数')
#  定义工作列表存储用户输入的工作
jobs = []
# 依次输入每个作业的起始时间和结束时间
for i in range(job_count):while True:try:start_time = int(input('请输入第{}个作业的起始时间:'.format(i + 1)))finsh_time = int(input('请输入第{}个作业的结束时间:'.format(i + 1)))if finsh_time < start_time:raise ValueErrorbreakexcept ValueError:print('结束时间必须晚于开始时间')job = Job(start_time,finsh_time)jobs.append(job)
# 调用函数找到最大互相兼容作业子集
max_compatible_jobs = find_max_compatible_jobs(jobs)
print('相互兼容的最大工作子集:')
for job in max_compatible_jobs:print('作业开始时间:{},作业结束时间:{}'.format(job.start_time,job.finsh_time))

2.区间划分

题目描述:

讲座j 从sj开始,到fj结束;
目标:找到安排所有讲座的最少教室数量
这样就不会在同一个房间里同时发生两次讲座。

解题思路分析:

具体的求解过程如下:

  1. 将所有的讲座按照结束时间进行排序,可以使用 sorted 函数,并将 key 参数设置为讲座的结束时间。这样可以保证在贪心算法中,我们首先处理最早结束的讲座。
  2. 创建一个空的教室列表,用于存储已经安排的讲座。
  3. 遍历排序后的讲座列表。对于每一个讲座,我们需要检查是否存在一个教室可以容纳它。
  4. 对于每个讲座,我们遍历已经安排的教室,并检查当前讲座是否可以安排在某个教室中。我们可以通过比较当前讲座的开始时间和已安排讲座的结束时间来判断是否可以安排在同一个教室中。
  5. 如果存在一个教室可以容纳当前讲座,则将该讲座添加到教室的列表中。
  6. 如果不存在可以容纳当前讲座的教室,则创建一个新的教室,并将当前讲座添加到新的教室中。
  7. 遍历完所有的讲座后,返回教室列表的长度,即为需要的最小教室数量。

python代码:

class Lecture:def __init__(self, start_time, end_time):self.start_time = start_timeself.end_time = end_time
def get_valid_lectures():lectures = []while True:try:num_lectures = int(input("请输入讲座数量: "))if num_lectures <= 0:raise ValueError("讲座数量必须大于0")for i in range(num_lectures):start_time = int(input(f"请输入讲座{i + 1}的开始时间: "))end_time = int(input(f"请输入讲座{i + 1}的结束时间: "))if start_time >= end_time:raise ValueError(f"讲座{i + 1}的开始时间必须早于结束时间")if start_time < 0:raise ValueError(f'讲座{i+1}的开始时间必须是正数')if end_time < 0:raise ValueError(f'讲座{i+1}的结束时间必须是正数')lecture = Lecture(start_time, end_time)lectures.append(lecture)breakexcept ValueError as e:print("输入错误:", e)return lecturesdef minimum_classrooms(lectures):sorted_lectures = sorted(lectures, key=lambda x: x.end_time)classrooms = []for lecture in sorted_lectures:found_classroom = Falsefor classroom in classrooms:if lecture.start_time >= classroom[-1].end_time:classroom.append(lecture)found_classroom = Truebreakif not found_classroom:classrooms.append([lecture])return len(classrooms)# 获取有效的讲座数据
valid_lectures = get_valid_lectures()
# 调用函数获取最小教室数量
min_classrooms = minimum_classrooms(valid_lectures)
print("需要的最小教室数量:", min_classrooms)

3.最小延迟时间

题目描述:

・单个资源一次处理一个作业。

・作业j需要tj个处理时间单位,并且在时间dj到期

・如果j在时间sj开始,它在时间fj=sj+tj结束

・潜伏时间:ℓj=最大{0,fj–dj}。

・目标:安排所有作业以最大限度地减少最大延迟L=maxjℓj

解题思路分析:

  1. 首先,将所有的任务按照截止时间 dj 进行排序,以确保优先处理截止时间最早的任务。
  2. 创建一个空闲时间变量 current_time,并初始化为 0。
  3. 对每个任务进行迭代,按照排序后的顺序:
    • 计算当前任务的完成时间 fj = current_time + tj,其中 tj 是当前任务的处理时间。
    • 计算当前任务的延迟时间 ℓj = max(0, fj - dj)
    • 更新最大延迟 L,如果当前任务的延迟时间 ℓj 大于 L,则将 L 更新为 ℓj
    • 更新当前时间 current_time,将其设置为当前任务的完成时间 fj
  4. 返回最大延迟 L

Python代码:

class Job:def __init__(self, processing_time, deadline):self.processing_time = processing_timeself.deadline = deadlinedef minimum_lateness(jobs):jobs.sort(key=lambda x: x.deadline)  # 按照截止时间排序L = 0  # 最大延迟current_time = 0  # 当前时间for job in jobs:# 验证处理时间和截止时间的格式和数值if not isinstance(job.processing_time, int) or job.processing_time <= 0:raise ValueError("Processing time should be a positive integer.")if not isinstance(job.deadline, int) or job.deadline <= 0:raise ValueError("Deadline should be a positive integer.")# 计算完成时间和延迟时间finish_time = current_time + job.processing_timelateness = max(0, finish_time - job.deadline)# 更新最大延迟L = max(L, lateness)# 更新当前时间current_time = finish_timereturn L# 用户输入数据
n = None
while not isinstance(n, int) or n <= 0:try:n = int(input("Enter the number of jobs: "))if n <= 0:print("Number of jobs should be a positive integer.")except ValueError:print("Invalid input. Number of jobs should be an integer.")jobs = []
for i in range(n):print(f"Enter information for job {i+1}:")processing_time = Nonewhile not isinstance(processing_time, int) or processing_time <= 0:try:processing_time = int(input("Enter the processing time: "))if processing_time <= 0:print("Processing time should be a positive integer.")except ValueError:print("Invalid input. Processing time should be an integer.")deadline = Nonewhile not isinstance(deadline, int) or deadline <= 0:try:deadline = int(input("Enter the deadline: "))if deadline <= 0:print("Deadline should be a positive integer.")except ValueError:print("Invalid input. Deadline should be an integer.")job = Job(processing_time, deadline)jobs.append(job)try:max_lateness = minimum_lateness(jobs)print("The maximum lateness is:", max_lateness)
except ValueError as e:print("Invalid input:", str(e))

2.分治算法

分治算法是一种重要的算法范式,它将一个大问题分解成几个小问题,分别解决这些小问题,然后将其合并以得到原始问题的解。其核心思想是递归地把问题分解成更小的子问题,并将这些子问题独立地解决。

分治算法的设计思路通常分为三个步骤:

  1. 分解:将原问题分解为若干个子问题。
  2. 解决:递归地解决这些子问题。
  3. 合并:将子问题的解合并成原问题的解。

分治算法常用于解决递归性质的问题,例如归并排序、快速排序、二叉树相关问题等。虽然它的效率通常较高,但并非适用于所有问题。

1.最近配对问题

题目描述:

最近配对问题。给定平面上的n个点,找一对点,使得它们之间的欧几里得距离最小。

解题思路分析:

分治法是一种解决问题的方法,将问题划分为更小的子问题,然后将子问题的解合并起来得到原问题的解。下面是使用分治法解决最近配对问题的详细步骤:

  1. 将所有点按照 x 坐标进行排序。
  2. 将点集分为左右两部分,分别记为 P_left 和 P_right。
  3. 递归地在 P_left 和 P_right 中分别找到最近配对的距离,记为 d_left 和 d_right。
  4. 取 d_left 和 d_right 中的较小值,记为 d。
  5. 在左右两个点集中,找到横跨中间区域的点集 P_mid,其 x 坐标与中间点的 x 坐标的差值小于等于 d。
  6. 在 P_mid 中按照 y 坐标进行排序。
  7. 遍历 P_mid 中的每个点,对于每个点 p,只需计算与其后续的 7 个点的距离(因为在排序后的 P_mid 中,距离任意两点最大为 d)。
  8. 找到 P_mid 中距离最小的两个点的距离,记为 d_mid。
  9. 返回 d、d_left 和 d_right 中的最小值作为最终的最近配对距离。

python代码:

import math
# 定义一个坐标类 包括x和y
class Point:def __init__(self, x, y):self.x = xself.y = y
# 求解两个点的欧几里得距离
def distance(p1, p2):return math.sqrt((p1.x-p2.x)**2+(p1.y-p2.y)**2)
# 使用分治法求解最近配对问题
def closest_pair(points):n = len(points)
#     这里如果n小于等于3 即直接使用暴力法计算if n <= 3:# 默认最小距离为最大值min_distance = float('inf')closest_points = Nonefor i in range(n):for j in range(i+1, n):if distance(points[i], points[j]) < min_distance:min_distance = distance(points[i], points[j])closest_points = (points[i], points[j])#函数直接返回最短距离点对和最小距离return closest_points, min_distance
#     如果点集个数大于3 则使用分治法求解
#     首先对点集按照x升序排列points.sort(key=lambda p: p.x)
#     然后将点集分为两部分mid = n // 2left_points = points[:mid]right_points = points[mid:]
#     分别求解左 右区间的最小距离与最近点集closest_left, d_left = closest_pair(left_points)closest_right, d_right = closest_pair(right_points)if d_left < d_right:d = d_leftclosest_points = closest_leftelse:d = d_rightclosest_points = closest_rightmid_x = (points[mid-1].x+points[mid].x)/2mid_points = [p for p in points if abs(p.x-mid_x) < d]
#     然后把这些点按照y值排序mid_points.sort(key=lambda p: p.y)min_distance = dfor i in range(len(mid_points)):for j in range(i+1, min(i+8, len(mid_points))):# 最多计算后序7个点的距离if distance(points[i], points[j]) < d:min_distance = distance(points[i], points[j])closest_points = (points[i], points[j])return closest_points, min_distance
# 获取用户输入的点坐标
points = []
while True:x_input = input("请输入点的 x 坐标(输入 'end' 结束输入): ")if x_input == 'end':breaky_input = input("请输入点的 y 坐标(输入 'end' 结束输入): ")if y_input == 'end':breaktry:x = float(x_input)y = float(y_input)# 进行校验逻辑,例如对 x 和 y 坐标进行合法性检查、范围检查等,根据需求自行定义# 如果校验通过,则创建 Point 对象并添加到 points 列表中point = Point(x, y)points.append(point)except ValueError:print("输入的坐标不合法,请重新输入")# 调用 closest_pair() 函数获取最近配对的结果
closest_points, min_distance = closest_pair(points)
point1, point2 = closest_points# 输出最近配对的距离和坐标
print("The closest pair distance is:", min_distance)
print("The closest pair coordinates are:", (point1.x, point1.y), (point2.x, point2.y))

2.第k小元素

题目描述:

给定一个完全有序的宇宙中的n个元素,找出第k个最小的元素。

解题思路分析:

  1. 首先,我们需要选择一个合适的基准元素。可以使用各种方法选择基准元素,例如选择第一个元素、最后一个元素或随机选择一个元素。选择一个好的基准元素可以提高算法的效率。
  2. 将数组划分为两个子数组,一个包含小于等于基准元素的元素,另一个包含大于基准元素的元素。这可以通过遍历数组,并将小于等于基准元素的元素放在一个子数组中,大于基准元素的元素放在另一个子数组中来实现。这个过程通常被称为分区(partition)。
  3. 然后,我们需要确定基准元素在整个数组中的位置。这可以通过分区的过程中,记录基准元素的位置来实现。假设基准元素的位置是 pivot_index。
  4. 现在,我们可以根据 pivot_index 与 k 的关系来决定继续查找的部分:
    • 如果 pivot_index == k-1,说明基准元素正好是第 k 小的元素,返回它。
    • 如果 pivot_index > k-1,说明第 k 小的元素在基准元素的左边,我们可以递归地在左边的子数组中查找第 k 小的元素。
    • 如果 pivot_index < k-1,说明第 k 小的元素在基准元素的右边,我们可以递归地在右边的子数组中查找第 k-pivot_index-1 小的元素。
  5. 递归地应用上述步骤,直到找到第 k 小的元素。

python代码实现:

def choose_pivot(arr, low, high):mid = (low + high) // 2if arr[low] <= arr[mid] <= arr[high] or arr[high] <= arr[mid] <= arr[low]:return midelif arr[mid] <= arr[low] <= arr[high] or arr[high] <= arr[low] <= arr[mid]:return lowelse:return high# 分区 分为小于基准元素和大于基准元素两个数组
def partition(arr, low, high):# 通过三数取中找到基准元素pivot_index = choose_pivot(arr, low, high)pivot = arr[pivot_index]i = low - 1arr[pivot_index], arr[high] = arr[high], arr[pivot_index]for j in range(low, high):if arr[j] <= pivot:i += 1arr[i], arr[j] = arr[j], arr[i]arr[i+1], arr[high] = arr[high], arr[i+1]return i+1def kth_smallest(arr, low, high, k):if low == high:return arr[low]pivot_index = partition(arr, low, high)if pivot_index == k-1:return arr[pivot_index]elif pivot_index > k-1:return kth_smallest(arr, low, pivot_index-1, k)else:return kth_smallest(arr, pivot_index+1, high, k)# 用户输入数据
arr_valid = False
arr = []
while not arr_valid:arr = input("请输入由空格分隔的整数数组:").split()if arr:try:arr = [int(num) for num in arr]arr_valid = Trueexcept ValueError:print("输入无效,请重新输入整数数组。")else:print("输入数组不能为空。")# 校验数据有效性
k_valid = False
k = 0
while not k_valid:k = input("请输入要查找的第几小的元素的值:")if k.isdigit():k = int(k)if 1 <= k <= len(arr):k_valid = Trueelse:print("输入的值超出了数组范围,请重新输入。")else:print("输入无效,请重新输入一个整数。")kth_smallest_element = kth_smallest(arr, 0, len(arr)-1, k)
print(f"The {k}th smallest element is: {kth_smallest_element}")

3.医院医生算法

题目描述:

医院-医生算法是一种解决问题的方法,旨在以最佳方式将医生分配给医院。该算法确保每个医院都有足够数量的医生,并根据特定的标准将每个医生分配到合适的医院。

解题思路分析:

以下是使用稳定婚姻算法解决医院-医生分配问题的步骤:

  1. 初始化医生和医院的偏好列表,并将所有医生和医院都标记为未匹配状态。
  2. 对于每个医生,按照其对医院的偏好将医生依次提议给最喜欢的医院。每个医生只能向一个医院提议。
  3. 对于每个医院收到的提议,根据医院的偏好列表进行选择。如果医院尚未被匹配,将医生分配给该医院。如果医院已经有医生,比较当前医生和已匹配医生对医院的偏好,并选择更受欢迎的医生。
  4. 如果医生被匹配到医院,医生和医院都将标记为已匹配状态。
  5. 如果有医生被拒绝或医院已经达到容量上限,则该医生将继续向下一个偏好医院提议。
  6. 重复步骤2和步骤3,直到所有医生都被匹配到医院为止。
  7. 最终得到的医生-医院匹配结果即为最优的医院-医生分配方案。

python代码:

def stable_marriage(doctors, hospitals):doctor_preferences = {}hospital_preferences = {}doctor_matches = {}hospital_matches = {}# 初始化医生和医院的偏好列表for doctor in doctors:doctor_preferences[doctor] = doctors[doctor]for hospital in hospitals:hospital_preferences[hospital] = hospitals[hospital]# 将所有医生和医院都标记为未匹配状态for doctor in doctors:doctor_matches[doctor] = Nonefor hospital in hospitals:hospital_matches[hospital] = Nonewhile None in doctor_matches.values():for doctor in doctors:if doctor_matches[doctor] is None:for hospital in doctor_preferences[doctor]:if hospital_matches[hospital] is None:doctor_matches[doctor] = hospitalhospital_matches[hospital] = doctorbreakelse:current_match = hospital_matches[hospital]if hospital_preferences[hospital].index(doctor) < hospital_preferences[hospital].index(current_match):doctor_matches[doctor] = hospitaldoctor_matches[current_match] = Nonehospital_matches[hospital] = doctorbreakreturn doctor_matches# 用户输入医生和医院的偏好
doctors = {}
hospitals = {}n_doctors = int(input("请输入医生人数:"))
n_hospitals = int(input("请输入医院数量:"))for i in range(n_doctors):doctor_name = input(f"请输入第{i+1}位医生的姓名:")doctor_preferences = input(f"请输入{doctor_name}医生对医院的偏好(以空格分隔):").split()doctors[doctor_name] = doctor_preferencesfor i in range(n_hospitals):hospital_name = input(f"请输入第{i+1}家医院的名称:")hospital_preferences = input(f"请输入{hospital_name}医院对医生的偏好(以空格分隔):").split()hospitals[hospital_name] = hospital_preferences# 调用稳定婚姻算法
matches = stable_marriage(doctors, hospitals)# 输出医生-医院匹配结果
for doctor, hospital in matches.items():print(f"{doctor} 匹配到 {hospital}")

4.动态规划

动态规划(Dynamic Programming)是一种优化问题求解的算法思想,适用于具有重叠子问题和最优子结构性质的问题。它将问题分解为一系列重叠的子问题,并通过保存子问题的解来避免重复计算,从而实现对整个问题的高效求解。

动态规划算法通常遵循以下步骤:

  1. 确定状态:将原问题划分为若干个子问题,并定义状态表示问题的解。

  2. 定义状态转移方程:通过递推关系式来描述子问题之间的关系,即如何通过已知的子问题的解来计算当前问题的解。

  3. 确定初始条件:确定最简单的子问题的解,作为递推的起点。

  4. 确定计算顺序:根据子问题之间的依赖关系,确定计算的顺序,通常采用自底向上的方式进行计算。

  5. 计算最优解:依据状态转移方程,按照确定的计算顺序,通过递推计算出问题的最优解。

  6. 构造最优解:根据计算得到的最优解和保存的状态信息,构造出原问题的最优解。

动态规划算法的核心思想是利用子问题的解来求解更大规模的问题,通过保存子问题的解,避免了重复计算,从而显著提高了算法的效率。动态规划常用于求解最优化问题,如最短路径问题、背包问题、序列比对等。

经典动态规划算法:接缝雕刻;文本相似和不同比较;样条曲线的赋值等。

以下是一些动态规划常用于解决的经典问题:

  1. Fibonacci 数列:计算第 n 个 Fibonacci 数的值。
  2. 背包问题(Knapsack Problem):在给定容量的背包中,选择一些物品放入背包,使得物品的总价值最大化,同时不超过背包的容量。
  3. 最长公共子序列(Longest Common Subsequence):给定两个字符串,找到它们的最长公共子序列的长度。
  4. 最长递增子序列(Longest Increasing Subsequence):给定一个序列,找到一个最长的子序列,使得子序列中的元素按顺序递增。
  5. 矩阵链乘法(Matrix Chain Multiplication):给定一系列矩阵,通过合理地加括号,使得矩阵相乘的次数最少。
  6. 最短路径问题(Shortest Path Problem):在带有权重的有向图中,找到从起点到终点的最短路径。
  7. 最大子数组和(Maximum Subarray Sum):给定一个整数数组,找到一个具有最大和的连续子数组。
  8. 最长回文子串(Longest Palindromic Substring):给定一个字符串,找到一个最长的回文子串。
  9. 编辑距离(Edit Distance):计算将一个字符串转换为另一个字符串所需的最少编辑操作次数。
  10. 切割钢条(Cutting Rod):将一根长度为 n 的钢条切割成若干段,使得卖出的总价值最大化。

1.加权区间调度

问题描述:

作业j从 s j s_{j} sj开始,在 f j f_{j} fj结束,且每个作业有个权重 w j w_{j} wj

如果两个作业不重叠,则它们是兼容的。

目标:找到相互兼容作业且权重最大的子集。

解题思路:

当解决加权区间调度问题时,我们希望找到一组相容的区间,使得它们的总权重和最大。

动态规划是一种常用的解决最优化问题的方法。在解决加权区间调度问题时,我们可以利用动态规划的思想来逐步计算出最优解。

首先,我们将所有的区间按照结束时间从小到大进行排序。这是因为结束时间早的区间有更大的潜力可以和后面的区间相容。

接下来,我们定义一个dp数组,dp[i]表示以第i个区间为最后一个区间时的最大权重和。我们需要逐个计算dp数组的值。

对于每个区间i,我们需要考虑两种情况:

  1. 区间i不被选中:此时dp[i]等于dp[i-1],即前一个区间为最后一个区间时的最大权重和。
  2. 区间i被选中:此时我们需要找到前面结束时间小于等于区间i开始时间的区间j,使得dp[j]+区间i的权重最大。因此,我们需要遍历前面的区间j,找到这样的区间,并计算dp[j]+区间i的权重。

最后,我们将dp[i]更新为这两种情况中的较大值,即dp[i] = max(dp[i-1], dp[j]+区间i的权重)。

通过这样的状态转移,我们可以逐个计算dp数组的值。最终,dp数组的最后一个元素即为最大权重和。

总结一下动态规划解决加权区间调度的思路:

  1. 将所有区间按照结束时间从小到大进行排序。
  2. 定义dp数组,长度为区间个数,初始化为0。
  3. 遍历每个区间i,计算dp[i]的值:
    a. 如果区间i不被选中,dp[i]等于dp[i-1]。
    b. 如果区间i被选中,找到前面结束时间小于等于区间i开始时间的区间j,计算dp[j]+区间i的权重。
  4. 将dp[i]更新为上述两种情况中的较大值。
  5. 返回dp数组的最后一个元素,即为最大权重和。

希望这样的解题思路能够帮助你更好地理解动态规划解决加权区间调度问题的思路。

python代码实现:

class Job:def __init__(self, start, end, weight):self.start = startself.end = endself.weight = weightdef weighted_interval_scheduling(jobs):jobs.sort(key=lambda x: x.end)  # 按照结束时间从小到大排序n = len(jobs)dp = [0] * ndp[0] = jobs[0].weight  # 初始化第一个作业的dp值为其权重for i in range(1, n):dp[i] = jobs[i].weight  # 初始化dp[i]为作业i的权重for j in range(i-1, -1, -1):if jobs[j].end <= jobs[i].start:dp[i] = max(dp[i], dp[j] + jobs[i].weight)  # 更新dp[i]的值return max(dp)  # 返回最大权重和# 示例输入
jobs = [Job(1, 4, 3),Job(3, 5, 2),Job(0, 6, 4),Job(4, 7, 1),Job(3, 8, 2),Job(5, 9, 6),Job(6, 10, 4),Job(8, 11, 2)
]# 输出最大权重和
print(weighted_interval_scheduling(jobs))

2.最大子数组问题

问题描述:
最大子数组问题是指在一个数组中,找到一个连续子数组,使得该子数组的和最大。

思路分析:

下面是使用动态规划解决最大子数组问题的一般思路:

  1. 定义一个dp数组,长度与原数组相同,用于存储以当前位置为结尾的最大子数组和。
  2. 初始化dp数组的第一个元素为原数组的第一个元素,即dp[0] = nums[0]。
  3. 从位置1开始遍历原数组,计算以当前位置为结尾的最大子数组和。
    • 如果dp[i-1]大于0,说明以前一个位置为结尾的最大子数组和对于当前位置的子数组和是有贡献的,因此dp[i] = dp[i-1] + nums[i]。
    • 如果dp[i-1]小于等于0,说明以前一个位置为结尾的最大子数组和对于当前位置的子数组和没有贡献,因此dp[i] = nums[i]。
  4. 遍历dp数组,找到最大的子数组和,即max_sum = max(dp)。
  5. 返回max_sum,即为最大子数组和。

python代码实现:

def max_subarray(nums):n = len(nums)if n == 0:return 0max_sum = nums[0]  # 初始化最大子数组和为第一个元素curr_sum = nums[0]  # 当前位置的最大子数组和for i in range(1, n):if curr_sum > 0:curr_sum += nums[i]else:curr_sum = nums[i]max_sum = max(max_sum, curr_sum)return max_sum# 用户输入和数据验证
while True:try:nums = [int(x) for x in input("请输入一个整数数组,数字之间用空格分隔:").split()]breakexcept ValueError:print("输入无效,请重新输入整数数组!")# 输出最大子数组和
print(max_subarray(nums))

在这个题的基础上,引申出一个新的题目:

goal:given an n-by-n matrix,find a rectangle whose sum is maxinum

要使用动态规划求解给定的 n × n 矩阵中和最大的矩形,可以按照以下步骤进行:

  1. 定义一个 dp 数组,其大小为 n × n,用于存储以矩阵中每个位置为右下角的矩形的和。
  2. 初始化 dp 数组的第一行和第一列为矩阵的第一行和第一列元素。
  3. 从第二行和第二列开始,遍历矩阵中的每个位置 (i, j):
    • 对于每个位置,计算以该位置为右下角的矩形的和,即 d p [ i ] [ j ] = d p [ i − 1 ] [ j ] + d p [ i ] [ j − 1 ] − d p [ i − 1 ] [ j − 1 ] + m a t r i x [ i ] [ j ] dp[i][j] = dp[i-1][j] + dp[i][j-1] - dp[i-1][j-1] + matrix[i][j] dp[i][j]=dp[i1][j]+dp[i][j1]dp[i1][j1]+matrix[i][j]
  4. 遍历 dp 数组,找到最大的矩形和。
  5. 返回最大的矩形和。

python代码实现:

def max_rectangle_sum(matrix):n = len(matrix)if n == 0:return 0dp = [[0] * n for _ in range(n)]  # 定义 dp 数组# 初始化第一行和第一列dp[0][0] = matrix[0][0]for i in range(1, n):dp[i][0] = dp[i-1][0] + matrix[i][0]dp[0][i] = dp[0][i-1] + matrix[0][i]# 计算 dp 数组for i in range(1, n):for j in range(1, n):dp[i][j] = dp[i-1][j] + dp[i][j-1] - dp[i-1][j-1] + matrix[i][j]max_sum = float('-inf')  # 初始化最大矩形和为负无穷大# 遍历 dp 数组,找到最大矩形和for i in range(n):for j in range(n):for k in range(i, n):for l in range(j, n):curr_sum = dp[k][l]if i > 0:curr_sum -= dp[i-1][l]if j > 0:curr_sum -= dp[k][j-1]if i > 0 and j > 0:curr_sum += dp[i-1][j-1]max_sum = max(max_sum, curr_sum)return max_sum# 示例输入
matrix = [[1, 2, -1],[-3, 0, 6],[7, 8, -9]
]# 输出最大矩形和
print(max_rectangle_sum(matrix))

3.背包问题

问题描述:

背包问题是一个经典的组合优化问题,旨在从一组物品中选择适合放入背包以最大化总价值,同时要求背包的容量不超过给定的限制。

实现思路:

  1. 定义一个一维数组 dp,大小为 (W+1),其中 W 是背包的容量。dp[j] 表示背包容量为 j 时的最大价值。
  2. 初始化 dp 数组为 0,即 dp[j] = 0。
  3. 从位置 1 开始遍历 dp 数组,并计算每个位置的最大价值。
    • 对于每个位置 j,我们需要判断当前物品的重量是否小于等于背包容量 j。如果是,则可以选择将该物品放入背包中,此时的最大价值为 max(dp[j], dp[j-w] + v),其中 w 是当前物品的重量,v 是当前物品的价值。
    • 我们将计算得到的最大价值更新到 dp[j] 中。
  4. 遍历完整个 dp 数组后,dp[W] 即为最大的背包价值。
  5. 可以通过回溯 dp 数组,找到放入背包的物品。
    • 我们从最后一个位置 W 开始,如果 dp[j] != dp[j-w] + v,表示第 j 个物品被放入背包中。
    • 然后,我们将 j 减去该物品的重量,继续向前搜索。
    • 重复上述步骤,直到搜索到第一个位置 1,即可得到放入背包的物品。
  6. 返回最大价值和放入背包的物品。

python代码实现:

def knapsack_dp(weight, value, capacity):n = len(weight)if n == 0 or capacity == 0:return 0, []# 将浮点数转换为整数,乘以一个大的倍数,以保留小数点后的精度multiplier = 100weight = [int(w * multiplier) for w in weight]capacity = int(capacity * multiplier)dp = [0] * (capacity + 1)picks = []for i in range(n):if weight[i] <= capacity:for j in range(capacity, weight[i] - 1, -1):if dp[j] < dp[j - weight[i]] + value[i]:dp[j] = dp[j - weight[i]] + value[i]max_value = dp[capacity]# 回溯找到放入背包的物品j = capacityfor i in range(n - 1, -1, -1):if j >= weight[i] and dp[j] == dp[j - weight[i]] + value[i]:picks.append(i)j -= weight[i]picks.reverse()return max_value, picks# 输入数据
n = int(input("请输入物品数量:"))
weight = []
value = []
for i in range(n):w = float(input(f"请输入第 {i+1} 个物品的重量:"))v = float(input(f"请输入第 {i+1} 个物品的价值:"))weight.append(w)value.append(v)
capacity = float(input("请输入背包的容量:"))# 数据合法性验证
if n <= 0 or capacity < 0:print("输入的物品数量或背包容量不合法,请重新输入!")
else:max_value, picks = knapsack_dp(weight, value, capacity)print("最大价值为:", max_value)print("放入背包的物品索引:", picks)

4.最长公共子序列问题

问题描述:

给定两个字符串,要求找到它们的最长公共子序列的长度。

解题思路:

  1. 定义问题:
    • 输入:两个字符串 s1s2
    • 输出:最长公共子序列的长度。
  2. 创建一个二维数组 dp,大小为 (len(s1)+1) × (len(s2)+1),用来保存最长公共子序列的长度。
    • dp[i][j] 表示字符串 s1 的前 i 个字符和字符串 s2 的前 j 个字符的最长公共子序列的长度。
  3. 初始化边界条件:
    • i=0j=0 时,dp[i][j] = 0,表示一个字符串为空时,最长公共子序列的长度为 0。
  4. 动态规划计算最长公共子序列的长度:
    • i=1j=1 开始,逐步计算 dp[i][j]
    • 如果 s1[i-1] 等于 s2[j-1],则这两个字符可以加入最长公共子序列中,即 dp[i][j] = dp[i-1][j-1] + 1
    • 如果 s1[i-1] 不等于 s2[j-1],则需要在 s1 的前 i-1 个字符和 s2 的前 j 个字符中找到最长公共子序列,或者在 s1 的前 i 个字符和 s2 的前 j-1 个字符中找到最长公共子序列,取两者中的较大值,即 dp[i][j] = max(dp[i-1][j], dp[i][j-1])
  5. 返回最长公共子序列的长度:
    • dp[len(s1)][len(s2)] 即为最长公共子序列的长度。

python代码:

def longest_common_subsequence(s1, s2):m = len(s1)n = len(s2)# 创建一个二维数组来保存最长公共子序列的长度dp = [[0] * (n + 1) for _ in range(m + 1)]# 动态规划计算最长公共子序列的长度for i in range(1, m + 1):for j in range(1, n + 1):if s1[i - 1] == s2[j - 1]:dp[i][j] = dp[i - 1][j - 1] + 1else:dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])# 返回最长公共子序列的长度return dp[m][n]# 用户输入和数据验证
s1 = input("请输入第一个字符串:")
s2 = input("请输入第二个字符串:")if not s1 or not s2:print("输入字符串不能为空")
else:lcs_length = longest_common_subsequence(s1, s2)print("最长公共子序列的长度为:", lcs_length)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/187112.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【学术综述】-如何写出一篇好综述-写好综述要注意的问题

文章目录 1.前置1.1 SSD 的结构1.2 FTL的架构和作用 2 动机-why&#xff1f;3 做了什么【做了哪些方面的survey】&#xff1f;4 背景知识【上下文】5 研究的问题6 每个问题对应的解决方案 从昨天晚上【2023.11.09 22:00】到今天22:29的&#xff0c;花了一天的时间在读这篇surve…

一文学会Scala【Scala一站式学习笔记】

文章目录 为什么要学习Scala语言什么是Scala如何快速掌握Scala语言Scala环境安装配置Scala命令行 Scala的基本使用变量数据类型操作符if 表达式语句终结符循环高级for循环 Scala的集合体系集合SetListMapArrayArrayBuffer数组常见操作Tuple总结 Scala中函数的使用函数的定义函数…

手握“发展密钥”,TCL科技或迎价值重估?

在高度竞争且快速变化的泛半导体产业&#xff0c;每一次周期性或结构性的变化&#xff0c;都会对企业经营策略带来深远的影响。 2023年前三季度&#xff0c;泛半导体产业迎来结构性复苏。其中&#xff0c;主流显示领域供需关系趋向健康化&#xff0c;半导体显示行业整体上量价…

【Node.js入门】1.1Node.js 简介

Node.js入门之—1.1Node.js 简介 文章目录 Node.js入门之—1.1Node.js 简介什么是 Node.js错误说法 Node.js 的特点跨平台三方类库自带http服务器非阻塞I/O事件驱动单线程 Node.js 的应用场合适合用Node.js的场合不适合用Node.js的场合弥补Node.js不足的解决方案 什么是 Node.j…

获取AAC音频的ADTS固定头部信息

文章目录 前言一、AAC音频中的ADTS二、解析ADTS信息1.标准文档中介绍2.解析3.采样率索引和值4.下载AAC标准文档 前言 调试嵌入式设备中播放aac音频的过程中&#xff0c;了解了aac音频格式&#xff0c;记录在此&#xff0c;防止遗忘。 一、AAC音频中的ADTS ADTS&#xff08;Audi…

2.【自动驾驶与机器人中的SLAM技术】左乘模型推导ESKF

目录 1. 证明题 证明&#xff1a;若某个高斯随机变量为零均值&#xff0c;协方差为对角线矩阵且大小相同&#xff08;各向同性&#xff09;&#xff0c;那么在乘任意旋转矩阵以后&#xff0c;其均值仍为零&#xff0c;且协方差不变&#xff1b; 2. 代码实现运动方程将F矩阵…

Webpack--动态 import 原理及源码分析

前言 在平时的开发中&#xff0c;我们经常使用 import()实现代码分割和懒加载。在低版本的浏览器中并不支持动态 import()&#xff0c;那 webpack 是如何实现 import() polyfill 的&#xff1f; 原理分析 我们先来看看下面的 demo function component() {const btn docume…

【ARM Coresight OpenOCD 系列 1 -- OpenOCD 介绍】

请阅读【ARM Coresight SoC-400/SoC-600 专栏导读】 文章目录 1.1 OpenOCD 介绍1.1.1 OpenOCD 支持的JTAG 适配器1.1.2 OpenOCD 支持的调试设备1.1.3 OpenOCD 支持的 Flash 驱动 1.2 OpenOCD 安装与使用1.2.1 OpenOCD 代码获取及安装1.2.2 OpenOCD 使用1.2.3 OpenOCD 启用 GDB…

Ubuntu 22.04 安装水星无线 USB 网卡

我的 USB 网卡是水星 Mercury 的&#xff0c; 在 Ubuntu 22.04 下面没有自动识别。 没有无线网卡的时候只能用有线接到路由器上&#xff0c;非常不方便。 寻思着把无线网卡驱动装好。折腾了几个小时装好了驱动。 1.检查网卡类型 & 安装驱动 使用 lsusb 看到的不一定是准确…

ChatGPT:something went wrong

今天下午不知什么原因&#xff0c;ChatGPT无法使用。我原来在使用ChatGPT for chrome&#xff0c;返回了一个答案&#xff0c;后来在网页端无法使用&#xff0c;以为是这个chrome插件泄露API KEY导致的。注销账号&#xff0c;删除API KEY后&#xff0c;wrong问题仍然存在。 我…

Centos配置邮件发送

在CentOS Linux上配置邮件发送 在这个指南中&#xff0c;我们将讨论如何配置CentOS Linux系统以通过外部邮件服务器发送电子邮件&#xff0c;使用自己的邮件账户进行发送。 第一步&#xff1a;开启SMTP授权码 首先&#xff0c;我们以QQ邮箱为例&#xff0c;需要开启SMTP授权…

类直径树上贪心

http://cplusoj.com/d/senior/p/SS231109C 场上想到枚举点&#xff0c;然后最大值为高&#xff0c;然后可以求最大值。但是感觉计数会重 计数其实不会重&#xff0c;如图中&#xff0c;红色线段显然比蓝色线段优 所以我们枚举3叉点时没错的 #include<bits/stdc.h> usin…

使用ffmpeg 压缩视频

我有一批1080p的视频,在网上播放占用空间太大,需要进行压缩以后再上传,下面是记录一下ffmpeg命令的使用情况 原视频大小:288mb --压缩加修改分辨率 640p ffmpeg -y -i C4995.mp4 -vcodec libx264 -crf 18 -s vga C4995\C4995_2.MP4 -y: 强制覆盖 -i :输入文件 -vcodec lib…

2、鸿蒙开发工具首次运行时开发环境配置

请务必在第一次运行时配置好开发环境&#xff0c;如果取消了配置&#xff0c;后续再配置会比较麻烦 1、点击工具图标运行 2、在欢迎页中点击“Agree” 3、默认“Do not import setting”&#xff0c;点击“OK” 3、此片设置Nodejs和Ohpm的安装&#xff0c;其中&#xff0c; …

计算机网络学习笔记(五):运输层(待更新)

目录 5.1 概述 5.1.1 TCP协议的应用场景 5.1.2 UDP协议的应用场景 5.2 三大关系 5.2.1 传输层协议和应用层协议之间的关系 5.3 用户数据报协议UDP(User Datagram Protocol) 5.3.1 UDP的特点 5.3.2 UDP的首部 5.4 传输控制协议TCP(Transmission Control Protocol) 5.…

系列一、Shiro概述

一、概述 Shiro是一款主流的Java安全框架&#xff0c;不依赖任何容器&#xff0c;可以运行在JavaSE 和 JavaEE项目中&#xff0c;它的主要作用是对访问系统的用户进行身份认证、授权、会话管理、加密等操作。 一句话&#xff1a;Shiro是一个用来解决安全管理的系统框架&#x…

MVCC中的可见性算法

在之前的文章 MVCC详解-CSDN博客中我们已经介绍过了MVCC的原理&#xff08;read viewundo log&#xff09;&#xff0c;今天来详细的说一下readview的匹配规则&#xff08;可见性算法&#xff09; 隔离级别在RC&#xff0c;RR的前提下 Read View是如何保证可见性判断的呢&#…

一篇带你精通php

华子目录 什么是phpphp发展史平台支持和数据库支持网站静态网站和动态网站的区别静态网站动态网站的特点 关键名词解析服务器概念IP的概念域名DNS端口 web程序的访问流程静态网站访问流程动态网站访问流程 php标记脚本标记标准标记&#xff08;常用&#xff09; php注释 什么是…

[LeetCode]-225. 用队列实现栈

目录 225. 用队列实现栈 题目 ​思路 代码 225. 用队列实现栈 225. 用队列实现栈 - 力扣&#xff08;LeetCode&#xff09;https://leetcode.cn/problems/implement-stack-using-queues/description/ 题目 请你仅使用两个队列实现一个后入先出&#xff08;LIFO&#xff0…

【Servlet】 三

本文主要介绍了基于serlvet的表白墙项目的编写. (附完整代码) 一.JS基础 作为后端开发,对于前端的要求是能前端代码能看懂七七八八 . JS是一个动态弱类型的编程语言 1. let/war定义变量 (推荐使用let) 2.querySelector是浏览器提供api , 能够获取到页面的元素的 (js的目的就…