135. 分发糖果
老师想给孩子们分发糖果,有 N 个孩子站成了一条直线,老师会根据每个孩子的表现,预先给他们评分。
你需要按照以下要求,帮助老师给这些孩子分发糖果:
每个孩子至少分配到 1 个糖果。
相邻的孩子中,评分高的孩子必须获得更多的糖果。
那么这样下来,老师至少需要准备多少颗糖果呢?
示例 1:
1 | 输入: [1,0,2] |
示例 2:
1 | 输入: [1,2,2] |
思路:只需要扫一遍数组
如何判断i
位置需要所少糖果,我们需要处理的有三种情况:
-
ratings[i - 1] == ratings[i]
,那么我们只需要1颗糖果 -
ratings[i - 1] < ratings[i]
,那么,我们只需要比前一个多一个糖果即可。 -
ratings[i - 1] > ratings[i]
,此时,我们不知道该如何判断了但是,如果我们知道递减的个数,我们就能判断,当前的
i(即局部最高点)
需要多少糖果了所以,我们保证,递减序列,是从1开始加的(方向加),例如:
如何判断
?
位置的糖果有多少,我们发现ratings
是3-2
递减的,递减序列个数des_num
,我们反向加,有公差为1的求和公式:(首项 + 尾项) * 项数 / 2,所以我们先假设ratings
在等于4的时候,也是满足等差的,那么就有(1 + des_num) * des_num / 2 = (1 + 2) * 2 / 2 = 3
颗糖果,是3-2
需要的糖果数:2-1
**但是,**还有一种情况,如下
也就是最高点的位置的糖果数不够,则此时,只需要变更最高点的糖果数目即可。
所以,时间复杂度是,空间复杂度是
上Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29class Solution {
public:
int candy(vector<int>& ratings) {
int maxCandy = 1, sum = 1, des_num = 0;
for(int i = 1;i < ratings.size();i++) {
if(ratings[i] >= ratings[i - 1]) {
if(des_num > 0) {
sum += (1 + des_num) * des_num / 2;
if(maxCandy <= des_num) {
sum += (des_num - maxCandy + 1);
}
des_num = 0;
maxCandy = 1;
}
maxCandy = ratings[i] == ratings[i - 1] ? 1 : maxCandy + 1;
sum += maxCandy;
} else {
des_num++;
}
}
if(des_num > 0) {
sum += (1 + des_num) * des_num / 2;
if(maxCandy <= des_num) {
sum += (des_num - maxCandy + 1);
}
}
return sum;
}
};
136. 只出现一次的数字
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
说明:
你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
示例 1:
1 | 输入: [2,2,1] |
示例 2:
1 | 输入: [4,1,2,1,2] |
一开始能想到的估计也就是Hash。但是看到评论区的大神,真的是很绝了。
方法一:数学
2*(a + b + c) - (a + a + b + b + c) - c
看完这个应该知道该怎么写了吧,但是这里需要用到set,空间复杂度是
1 | int singleNumber(vector<int>& nums) { |
方法二:位运算
概念:
-
我们如果对0和二进制位做XOR运算,得到的仍然是这个二进制位
a ⊕ 0 = a
-
我们如果对相同的二进制位做XOR运算,返回的结果是0
a ⊕ a = 0
-
XOR满足交换律和结合律
综上,只要将nums
里面的所有数字相异或,即可得到最终答案:
因为只要出现过两次,这两个数字的异或结果就是0,0和任何数字异或仍然是0,所以一伙完之后,凡是一对出现的,结果都是0,只剩下出现过一次的数字和0进行异或,还是它本身,因此,最终结果就是只出现一次的数字。
位运算大法好!
1 | class Solution { |
通用解法:
原问题:数组中某数出现k
次,k>=2
,有一数出现一次,求出该数
解法:
使用一个32维的数组,用这个32维的数组存储所有数里面的第0位1的总数,第一位1的总数…第31位1的总数
假如第0位1的个数是k的倍数,那么要求的这个数在该位一定是0,若不是k的倍数,那么要求的这个数在改为一定是1,第1位的1一直到第31位的1的个数同理。
所以在下一个进阶的题目中:
137. 只出现一次的数字II
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现了三次。找出那个只出现了一次的元素。
说明:
你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
示例 1:
1 | 输入: [2,2,3,2] |
示例 2:
1 | 输入: [0,1,0,1,0,1,99] |
直接上通用Code:
1 | class Solution { |
仅仅针对这道题而言,会有更加快速的方法:
即三进制下不考虑进位的加法:过定义某种运算 #,使得 0 # 1 = 1,1 # 1 = 2,2 # 1 = 0。在此运算规则下,出现了 33 次的数字的二进制所有位全部抵消为 00,而留下只出现 11 次的数字二进制对应位为 11。因此,在此运算规则下将整个arr
中数字遍历加和,留下来的结果则为只出现 11 次的数字。
代码分析:
ones ^= num;
:记录至目前元素num
,二进制某位出现1次(当某位出现3次,有ones = 1, twos = 1
共同表示出现3次)twos |= ones & num
:记录至目前元素num
,二进制某位出现2次(当某位出现2次时,twos = 1, ones = 0
)threes = ones & twos
:记录至目前元素num
,二进制某位出现3次(即当ones
和twos
对应位同时为1时,three
为1)ones &= ~threes, two &= ~threes
:将ones,twos中出现了3次的对应位清零,实现“不考虑进位的三进制加法”
1 |
141. 环形链表
给定一个链表,判断链表中是否有环。
为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。
示例 1:
输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
输入:head = [1,2], pos = 0
输出:true
解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:
输入:head = [1], pos = -1
输出:false
解释:链表中没有环。
1 | /** |
142. 环形链表II
在上一题的基础上,要求返回成环的起点;
一开始我想到的是先判断是否成环,然后记录下从起点到成环需要经过多少步数index
,然后利用快慢指针,快指针先走index步,然后当快慢指针指向同一个地方的时候,就是成环的起点。
后来发现并不需要这么做,经过推导之后,会发现从它们相遇的起点开始,将慢指针重新定位为head
,然后一起走,遇到一起的时候,就是成环的起点。
推导如下:

1 | class Solution { |
143. 重排链表
给定一个单链表 L:L0→L1→…→Ln-1→Ln ,
将其重新排列后变为: L0→Ln→L1→Ln-1→L2→Ln-2→…
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
示例 1:
1 | 给定链表 1->2->3->4, 重新排列为 1->4->2->3. |
示例 2:
1 | 给定链表 1->2->3->4->5, 重新排列为 1->5->2->4->3. |
思路:
先利用快慢指针,快指针走两步,慢指针走一步,当快指针到达最后的时候,慢指针到达中间,然后将后半部分链表进行倒序。最后合并两个链表。
即:设原链表为1->2->3->4
分成两部分1->2 3->4
将后面倒序4->3
然后合并两个链表1->4->2->3
Code如下:
1 | /** |