目录
2.柠檬水找零
class Solution {
public:
bool lemonadeChange(vector<int>& bills)
{
int five = 0;
int ten = 0;
for(auto ch : bills)
{
if(ch == 5)five++;
else if(ch == 10)
{
if(five < 1)return false;
five--;
ten++;
}
else
{
if(five>=1 && ten >=1)//这里是贪心的点,如果有十元,优先把十元用掉,因为五元的
//用处更大,贪五元
{
five--;
ten--;
}
else if(five >= 3)
{
five -= 3;
}
else return false;
}
}
return true;
}
};
3.将数组和减半的最小次数
class Solution {
public:
int halveArray(vector<int>& nums)
{
priority_queue<double> heap;//使用一个大根堆
double sum = 0;
int count = 0;
for(auto ch : nums)
{
sum += ch;
heap.push(ch);
}
sum /= 2.0;
while(sum > 0)
{
double tmp = heap.top()/2;
sum -= tmp;//每次把最大数的一半减掉,贪心的点在于找最大数
heap.pop();
heap.push(tmp);
count ++;
}
return count;
}
};
4.最大数
class Solution {
public:
string largestNumber(vector<int>& nums)
{
vector<string>str;
for(auto ch: nums)
{
str.push_back(to_string(ch));//1.把数字转换成字符串,拼接字符串,比较字典序
}
sort (str.begin(), str.end(), [](const string& s1, const string & s2)
{
return s1 + s2 > s2 + s1;//贪心的点在于每两个字符串都在两种顺序中选择最大的那个
}
);
string n;
for(auto& ch : str)
{
n += ch;
}
if(n[0] == '0')return "0";//处理前导零
return n;
}
};
这个排序规则为什么能排序
具有传递性
a b > b a
b c > c b
那么a c > c a(但是这里需要证明)
5.摆动序列
使用全局变量left,left 最开始设为 0 ,因为右边的点无论值为多少,第一个点都算
right 为右节点值减左节点值
left * right <= 0 计数加一
class Solution {
public:
int wiggleMaxLength(vector<int>& nums)
{
int left = 0;
int size = nums.size();
if(size == 1)return 1;
int count = 0;
for(int i = 0; i <= size - 2; i++)
{
int right = nums[i+1] - nums[i];
if(right == 0)continue;
if(left * right <= 0)count++;
left = right;
}
return count + 1;
}
};
即选取折点,贪心的点在于,无论是选取折点左边或折点右边的点,最终的节点数都可能更少
6.最长的递增子序列
class Solution {
public:
int lengthOfLIS(vector<int>& nums)
{
vector<int> nums1;
nums1.push_back(nums[0]);
for(auto ch: nums)
{
if(ch > nums1.back())nums1.push_back(ch);
int left = 0;
int right = nums1.size()-1;
while(left < right)
{
int mid = (left + right) / 2;
if(ch > nums1[mid])left = mid +1;
else right = mid;
}
nums1[left] = ch;//遍历过程中,用ch的值替换数组中大于它的最小值
}
return nums1.size();
}
};
[7,3,8,4,7,2,14,13]
长度为1的序列的末尾值:7(x),3(x),2
长度为2的序列的末尾值:8(x),4,
长度为3的序列的末尾值:7,
长度为4的序列的末尾值:14(x),13
贪心的地方在于,可以接到7后面的肯定能接到3后面,可以接到3后面的肯定可以接到2后面。
(也因此这个数组是严格递增,因此可以使用二分查找)
这个数组的作用是记录最大的序列长度,比如说长度为1的序列末位置为2,4无法接在2后面,但是先前已经储存了接在3后面的序列了,就不需要担心序列长度丢失问题
7递增的三元子序列
思路同5,只需要判断三个就可以了
class Solution {
public:
bool increasingTriplet(vector<int>& nums)
{
int a,b;
a = nums[0];
b = INT_MAX;
for(int i = 0; i < nums.size();i++)
{
if(nums[i] < a)a = nums[i];
if(nums[i] > a && nums[i] < b)b = nums[i];
else if (nums[i] > b)return true;
}
return false;
}
};
8.最长连续递增序列
1 2 3 0 4
贪心的点在于,从1开始,到0处序列断开,那么我们不需要从2开始,因为从开始的序列长度一定小于从1开始。所以我们只需要记录最大值,然后从0再开始比较
class Solution {
public:
int findLengthOfLCIS(vector<int>& nums) {
int counttmp = 1;
int countmax = 1;
for(int j = 1; j < nums.size();j++)
{
if(nums[j] > nums[j-1]){
counttmp++;
countmax = max(counttmp,countmax);
}
else
counttmp = 1;
}
return countmax;
}
};
9.买卖股票的最佳时机
贪心的点在于,我们每次寻找最小值只需要和下一个节点比较一次就可以了,因为我们已经储存了之前节点的最小值
class Solution {
public:
int maxProfit(vector<int>& pr) {
int pmin = INT_MAX;
int money = 0;
for(int i = 1; i < pr.size(); i++)
{
pmin = min(pmin, pr[i-1]);
money = max(money, pr[i]-pmin);
}
return money;
}
};
10.买股票的最佳时机2
贪心的点在于,只要出现正收益,我就交易
1.分成一节一节
class Solution {
public:
int maxProfit(vector<int>& pr) {
int money = 0;
for(int i = 0,j = 1; j < pr.size(); i++, j++)
{
if(pr[j]-pr[i]>0)money += pr[j]-pr[i];
}
return money;
}
};
2.双指针,整段求
class Solution {
public:
int maxProfit(vector<int>& pr) {
int money = 0;
int n = pr.size();
for(int i = 0;i < n;i++)
{
int j = i;
while(j+1 < n && pr[j+1] > pr[j])//这里要注意判断j+1要小于n,不能越界
{
j++;
}
money += pr[j]-pr[i];
i = j;
}
return money;
}
};
11.k次取反后的最大化数组和
假设数组中负数为n个
1.n >= k 把数组中前k个小的负数转化成正数,再相加
2.n < k 先把数组中全部负数转换成正数,然后再计算k - n的值,如果为偶数,那么取正与取反抵消,如果为奇数,那么我们将最小的那个数取反即可
class Solution {
public:
int largestSumAfterKNegations(vector<int>& nums, int k) {
int n = 0;
int ret = 0;
int MIN = INT_MAX;
for(auto ch : nums)
{
if(ch < 0)n++;
if(abs(ch) < MIN)MIN =abs(ch);
}
if(n >= k)
{
sort(nums.begin(), nums.end());
for(int i = 0; i < k;i++)
{
ret += -nums[i];
}
for(int i = k; i < nums.size();i++)
{
ret+= nums[i];
}
return ret;
}
else
{
for(int i = 0;i < nums.size();i++)
{
ret += abs(nums[i]);
}
if((k-n)%2==0)return ret;
else return ret-2*MIN;
}
}
};
12.按身高排序
class Solution {
public:
vector<string> sortPeople(vector<string>& names, vector<int>& heights) {
int size = names.size();
vector<int>arr(size);
for(int i = 0; i < size;i++)
{
arr[i] = i;
}
sort(arr.begin(),arr.end(),[&](int i,int j)
{
return heights[i]>heights[j];
});
vector<string>ret(size);
for(int i = 0; i < size; i++)
{
ret[i] = names[arr[i]];
}
return ret;
}
};
是13题的前置提示,具体就是创建一个下标数组,然后根据身高的高低重新排序这个数组,最后将人名再填充到一个新的数组中
13.优势洗牌
class Solution {
public:
vector<int> advantageCount(vector<int>& nums1, vector<int>& nums2) {
int size = nums1.size();
sort(nums1.begin(),nums1.end());//重新排序nums1
vector<int>arr(size);
vector<int>ret(size);
for(int i = 0; i < size; i++)//同上题,进行下标排列
{
arr[i] = i;
}
sort(arr.begin(),arr.end(),[&](int i, int j)
{
return nums2[i] < nums2[j];
});
int left = 0;
int right = size-1; //Left 与 right 用来指向当前arr最左边与最右边的元素
for(int i = 0; i < size; i++)定义一个i遍历新的nums1数组
{
if(nums1[i] > nums2[arr[left]])
{
ret[arr[left]] = nums1[i];
left++;
}
else
{
ret[arr[right]] = nums1[i];//Arr[left] 与arr[right]找到该数字相对原数组的
right--; //下标,再将nums1中的值放进ret中
}
}
return ret;
}
};
我们的贪心策略是从左往右比较,有两种情况
第一种例如上面8小于11,那么我们把8与最大的32配对,把最大的抵消掉
第二种例如 9 12
6 11
这里9比6大,直接把9与6配对,因为如果我们不这样做,而是把12与6配对,这样的话9比11小,我们就损失了一对优势
但是我们需要把8放到原数组中32对应的位置,因此需要使用arr[left] arr[right]取得这个数原数组的下标
14.最长回文串
class Solution {
public:
int longestPalindrome(string s) {
vector<int>s1(128);//因为这里只有52个字母,那么直接开128个
int size = s.size();
int ret = 0;
for(int i = 0; i < size; i++)//这个字母的阿斯克码值就对应新数组的下表,进行计数
{
s1[s[i]]++;
}
for(int i = 0; i < 128; i++)
{
ret+=s1[i]/2*2;//这里是一个化简操作,我们需要将偶数个排列在两边对称,奇数时会多一个,
//使用/2向下取整
}
if(ret < size)return ret+1;//如果全为偶数ret= size,存在至少一个奇数时ret<size,此时我们
//可以在对称轴再放置一个字母
else return ret;
}
};
贪心策略是,假设有一条对称轴,两边能放几个就放几个,如果是偶数那么全放完,如果是奇数只能放偶数个,最后如果还有剩下的字母,还可以再对称轴再放一个
15.增减字符串匹配
class Solution {
public:
vector<int> diStringMatch(string s) {
int size = s.size();
vector<int>ret(size+1);
int minnum = 0;
int maxnum = size;
for(int i = 0 ;i < size;i++)
{
if(s[i]=='I')
{
ret[i]= minnum;
minnum++;
}
else
{
ret[i]= maxnum;
maxnum--;
}
}
ret[size]= minnum;
return ret;
}
};
贪心的点在于,如果是I,即这个数到下个数要增长,那么我们选取当前最小的那个数,可以使得出现增长的情况数目最多,同样,当我们判断到D,我们选取最大的那个数,可以使得出现减少的情况数目最多。
严格地说当我们采取最大或者最小的那个数,那么后面的排序一定是下降或者上升趋势的。
最后一个位置需要我们手动排,这时候minnum=maxnum,任意选一个加入即可
16.分发饼干
class Solution {
public:
int findContentChildren(vector<int>& g, vector<int>& s) {
sort(g.begin(),g.end());
sort(s.begin(),s.end());
int sizeg = g.size();
int sizes = s.size();
int ret = 0;
int i = 0;
int j = 0;
while(i < sizeg &&j < sizes)
{
if(s[j]>=g[i])
{
i++;
j++;
ret++;
}
else j++;
}
return ret;
}
};
原理类似与13优势洗牌,我们把两个数组排序好,然后使用双指针依次同时向右即可,当饼干不能满足胃口时,直接丢弃饼干
17.最优除法
class Solution {
public:
string optimalDivision(vector<int>& nums) {
int size = nums.size();
string ret;
if(size == 1)return to_string(nums[0]);
else if(size == 2)
{
ret += to_string(nums[0]);
ret += '/';
ret += to_string(nums[1]);
}
else
{
ret += to_string(nums[0]);
ret += '/';
ret += '(';
for(int i = 1; i < size -1; i++)
{
ret += to_string(nums[i]);
ret += '/';
}
ret += to_string(nums[size - 1]);
ret += ')';
}
return ret;
}
};
这题主要是一个思路的问题,只要找到贪心的策略就比较简单了,只需要把数字填进去。
首先这么多数字相除a/b/c/d/e/f....
最后肯定会化成x/y,我们只需要使x最大,y最小即可,再观察式子,我们可以知道a肯定在分子上,而b无论括号怎么加,肯定在分母中。因此我们贪心地想,只需要把cdef....放在分子上即可,那么我们只需要在这样添加括号a/(b/c/d/e/f...),最后cdef....都会翻到分子上
18.跳跃游戏
class Solution {
public:
bool canJump(vector<int>& nums) {
int left = 0;
int right = 0;
int maxpos = 0;
int ret = 0;
int size = nums.size();
while(left <= right)
{
if(maxpos >= size -1)
{
return true;
}
for(int i = left; i <= right; i++)
{
maxpos = max(maxpos,i+nums[i]);
}
ret++;
left = right + 1;
right = maxpos;
}
return false;
}
};
这题实际上并不是严格使用贪心的而是使用层序遍历的思想
例如一个数组[2,3,1,1,4]
它开始时的左下标 =右下标 =0
然后从左下标开始遍历到右下标,i+nums[i]中的最大下标就是最远距离,也就是下一次遍历的右端点
下一次的左端点假设是right + 1。但是有可能跳不到,比如0 1 2 3第一次跳跃最远距离是0.
所以 2 3 1 1 4 被我们分成了
2 | 3 1| 1 4他们分别是第一次第二次第三次可以跳跃到的范围
19.跳跃游戏2
代码思路类似上一题
class Solution {
public:
int jump(vector<int>& nums) {
int left = 0;
int right = 0;
int maxpos = 0;
int ret = 0;
int size = nums.size();
while(1)
{
if(maxpos >= size -1)
{
return ret;
}
for(int i = left; i <= right; i++)
{
maxpos = max(maxpos,i+nums[i]);
}
ret++;
left = right + 1;
right = maxpos;
}
}
};
20.加油站
我们先把gas-cos,得到从这个加油站到下个加油站的净收益ret,如果小于0那么无法到下一个位置,首先我们会想到使用暴力解法,即从第一个位置开始遍历,在这个位置到向后size个位置
将每一个位置的ret累加进入count中,当count小于0时说明无法完成一周,我们break,在跳出后我们有两种情况,一种是count<0跳出,另一种是完成循环,count >= 0,因此进行一次判断,如果成功直接返回i的值
class Solution {
public:
int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
int size = gas.size();
vector<int>ret(size);
for(int i = 0; i < size;i++)
{
ret[i] = gas[i]-cost[i];
}
for(int i = 0; i < size;i++)
{
int count = 0;
for(int step = 0; step < size; step++)
{
int index =(i+step)%size;
count += ret[index%size];
if(count < 0)break;
}
if(count >= 0)return i;
}
return -1;
}
};
但是上面的方法时间复杂度太高了
我们可以知道
如果我们跑到e
a + b + c +d +e < 0
因为我们已经成功跑完abcd了所以
a >=0
a+b >= 0
a+b+c >= 0
a +b +c +d >=0
所以我们有
b+c+d+e < 0
c+d+e <0
d+e <0
e <0
因此从b到e我们都无法成功走完。
因此我们从f开始继续遍历
class Solution {
public:
int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
int size = gas.size();
vector<int>ret(size);
for(int i = 0; i < size;i++)
{
ret[i] = gas[i]-cost[i];
}
for(int i = 0; i < size;i++)
{
int count = 0;
int step;
for(step = 0; step < size; step++)
{
int index =(i+step)%size;
count += ret[index%size];
if(count < 0)break;
}
if(count >= 0)return i;
i = i + step;
}
return -1;
}
};
a b c d e f g
关于时间复杂度
当站点为a,我们仅遍历一次数组,复杂度N
当站点为b,我们先遍历了a-a,,再遍历了一次数组复杂度N+1
当站点为c,我们遍历了a-b,再遍历了一次数组复杂度N+2
最差的情况是g为开始的站点,时间复杂度为2N-1,即o(n)