题36:分糖果(困难)
n 个孩子站成一排。给你一个整数数组 ratings 表示每个孩子的评分。
你需要按照以下要求,给这些孩子分发糖果:
每个孩子至少分配到 1 个糖果。
相邻两个孩子中,评分更高的那个会获得更多的糖果。
请你给每个孩子分发糖果,计算并返回需要准备的 最少糖果数目 。
示例 1:
输入:ratings = [1,0,2]
输出:5
解释:你可以分别给第一个、第二个、第三个孩子分发 2、1、2 颗糖果。
示例 2:
输入:ratings = [1,2,2]
输出:4
解释:你可以分别给第一个、第二个、第三个孩子分发 1、2、1 颗糖果。
第三个孩子只得到 1 颗糖果,这满足题面中的两个条件。
思路:
两次遍历(两遍贪心算法)
我们可以将「相邻的孩子中,评分高的孩子必须获得更多的糖果」这句话拆分为两个规则,分别处理。
左规则:当 ratings[i−1]<ratings[i] 时,i 号学生的糖果数量将比 i−1 号孩子的糖果数量多。
右规则:当 ratings[i]>ratings[i+1] 时,i 号学生的糖果数量将比 i+1 号孩子的糖果数量多。
我们遍历该数组两次,处理出每一个学生分别满足左规则或右规则时,最少需要被分得的糖果数量。每个人最终分得的糖果数量即为这两个数量的最大值。
在实际代码中,我们先计算出左规则 left 数组,在计算右规则的时候只需要用单个变量记录当前位置的右规则,同时计算答案即可。
代码:
class Solution
{
public:
int candy(vector<int> &ratings)
{
int ret =0;
int n = ratings.size();
vector<int> left(n);
for (int i = 0; i < n; i++)
{
if (i > 0 && ratings[i - 1] < ratings[i])
{
left[i] = left[i - 1] + 1;
}
else
{
left[i] = 1;
}
}
vector<int> right(n);
for (int i = n - 1; i >= 0; i--)
{
if (i < n - 1 && ratings[i] > ratings[i + 1])
{
right[i] = right[i + 1] + 1;
}
else
{
right[i] = 1;
}
ret += max(right[i], left[i]);
}
return ret;
}
};
题37:接雨水(困难)

思路:
方法一:动态规划
对于下标i,下雨后水能到达的最大高度等于下标 i 两边的最大高度的最小值,下标 i 处能接的雨水量等于下标 i 处的水能到达的最大高度减去 height[i]。
朴素的做法是对于数组 height 中的每个元素,分别向左和向右扫描并记录左边和右边的最大高度,然后计算每个下标位置能接的雨水量。假设数组 height 的长度为 n,该做法需要对每个下标位置使用 O(n) 的时间向两边扫描并得到最大高度,因此总时间复杂度是 O(n2)。
上述做法的时间复杂度较高是因为需要对每个下标位置都向两边扫描。如果已经知道每个位置两边的最大高度,则可以在 O(n) 的时间内得到能接的雨水总量。使用动态规划的方法,可以在 O(n) 的时间内预处理得到每个位置两边的最大高度。
创建两个长度为 n 的数组 leftMax 和 rightMax。对于 0≤i<n,leftMax[i] 表示下标 i 及其左边的位置中,height 的最大高度,rightMax[i] 表示下标 i 及其右边的位置中,height 的最大高度。
显然,leftMax[0]=height[0],rightMax[n−1]=height[n−1]。两个数组的其余元素的计算如下:
当 1≤i≤n−1 时,leftMax[i]=max(leftMax[i−1],height[i]);
当 0≤i≤n−2 时,rightMax[i]=max(rightMax[i+1],height[i])。
因此可以正向遍历数组 height 得到数组 leftMax 的每个元素值,反向遍历数组 height 得到数组 rightMax 的每个元素值。
在得到数组 leftMax 和 rightMax 的每个元素值之后,对于 0≤i<n,下标 i 处能接的雨水量等于 min(leftMax[i],rightMax[i])−height[i]。遍历每个下标位置即可得到能接的雨水总量。
动态规划做法可以由下图体现。
代码:
class Solution
{
public:
int trap(vector<int> &height)
{
int n = height.size();
if (n == 0)
{
return 0;
}
vector<int> leftMax(n);
leftMax[0] = height[0];
for (int i = 1; i < n; i++)
{
leftMax[i] = max(leftMax[i - 1], height[i]);
}
vector<int> rightMax(n);
rightMax[n - 1] = height[n - 1];
for (int i = n - 2; i >= 0; --i)
{
rightMax[i] = max(rightMax[i + 1], height[i]);
}
int ans = 0;
for (int i = 0; i < n; ++i)
{
ans += min(leftMax[i], rightMax[i]) - height[i];
}
return ans;
}
};
题38:两数之和(简单)
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案,并且你不能使用两次相同的元素。
你可以按任意顺序返回答案。
示例 1:
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
示例 2:
输入:nums = [3,2,4], target = 6
输出:[1,2]
示例 3:
输入:nums = [3,3], target = 6
输出:[0,1]
思路:
使用哈希表,可以将寻找 target - x 的时间复杂度降低到从 O(N) 降低到 O(1)。
这样我们创建一个哈希表,对于每一个 x,我们首先查询哈希表中是否存在 target - x,然后将 x 插入到哈希表中,即可保证不会让 x 和自己匹配。
时间复杂度O(n)
空间复杂度O(n)
代码:
class Solution
{
public:
vector<int> twoSum(vector<int> &nums, int target)
{
unordered_map<int, int> table;
for (int i = 0; i < nums.size(); i++)
{
auto it = table.find(target - nums[i]);
if (it != table.end())
{
return {i,it->second};
}
table[nums[i]] = i;
}
return {};
}
};
题39:快乐数(简单)
编写一个算法来判断一个数 n 是不是快乐数。
「快乐数」 定义为:
对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
如果这个过程 结果为 1,那么这个数就是快乐数。
如果 n 是 快乐数 就返回 true ;不是,则返回 false 。
示例 1:
输入:n = 19
输出:true
解释:
1*1 + 9*9 = 82
8*8 + 2*2 = 68
6*6 + 8*8 = 100
1*1 + 0*0 + 0*0 = 1
示例 2:
输入:n = 2
输出:false
思路:
哈希表
首先此种循环有以下三种情况
1.最终会得到 1。
2.如果新出现的值是曾经出现过的值,那么最终会进入无限循环。
3.值会越来越大,最后接近无穷大。
那么第三种情况是否存在呢?
位数 最大值 下一个最大值
1 9 81
2 99 162
3 999 243
4 9999 324
5 9999999999999 1053
对于 3 位数的数字,它不可能大于 243。这意味着它要么被困在 243 以下的循环内,要么跌到 1。
4 位或 4 位以上的数字在每一步都会丢失一位,直到降到 3 位为止。
所以我们知道,最坏的情况下,算法可能会在 243 以下的所有数字上循环,然后回到它已经到过的一个循环或者回到 1。
但它不会无限期地进行下去,所以我们排除第三种选择。
代码:
class Solution
{
public:
int getNext(int n)
{
int totalSum = 0;
while (n > 0)
{
int d = n % 10;
n = n / 10;
totalSum += d * d;
}
return totalSum;
}
bool isHappy(int n)
{
unordered_set<int> table;
while (n != 1 && table.find(n) == table.end())
{
table.insert(n);
n = getNext(n);
}
return n == 1;
}
};
题40:存在重复元素II(简单)
给你一个整数数组 nums 和一个整数 k ,判断数组中是否存在两个 不同的索引 i 和 j ,满足 nums[i] == nums[j] 且 abs(i - j) <= k 。如果存在,返回 true ;否则,返回 false 。
示例 1:
输入:nums = [1,2,3,1], k = 3
输出:true
示例 2:
输入:nums = [1,0,1,1], k = 1
输出:true
示例 3:
输入:nums = [1,2,3,1,2,3], k = 2
输出:false
思路:
哈希表
首先从左到右依次遍历数组,如果哈希表内存在相同的值,且abs(i - j) <= k,则返回true
如果哈希表内存在相同的值,但是abs(i - j) > k,则更新哈希表的值的位置为最大的那个,这样后面如果再次有相同的值,才会有可能abs(i - j) <= k
如果哈希表内不存在相同的值,则将值和其下标放入哈希表中,
代码:
class Solution
{
public:
bool containsNearbyDuplicate(vector<int> &nums, int k)
{
unordered_map<int, int> table;
for (int i = 0; i < nums.size(); i++)
{
int value = nums[i];
if (table.count(value) && (i - table[value]) <= k)
{
return true;
}
table[value] = i;
}
return false;
}
};