文章目录
- 22、括号生成
- 31、下一个排列
- 45、跳跃游戏Ⅱ
- 48、旋转图像
- [50、Pow(x,n)]
- 53、最大子数组和
- 88、合并两个有序数组
- [122、买卖股票的最佳时机 II](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-ii/)
- 134、加油站
- 136、只出现一次的数字
- 169、多数元素
- [179. 最大数](https://leetcode.cn/problems/largest-number/)
- 217、存在重复元素
- 219、存在重复元素Ⅱ
- 228、区间汇总
- 268、丢失的数字
- 283、移动零
- [303、区域和检索 - 数组不可变 ](https://leetcode.cn/problems/range-sum-query-immutable/)
- 349、两个数组的交集
- 350、两个数组的交集Ⅱ
- 376、摆动序列
- 392、判断子序列
- 414、第三大的数
- 435、无重叠区间
- 448、找到所有数组中消失的数字
- 455、分发饼干
- 463、岛屿的周长
- 485、最大连续1的个数
- 495、提莫攻击
- 500、键盘行
- 506、相对名次
- [521、最长特殊序列]————————未解决
- 561、数组拆分
- 566、重塑矩阵
- 575、分糖果
- 594、最长和谐子序列
- 598、范围求和Ⅱ
- 599、两个列表的最小索引总和
- 605、种花问题
- 1588、所有奇数长度子数组的和
- [1005、K 次取反后最大化的数组和](https://leetcode.cn/problems/maximize-sum-of-array-after-k-negations/)
- 2348、全0子数组的数目
- 优化策略
22、括号生成
经典回溯问题
class Solution {
public:
vector<string> rst;
string path;
vector<string> generateParenthesis(int n) {
backtracking(n,0,0);
return rst;
}
// 左括号数量和右括号数量
void backtracking(int n,int left, int right){
if(left==n&&right==n){// 回溯出口就是当括号加满的时候
rst.push_back(path);
return ;
}
if(left<n){ // 左括号没有加满,那么优先加左括号进行递归
path.push_back('(');
backtracking(n,left+1,right);
path.pop_back();
}
if(right<left){ // 还可以加右括号的时候,加右括号进行递归
path.push_back(')');
backtracking(n,left,right+1);
path.pop_back();
}
}
};
31、下一个排列
首先需要知道字典序算法:
1)从右到左找到第一个比i-1处小于i处的序列号,a=i-1;
2)从右到左找到第一个比序列号a处数字大的序列号b;
3)交换a,b处的数
例如:
123 => 132(list[1]<=>list[2]) index = 1之后的由小到大重排列
132 => 231(list[0]<=>list[2])index = 0之后的由小到大重排列
231 => 321(list[0]<=>list[1])index = 0之后的由小到大重排列
321 => 123 逆置整体
该题就是字典序算法
class Solution {
public:
void nextPermutation(vector<int>& nums) {
int a = 0;
int b = 0;
// 找到从右到左第一个减少的数
for(int i = nums.size()-1;i>0;i--){
if(nums[i]>nums[i-1]){
a = i-1;
break;
}
}
// 从右到左找到第一个比nums[a]大的数字
for(int i = nums.size()-1;i>=0;i--){
if(nums[i]>nums[a]){
b = i;
break;
}
}
// 如果不存在字典序更大的排列,则逆置整体
if(a==0&&b==0){
reverse(0,nums);
return;
}
// 交换a,b,逆置a之后的数字
swap(nums,a,b);
reverse(a+1,nums);
}
// 逆置k之后的内容
void reverse(int k,vector<int>& nums){
for(int i=k;i<(nums.size()+k)/2;i++){
cout<<i<<endl;
swap(nums,i,nums.size()-1+k-i);
}
}
// 交换nums[a]与nums[b]
void swap(vector<int>& nums,int a,int b){
int k = nums[a];
nums[a] = nums[b];
nums[b] = k;
}
};
45、跳跃游戏Ⅱ
每次跳的时候,计算一下我能跳多远,然后对每一个能跳的位置进行计算:跳到哪里那么下一次能跳的更远。
class Solution {
public:
int jump(vector<int>& nums) {
// 如果是一个节点,那么不需要跳
if(nums.size()==1)return 0;
// 当前所在位置
int position = 0;
// 跳的次数
int jump = 0;
// 还没跳到最后
while(position<nums.size()-1){
++jump;
// 当前相对该位置可以跳到的最远距离(index)
int now_distance = nums[position]+position;
// 如果当前可以直接跳到最后一个节点,那么就不去看下一跳
if(now_distance>=nums.size()-1)return jump;
// 下一跳的最远距离
int next_max_distance = 0;
// 遍历能跳的范围,判断在本次能跳到的距离内,下一跳所能跳到的最远距离
for(int i=position+1;i<=now_distance;++i){
// 如果直接跳到了最后,那么不再进行循环,选择该点进行跳跃
if(i==nums.size()-1)break;
// 获得下一跳所能跳到的最远距离,并将 position跳到这里
if(nums[i]+i>next_max_distance){
next_max_distance=nums[i]+i;
position = i;
}
}
}
return jump;
}
};
48、旋转图像
遍历[i][j]
要求j>=i
,并且i<(nums.size()+1)/2
(上半部分矩阵) 并且j<nums-i-1
类似于下图,但是需要注意的是每一行标记部分的最后一个不用遍历到(因为他已经在每一行第一次旋转的时候进行了修改)。
假设:左上的行 = i,左上的列 = j
那么
右上的行 = 左上的列
右上的列 = nums.size()-1-左上的行
右下的行 = nums.size()-1-左上的行
右下的列 = nums.size()-1-左上的列
左下的行 = 右下的列
左下的列 = 左上的行
左上-》右上
右上-》右下
右下-》左下
左下-》左上
class Solution {
public:
void rotate(vector<vector<int>>& matrix) {
int num = matrix.size();
for(int i=0;i<(num+1)/2;i++){
for(int j=i;j<num-i-1;j++){
//左上存储
int a = matrix[i][j];
//左上等于左下
matrix[i][j] = matrix[num-j-1][i];
//左下等于右下
matrix[num-j-1][i] = matrix[num-i-1][num-j-1];
//右下等于右上
matrix[num-i-1][num-j-1] = matrix[j][num-1-i];
//右上等于存储的左上
matrix[j][num-1-i]=a;
}
}
}
};
[50、Pow(x,n)]
53、最大子数组和
就一句话:祖辈留给你的房子好的话就接着往上盖,不好的话自己重新盖
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int max = nums[0];
int res = max;
for(int i = 1;i<nums.size();i++){
max = (nums[i]+max)>nums[i]?nums[i]+max:nums[i];
res = max>res?max:res;
}
return res;
}
};
88、合并两个有序数组
方法:
①从前往后依次遍历
缺点:每次都需要移动(因为这里是数组不是链表)
②直接合并然后排序
缺点:没有利用有序数组这个初始性质
而采用从后往前遍历的方式,能够有效解决上面两个缺点
分别设置两个指针分别指向两个数组的尾部(m-1,n-1)的位置,并且另外设置一个i指向最后的位置。每次i–,并且将两个数组较大的元素放入i位置,移动较大元素的指针。
从后到前遍历,当全部遍历结束时,或者nums2遍历到起始位置时截至(此时i始终和m-1大小一致)。
class Solution {
public:
void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
for(int i=m+n-1;i>=0&&n>0;i--){
if(m!=0&&nums1[m-1]>nums2[n-1]){
nums1[i] = nums1[--m];
}else{
nums1[i] = nums2[--n];
}
}
}
};
123000
256
为例
每次结束时
① i = 5,m = 3,n = 2;nums1[m-1]>nums2[n-1] (3>6)不成立,移动n,nums1 = [1,2,3,0,0,6]
② i = 4,m = 3,n = 1;nums1[m-1]>nums2[n-1] (3>5)不成立,移动n,nums1 = [1,2,3,0,5,6]
③ i = 3,m = 2,n = 1;nums1[m-1]>nums2[n-1] (3>2)成立,移动m,nums1 = [1,2,3,3,5,6]
④ i = 2,m = 2,n = 0;nums1[m-1]>nums2[n-1] (2>2)不成立,移动n,nums1 = [1,2,2,3,5,6]
⑤ n = 0,nums2元素全部使用,之后i和m指向位置相同,不需要再进行操作。
122、买卖股票的最佳时机 II
只要我能挣钱,我就进行这次交易
class Solution {
public:
int maxProfit(vector<int>& prices) {
int money = 0;
for(int i=1;i<prices.size();++i){
if(prices[i]-prices[i-1]>0)money+=prices[i]-prices[i-1];
}
return money;
}
};
134、加油站
只能绕环路行驶一周
那么假设总共的加油量 < 总共的耗油量,那么必然有一个位置会跑不过去。
假如我现在跑到了第i站,在第i站加了油,但是一算,我跑到i+1站,油就不够了。所以我第二次选择从i+1站开始跑(因为在第1站加了油是必然能跑到第2站的,因此在第二站剩余的油>=0,同理在前i站的任意一站剩余的油均>=0,但是从2~i任意一个位置开始的话起始油量都是0,那么之前任意一站的剩余油量>=0时都跑不到i+1,起始油量=0更跑不到i+1了)
因此计算从i+1开始看能否跑到终点,如果能跑到终点必然能够再次从0跑到i,因为之前计算过跑到i的时候还有剩余油量。
如果不能,那么只要总加油量-总耗油量>0,并且存在一个位置能跑到终点就能行驶一周。
证明:
如果不能,那么继续从i+a+1开始看能否跑到终点,如果能跑到终点,那么说明能从i+a+1跑到n再从1跑到 i,因此就只剩下一个问题就是能否继续从i+1跑到i+a?(可以将i+1 到 i+a看成一个加油站)既然不选择i+1到i+a作为起点,说明从i+1到i+a任意位置出发都不可能到达i+a,说明i+1到i+a位置油量累加的话为负数,且越来越负,性质都是一样的。那么假如绕一圈跑到i+1时的剩余油量 > i+1到i+a位置油量累加
,也就是总加油量-总耗油量是否大于0
。
class Solution {
public:
int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
int nowGas = 0;
int first = 0;
int allSum = 0;
for(int i = 0;i<gas.size();i++){
nowGas = nowGas + gas[i] - cost[i];
allSum = allSum + gas[i] - cost[i];
if(nowGas<0){
first = i+1;
nowGas = 0;
}
}
return allSum<0?-1:first;
}
};
136、只出现一次的数字
知识点:
两个相同元素异或 = 0
一个元素与0异或 = 元素本身
异或符号^
class Solution {
public:
int singleNumber(vector<int>& nums) {
int rst=0;
for(auto k: nums)
rst^=k;
return rst;
}
};
169、多数元素
- 排序法
如果是多数元素,则该元素数量大于[n/2],那么在排序之后,中间位置上的数字必定为目标元素。
class Solution {
public:
int majorityElement(vector<int>& nums) {
sort(nums.begin(), nums.end());
return nums[nums.size() / 2];
}
};
- 哈希表法
通过哈希表记录各个元素出现的数量,动态记录出现的数量最多的元素。
class Solution {
public:
int majorityElement(vector<int>& nums) {
unordered_map<int, int> counts;
int rst = 0, max_count = 0;
for(auto num:nums){
++counts[num];
if(counts[num]>max_count){
rst = num;
max_count = counts[num];
}
}
return rst;
}
};
- Boyer-Moore投票算法
设当前票数最多的是major,票数是count。数组元素从头到尾开始投票。
①如果和自身的值相同,应该投支持票,那么major不变,count++
②如果和自身的值不同,应该投反对票,那么看当前major有多少票
如果count>0,那么count–,相当于投了一个反对票;
如果count==0,那么代表从本轮开始已经没人支持当前的major了。于是当前元素把major踢下去,自己当major,count = 1。
因为该数组中有一个元素数量大于【n/2】,那么最后当选的肯定是major,因为其他反对者票数不过半
以2,2,1,1,1,2,2举例
2,2之后maj = 2, count = 2
1,1之后maj = 2, count = 0
1之后maj = 1,count = -1
2之后maj = 1,count = 0
2之后maj = 2,count = 1
class Solution {
public:
int majorityElement(vector<int>& nums) {
int major, count=0;
for(auto num:nums){
if(num==major)++count;
else{
if(count>0)--count;
else{
major = num;
count = 1;
}
}
}
return major;
}
};
179. 最大数
本道题的思路就是对数组进行排序,排序的方式就是先按照最高位进行排序,最高位相同按照次高位,依次比较至最低位。
那么如果对A和B进行这种排序,那么如果A+B>B+A
那么就应该把A放在前面,否则把B放在前面。
关于这种排序方式,可以去了解lambda表达式,以及lambda表示式与sort的组合使用
class Solution {
public:
string largestNumber(vector<int>& nums) {
string rst;
vector<string> nums_str;
for(auto num:nums){
nums_str.push_back(to_string(num));
}
auto cmp = [](string a,string b){
return a+b>b+a;
};
sort(nums_str.begin(),nums_str.end(),cmp);
// 因为数字只有0的最高位数未0,如果0在首位必然比0在末位要小,所以正常情况不会在前面,只有都是0时才会在前面。
if(nums_str[0][0]=='0')return "0";
for(auto num_str : nums_str){
rst+=num_str;
}
return rst;
}
};
// bool cmp(int a,int b)
// {
// return a>b;//降序排列
// }
// int array[5]={1,2,3,4,5};
// sort(array,array+4,cmp);
// 对0-4进行排序,如果满足a>b时,把a放前面
217、存在重复元素
哈希表法
记录出现了的元素,如果出现次数大于1则返回true
class Solution {
public:
bool containsDuplicate(vector<int>& nums) {
unordered_map<int,int> counts;
for(auto num:nums){
++counts[num];
if(counts[num]>1)return true;
}
return false;
}
};
219、存在重复元素Ⅱ
哈希表法
因为需要获取两次相接的索引,因此哈希表中存储的应该是元素的索引,注意因为哈希表起始值为0,所以这里存储应该是索引值+1
更为方便。
- 如果当前遍历到的num位置哈希值不为0,那么设置当前索引值+1
- 如果当前遍历到的num位置哈希值为0,那么查看和上次的索引值差是否小于k
小于k,则返回true.
大于k,则记录新的索引值+1,因为假如之后还有重复的元素,必然和上一个重复的元素索引值之差大于k,那么就不需要记录上一个的索引值,只需要维护当前索引值。。
class Solution {
public:
bool containsNearbyDuplicate(vector<int>& nums, int k) {
unordered_map<int,int> num_v;
for(int i=0;i<nums.size();i++){
if(num_v[nums[i]]==0){
num_v[nums[i]] = i+1;
}
else{
if(abs(num_v[nums[i]]-i-1)<=k)return true;
else{
num_v[nums[i]] = i+1;
}
}
}
return false;
}
};
228、区间汇总
开始设置第一个区间的起始索引为 a = 0
从i = 1
开始,如果第i项 = 第i-1项+1
,那么代表还是一个区间,继续循环;否则从当前开始就是一个新的区间,那么此时如果a = i-1
,那么代表该区间只有一个数字,直接字符串化加入结果集,此时如果a != i-1
,那么将字符串a->i-1
加入结果集。
最后循环结束之后还需要对最后一段区间进行处理,处理结果类似。
class Solution {
public:
vector<string> summaryRanges(vector<int>& nums) {
vector<string> rst;
if(nums.size()==0)return rst;
int a = 0;
for(int i=1;i<nums.size();++i){
if(nums[i] == 1+nums[i-1])continue;
if(i-1==a)rst.emplace_back(to_string(nums[a]));
else{
rst.emplace_back(to_string(nums[a])+"->"+to_string(nums[i-1]));
}
a = i;
}
if(nums.size()-1==a)rst.emplace_back(to_string(nums[a]));
else{
rst.emplace_back(to_string(nums[a])+"->"+to_string(nums[nums.size()-1]));
}
return rst;
}
};
268、丢失的数字
- 哈希表法
两次循环,第一次通过哈希表记录出现过的数,第二次遍历哈希表,返回哈希表[0-n]上value为 0的索引值。
class Solution {
public:
int missingNumber(vector<int>& nums) {
unordered_map<int,int> val;
int n = nums.size();
for(auto num:nums){
val[num] = 1;
}
for(int i=0;i<n+1;i++){
if(val[i]==0)return i;
}
return 0;
}
};
- 数学方法
sum = n*(n+1)/2
表示从[0-n]的数的总和,通过sum依次减去各个元素,最终的结果就是缺少的元素
class Solution {
public:
int missingNumber(vector<int>& nums) {
int n = nums.size();
int sum = (n)*(n+1)/2;
for(auto num:nums){
sum -= num;
}
return sum;
}
};
283、移动零
设置两个索引,从开始进行遍历到最后,一个指向零元素,一个指向非零元素,每次遇见一个0元素,就与之后的一个非0元素进行交换,非零元素的索引值必定大于零元素的索引值。
循环的条件就是:非零元素的索引还没有到最后(指到了最后表示零之后已经没有非零元素)并且零元素的索引没有到最后。
需要注意的是,如果是遇见第一个0,那么需要对非零元素的索引值进行初始化,设置为与当前零元素索引值大小一样。
class Solution {
public:
void moveZeroes(vector<int>& nums) {
int j=0;
for(int i = 0;j!=nums.size()&&i<nums.size();++i){
if(nums[i]!=0)continue;
// 遇见了一个0
// 如果是遇见第一个0,那么j从第一个0开始向后找非零的数
if(j==0)j=i;
for(j;j<nums.size();++j){
if(nums[j]!=0){
// 并进行交换,将0与非零交换,然后继续从上次找到的零开始继续找0
swap(nums[i],nums[j]);
break;
};
}
}
}
};
303、区域和检索 - 数组不可变
直接将数一个个加入数组中,返回时进行累加
class NumArray {
public:
vector<int> nums_;
NumArray(vector<int>& nums) {
for(auto &num: nums)nums_.emplace_back(num);
}
int sumRange(int left, int right) {
int sum = 0;
for(int i = left;i<right+1;i++){
sum+=nums_[i];
}
return sum;
}
};
直接在存储时进行求和
sum[i]记录前i项和
sum[j]记录前j项和
class NumArray {
public:
vector<int> sum;
NumArray(vector<int>& nums) {
sum.resize(nums.size());
sum[0] = nums[0];
// 前i项和 = 当前元素 + 前i-1项和
for(int i=1;i<nums.size();++i)
sum[i] = sum[i-1]+nums[i];
}
// 如果是left = 0,返回【0-right】,也就是sum[right]-0
// 如果是left !=0, 返回【left-right】,也就是sum[right]-sum[left-1]
int sumRange(int left, int right) {
return left==0?sum[right]:sum[right]-sum[left-1];
}
};
349、两个数组的交集
设置一个哈希表,存储nums1,再次循环遍历nums2,查看是否在哈希表中存在,如果存在那么加入结果集,并且从哈希表中删除(因为每个元素需要获取一次)
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
unordered_set<int> count(nums1.begin(),nums1.end());
vector<int> rst;
for(auto num:nums2){
if(count.find(num)!=count.end()){
rst.push_back(num);
count.erase(num);
}
}
return rst;
}
};
350、两个数组的交集Ⅱ
这次需要记录出现的次数,那么通过哈希表记录nums1出现的次数,再次循环遍历nums2,查看是否在哈希表中记录的数量是否大于0,如果存在那么加入结果集,并且将哈希表中次数减一。这样总能收集次数较小的值
class Solution {
public:
vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
unordered_map<int,int> count;
vector<int> rst;
for(auto num: nums1)count[num]++;
for(auto num: nums2){
if(count[num]!=0){
rst.push_back(num);
count[num]--;
}
}
return rst;
}
};
376、摆动序列
将当前元素与上一项差值,以及上一次的差值进行记录。
如果与上一项值相同的话,那么与上一个不同的元素差值必然不变,摆动序列无法扩大,那么不需要进行操作。
如果与上一项值不同,那么要求与上一次的差值正负号性质相反;等于0情况主要针对前几项一直相同的情况,因为只要last之后修改为d,而d肯定不等于0。-》如果满足的话则摆动序列+1,并且上一次的差值产生变化。
class Solution {
public:
int wiggleMaxLength(vector<int>& nums) {
if(nums.size()==1)return 1;
int last = nums[1]-nums[0];
int max = last==0?1:2;
for(int i=2;i<nums.size();i++){
int d = nums[i] - nums[i-1];
if(d==0)continue;//如果与上一项值相同的话,那么与上一个不同的元素差值必然不变,那么不需要进行修改
if(last*d<=0){// 如果与上一项值不同,那么要求与上一次的差值正负号性质相反。等于0情况主要针对前几项一直相同的情况,因为只要last之后修改为d,而d肯定不等于0
max++;
last = d;
}
}
return max;
}
};
392、判断子序列
设置一个指针j
指向t[0]
遍历s,如果当前元素与t[j]
相同,那么移动j,如果j移出t那么返回true;否则返回false
class Solution {
public:
bool isSubsequence(string s, string t) {
if(s.size()>t.size())return false;
int j=0;
for(int i=0;i<t.size();++i){
if(s[j]==t[i])j++;
if(j==s.size())return true;
}
return j==s.size()?true:false;
}
};
414、第三大的数
通过max、mid、min分别记录第一大、第二大、第三大的数字。开始设为空指针(或者极小值)表示还没有进行初始化。
class Solution {
public:
int thirdMax(vector<int>& nums) {
int *max=nullptr, *mid=nullptr ,*min=nullptr;
for(int &num:nums){
// 如果最大值是空指针或者大于最大值
if(max == nullptr||num>*max){
min = mid;
mid = max;
max = #
}
// 小于最大值,并且(中间值为空指针或者大于中间值)
else if(num<*max&&(mid == nullptr||num>*mid)){
min = mid;
mid = #
}
// 中间值存在并且小于中间值,并且(最小值为空指针或者大于最小值)
else if((mid!=nullptr&&num<*mid)&&(min == nullptr||num>*min)){
min = #
}
}
return min == nullptr ? *max : *min;
}
};
435、无重叠区间
将题目问题转换为能最多能保留多少个区间,那么每一次就是选择结尾最靠前的(选择时需要满足本次选择与已经产生的区间不重叠)
class Solution {
public:
int eraseOverlapIntervals(vector<vector<int>>& intervals) {
if(intervals.size()<2)return 0;
int rst = 0;
// 先对intervals中的数组按照第二个数进行升序排序。
sort(intervals.begin(), intervals.end(), [](vector<int>& u, vector<int>& v) {
return u[1] < v[1];
});
// 设i为前一个包,next为当前遍历的包
int next = 1;
for(int i=0;next<intervals.size();++next){
// 当前遍历的数组的开头应该大于或者等于前一个纳入的包的尾部
// 如果不满足,那么当前便利的不纳入
if(intervals[next][0]<intervals[i][1]){
rst++;
continue;
}
// 如果满足,那么下一次的前一个包就是当前的包
i=next;
}
return rst;
}
};
448、找到所有数组中消失的数字
数组中各个元素指向 自身大小 - 1
的位置的元素。
那么将被指的元素的绝对值进行取负操作,修改为负数,在进行后续遍历时对所有元素再次进行取绝对值操作,那么负号只是作为标记是否出现的一个符号而已
以4 3 2 7 8 2 3 1为例
第一次遍历4,那么将3位置上的数字取负修改为:
4 3 2 -7
8 2 3 1
第一次遍历3,那么将2位置上的数字取负修改为:
4 3 -2
-7 8 2 3 1
第一次遍历2,那么将1位置上的数字取负修改为:
4 -3
-2 -7 8 2 3 1
第一次遍历7,那么将6位置上的数字取负修改为:
4 -3 -2 -7 8 2 -3
1
第一次遍历8,那么将7位置上的数字取负修改为:
4 -3 -2 -7 8 2 -3 -1
第一次遍历2,那么将1位置上的数字取负修改为:
4 -3
-2 -7 8 2 -3 -1
第一次遍历3,那么将2位置上的数字取负修改为:
4 -3 -2
-7 8 2 -3 -1
第一次遍历1,那么将0位置上的数字取负修改为:
-4
-3 -2 -7 8 2 -3 -1
class Solution {
public:
vector<int> findDisappearedNumbers(vector<int>& nums) {
int n = nums.size();
for (auto num : nums) {
nums[abs(num)-1] = -abs(nums[abs(num)-1]);
}
vector<int> rst;
for (int i = 0; i < n; i++) {
if (nums[i] > 0) {
rst.push_back(i + 1);
}
}
return rst;
}
};
455、分发饼干
本题只要就计算最大满足数量,并且每个饼干对应一个孩子,因此利用简单的贪心算法,将饼干大小和孩子胃口大小进行排序。
对饼干大小从小到大进行遍历,并且当满足当前孩子胃口大小时直接满足这个孩子,下一次从下一个孩子开始尝试满足。
class Solution {
public:
int findContentChildren(vector<int>& g, vector<int>& s) {
sort(g.begin(),g.end());
sort(s.begin(),s.end());
int rst=0,j=0;
for(int i=0;i<s.size()&&j<g.size();i++){
if(s[i]>=g[j]){
rst++;
j++;
}
}
return rst;
}
};
463、岛屿的周长
每次出现一块陆地,那么看这个陆地四个边是不是边界或者海洋,如果是边界或者海洋都需要让周长+1
class Solution {
public:
int islandPerimeter(vector<vector<int>>& grid) {
int rst=0;
for(int i=0;i<grid.size();++i){
for(int j=0;j<grid[i].size();++j){
if(grid[i][j]){
// 右是边界
if(j==0){
//cout<<i<<j<<"左是边界"<<endl;
rst++;
}
// 右侧是边界
if(j==grid[i].size()-1){
//cout<<i<<j<<"右是边界"<<endl;
rst++;
}
// 左侧是水
if (j!=0&&grid[i][j-1]==0){
//cout<<i<<j<<"左侧是水"<<endl;
rst++;
}
// 右侧是水
if (j!=grid[i].size()-1&&grid[i][j+1]==0){
//cout<<i<<j<<"右侧是水"<<endl;
rst++;
}
// 上是边界
if(i==0){
//cout<<i<<j<<"上是边界"<<endl;
rst++;
}
//下是边界
if(i==grid.size()-1){
//cout<<i<<j<<"下是边界"<<endl;
rst++;
}
// 上方是水
if(i!=0&&grid[i-1][j]==0){
//cout<<i<<j<<"上方是水"<<endl;
rst++;
}
// 下方是水
if(i!=grid.size()-1&&grid[i+1][j]==0){
//cout<<i<<j<<"下方是水"<<endl;
rst++;
}
}
}
}
return rst;
}
};
485、最大连续1的个数
如果当前数是1则让计数器+1;
否则查看当前计数器是否大于最大值,如果大于最大值则进行更新,同时将计数器设置为0。
最后进行最后一次结果的收集。
class Solution {
public:
int findMaxConsecutiveOnes(vector<int>& nums) {
int max = 0;
int a = 0;
for(int i=0;i<nums.size();i++){
if(nums[i]==1){
a++;
}else{
max = (a) > max? a:max;
a = 0;
}
}
max = (a) > max? a:max;
return max;
}
};
495、提莫攻击
如果没有攻击,则直接返回零
如果下次攻击距离本次攻击,时间间隔大于duration秒,那么中毒延续duration秒
如果下次攻击距离本次攻击,时间间隔小于duration秒,那么中毒延续两次攻击时间间隔
最后一次攻击后中毒再次延续duration秒。
class Solution {
public:
int findPoisonedDuration(vector<int>& timeSeries, int duration) {
if(timeSeries.size()==0)return 0;
int sum=0;
for(int i=0;i<timeSeries.size()-1;++i){
sum = sum + min(timeSeries[i+1]-timeSeries[i],duration);
}
return sum+duration;
}
};
500、键盘行
初始化每个字母所在的行
然后遍历每个字符串
获取字符串第一个字符所在的行
再次遍历字符串从1开始的每个字符,看所在行是不是与第一个一致,如果不一致则直接进行下一次循环(每次循环开始默认是相同的一行,出现不同时进行修改)。
如果没有检测到不一致,那么加入结果集。
class Solution {
public:
vector<string> findWords(vector<string>& words) {
vector<string> rst;
int rows[26] = {1, 2, 2, 1, 0, 1, 1, 1, 0, 1, 1, 1, 2, 2, 0, 0, 0, 0, 1, 0, 0, 2, 0, 2, 0, 2};
int isOne = 1;
for (auto word : words) {
// 记录第一个字符所在的行
int c_row = rows[tolower(word[0])-'a'];
for (int i = 1; i < word.size(); ++i) {
// 存在字符所在行与第一个不一致
if(rows[tolower(word[i]) - 'a'] != c_row) {
isOne = false;
break;
}
}
// 如果存在不一致,直接进行下一次循环
if (!isOne) {
isOne = 1;
continue;
}
rst.emplace_back(word);
}
return rst;
}
};
506、相对名次
通过pair类型(类似于哈希表,不过能通过其中某一项进行整体的排序),通过分数对编号进行排序,然后再根据分数从低到高给予名词
class Solution {
public:
vector<string> findRelativeRanks(vector<int>& score) {
int n = score.size();
vector<string> rst(n);
vector<pair<int,int>> s;
for(int i=0;i<score.size();i++){
s.push_back(pair(score[i],i));
}
// 排序,从低到高
sort(s.begin(),s.end());
//从倒数第一开始往前开始
for(int i=0;i<n-3;i++){
rst[s[i].second] = to_string(n-i);
}
if(n>2)rst[s[n-3].second] = "Bronze Medal";
if(n>1)rst[s[n-2].second] = "Silver Medal";
if(n>0)rst[s[n-1].second] = "Gold Medal";
return rst;
}
};
[521、最长特殊序列]————————未解决
561、数组拆分
本题意思简单理解就是:将2n个数,每两个一组划分为n组,将每组中的较小值累加,寻找能够产生的最大总和。
那么通过排序,将最大和第二大放在一组,第三大和第四大放在一组。。。。。
这样能够有效利用较大的值。因为最大的一定没法利用,此时考虑利用第二大的;当第二大的利用之后,第三大一定也没办法进行利用,此时考虑利用第四大的,以此类推。
class Solution {
public:
int arrayPairSum(vector<int>& nums) {
sort(nums.begin(),nums.end());
int rst = 0 ;
for(int i=0;i<nums.size();i++){
if(i%2==0)
rst+= nums[i];
}
return rst;
}
};
566、重塑矩阵
首先获取原来矩阵的形状,然后从左到右,从上到下对数组进行遍历,并且通过index记录当前元素在整体的下标,再计算在新的矩阵中的行(index/c)
和列(index%c)
class Solution {
public:
vector<vector<int>> matrixReshape(vector<vector<int>>& mat, int r, int c) {
if(r*c!=mat.size()*mat[0].size())return mat;
vector<vector<int>> rst(r,vector<int>(c));
int _r = mat.size(), _c = mat[0].size();
int k = 0;
for(int i=0;i<_r;++i){
for(int j=0;j<_c;++j){
// index也可以直接通过原来数组的行和列计算出来。
///rst[(i*_c + j)/c][(i*_c + j)%c] = mat[i][j];
rst[(k)/c][(k)%c] = mat[i][j];
k++;
}
}
return rst;
}
};
575、分糖果
计算糖果种类,如果糖果种类比能够吃的数量多,那么品尝的种类大小等于能吃的数量
如果糖果种类比能够吃的数量少,那么品尝的种类大小等于糖果种类
class Solution {
public:
int distributeCandies(vector<int>& candyType) {
int type = 0;
unordered_map<int,int> count;
for(int candy:candyType)count[candy]++;
type = count.size();
if(type>candyType.size()/2)return candyType.size()/2;
else{
return type;
}
}
};
594、最长和谐子序列
类似于一种滑动窗口的方法,设置两个指针分别指向窗口头尾。对数组进行排序,并进行遍历。
不断移动尾指针,如果指针两元素之差大于1,那么移动头指针。
如果nums[end]-nums[begin]>1,那么移动头指针,移动了begin后,继续移动end,产生的子序列不会比上次长,那么可以移动end
如果nums[end]-nums[begin]==1,那么查看当前长度,动态维护最长子序列。
class Solution {
public:
int findLHS(vector<int>& nums) {
sort(nums.begin(),nums.end());
int begin = 0;
int res = 0;
for (int end = 0; end < nums.size(); end++) {
while (nums[end] - nums[begin] > 1) {
begin++;
}
if (nums[end] - nums[begin] == 1) {
res = max(res, end - begin + 1);
}
}
return res;
}
};
class Solution {
public:
int findLHS(vector<int>& nums) {
sort(nums.begin(),nums.end());
int max = 0;
// 记录本次数量
int begin = 0;
for(int end=0;end<nums.size();++end){
// 如果移动了begin后,直接移动end,产生的子序列不会比上次长,那么可以直接移动end
if(nums[end]-nums[begin]>1)++begin;
if(nums[end]-nums[begin]==1){
max = end-begin+1>max?end-begin+1:max;
}
}
return max;
}
};
598、范围求和Ⅱ
又是一道脑筋急转弯题,其实就是求解op中出现的最小行和最小列,那么个数就是 最小行*最小列
例如【3,3】操作的话肯定会影响 到【2,2】操作影响的所有数,但是【2,2】操作不会影响到【3,3】操作影响的全部数字
class Solution {
public:
int maxCount(int m, int n, vector<vector<int>>& ops) {
int min_r = m, min_l = n;
for(vector<int> op:ops){
min_r = min(min_r,op[0]);
min_l = min(min_l,op[1]);
}
return min_l*min_r;
}
};
599、两个列表的最小索引总和
通过哈希表记录第一个列表出现的值,key为饭店,value为下标
设置s = 两个列表长度之和
然后循环遍历list2,如果能够在哈希表中找到,计算当前索引之和是否小于s,如果小于s那么就清空结果集,重新赋值s为当前索引值之和,
如果当前索引之和等于s,那么加入结果集。
class Solution {
public:
vector<string> findRestaurant(vector<string>& list1, vector<string>& list2) {
unordered_map<string,int> index_;
for(int i=0;i<list1.size();i++){
index_[list1[i]] = i;
}
int min_ = list2.size()+list1.size();
int s = 0;
vector<string> rst;
for(int i=0;i<list2.size();i++){
if(index_.count(list2[i])){
s = index_[list2[i]]+i;
if(s<min_){
rst.clear();
min_ = s;
}
if(s==min_)
rst.emplace_back(list2[i]);
}
}
return rst;
}
};
605、种花问题
1.思路:每次可以种的时候就把花种上,如何判断能否种花,就是前面和后面和自己都是空的那么就可以种,需要特别处理前后,只需要两个位置就可以种植,而且前面两个位置种第一个,那么产生的种植棵树更大,后面两个位置种后一个,产生的种植棵树更大。
class Solution {
public:
bool canPlaceFlowers(vector<int>& flowerbed, int n) {
int size = flowerbed.size();
// 不需要种植,直接返回
if(n==0)return true;
// 就一块地,那么单独处理
if(size==1){
if(flowerbed[0]==0)return n<=1;
else return false;
}
// 前两块地进行处理
if(flowerbed[0]==0&&flowerbed[1]==0){
flowerbed[0]=1;
n--;
}
// 从第三块地开始种植,因为不管怎样,第二块地一定都不会进行种植,一直种植到倒数第三块,倒数第二块一定不会种植。
for(int i=2;i<size-2;++i){
if(!flowerbed[i-1]&&!flowerbed[i+1]&&!flowerbed[i]){
flowerbed[i] = 1;
n--;
}
//if(n==0)return true;
}
// 后两块地进行处理
if(flowerbed[size-1]==0&&flowerbed[size-2]==0){
// flowerbed[size-1]=1;
n--;
}
return n<=0;
}
};
- 充分利用原来题中也是符合种花的条件。
- 如果 i 处种了花即
flowerbed[i] == 1
,直接跳过中间,i = i + 2
(跳过的位置必定没有种花,那么意思就是我当前浏览位置的前一个位置,必定没有种花) - 如果 i 处没有种花即
flowerbed[i] == 0
,判断i + 1
是否种花,如果没有则可以在i处种花(因为上面可以证明i-1位置必定没有种花)。同样种花之后需要i=i+2
。如果是最后一位那么可以直接种花,不用判断i+1是否种花.
class Solution {
public:
bool canPlaceFlowers(vector<int>& flowerbed, int n) {
for(int i=0;i<flowerbed.size();i++){
if(flowerbed[i]==1){
i++;
}else if(flowerbed[i]==0&&(i==flowerbed.size()-1||flowerbed[i+1]==0)){
n--;
i++;
}
}
return n<=0;
}
};
1588、所有奇数长度子数组的和
可以采用回溯法遍历完所有的结果,虽然存在不少剪枝,但是时间复杂度还是较高
这道题采用数学推导方式比较简单
0 1 2 3 4 5 6 7 8 9
以其中3为例,那么可以构成的奇数长度子数组
假如左侧只有一个2,那么右侧可以有4、4 5 6 、 4 5 6 7 8(也就是左侧有一个,右侧也必须有奇数个)
假如左侧只有一个1 2,那么右侧没有数字,也可以有4 5、4 5 6 7 、 4 5 6 7 8 9(也就是左侧有两个,右侧也必须有偶数个)
同理可以推出:
假如左侧有奇数个,右侧也必须是奇数个,那么加上这个数字自身可以构成奇数长度的子数组
假如左侧有偶数个,右侧也必须是偶数个,那么加上这个数字自身可以构成奇数长度的子数组(不要忘记0个也是偶数个)
class Solution {
public:
int sumOddLengthSubarrays(vector<int>& arr) {
int rst = 0;
int n = arr.size();
int type = 0;
for(int i=0;i<n;i++){
// (i+1)/2*(n-i)/2 奇
// (i/2+1)*((n-i-1)/2+1) 偶,0也算偶数,因此左侧和右侧偶数需要+1
type = ((i+1)/2)*((n-i)/2) + (i/2+1)*((n-i-1)/2+1);
rst += arr[i]*type;
}
return rst;
}
};
1005、K 次取反后最大化的数组和
首先将数组进行排序,并且对负数按绝对值大小从大到小改变为正数。
如果改变次数为0,那么直接计算总和
如果所有数全部修改为正数,但是改变次数还有剩余,那么判断改变次数为奇数还是偶数=》如果是奇数,那么对数组再次进行排序,改变最小值的正负号;如果是偶数,仍然直接计算综合(偶数的话就是把一个数变过来变过去)
class Solution {
public:
// 先把负数从绝对值大的往小取反
int largestSumAfterKNegations(vector<int>& nums, int k) {
sort(nums.begin(),nums.end());
for(int i=0;i<nums.size()&&k>0;++i){
if(nums[i]<0)nums[i]=-nums[i];
else if(nums[i]>0)break;
--k;
}
sort(nums.begin(),nums.end());
nums[0]=k%2==1?-nums[0]:nums[0];
int res = 0;
for(int i=0;i<nums.size();++i){
res+=nums[i];
}
return res;
}
};
2348、全0子数组的数目
脑筋急转弯题
以0 0 0 2 0 0为例
01,02,03,04,05分别代表五个零
全0数组:
01,02,03构成 000
01,02构成00
02,03构成00
04,05构成00
01构成0
02构成0
03构成0
04构成0
05构成0
那么可以发现:
01总共使用了三次
02总共使用了两次
03总共使用了一次
04总共使用了两次
05总共使用了一次
那么其实就是假如存在连续的几个零,如果第i个零前有3个零,那么以这个零
为右端点的全零子数组就有4个(0000
长度分别为4,3,2,1)
class Solution {
public:
long long zeroFilledSubarray(vector<int>& nums) {
long long rst=0;
int c = 0;
for(auto num:nums){
if(num==0)rst+=++c;
else{
c=0;
}
}
return rst;
}
};
优化策略
- a+=b 时间花费低于 a = a+b
- ++i时间花费低于i++
- 采用引用的方式(直接操作空间)时间花费低
>>1
替代/2
switch
替代if else if