LeetCode分类算法笔记1——分治

本文深入探讨了分治算法的基本思想及其在逆序对个数计算、众数查找、最大子序和求解以及幂函数计算等典型问题中的应用。通过实例解析,详细介绍了分治策略如何将复杂问题分解为子问题并最终求解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

算法学习笔记——LeetCode分类算法

1.分而治之

1.主要思想

对一个大规模的问题运用分治策略,就是将这个大规模的问题划分成若干小规模的子问题,然后对这这些子问题进行求解,最后按划分将求得的结果进行合并,就得到了原问题的解。分治思想就是将分解,即将大问题分解为若干小问题,然后各个击破。

利用分治算法求解问题的步骤可以如下表示:

1.分:将大问题分解成众多小问题;

2.治:求解各个小问题;

3.合:将解决的问题合并

【注】分治是一种策略,如果一个大问题进行一次分解后仍然无法轻易解出,则可继续对其子问题进行划分,直至子问题解出。

分治过程可以用树状图表示如下:

分而治之

 

2.分治策略的适用情况

  1. 原问题的计算复杂度随问题规模的增加而增加
  2. 原问题能够被分解为更小的子问题
  3. 子问题的结构和性质与原问题一样,并且相互独立,子问题之间不包含公共的子子问题(子问题的子问题)
  4. 原问题分解出的子问题可以合并为该问题的解

3.分治策略的应用

1)逆序对个数

相关概念:

逆序对:一个序列(a1,a2,…,an),设有整数i、j,0 < i < j <n+1,且ai > aj,则(ai,aj)为一个逆序对。

问题描述:求序列(a1,a2,…,an)中逆序对个数。

解题思路:

1.暴力破解

选取序列最左端的元素,比较其与其后各元素的大小,但凡有比其小者将计数器加1,比完之后再对第二个元素与其后各元素进行比较,依次反复进行,直至全部找出。

2.分而治之

将序列分为左右两个子序列A、B,a、b分别为A、B中的元素,假设A中a左侧元素全部小于a,右侧全部大于a,B中b左侧元素全部小于b,右侧全部大于b,若a>b,则(a,b)为一个逆序对,同时对于B中b左侧的所有元素b1、b2、……,(a,b1)、(a,b2)……也都为逆序对。

直观描述如下图所示:

求逆序对

 

伪代码描述:

 

2)众数

问题描述:

给定一个大小为 n 的数组,找到其中的众数。众数是指在数组中出现次数大于 [n/2] 的元素。(此处假设数组是非空的,并且给定的数组总是存在众数。)

解题思路:

将数组从中间分为左右两个子数组,求左右两个子数组的众数,比较左右两数组的众数是否相同,相同则返回此众数;若不相同,则比较左右各众数在数组中出现的次数,返回次数多者。

此问题解题思路的直观描述如下:

【注】图中b1b2以及蓝色元素为数组中的众数

求众数

python实现:

def Mode(nums):
    '''nums为一个实数序列'''
    if len(nums) == 0:
        return None;
    if len(nums) == 1:
        return nums[0];
    # 将序列划分为左右两部分
    left = Mode(nums[:len(nums) // 2])
    right = Mode(nums[len(nums) // 2:])
    # 判断左右两部分的众数
    if left == right:
        return left
    if nums.count(left) > nums.count(right):
        return left
    else:
        return right

C++实现:

header.h文件

/*定义数组类*/
class Array {
protected:
	double *A, *leftArray, *rightArray, left, right;
	int length;
public:
	Array(int size) {
		if (size <= 0)
			cerr << "数组长度不能为负,数组创建失败!";
		A = new double[size];
		length = size;
	}
	~Array() {
		delete[]A;
	}
	Array(int size, double &B) {
		*A = B;
		length = size;
	}
	double Mode();    //求众数函数
	int count(double a);    //求数组元素个数
};

Mode.cpp文件

// 数组中与a相同的元素个数
int Array::count(double a) {
	int count = 0;
	for (int i = 0; i < length; i++) {
		if (A[i] == a)
			count ++;
	}
	return count;
}
// 求众数
double Array::Mode() {
	if (length == 1)
		return A[0];
	//拆分问题
	leftArray = new double[length / 2];
	rightArray = new double[length - length / 2];
	for (int i = 0; i < length; i++) {
		if (i < length / 2)
			leftArray[i] = A[i];
		else
			rightArray[i - length / 2] = A[i];
	}
	Array *Left = new Array(length / 2, *leftArray);
	Array *Right = new Array(length - length / 2, *rightArray);
	left = Left->Mode();    //递归
	right = Right->Mode();
	//返回数量多的众数
	if (left == right)
		return left;
	if (Left->count(left) > Right->count(right)) {
		Left->~Array();
		Right->~Array();
		return left;
	}
	else {
		Left->~Array();
		Right->~Array();
		return right;
	}
}

3)最大子序和

问题描述:

给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

解题思路:

将数组从中间一分为二,分别求两个子数组的最大子序和,然后将二者的最大子序和相加,比较三者的大小,最大的即为原数组的最大子序和。数组的子数组同这样分治,直至数组分至最小单元。

此问题的解题思路直观描述如下:

【注】b1b2为两子序列各自的最大子序和,原数组的最大子序和及b1b2b2+b1中最大者,如图中绿色返回语句所示

最大子序和

python实现:

def maxSubArray(Array):
    n = len(Array)
    if(n == 0):
        return None
    if(n == 1):
        return Array[0]
    # 将数组拆分为左右两部分(分)
    left = maxSubArray(Array[: n // 2])
    right = maxSubArray(Array[n // 2:])
    #(治)
    # 求左子数列最大子序和
    temp = 0
    left_max = Array[n // 2 - 1]
    for i in Array[:n // 2][::-1]:
        temp += i
        left_max = max(left_max, temp)
    # 求右子数列最大子序和
    temp = 0
    right_max = Array[n // 2]
    for i in Array[n // 2:]:
        temp += i
        right_max = max(right_max, temp)
    # 返回最大结果(合)
    return max(left_max, right_max, left_max + right_max)

C++实现:

int maxSubArray(int *A) {
	int n = sizeof(A);
	if (n == 0) {
		cerr << "This array is none!";
		return 0;
	}
	if (n == 1)
		return A[0];
	// 拆分数组
	int *leftArray = new int[n / 2];
	int *rightArray = new int[n - n / 2];
	for (int i = 0; i < n; i++) {
		if (i < n / 2)
			leftArray[i] = A[i];
		else
			rightArray[i - n / 2] = A[i];
	}
	int left = maxSubArray(leftArray);
	int right = maxSubArray(rightArray);
	// 求左子序列的最大子序和
	int temp = 0;
	int left_max = leftArray[n / 2 - 1];
	for (int i = n / 2 - 1; i >= 0; i--) {
		temp += leftArray[i];
		if (temp > left_max)
			left_max = temp;
	}
	// 求右子序列的最大子序和
	temp = 0;
	int right_max = rightArray[0];
	for (int i = 0; i < n / 2; i++) {
		temp += rightArray[i];
		if (temp > right_max)
			right_max = temp;
	}
	// 求左右子序和与两和之和的最大值
	temp = left_max + right_max;
	if (left_max > temp)
		temp = left_max;
	if (right_max > temp)
		temp = right_max;
	delete[] leftArray;
	delete[] rightArray;
	return temp;
}

4)幂函数

问题描述:实现 pow(x, n) ,即计算 x 的 n 次幂函数。

解题思路:

用分治进行幂函数求解可以将幂函数分解为若干低次幂的乘积,这里使用二分,分解方法如下:

指数为偶数情况:

A^(2n) = (A^2)^n

指数为奇数情况:

A^(2n+1) = A * A^(2n)

python实现:

def Power(x, n):
    if n == 0:
        return 1
    if n == 1:
        return x
    if n < 0:
        return Power(1 / x, -n)
    # 若指数不为2的倍数,则提出一个底数
    if n % 2 == 1:
        return x * Power(x, n-1)
    # 若指数为2的倍数,则将底数乘方,指数减倍
    return Power(x * x, n / 2)

C++实现:

double Power(double x, int n) {
	if (n == 0)
		return 1;
	if (n < 0)
		return Power(1 / x, -n);
	//拆解幂函数
	if (n % 2 == 0)
		return Power(x * x, n / 2);
	else
		return x * Power(x, n - 1);
}

 

<think>好的,用户正在寻找关于数据结构的大题总结或学习资料。首先,我需要理解用户的具体需求。他们提到了“大题”,可能是指考试或面试中常见的综合性问题,需要详细解答的那种。用户还提到了“总结”和“学习笔记”,说明他们希望有结构化的资料,便于复习或深入学习。 接下来,我要回顾现有的引用内容。用户提供的引用[1]和[2]分别涉及集合(set)数据结构的基本概念以及数据结构与算法的关系。这些可以作为回答的一部分,但用户可能需要更广泛的资源,覆盖更多数据结构类型,比如链表、树、图等。 考虑到用户需要大题总结,我应该推荐涵盖常见问题类型的学习资料,比如算法分析、复杂度计算、实际应用题等。同时,需要确保推荐的资源包含详细的解题步骤和示例代码,这对理解和练习非常重要。 然后,我需要整理一些常见的数据结构大题主题,例如树和图的遍历、动态规划、排序算法比较等。这些主题通常在考试和面试中出现频率较高。此外,结合引用[2]提到的数据结构与算法的关系,强调两者结合学习的必要性,可能对用户有帮助。 用户可能还需要知道如何有效利用这些资料,比如分模块练习、理论与实践结合的方法。因此,在回答中建议分步骤的学习方法,并推荐具体的学习资料类型,如教材、在线课程、题库等,会更有指导性。 最后,生成相关问题时要围绕用户的需求,可能涉及如何准备数据结构考试、推荐的学习资源、常见题型的解题技巧等。确保问题能够引导用户进一步探索或解决他们的疑问。</think>### 数据结构大题总结与学习资源推荐 #### 一、核心数据结构大题类型总结 1. **线性表与链表** - 实现单链表/双向链表的插入、删除、反转操作 - 时间复杂度分析比较:顺序表 vs 链表 - 典型例题:合并两个有序链表[^2] 2. **树与二叉树** - 遍历算法实现(前序/中序/后序/层次) - 二叉树重构问题(根据遍历序列构造树) - 平衡二叉树(AVL树)的旋转操作 - 哈夫曼编码与带权路径计算 3. **图论算法** - 最短路径算法(Dijkstra、Floyd) - 最小生成树(Prim、Kruskal) - 拓扑排序与关键路径 - 典型例题:社交网络关系分析 4. **排序与查找** - 各类排序算法比较(快速排序/归并排序/堆排序) - 散列表冲突解决方法(链地址法/开放定址法) - B树/B+树的结构特性与插入删除过程 #### 二、推荐学习资料 1. **经典教材** - 《算法导论》(包含复杂度证明与算法设计) - 《数据结构与算法分析——C语言描述》(Mark Allen Weiss著) - 《剑指Offer》(面试题精选) 2. **在线资源** - LeetCode经典题目分类训练(树/图/动态规划) - VisuAlgo数据可视化学习平台(含动态演示) - MIT OpenCourseWare 6.006课程视频 3. **大题解题方法论** - 分治法模板:分解问题 → 解决子问题 → 合并结果 - 动态规划四步法:状态定义 → 转移方程 → 初始条件 → 计算顺序 - 回溯法剪枝技巧:约束条件与限界条件设置 #### 三、典型大题示例(树相关) **题目**:给定二叉树的前序遍历和中序遍历序列,重建该二叉树 **解法**: ```python def buildTree(preorder, inorder): if not preorder: return None root_val = preorder[0] root = TreeNode(root_val) idx = inorder.index(root_val) root.left = buildTree(preorder[1:idx+1], inorder[:idx]) root.right = buildTree(preorder[idx+1:], inorder[idx+1:]) return root ``` **复杂度分析**:时间复杂度$O(n^2)$(可通过哈希表优化到$O(n)$)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值