力扣刷题笔记

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、多数元素

  1. 排序法
    如果是多数元素,则该元素数量大于[n/2],那么在排序之后,中间位置上的数字必定为目标元素。
class Solution {
public:
    int majorityElement(vector<int>& nums) {
        sort(nums.begin(), nums.end());
        return nums[nums.size() / 2];
    }
};
  1. 哈希表法
    通过哈希表记录各个元素出现的数量,动态记录出现的数量最多的元素。
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;
    }
};
  1. 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、丢失的数字

  1. 哈希表法
    两次循环,第一次通过哈希表记录出现过的数,第二次遍历哈希表,返回哈希表[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;
    }
};
  1. 数学方法
    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 = &num;
            }
            // 小于最大值,并且(中间值为空指针或者大于中间值)
            else if(num<*max&&(mid == nullptr||num>*mid)){
                min = mid;
                mid = &num;
            }
            // 中间值存在并且小于中间值,并且(最小值为空指针或者大于最小值)
            else if((mid!=nullptr&&num<*mid)&&(min == nullptr||num>*min)){
                min = &num;
            }
        }
        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;
    }
};
  1. 充分利用原来题中也是符合种花的条件。
  • 如果 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;
    }
};

优化策略

  1. a+=b 时间花费低于 a = a+b
  2. ++i时间花费低于i++
  3. 采用引用的方式(直接操作空间)时间花费低
  4. >>1替代/2
  5. switch替代if else if
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值