文章目录
day31学习内容
day31主要内容
- 分发饼干
- 摆动序列
- 最大子序和
声明
本文思路和文字,引用自《代码随想录》
一、分发饼干
1.1、思路
千万要记得排序
核心逻辑
:两种写法的核心逻辑完全相同。首先对两个数组进行排序,确保按照孩子的胃口大小和饼干的大小排序。然后从胃口最大的孩子和最大的饼干开始匹配,如果当前饼干可以满足当前孩子的胃口,就将计数器count加一,并继续尝试满足下一个胃口更小的孩子。
1.2、代码-错误写法
class Solution {
public int findContentChildren(int[] g, int[] s) {
int count = 0;
int index = s.length - 1;
for (int i = g.length - 1; i > 0; i--) {
// 这种写法也可以的。 if (index >= 0 && g[i]<=s[index]) {
if (index > 0 && s[index] >= g[i]) {
count++;
index--;
}
}
return count;
}
}
这段代码错在哪里。一共有2个地方
- 没有对孩子数字和饼干数组排序
- 去掉了=号,为什么不能去掉等于号,请看1.3.1。
1.3、代码-大饼干给大胃口的孩子-写法1
class Solution {
public int findContentChildren(int[] g, int[] s) {
Arrays.sort(g);
Arrays.sort(s);
int count = 0;
int index = s.length - 1;
for (int i = g.length - 1; i >= 0; i--) {
// 这种写法也可以的。 if (index >= 0 && g[i]<=s[index]) {
if (index >= 0 && s[index] >= g[i]) {
count++;
index--;
}
}
return count;
}
}
1.3.1、i >= 0和index >= 0,为什么不能把等于号去掉呢
在这个问题中,包含等于号>=
是有重要意义的,原因在于遍历是从数组的末尾(即最大值)开始的,并且包括数组的第一个元素(即索引为0的元素)。移除等于号将改变算法的行为,具体来说:
-
对于
i >= 0
:这里i
代表孩子数组g
的索引。如果我们移除等于号,使之成为i > 0
,那么当i
为0时循环将不会执行。这意味着我们将完全忽略数组g
中的第一个元素(即胃口最小的孩子),从而可能错失一个分配饼干的机会。 -
对于
index >= 0
:这里index
代表饼干数组s
的索引。同样地,如果我们移除等于号,使之成为index > 0
,那么当index
为0时循环将不会执行。这意味着我们将忽略数组s
中的第一个元素(即最小的饼干),可能导致无法分配到这个饼干,即使它能满足某个孩子的胃口。
因此,包含等于号是为了确保算法能够遍历整个数组,包括数组的第一个元素。这是确保算法正确性和完整性的重要条件。在编程和算法中,考虑数组或列表的边界条件(如起始和结束点)是非常重要的,移除等于号将导致算法不能正确处理这些边界条件,从而可能错失正确的解决方案。
二、摆动序列
2.1、思路
2.1.1、什么是摆动序列
摆动序列指的是序列中连续的数字之间的差值严格地交替在正和负之间。比如,序列[1, 7, 4, 9, 2, 5]是一个摆动序列,因为差值序列[+6, -3, +5, -7, +3]正负交替。
2.1.2、摆动的情况分析
算摆动的情况:
-
正负交替:序列中的差分(即后一个元素与前一个元素的差)在正负之间交替。例如,序列
[1, 3, 2, 4, 3]
中的差分为[+2, -1, +2, -1]
,呈现正负交替,因此算作摆动。 -
序列起始的特殊处理:即使序列起始于一段增加或减少(例如,
[1, 2]
或[2, 1]
),也算作摆动的起始。如果序列开始的两个元素不相等,它就已经是一个摆动序列了。 -
去除连续相等元素后的摆动:如果去掉序列中连续相等的元素后,剩余序列显示出摆动特征,这样的序列也算作摆动。例如,
[1, 1, 2, 3, 2, 2, 2, 4]
去除连续相等元素后可以视为[1, 2, 3, 2, 4]
,它显示出了摆动特征。
不算摆动的情况:
-
全递增或全递减序列:如果序列从头到尾完全是递增或递减的,没有交替出现的增减变化,那么这样的序列不算作摆动。例如,
[1, 2, 3, 4]
或[4, 3, 2, 1]
。 -
连续平坡不变:如果整个序列的元素都相等,没有任何的增减变化,这样的序列也不算作摆动。例如,
[2, 2, 2, 2]
。
简而言之,摆动序列需要元素值在增加和减少之间至少交替进行一次,且这种交替应持续出现,而不是仅在序列的一小部分中出现。
2.2、错误写法
class Solution {
public int wiggleMaxLength(int[] nums) {
// count默认为1
int count = 1;
int curDiff = 0;
int preDiff = 0;
for (int i = 0; i < nums.length-1; i++) {
curDiff = nums[i] - nums[i - 1];
if ((curDiff > 0 && preDiff <= 0) || (curDiff < 0 && preDiff >= 0)) {
count++;
preDiff = curDiff;// 只有摆动时才更新preDiff
}
}
return count;
}
}
2.2.1、为什么不能从0开始循环。
很简单,从0开始,nums[0-1] =nums[-1]。数组越界了。
当然也可以这么写
class Solution {
public int wiggleMaxLength(int[] nums) {
if (nums.length < 2) {
return nums.length;
}
// count默认为1
int count = 1;
int curDiff = 0;
int preDiff = 0;
// 注意不要从0开始循环
for (int i = 0; i < nums.length - 1; i++) {
curDiff = nums[i + 1] - nums[i];
if ((curDiff > 0 && preDiff <= 0) || (curDiff < 0 && preDiff >= 0)) {
count++;
preDiff = curDiff;// 只有摆动时才更新preDiff
}
}
return count;
}
}
这样就不会有数组越界了
2.3、正确写法1
class Solution {
public int wiggleMaxLength(int[] nums) {
if (nums.length < 2) {
return nums.length;
}
// count默认为1
int count = 1;
int curDiff = 0;
int preDiff = 0;
// 注意不要从0开始循环
for (int i = 1; i < nums.length; i++) {
curDiff = nums[i] - nums[i - 1];
if ((curDiff > 0 && preDiff <= 0) || (curDiff < 0 && preDiff >= 0)) {
count++;
preDiff = curDiff;// 只有摆动时才更新preDiff
}
}
return count;
}
}
2.3.1、 如何理解上面这段代码
变量解释
curDiff
:存储当前遍历到的相邻元素之间的差值。preDiff
:存储上一对相邻元素之间的差值。count
:记录摆动序列的最大长度。初始值为1,因为序列至少有一个元素时,就被视为一个有效的摆动序列。
逻辑流程
-
特殊情况处理:如果数组长度小于或等于1,直接返回数组的长度。因为没有足够的元素形成摆动。
-
初始化变量:
curDiff
和preDiff
初始值都是0,count
初始化为1。 -
遍历数组:从数组的第二个元素开始遍历(即索引为1),计算当前元素与前一个元素的差值,即
curDiff = nums[i] - nums[i - 1]
。 -
判断摆动条件:
- 如果
curDiff > 0 && preDiff <= 0
,表示当前差值为正数,而上一个差值为负数或0(0表示还未开始摆动,即初始状态),满足摆动条件。 - 如果
curDiff < 0 && preDiff >= 0
,表示当前差值为负数,而上一个差值为正数或0,同样满足摆动条件。
当上述任一条件满足时,摆动序列长度
count
加1,并更新preDiff
为当前差值curDiff
,以便用于下一次比较。 - 如果
-
返回结果:遍历结束后,返回
count
作为摆动序列的最大长度。
关键点
这种方法的关键在于通过比较连续的差值(即相邻元素之间的差值)的符号变化来判断摆动。通过更新preDiff
和curDiff
,算法能够有效地跟踪摆动模式的变化,并计算出摆动序列的最大长度。
2.3.2、 如何理解preDiff = curDiff;
在摆动序列的问题中,我们通常关注序列中相邻元素之间的差异(差值),因为摆动序列的定义是基于这种差异正负交替出现的。curDiff
表示当前考察的两个相邻元素之间的差值,而preDiff
表示前一对相邻元素之间的差值。
preDiff = curDiff;
这行代码的作用是:将当前的差值curDiff
赋值给preDiff
,以便在下一次循环迭代时,preDiff
代表着当前迭代之前的一对元素之间的差值。这样做的目的是保留和传递之前一步的差值信息,以便可以和当前步的差值curDiff
进行比较,从而判断序列是否继续保持摆动(即差值的正负交替出现)。
具体来说,在算法的每次迭代中,我们计算当前元素和前一个元素之间的差值(curDiff = nums[i] - nums[i - 1]
),然后判断这个差值与前一次迭代的差值(preDiff
)是否符合摆动的条件(即一个正一个负)。如果符合,就意味着序列在这一段继续摆动,我们更新摆动序列的长度,并且将curDiff
的值更新到preDiff
,为下一次比较做准备。这样,每次迭代都在检查并跟踪序列的摆动状态,并适当地更新preDiff
以保持算法的连续性和正确性。
三、最大子序和
3.1、思路
- 当前连续和是负数,直接放弃,因为负数加上后面一个数,不管后面的数是正是负,都会让后面一个数变小。所以直接取下一个数重新遍历求和。
- 当前连续和是正数,就继续累加,因为正数加上后面一个数,不管后面的数是正是负,都会让后面一个数变大。
3.2、正确写法1
class Solution {
public int maxSubArray(int[] nums) {
if (nums.length == 1) {
return nums[0];
}
int result = Integer.MIN_VALUE;
int sum = 0;
for (int i = 0; i < nums.length; i++) {
// 如果遇到正数,就继续累加,并且取最大值
sum += nums[i];
result = Math.max(sum, result);
// 如果遇到负数,就从下一个元素重新求和
if (sum <= 0) {
sum = 0;
}
}
return result;
}
}
总结
1.感想
- 贪心算法的第一天加油。。
- 贪心算法是一种在每一步选择中都采取在当前状态下最优(即最有利)的选择,以希望导致结果是全局最优的算法策略。它不像动态规划算法那样考虑整个问题的所有可能解,贪心算法只关注如何通过局部最优解来达到全局最优解。
2.思维导图
本文思路引用自代码随想录,感谢代码随想录作者。