文章目录
- 1、分发饼干
- 2、最优除法
- 3、跳跃游戏Ⅱ
- 4、跳跃游戏Ⅰ
- 5、加油站
- 6、单调递增的数字
- 7、坏了的计算器
1、分发饼干
455. 分发饼干
其实看完这个题会发现,如果给定的两个数组不排序的话会非常难受,所以无论怎样,先排序。接下来需要比较两个数组的值,可以用双指针来指向。两个数组的两个元素比较时,和之前有相同的思路,如果满足条件,那么后面的元素都比这个元素大,肯定也满足,但为了满足更多次的条件,所以就选用最小的那个值;如果不满足条件,这里就跳过去,找下一个更大的元素去看看能否满足条件。这也就是贪心思想。
int findContentChildren(vector<int>& g, vector<int>& s) {
sort(g.begin(), g.end());
sort(s.begin(), s.end());
int res = 0, m = g.size(), n = s.size();
for(int i = 0, j = 0; i < m && j < n; ++i, ++j)
{
while(j < n && s[j] < g[i]) ++j;
if(j < n) res++;
}
return res;
}
2、最优除法
553. 最优除法
无论怎样,假设abcdefg7个数,a / b / c / d / e / f,整个式子就是一个分式,a一定在分子,b一定在分母。对于贪心来说,让分子变大,让分母变小,就是最优解。这道题来看,其实应当让分子变大就是它的最优解,所以接下来就要让分子变大。让分子变大的办法就是在把b ~ f都放到一个括号里,这样就变成了 a * c * d * e * f / b。
string optimalDivision(vector<int>& nums) {
int n = nums.size();
if(n == 1) return to_string(nums[0]);
if(n == 2) return to_string(nums[0]) + "/" + to_string(nums[1]);
string res = to_string(nums[0]) + "/(" + to_string(nums[1]);
for(int i = 2; i < n; ++i)
{
res += "/" + to_string(nums[i]);
}
res += ")";
return res;
}
3、跳跃游戏Ⅱ
45. 跳跃游戏 II
这道题意思就是如果是[2, 3, 1, 5, 4],那么在0下标位置时最多可以跳2步到1这个位置。
这道题可以用动规,以i位置为结尾,遍历一遍前面所有的元素,如果能从某个位置跳过来,那就选那个位置,而那个位置存储了到它的最小跳跃数,然后+1即可,但这样是n ^ 2的时间复杂度,思路并不行。
这道题的思路可以是一个类似层序遍历的过程。假设一个数组[2, 3, 1, 1, 4, 2, 6, 7, 1, 5, 8],从0下标开始,是2,我们能够确定跳到3或1这个点,也就是第一次选定起点后确定了下一次起跳的左端点和右端点;接着,3可以跳到1,1,4,而1这里,就加上贪心,因为3跳得远,且它一定比1要至少1,所以1能跳到的,3一定能跳到的,所以这里就只考虑大的数字,跳的区间为114,但是重叠了,重叠部分是2下标处的1,所以把这部分去掉,只看1和4,1就是左端点,4就是右端点;接着从4走,能到2671,这时候14和2671没有重叠的,所有不会划掉一部分,2就是左端点,1就是右端点。这样的过程就是选定了一个点后,就能确定下一次的左端点和右端点,所以很像层序遍历。
这个思路的时间复杂度是O(N)。
int jump(vector<int>& nums) {
int left = 0, right = 0, maxPos = 0, n = nums.size(), res = 0;
while(left <= right)//防止跳不到n - 1位置
{
if(maxPos >= n - 1)//先判断是否已经能跳到最后一个位置
return res;
for(int i = left; i <= right; ++i)
{
maxPos = max(maxPos, nums[i] + i);
}
left = right + 1;
right = maxPos;
res++;
}
return -1;
}
4、跳跃游戏Ⅰ
55. 跳跃游戏
先看跳跃游戏Ⅱ。
其实就是改两处
bool canJump(vector<int>& nums) {
int left = 0, right = 0, maxPos = 0, n = nums.size();
while(left <= right)//防止跳不到n - 1位置
{
if(maxPos >= n - 1)//先判断是否已经能跳到最后一个位置
return true;
for(int i = left; i <= right; ++i)
{
maxPos = max(maxPos, nums[i] + i);
}
left = right + 1;
right = maxPos;
}
return false;
}
5、加油站
134. 加油站
按照这个题的思路来看,两个数组要同时看,这时不如作为一个数组,因为真正需要的是差。gas和cost两个数组每每对应,用gas的减cost的,比如例1,就得到[-2, -2, -2, 3, 3]。最简单的办法就是暴力解法,依次枚举所有起点,模拟加油的流程。
实际上,这道题可以在暴力解法上改进而得到。差值的数组不需要创建出来,不过下面还是看差值数组。用i表示下标,然后用一个step变量,表示走多少步,比如走0步就还是原地,走1步就到下一个位置,不过要%上数组大小,这样就不会越界了。
int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
int n = gas.size();
for(int i = 0; i < n; i++)
{
int rest = 0;
for(int step = 0; step < n; step++)
{
int index = (i + step) % n;
rest = rest + gas[index] - cost[index];
if(rest < 0) break;
}
if(rest >= 0) return i;
}
return -1;
}
不过这样肯定超出时间限制。现在基于这个来优化。假设差值数组是abcdefg,从a走到f就不能走了,说明a + b + c + d + e + f < 0,暴力解法就会从b再来一遍,但这样明显做了无用功。上面的式子小于0,那么去掉a,还是小于0,所以就不用管这些了,直接从g位置再出发。这样平均时间复杂度就是O(N)了。
int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
int n = gas.size();
for(int i = 0; i < n; i++)
{
int rest = 0;
int step = 0;
for(; step < n; step++)
{
int index = (i + step) % n;
rest = rest + gas[index] - cost[index];
if(rest < 0) break;
}
if(rest >= 0) return i;
i = i + step;
}
return -1;
}
6、单调递增的数字
738. 单调递增的数字
当然最简单的方法就是暴力枚举,从这个数字到0,判断每一个数字是否是单调递增,找到的第一个就是结果。不过重点在于如何判断单调递增。
对于一个数字,如果想对每一位做一些判断,转换成字符串就好。另一个经典操作就是模10再除10,就能从个位开始拿到每一位。
这个暴力解法的时间复杂度是O(nlogn),取一个数字的每一位的时间复杂度是logn。
但这里不用暴力解法,要用贪心,不过这更像找规律。
从头开始判断,如果发现了不是递增,那要对字符串如何操作?比如123454367,到了5这个位置就不能继续了,但因为要找更小的数字,那么4367不能改为6367。我们可以修改5,把它减1,然后后面的数字全变成9,就是要求的数字。但这里还有问题,如果是连续的几个5之后有个4呢?这样就得把第一个5改成4,后面全变成9。
int monotoneIncreasingDigits(int n) {
string s = to_string(n);
int i = 0, m = s.size();
while(i + 1 < m && s[i] <= s[i + 1]) ++i;
if(i + 1 == m) return n;
while(i - 1 >= 0 && s[i] == s[i - 1]) --i;
s[i]--;
for(int j = i + 1; j < m; ++j) s[j] = '9';
return stoi(s);
}
7、坏了的计算器
991. 坏了的计算器
对于小于目标的数,那么乘2会更快地接近目标,但是也有不是最优解的,比如6和目标10。
这道题适合逆着思考,把操作变成除2和+1。
这个题没有小数,所以能除2的只能是偶数,那么遇到奇数的话就只能+1。偶数可以除2,可以+1。把目标值变成原始值,原始值则变成目标值。假设原有的目标值是end,原始值是begin。
针对偶数,如果end <= begin,也就是目标值更小,那么遇到偶数就得+1。如果end > begin,奇数还是只能+1,而偶数,经过证明,其实是先除更优。
int brokenCalc(int startValue, int target) {
//正难则反 + 贪心
int res = 0;
while(target > startValue)
{
if(target % 2 == 0) target /= 2;
else target += 1;
res++;
}
return res + startValue - target;//目标值变成小于原始值了,奇偶数都是+1,所以就是加上差值。
}
结束。