LeetCode题目总结——一维数组

本文详细总结了LeetCode中涉及一维数组的典型问题,包括按规则移动指针、单次遍历记录、双指针、排序后操作、单调栈、二分搜索、动态规划、贪心策略、树状数组和优先级队列等解题方法。通过实例解析了各种算法在实际问题中的应用,帮助读者掌握数组问题的解决思路。

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

LeetCode题目总结——一维数组

提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加
例如:第一章 Python 机器学习入门之pandas的使用


提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档

1. 按规则移动指针

定义一个索引指针,按题目给定要求移动指针并搜索符合条件的元素。

  1. 0031 - 下一个排列
    首先,从数组末尾向左移动指针寻找第一个降序元素a;然后,从a向右移动指针寻找大于a且最小的元素,并与a交换位置;将交换前a位置右侧所有元素倒序。
  2. 0058 - 最后一个单词的长度
    倒序遍历字符串,找到不为空格的字符停止,再继续倒序遍历,找到空格停止,两索引之间的长度即为结果。
  3. 0066 - 加一

2. 单次遍历并记录

遍历数组,同时使用HashMap记录已遍历过的信息。

  1. 0001 - 两数之和
    1)注意处理重复元素:先判断map中是否存在target - n,再将n存入map
    2)若数组有序,可用双指针法,消除map运算负担。(比如处理三数之和、四数之和的情况)

遍历数组,同时统计已遍历过的信息,如最大值、最小值、和等。

  1. 0055 - 跳跃游戏
    维护目前为止可跳跃的最远距离,若最远距离大于数组长度,则返回true,若最远距离小于当前元素位置,则返回false。

  2. 0121 - 买卖股票的最佳时机
    遍历数组,同时记录之前所有元素的最小值。
    然后将当前元素与之前元素中的最小值对减,计算当前最大利润,最后取最大利润即可(最大利润小于0则返回0)。

3. 双指针

常见定式1 : 一个主指针与一个副指针,主指针依序遍历数组,副指针根据条件移动。主指针可以是快指针,也可以是慢指针。

# 主指针循环条件
j = 0
i = 0
ans = ...
while i < N and j <= N:
	# 副指针移动与处理
	while ...:
		j += 1
	# 结果检测
	ans = ...
	# 主指针移动前处理
	...
	i += 1
return ans
  1. 0003 - 无重复字符的最长字串
    主指针为快指针,依次遍历数组并记录每个字符最后一次出现的位置;副指针为慢指针,根据记录的字符最后一次出现位置进行跳转。
  2. 0076 - 最小覆盖子串

常见定式2 : 两指针分别指向数组的左右侧,两指针基于贪心策略相向运动,直至相遇。

# 左右指针初始化
l = 0
r = len(nums) - 1
while l < r:
	# 结果检测
	ans = ...
	# 指针移动
	if cond1(): l += 1
	if cond2(): r -= 1
return ans
  1. 0011 - 盛最多水的容器
    每次移动左右指针中指向线条较短的指针。

其他

  1. 0075 - 颜色分类
    采用单次遍历且不使用额外空间的算法时,需要使用双指针法。

4. 排序后操作

用于最终算法复杂度在nlogn(如n2)以上的算法。

  1. 0015 - 三数之和
    1)固定一个元素,双指针法求该元素后方有序数组的两数之和
    2)注意跳过重复元素
    3)注意剪枝
  2. 0016 - 最接近的三数之和
  3. 0018 - 四数之和
    多一层循环的三数之和。
  4. 0976 - 三角形的最大周长
    排序后,倒序查找第一组满足三角形周长条件(A[i] < A[i - 1] + A[i - 2])的连续三元素。
  5. 0621 - 任务调度器
    按任务出现频率排序,从出现频率最多的元素开始,模拟填桶,最终公式:
    max(N, (n + 1) * (fs[0] - 1) + add),其中,n为最小任务间隔,N为任务长度,fs为排序后的任务出现频率,add为出现频率最高的任务个数。

5. 单调栈

  1. 0020 - 有效的括号
  2. 0042 - 接雨水
    维护最小栈,累加当前元素与之前元素之间所能盛水量。
// 栈维护逻辑
while (!s.empty() && height[i] >= height[s.top()]) {
	int bottom = height[s.top()];
    s.pop();
    if (!s.empty()) {
    	res += (min(height[s.top()], height[i]) - bottom) * (i - s.top() - 1);
    }
}
  1. 0084 - 柱状图中最大矩形
    维护最大栈,寻找某元素两侧首先出现小于该元素的位置a,b,选择height * (b - a - 1)中的最大值。
// 栈维护逻辑
 while (!stack.empty() && heights[i] < heights[stack.top()]) {
	int height = heights[stack.top()];
	stack.pop();
	res = max<int>(res, height * (i - stack.top() - 1));
}
stack.push(i);  
  1. 0085 - 最大矩形
    可转换为多次0084问题进行求解。

6. 二分搜索

对于有序的数组,可以使用二分法将搜索复杂度从n降低至logn。根据题目要求,设计左右边界移动条件。

# 左右边界初始化
l = 0
r = len(nums) - 1
# 结束条件,若右边界移动条件位r = m或左边界移动条件位l = m时,必须使用l < r
while l <= r:
	# 防止(l + r)越界
	# 若右边界移动条件位r = m时,必须使用:
	m = l + (r - l) >> 2
	# 若左边界移动条件位l = m时,必须使用:
	m = l + (r - l + 1) >> 2
	# 终止条件,可无
	if nums[m] ...:
		...
	# 左边界移动条件
	if ...
		l = m or l = m + 1
	# 右边界移动条件
	if ...
		r = m or r = m - 1
  1. 0033 - 搜索旋转排序数组
    1)首先判断nums[m]位于数组的前半段还是后半段,若nums[m] > nums[i]则位于前半段,否则位于后半段。
    2)再分别讨论前半段后半段两种情况下,target于nums[m]的关系,从而确定i,j走向。前半段时,nums[i] <= target < nums[m]则j = m - 1,否则i = m + 1;后半段时,nums[m] > target >= nums[j]则i = m + 1,否则j = m - 1。
  2. 0034 - 在排序数组中查找元素的第一个和最后一个位置
  3. 0035 - 搜素插入位置
  4. 0081 - 搜索旋转排序数组 II
    相对于搜索旋转数组 I,本题增加了数组元素可能相等的条件,因此,若nums[m] > nums[i]则位于前半段,若nums[m] < nums[i]则位于后半段,若nums[m] == nums[i]则不确定位于前半段还是后半段,但可以确定nums[i] = nums[m] != target,所有这种情况下就仅将i移动一步即可(i = i + 1)。

7. 动态规划

7.1 单数组一维动态规划

遍历一次数组,动态规划记录以当前元素结尾时的状态。

  1. 0053 - 最大子序和
# 状态转移方程:
dp[i] = max(nums[i], dp[i] + nums[i])
res = max(res, dp[i])
  1. 0070 - 爬楼梯
# 状态转移方程:
dp[i] = dp[i - 1] + dp[i - 2]
  1. 0091 - 解码方法
// 转移方程
if (s[i] == '0') {
	if (s[i - 1] == '1' || s[i - 1] == '2') dp[i] = dp[i - 2];
	else return 0;
}
else if (s[i - 1] == '1' || (s[i - 1] == '2' && s[i] >= '0' && s[i] <= '6')) {
	dp[i] = dp[i - 1] + dp[i - 2];
}
else {
	dp[i] = dp[i - 1];
}
  1. 0096 - 不同的二叉搜索树
dp[0] = 1;
dp[1] = 1;
for (int i = 2; i <= n; ++i) {
	for (int j = 0; j < i; ++j) {
		dp[i] += dp[j] * dp[i - j - 1];
	}
}
  1. 0118 - 杨辉三角
    简单的动态规划题目。
// 状态转移方程
dp[i][j] = dp[i - 1][j] + dp[i - 1][j - 1];
// 优化后
dp[[j] += dp[j - 1];
  1. 0119 - 杨辉三角II
  2. 0120 - 三角形最小路径和
    杨辉三角的变种题目,使用动态规划求解。
// 状态转移方程
dp[i][j] += min(dp[i - 1][j], dp[i - 1][j - 1]);
// 优化后
dp[j] += min(dp[j], dp[j - 1])
  1. 0123 - 买卖股票的最佳时机 III
// 状态转移方程
p1_min = min(p, p1_min);
res1 = max(res1, p - p1_min); // 与0121相同,计算买卖一次的情况下,至某元素为止的最大利润
p2_min = min(p2_min, p - res1); // 对于第二次买卖,当前买入股票的价格,相当于:当前买入价格 - 当前元素之前进行买卖一次的最大收益
res2 = max(res2, p - p2_min); // 第二次买卖的最大收益即为最终收益

7.2. 双数组二维动态规划

遍历两数组的元素对,动态规划记录以每对元素结尾时的状态。注意动态规划表格可以简化为一维形式。

  1. 0072 - 编辑距离
# 状态转移方程:
if w1[i] == w2[j]: 
	dp[i][j] = dp[i - 1][j - 1]
else 
	dp[i][j] = min(dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]) + 1
  1. 0097 - 交错字符串
// 状态转移方程
dp[0][0] = true;
for (int i = 0; i <= M; ++i)
	for (int j = 0; j <= N; ++j) {
		if (i > 0 && dp[i - 1][j] && s3[i + j - 1] == s1[i - 1]) {
			dp[i][j] = true;
		}
		else if (j > 0 && dp[i][j - 1] && s3[i + j - 1] == s2[j - 1]) {
			dp[i][j] = true;
		}
}
  1. 0115 - 不同的子序列
// 状态转移方程
for (auto& d : dp[0]) d = 1;
for (int i = 1; i <= M; ++i)
	for (int j = 1; j <= N; ++j) 
{
	if (s[j - 1] == t[i - 1]) {
		dp[i][j] = dp[i - 1][j - 1] + dp[i][j - 1];
	}
	else {
		dp[i][j] = dp[i][j - 1];
	}
}

7.3. 区间动态规划

  1. 0005 - 最长回文字串
// 转移方程
if (l == 0) {
	dp[i][j] = true;
} 
else if (l == 1) {
	dp[i][j] = (s.charAt(i) == s.charAt(j));
} 
else {
	dp[i][j] = (s.charAt(i) == s.charAt(j) && dp[i + 1][j - 1]);
}
  1. 0087 - 扰乱字符串
// 转移方程
for (int k = 1; k < len; ++k) {
	if (dp[i][j][k] && dp[i + k][j + k][len - k]) {
		dp[i][j][len] = true;
        break;
    }
    if (dp[i][j + len - k][k] && dp[i + k][j][len - k]) {
    	dp[i][j][len] = true;
    	break;
    }
}

8. 贪心

  1. 0122 - 买卖股票的最佳时机 II
    遍历数组,当prices[i] > prices[i - 1]时,res += prices[i] - prices[i - 1]

9. 树状数组

用于解决区间更新及求和类问题。

// 总共有N个元素
vector<int> tr(N, 0);
// 添加k个元素n
void add(int n, int k) {
	for (int i = n; i <= N; i += i &(-i)) {
		tr[i] += k;
	}
}
// 返回小于等于n的元素个数
int sum(int n) {
	int res = 0;
	for (int i = n; i > 0; i -= i&(-i) {
		res += tr[i];
	}
	return res;
}
  1. 5564 - 通过指令创建有序数组

10. 优先级队列

  1. 周赛221 - 5638 - 吃苹果的最大数目
    使用优先级队列,按照坏掉的日期从近到远的顺序存储当前的苹果,每次贪心地吃坏掉日期最近的一个苹果。若坏掉日期小于当前日期,则直接从队列中排除。

特殊算法

Manacher 算法:

  1. 0005 - 最长回文字串

KMP 算法:

# 建立next数组
nxt = [0] * len(pattern)
k = 0
for i in range(len(pattern)):
	while k != 0 and pattern[k] != pattern[i]:
		k = nxt[k - 1]
	if pattern[k] == pattern[i]:
		k += 1
	nxt[i] = k
# 字符串匹配
k = 0
for i in range(len(string)):
	if k != 0 and pattern[k] != string[i]:
		k = nxt[k - 1]
	if pattern[k] == string[i]:
		k += 1
	if k > len(pattern):
		# 匹配成功
		return True
# 匹配失败
return False
  1. 0028 - 实现strStr()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值