刷题笔记。

前言

以前在学校只专注于研究感兴趣的技术,开发一些乱七八糟的项目,几乎没刷过题,现在面试笔试被各种毒打。现在开始亡羊补牢。只记录算法或思路,不记录题目,不间断更新,顺便重拾C++


排序算法

冒泡排序

void BubbleSort(int arr[], int len)
{
	for (int i = 0; i < len; i++) {
		//因为第len - i后面的元素是已经排好了,所以每次只需要遍历 0 到 len - 1 - i 个元素
		for (int j = 0; j < len - 1 - i; j++) {
			//若当前元素大于下一个元素,则交换
			if (arr[j] > arr[j + 1]) {
				int temp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = temp;
			}
		}
	}
}

选择排序

void SelectSort(int arr[], int len) 
{
	for (int i = 0; i < len; i++) {

		int minIndex = i;

		//从元素下一个开始往后找最小的
		for (int j = i + 1; j < len; j++) {
			if (arr[minIndex] > arr[j]) {
				minIndex = j;
			}
		}

		//将当前下标的元素与最小元素交换
		int temp = arr[i];
		arr[i] = arr[minIndex];
		arr[minIndex] = temp;
	}
}

插入排序

void InsertSort(int arr[], int len) 
{
	//第一个不用排,从1开始
	for (int i = 1; i < len; i++) {

		int cur = arr[i];//先拿出要进行插入的元素
		int insertIndex = 0;//下面循环中如果一直找不到可以插的位置,直到循环结束,说明目前它最小,就插到第一个元素的位置

		//往前找有没有还可以插入的地方,同时把排好的元素向后移动
		for (int j = i - 1; j >= 0; j--) {
			if (cur < arr[j]) {
				arr[j + 1] = arr[j];//当前选择的元素比排好的小,排好的元素后移
			} else {
				insertIndex = j + 1;//插入位置为当前与cur比较的元素的前面
				break;
			}
		}

		arr[insertIndex] = cur;
	}
}

希尔排序

//希尔排序 使数据逐渐变得有序,最后在比较有序的数据里进行插入排序,减少插入排序时移动元素的次数
void ShellSort(int arr[],int len)
{
	int gap = len / 2;
	while (gap > 0)
	{
		//外面一层循环次数为数组的组数,里面一层为每组的元素个数
		for (int i = gap; i < len; i++) {

			int cur = arr[i];//先拿出要进行插入的元素
			int insertIndex = i - gap;//下面循环中如果一直找不到可以插的位置,直到循环结束,说明目前它最小,就插到第一个元素的位置

			//往前找有没有还可以插入的地方,同时把排好的元素向后移动,
			//不过希尔排序需要跨步,把一个数组看成若干个交错在一起的数组,gap就是跨的距离,直到gap = 1,就变成普通插入排序
			for (int j = i - gap; j >= 0; j -= gap) {
				if (cur < arr[j]) {
					arr[j + gap] = arr[j];//当前选择的元素比排好的小,排好的元素后移
				} else {
					insertIndex = j + gap;//插入位置为当前与cur比较的元素的前面
					break;
				}
			}

			arr[insertIndex] = cur;
		}
		gap /= 2;
	}
}

归并排序

//将数组start到mid 和 mid+1到end进行合并
void MergeArr(int arr[], int start, int mid, int end, int temArr[])
{
	int i = start, j = mid + 1, k = 0;

	//比较长度相同部分
	while (i <= mid && j <= end) {
		if (arr[i] < arr[j])
			temArr[k++] = arr[i++];
		else
			temArr[k++] = arr[j++];
	}

	//i超出部分
	while (i <= mid) {
		temArr[k++] = arr[i++];
	}

	//j超出部分
	while (j <= end) {
		temArr[k++] = arr[j++];
	}

	for (int i = 0; i < k; i++)
		arr[start + i] = temArr[i];
}

void RecursionMergeSort(int arr[], int start, int end, int temArr[])
{
	if (start >= end) return;//结束递归的条件

	//排序两边的数组
	int mid = (start + end) / 2;
	RecursionMergeSort(arr, start, mid, temArr);
	RecursionMergeSort(arr, mid + 1, end, temArr);

	//将排好的数组合并
	MergeArr(arr, start, mid, end, temArr);
}

//归并排序
void MergeSort(int arr[],int len)
{
	int* temArr = (int*)malloc(len * sizeof(int));
	RecursionMergeSort(arr, 0, len - 1, temArr);
}

快速排序

void Swap(int arr[], int i, int j) {
	int temp = arr[i];
	arr[i] = arr[j];
	arr[j] = temp;
}

//快速排序 前后指针法
void QuckSort(int arr[], int start, int end)
{
	if (start >= end) return; //递归结束条件

	//取第一个元素为基准
	int key = arr[start];
	int prev = start;
	int latter = prev + 1;

	while (latter <= end) {
		//若后面的值比key小,交换到前面去,不要交换第一个位置,交换完成后latter后移
		if (arr[latter] < key) {
			Swap(arr, ++prev, latter);
		}
		latter++;
	}

	//当latter越界后prev所在的位置就是数列中比key小的最后那个数,交换它们
	Swap(arr, start, prev);

	//继续排序左右两边 prev所指的key不用继续排列了
	QuckSort(arr, start, prev - 1);
	QuckSort(arr, prev + 1, end);
}

动态规划

个人理解动态规划就是把一个大的问题分解成逐个小问题,把每个小问题的计算结果记录起来,后续的计算可以引用之前已经计算好的部分,避免大量的重复计算。
例如:
最长上升子序列
思路:
例如从[6,3,1,5,2,3,7]找最长上升子序列长度,先分解成小问题,用一个数组bp来记录每个小问题的结果:

[6]:由于只有一个元素,所以长度就是1,序列为[6],bp[0]=1。

[6,3]:已知[6]的最长上升子序列是1,3需要往前找有没有比它小的元素,有的话最长上升子序列长度=前面所有比它小的元素里,最长上升子序列最大的长度+1,没有的话就是它自己而已那就是1,很显然它前面只有6,3不大于6,所以[6,3]最长上升子序列长度为1,bp[1]=1。

[6,3,1]:同上,还是1,bp[2]=1。

[6,3,1,5]:5的前面有比它小的1和3,取[1,5]时,bp[3]=bp[2]+1;取[3,5]时,bp[3]=bp[1]+1;对比一下[1,5]和[3,5],找最长的,发现一样长,所以取其一即可,序列为[3,5]或者[1,5],bp[3]=2。

[6,3,1,5,2]:2前面比它小的只有1,[6,3,1]最大长度为1,bp[4]=bp[2]+1=2,序列为[1,2]。

[6,3,1,5,2,3]:3前面有1和2可取,[6,3,1]最大长度是1,[6,3,1,5,2]最大长度是2,取2比取1大,所以bp[5]=bp[4]+1=3,序列为[1,2,3]。

[6,3,1,5,2,3,7]:7前面的数都比它小,找最大的,最大是bp[5]=3,所以bp[6]=bp[5]+1=4,所以数组[6,3,1,5,2,3,7]的最长上升子序列为[1,2,3,7],长度为4。

可以看到这里使用数组bp来记录之前的计算结果,用到的时候去查一下即可,避免了重复运算。例如计算[6,3,1,5,2,3,7]的最长上升子序列长度时,需要知道[6,3,1,5,2,3]里有没有比7小的元素,如果有,那么这个元素当前的最长上升子序列长度是多少。如果没有bp进行记录,又需要一个个去计算[6,3,1,5,2,3]每个比7小的元素为结尾的最长上升子序列长度。

public int LIS(List<int> arr)
{
    if (arr.Count == 0) return 0;

    int max = 0;
    int[] bp = new int[arr.Count];
    bp[0] = 1;//当i=0,长度是1

    for (int i = 1; i < arr.Count; i++)
    {
        //初始化最小长度是1,不能是0
        if (bp[i - 1] == 0) bp[i - 1] = 1;

        for(int j = i - 1; j >= 0; j--)
        {
            //在后面比当前元素小的元素里,查找最大的上升子序列长度
            if (arr[i] > arr[j] && bp[j] + 1 > bp[i])
            {
                bp[i] = bp[j] + 1;
                max = bp[i] > max ? bp[i] : max;
            }
        }
    }
    return max;
}

一些题型解题思路

统计素数(或者叫质数)个数

//暴力方式
/*
* 3*6 和 6*3是一样的,乘法交换律
* 判断了3*6就没必要再去判断6*3,所以循环次数为sqrt(n),也可表示为i*i<n
* 是偶数也没必要判断
*/
bool isPrimer(int n)
{
	//i * i <= n不能是i * i < n,如果n能被i整除也不是素数,例如9,如果仅仅是小于号就判断为素数了
	for (int i = 2; i * i <= n; i++) {
		if (n % i == 0) return false;
	}
	return true;
}

int PrimerCount(int n)
{
	if (n < 2) return 0;
	if (n == 2) return 1;
	int count = 1;//2算一个
	for (int i = 3; i <= n; i+=2) {
		if (isPrimer(i)) count++;
	}
	return count;
}

//埃筛法
/*
* 每次找到素数后都去把它的倍数标记起来,能被它整除的都不是素数了
* 每个合数(不是素数就叫合数)都有素数因子
*/
int PrimerCountAS(int n)
{
	if (n < 2) return 0;

	int count = 0;
	bool *isPrimer = new bool[n + 1];
	memset(isPrimer, true, n * sizeof(bool));

	for (int i = 2; i <= n; i++) {
		if (isPrimer[i]) {
			count++;
			//查找i的倍数并标记不是素数
			for (int j = i + i; j <= n; j += i) {
				isPrimer[j] = false;
			}
		}
	}

	delete[] isPrimer;
	return count;
}

数组中3数最大乘积

同符号直接取最大前3个数相乘,有正有负则保证结果为正的前提下,取绝对值最大的3

判断链表是否有环

双指针算法,慢指针每次位移一个位置,快指针比慢指针快一步,每次位移两个位置。
如果进入到环,因为指针比较快会迟早从后面追上慢指针。
当快指针==慢指针,代表有环,但快指针为null或它的下一个为null,则遍历结束,没有环。

另一个方法:
每次遍历到一个节点的时候,给它标上一个下标,当发现下一个节点被标记过,则有环

求最长为n的数组能构成的二叉搜索树的个数

其实就是求卡塔兰数,它的递推公式:
在这里插入图片描述

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
"Labuladong"是一个著名的算法题解博主,他的刷题笔记非常受欢迎。他的笔记具有以下几个特点: 1. 思路清晰:Labuladong的刷题笔记总是能够很清晰地阐述解题思路。他善于将复杂的问题简化为易于理解的小问题,并通过逐步引入关键概念和方法,帮助读者理解并掌握解题思路。 2. 逻辑严谨:Labuladong的刷题笔记经过深思熟虑,逻辑严谨。他会从问题的定义开始,逐步引入相关的概念和解题思路,循序渐进地解决问题。这种严谨的逻辑结构有助于读者理解和消化算法的核心思想。 3. 举例详细:Labuladong的刷题笔记通常会通过具体的例子来说明解题思路。这种举例的方式不仅能够帮助读者更好地理解解题方法,还可以帮助读者更好地应用这些方法解决其他类似的问题。 4. 知识点整合:Labuladong的刷题笔记不仅仅是一个题解,而是将相关的算法知识点整合起来,构建出一个完整的学习体系。他会引入一些底层的算法原理,将不同的解题方法进行比较和总结。这种整合的方式能够帮助读者更好地理解和掌握算法的本质。 总之,Labuladong的刷题笔记以其思路清晰、逻辑严谨、举例详细和知识点整合等特点,为广大读者提供了一种深入学习和理解算法的有效途径。通过阅读他的刷题笔记并进行实践,读者能够提高解题能力,并在面对各种算法问题时能够找到正确、高效的解决方法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

吾无法无天

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值