day07 原地堆排序

前言

前面我们学习了堆排序的两种实现方式,不知道大家还记不记得。我们之前学习的堆排序都是通过把一个无序的数组复制到堆中,然后进行排序,但是这样一来,我们就需要开辟额外的存储空间去存储。那么我们今天就来学习如何去解决这个问题,然后对堆排序进行优化。

思想:

其实我们可以的数组本身就是一个堆,只不过并不是一个最大堆,那么通过之前的学习我们可以通过shitDown操作去把数组变成一个最大堆,这个操作完全可以数组中操作,无序进行额外的空间开辟。

在进行了shitDown之后,此时我们的数组就是一个最大堆了,此时我们最大的元素就放在第一个位置。我们可以每次将最大的元素与数组最后边的数字进行交换,此时我们最大的元素就放到了正确的位置上,此时第一个位置上的元素不符合最大堆的规范,我们对这个位置进行shitDown操作,然后对【0 ,n-1】区间重复前边的操作,直至只剩下一个元素。

 

注意要点:

第一幅图就是对堆排序的一个直观的描述。不断的把最大值放到数组的最后边,然后数组长度进行缩减一个索引。最后数组只剩下一个元素的时候就不需要再进行操作了,此时的数组就已经是有序的了。

这里要注意,由于之前我们的堆中,我们是从索引为1的位置开始存储的,但是原地排序中,由于我们是在原来的数组中,原地排序,所以我们的数组的下标是从0开始,那么我们进行shitDown操作的时候,对于最后一个非叶子节点的判断就需要进行一下改变了。具体参照第二张图

 

 

 

代码实现:

package com.smarking.lzy.part2;

import com.smarking.lzy.part1.SortTestHelper;

/*
 * 原地堆排序的实现
 * 在目标数组中进行排序
 * 思想:每次将最大的元素放到本轮区间的最后一个位置,直至只剩下一个元素
 * **/
public class HeapSort3 {
	
	/*
	 * 排序入口
	 * arr:目标数组
	 * size:数组大小
	 * */
	public static void heapSort3(int [] arr) {
		int size = arr.length;
		if(arr == null) {
			throw new RuntimeException("arr is null!");
		}
		
		//对数组进行heapify操作
		for(int i = (size - 2)/2;i >= 0;i--) {
			shitDown(arr,i,size);
		}
		
		for(int i = size-1;i >= 1;i--) {
			SortTestHelper.swap(arr, i, 0);
			shitDown(arr,0,i);//应该是在[0,n-1]范围内进行heapify,因为最后一个位置不考虑
		}
	}
	
	/**
	 * 对index位置上的元素进行位置的调整,使得整个堆符合最大堆的规范
	 * 
	 * ps:
	 * 这里的这个shitDown和之前的那个shitDown不同之处:
	 * 		必须注意,两个shitDown不同的地方在于,这里的这个shitDown并不是对整个数组进行调整,而是对数组中我们指定的范围内的元素进行调整
	 * 我们之前写的那个shitDown是针对整个数组进行调整,所以两个shitDown对边界问题的处理不一样,必须得想明白。
	 * 		弄清楚size的实际意义!
	 * */
	private static void shitDown(int [] arr ,int index,int size) {
		while((index*2+1) < size && index >= 0) {
			int j = index*2+1;
			if((j+1) < size && arr[j+1] > arr[j]) {
				if(arr[index] > arr[j+1]) {
					return;
				}
				j+=1;
			}else{
				if(arr[index] > arr[j]) {
					return;
				}
			}
			SortTestHelper.swap(arr, index, j);
			index = j;
		}
	}
	
	
	public static void main(String[] args) {
		int testTime = 1000;
		int maxValue = 100;
		int maxSize = 10000;
		boolean success = true ;
		Heap heap = null;
		for(int i = 0;i < testTime;i++) {
			int [] arr1 = SortTestHelper.generateRandomArray(maxSize, maxValue);
			int [] arr2 = SortTestHelper.copyArray(arr1);
			int [] arr3 = SortTestHelper.copyArray(arr1);
			SortTestHelper.comparator(arr2);
			heapSort3(arr3);
			if(!SortTestHelper.isEqual(arr2, arr3)) {
				success = false;
				SortTestHelper.printArray(arr1);
				break;
			}
		}
		
		if(success) {
			System.out.println("Nice!!!");
		}else {
			System.out.println("Fuck!");
		}
	}
	
}

改进版代码:

package com.smarking.lzy.part2;

import com.smarking.lzy.part1.SortTestHelper;

/**
 * 把原地堆排序重新写一下
 * 巩固一下
 * */
public class HeapSort4 {
	
	public static void heapSort4(int [] arr) {
		if(arr == null || arr.length == 0) {
			return;
		}
		
		int count = arr.length;
		for(int i = (count - 2)/2 ;i >= 0;i--) {
			shitDown(arr,count,i);
		}
		
		for(int i = count - 1;i >= 1;i--) {
			SortTestHelper.swap(arr, i, 0);
			shitDown(arr,i,0);
		}
	}
	
	private static void shitDown(int [] arr,int count,int index) {
		while((index*2 + 1) < count && index >= 0) {
			int j = index*2+1;
			if((j+1) < count && arr[j+1] > arr[j]) {
				j+=1;
			}
			
			if(arr[index] > arr[j]) {
				return;
			}
			
			SortTestHelper.swap(arr, index, j);
			index = j;
			}
	}
	
	
	public static void main(String[] args) {
		int testTime = 100000;
		int maxValue = 100;
		int maxSize = 10000;
		boolean success = true ;
		Heap heap = null;
		for(int i = 0;i < testTime;i++) {
			int [] arr1 = SortTestHelper.generateRandomArray(maxSize, maxValue);
			int [] arr2 = SortTestHelper.copyArray(arr1);
			int [] arr3 = SortTestHelper.copyArray(arr1);
			SortTestHelper.comparator(arr2);
			heapSort4(arr3);
			if(!SortTestHelper.isEqual(arr2, arr3)) {
				success = false;
				SortTestHelper.printArray(arr1);
				break;
			}
		}
		
		if(success) {
			System.out.println("Nice!!!");
		}else {
			System.out.println("Fuck!");
		}
	}

}

 

<think>嗯,用户提到在LeetCode上很多题目读不懂,甚至不会写,总是需要看答案。跟着一个博主学习科学刷题,比如滑动窗口、双指针、二分算法,但做两个滑动窗口题就完全不会。首先,我需要分析用户的问题所在。可能的原因有几个:对题目理解不够,算法掌握不牢固,或者缺乏解题思路的训练。 用户读不懂题目,可能是因为对题目描述中的术语或结构不熟悉,或者英语理解有困难(如果题目是英文的话)。这时候需要建议用户多练习题目,先尝试理解题意,划出关键点,比如输入输出样例,再结合示例来辅助理解。 接下来,用户提到跟着科学刷题的方法,但遇到困难。滑动窗口、双指针、二分法这些确实是常见的算法,但掌握它们需要系统性的学习和练习。用户可能在学习了理论后,没有足够的实践,或者没有正确应用这些算法到具体问题中。可能需要建议用户从基础题目开始,逐步提升难度,同时注重理解每个算法的适用场景和解题步骤。 另外,用户可能缺乏解题的思路训练。比如,遇到问题不知道如何分解,如何将问题转化为已知的算法。这时候可以建议用户先尝试暴力解法,再分析优化的方向,比如是否存在重复计算,是否可以通过滑动窗口减少时间复杂度。同时,总结常见题型的解题模式,比如滑动窗口常用于子串、子数组问题,双指针用于有序数组或链表的问题等。 用户看答案后可能只是记住解法,而没有真正理解背后的原理,导致遇到类似题目仍不会。需要强调理解的重要性,建议用户在看完答案后,自己复现解题过程,并尝试用不同的方法解决同一问题,加深理解。 此外,制定合理的刷题计划也很重要。建议用户分专题练习,每个专题集中攻克,比如两周专攻滑动窗口,每天2-3题,从简单到困难。同时,整理错题,定期复习,分析错误原因,避免重复犯错。 最后,鼓励用户保持耐心,算法学习是一个长期过程,需要持续的努力和正确的学习方法。避免急于求成,应注重基础积累和思维训练。</think>你遇到的问题非常普遍,尤其是刚接触算法训练的新手。以下是分步骤的解决方案,帮你系统性地突破瓶颈: 一、先解决「读不懂题」的问题 1. 刻意训练题目解析能力 - 用「三步拆解法」: $$1.\ 划关键词 \quad 2.\ 画示例图 \quad 3.\ 找输入输出规律$$ - 例:看到「最长无重复子串」,立刻标记「连续」「无重复」「最长」三个核心条件 2. 建立常见术语词典 - 空间复杂度$O(1)$ → 不能用额外数据结构 - 原地修改 → 必须直接操作输入参数 - 子序列 vs 子串 → 前者不连续,后者必须连续 二、掌握科学刷题的正确姿势 1. 专题突破法(以滑动窗口为例): ```text Day1:学习模板 → 做《最小覆盖子串》→ 卡住就研究动画演示 Day2:同类题《长度最小的子数组》→ 强制自己先写伪代码 Day3:对比《水果成篮》→ 总结窗口移动条件的异同 ``` 2. 必须掌握的算法模板(以滑动窗口为例): ```python def sliding_window(s: str): left = 0 window = defaultdict(int) for right in range(len(s)): window[s[right]] += 1 # 右扩窗口 while 满足收缩条件: # 关键!找到具体条件 window[s[left]] -= 1 if window[s[left]] == 0: del window[s[left]] left += 1 # 左缩窗口 # 在此更新答案 ``` 三、建立解题思维模型 1. 遇到新题时的思考路径: $$ \begin{cases} \text{暴力解法复杂度} & \text{(先写出O(n²)方案)} \\ \text{重复计算点在哪} & \text{(找优化突破口)} \\ \text{数据结构选择} & \text{(哈希表??单调队列?)} \\ \text{边界条件} & \text{(空输入?负数?溢出?)} \end{cases} $$ 2. 经典题型对应表: | 算法 | 特征信号 | 例题难度梯度 | |---------------|----------------------------------|------------------------| | 滑动窗口 | 「连续子数组」「最值」「计数」 | 简单→困难:209→76→992 | | 双指针 | 「有序数组」「去重」「合并」 | 167→15→42(接雨水) | | 二分查找 | 「排序」「搜索」「O(logn)要求」 | 704→34→410(困难) | 四、实战训练建议 1. 初学阶段每日计划: ``` AM 9:00-10:30 学习新算法(看讲解视频+手写模板) AM 11:00-12:00 做相关简单题(LC题库按标签筛选) PM 3:00-4:30 订正错题(必须手写debug过程) PM 8:00-9:00 复习同类题(对比不同解法的异同) ``` 2. 推荐入门题目清单(滑动窗口专题): - 热身:209. 长度最小的子数组(暴力→滑动窗口优化) - 进阶:3. 无重复字符的最长子串(哈希表+窗口) - 挑战:76. 最小覆盖子串(复合条件的窗口维护) 关键提醒:当连续3题都需要看答案时,说明该专题基础不牢,应该: 1. 回到该算法的教学视频/图解文章 2. 用白纸重写算法步骤 3. 用简单测试用例手动模拟执行过程 记住这个进步公式: $$ \text{有效刷题量} = \frac{\text{独立思考时间}}{\text{看答案时间}} \times \log(\text{总结次数}) $$ 坚持2-3个月系统性训练后,你会发现原来需要看答案的题目,现在能自己推导出解法了。保持刻意练习,量变一定会引发质变!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值