左程云算法学习
左程云算法学习
二分法
- 二分查找法:704.二分查找
- 代码如下:
1 |
|
- 变式 1:查找第一个和
target
相等的元素 - 代码如下:
1 |
|
- 变式 2:查找第一个大于等于
target
的元素 - 代码如下:
1 |
|
- 变式 3:查找最后一个小于等于
target
的值 - 代码如下:
1 |
|
异或运算
题目一
- 如何不用额外变量交换两个数
- 关键部分:
1 |
|
- 代码:
1 |
|
- 证明:
- 1、异或可以看成不进位二进制位加法;
- 2、具有
a^a=0
和a^0=a
以及a^b=b^a
的性质。 - 各个步骤详细推导如下:
题目二
- 一个数组中有一种数出现了奇数次,其他数都出现了偶数次,怎么找到并打印这种数?
- 关键部分:
1 |
|
- 代码:
1 |
|
- 证明:
- 1、具有
a^a=0
和a^0=a
的性质; - 2、偶数个数据累积异或后是
0
,奇数个数据累积异或后就剩下它自身。
- 1、具有
题目三
- 怎么把一个
int
类型的数,提取出最右侧的1
出来。 - 关键部分:
ans=a&(-a);
- 代码:
1 |
|
- 证明:
题目四
- 一个数组中有两个不相等的数出现了奇数次,其他数都出现了偶数次,怎么找到并打印这两个数?
- 关键代码:
1 |
|
- 代码:
1 |
|
题目五
- 一个数组中有一种数出现
K
次,其他数都出现了M
次,M>1
,K<M
,请找到出现了K
次的数,要求额外空间复杂度O(1)
,时间复杂度O(N)
。 - 关键代码:
1 |
|
- 代码:
1 |
|
数组
题目一
- 有序数组的平方:977.有序数组的平方
- 思路:
- 能够找到数组
nums
中负数与非负数的分界线,那么就可以用类似「归并排序」的方法。 - 具体地,设
index
为数组nums
中负数与非负数的分界线,也就是说,nums[0]
到nums[index]
均为负数,而nums[index+1]
到nums[len−1]
均为非负数。当我们将数组nums
中的数平方后,那么nums[0]
到nums[index]
单调递减,nums[index+1]
到nums[len−1]
单调递增。 - 由于我们得到了两个已经有序的子数组,因此就可以使用归并的方法进行排序了。具体地,使用两个指针分别指向位置
index
和index+1
,每次比较两个指针对应的数,选择较小的那个放入答案并移动指针。当某一指针移至边界时,将另一指针还未遍历到的数依次放入答案。
- 能够找到数组
- 代码:
1 |
|
题目二
- 长度最小的子数组:209.长度最小的子数组
- 思路:
- 滑动窗口就是满足总和
sum
大于等于target
的长度最小的连续子数组。 - 滑动窗口的起始位置如何移动:如果当前窗口的总和
sum
大于等于target
了,窗口就要向前移动了(也就是该缩小i++
了)。 - 滑动窗口的结束位置如何移动:窗口的结束位置就是遍历数组的指针,也就是
for
循环里的索引,j++
。 - 可以发现滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置。从而将 暴力解法降为 。
- 滑动窗口就是满足总和
- 代码:
1 |
|
题目三
- 螺旋矩阵 II:59.螺旋矩阵 II
- 思路:
- 这道题就是模拟螺旋矩阵生成的过程,分为四个方向进行循环,同时设定相应的循环边界条件达到模拟的实现。
- 注意在 C++ 代码中
vector
的二维动态数组可以定义为:vector<vector<int>> mat(n,vector<int>(n));
- 代码:
1 |
|
题目四
- 区间和:58. 区间和(第九期模拟笔试)
- 本题主要用于熟悉 ACM 模式的输入输出!
- 代码如下:
1 |
|
题目五
- 开发商购买土地:44. 开发商购买土地(第五期模拟笔试)
- 思路:
- 前缀和计算
- 横向前缀和:计算每一行从左到右的累加和,存储在
heng
数组中; - 竖向前缀和:计算每一列从上到下的累加和,存储在
shu
数组中。
- 横向前缀和:计算每一行从左到右的累加和,存储在
- 寻找最小差值:
- 竖向分割:尝试所有可能的竖向分割线位置,计算左右两部分和的差值,差值计算公式为
shu[m-1] - 2*shu[i]
(总列和减去两倍左侧和); - 横向分割:尝试所有可能的横向分割线位置,计算上下两部分和的差值,差值计算公式为
heng[n-1] - 2*heng[i]
(总行和减去两倍上方和); - 记录所有可能分割中的最小绝对差值。
- 竖向分割:尝试所有可能的竖向分割线位置,计算左右两部分和的差值,差值计算公式为
- 前缀和计算
- 代码:
1 |
|
链表
题目一
- 移除链表元素:203.移除链表元素
- 直接解法代码:
1 |
|
- 虚拟头节点解法代码:
1 |
|
题目二
- 移除链表元素:707.设计链表
- 代码:
1 |
|
题目三
- 反转链表:206.反转链表
- 代码:
1 |
|
题目四
- 两两交换链表中的节点:24.两两交换链表中的节点
- 代码:普通解法
1 |
|
- 虚拟头节点解法:
1 |
|
题目五
- 删除链表的倒数第
N
个节点:19.删除链表的倒数第 N 个节点 - 思路:使用双指针的方法,在
now
指针往后移动的同时使得pre
指针与now
指针保持N
的距离,当now
指针遍历到链表末尾后就可以采用pre
指针来删除链表的倒数第N
个节点。 - 代码:
1 |
|
题目六
- 相交链表:160.相交链表
- 思路:
- 代码:
1 |
|
题目七
- 环形链表 II:142.环形链表II
- 思路:代码随想录-142.环形链表
- 代码:
1 |
|
队列和栈
题目一
- 用栈实现队列:232.用栈实现队列
- 思路:代码随想录-232.用栈实现队列
- 代码:
1 |
|
题目二
- 用队列实现栈:225.用队列实现栈
- 思路:采用两个队列,始终保持一个队列为空,当要
pop
数据的时候,把非空队列的元素依次输出到另外一个空队列,剩下最后一个元素进行返回和pop
。push
数据时要向非空队列传入数据,若两个队列都是空的就向任意一个队列传数据即可。 - 代码:
1 |
|
题目三
- 删除字符串中的所有相邻重复项:1047. 删除字符串中的所有相邻重复项
- 思路:
- 初始化一个空栈:用于存储字符。
- 遍历字符串:
- 如果栈为空,或者当前字符与栈顶字符不同,则将当前字符压入栈。
- 如果当前字符与栈顶字符相同,则弹出栈顶字符(表示删除这对相邻重复字符)。
- 构建结果字符串:
- 遍历结束后,栈中剩下的字符就是删除所有相邻重复字符后的结果。
- 由于栈是后进先出的,我们需要将栈中的字符逆序才能得到正确的顺序。
- 字符串逆序采用
reverse(res.begin(),res.end());
函数,需要引入#include <algorithm>
头文件,该函数可以用于翻转数组、字符串和向量(vector
)。
- 代码:
1 |
|
题目四
- 逆波兰表达式求值:150. 逆波兰表达式求值
- 注意点:将
string
变量转换为int
型、long
型、long long
型变量可以使用std::stoi
、std::stol
和std::stoll
函数! - 代码:
1 |
|
排序问题
选择排序
普通选择排序
- 选择排序理论:
- 代码:
1 |
|
- 分析:
冒泡排序
普通冒泡排序
- 冒泡排序理论:
- 代码:
1 |
|
- 分析:
插入排序
普通插入排序
- 插入排序理论:
- 代码:
1 |
|
- 性质和分析:
- 在原数组有序的最好情况下,算法时间复杂度是 !
- 空间复杂度为 !
- 插入排序算法具有稳定性(假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序后,这些记录的相对次序保持不变)
- 使用场景:
- 数组长度比较小;
- 当数组基本有序(离排好序的最终位置很近),插入排序可以在 时间内完成排序!
希尔排序
普通希尔排序
- 希尔排序理论:
- 代码:
1 |
|
- 分析:
- 时间复杂度和
gap
序列相关,一般而言平均情况小于 ; - 空间复杂度为 ;
- 插入排序算法不具有稳定性,因为发生了长距离交换,通过牺牲不稳定性来换取时间。
- 时间复杂度和
归并排序
普通归并排序
- 递归方法:912.排序数组
- 思路:采用二分法和递归的方式实现 的归并排序;
- 代码:
1 |
|
- 分析:
- 归并排序算法是稳定的,但是空间复杂度比较高;
小和问题
- 问题描述:
- 在一个数组中,每一个数的左边比当前数小的数累加起来,叫做这个数组的小和。求一个数组的小和。
- 例子:对于数组
[1,3,4,2,5]
,1
左边比1
小的数,没有;3
左边比3
小的数,1
;4
左边比4
小的数,1,3
;2
左边比2
小的数,1
;5
左边比5
小的数,1,2,3,4
;- 所以小和为
1+1+3+1+1+2+3+4 = 16
。
- 例子:对于数组
- 思路:
- 此处使用归并排序,在
merge
时,由于左右两部分都已经有序,可以确定一侧的数都大于正在比较的数,例如, - 归并
2 4 5 | 1 3 7
两个部分时,2
比3
小,此时可以确定后面的数都大于2
,此时便可以一次性计算小和2 * 2
(两个数大于2
),而不用一个个遍历。
- 此处使用归并排序,在
- 代码:
1 |
|
逆序对问题
- 交易逆序对总数:LCR 170 交易逆序对总数
- 思路:参考归并排序的算法,区别在于在
merge
的时候从右到左进行比较,- 左组数比右组数大时,通过
ans+=j-(M+1)+1;
计算逆序对的个数,并将左组数拷贝到temp
数组并左移一位; - 右组数比左组数大时,无需计算逆序对个数,只需要将右组数拷贝到
temp
数组并左移一位即可。 - 注意递归的边界条件!
- 左组数比右组数大时,通过
- 代码:
1 |
|
双倍逆序对问题
- 题目描述:
- 给定一个整数数组
nums
,统计其中满足以下条件的逆序对数量:
- 条件:存在两个下标
i
和j
(i < j
),使得nums[i] > 2 * nums[j]
。要求设计一个时间复杂度为 的算法解决此问题。 - 示例:
- 输入:
nums = [8, 3, 5, 1, 2]
- 输出:
6
- 解释: 满足条件的逆序对为:
(8,3)
,(8,1)
,(8,2)
,(3,1)
,(5,1)
,(5,2)
,共6
对。
- 输入:
- 给定一个整数数组
- 思路:
- 基于归并排序的分治框架,在合并两个有序子数组的过程中,利用双指针滑动窗口高效统计满足条件的逆序对。具体步骤如下:
- 递归划分:将数组递归分割为子数组,直到子数组长度为
1
。 - 合并与统计:在合并两个已排序的子数组时,遍历左半部分的每个元素,通过滑动窗口找到右半部分中满足
nums[i] > 2 * nums[j]
的元素数量。 - 排序保证:合并完成后,保证当前子数组有序,为后续递归合并提供正确输入。
- 代码:
1 |
|
区间和的个数问题
- 区间和的个数问题:327.区间和的个数
- 思路:
- 转化为归并排序进行求解,复杂度为 。将原问题转化为前缀和数组的有序对统计问题,区间和
[i, j]
对应前缀和sum[j] - sum[i]
,要求满足lower ≤ sum[j] - sum[i] ≤ upper
,即等价于统计满足sum[j] - upper ≤ sum[i] ≤ sum[j] - lower
的有序对(i, j)
(i < j
)。
- 转化为归并排序进行求解,复杂度为 。将原问题转化为前缀和数组的有序对统计问题,区间和
- 代码:
1 |
|
快速排序
颜色分类
- 颜色分类问题:75.颜色分类
- 思路:
- 代码:
1 |
|
随机快排
- 思路:
- 在前面所述的颜色分类问题的基础上,随机快排的
partition()
函数将数组分为<基准值区域
、=基准值区域
、>基准值区域
三个区域,通过pre
和last
标记边界; - 快速排序随机选择从
[L, R-1]
范围上选择基准值与nums[R]
进行交换,在代码中通过swap(nums,R,rand()%(R-L+1))
体现; - 随机快排采用递归的方式完成对数组的排序,需要注意随机快排依赖
qickSort()
函数中的递归终止条件(L >= R
)。
- 在前面所述的颜色分类问题的基础上,随机快排的
- 代码:
1 |
|
- 分析:
- 快排的空间复杂度是 ,并且快排算法是不稳定的。
左程云算法学习
http://example.com/2025/03/07/zuo_algorithm/