【总结】数组类解题归纳

1.基础题目

1.回文数(leetcode 9

  • 题目描述:

判断一个整数是否是回文数。回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。

示例 1:

输入: 121
输出: true

示例 2:

输入: -121
输出: false
解释: 从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。

示例 3:

输入: 10
输出: false
解释: 从右向左读, 为 01 。因此它不是一个回文数。

进阶:

你能不将整数转为字符串来解决这个问题吗?

  • 分析:

判断是不是回文数,如果是个字符串,可以用中心扩散法判断是不是回文,对于这道题,给的输入是一个整数,我们不能直接知道其含有多少个数,所以中心扩散法就不太好用了,根据回文的性质,一个数正着读和反着读相同那么就是个回文。这里我们将该整数的每个位分离出来重新组合在一起,判断组合的数和原数是否相等,如果相等,那么就是回文数。

class Solution {
public:
    bool isPalindrome(int x) {
        if(x<0)
            return false;
        unsigned int sum=0;
        int n=x;
        while(n)
        {
            sum=sum*10+n%10;
            n=n/10;
        }
        return sum==x;
    }
};

2. 拥有最多糖果的孩子(leetcode 1431

  • 题目描述:

给你一个数组 candies 和一个整数 extraCandies ,其中 candies[i] 代表第 i 个孩子拥有的糖果数目。

对每一个孩子,检查是否存在一种方案,将额外的 extraCandies 个糖果分配给孩子们之后,此孩子有 最多 的糖果。注意,允许有多个孩子同时拥有 最多 的糖果数目。

示例 1:

输入:candies = [2,3,5,1,3], extraCandies = 3
输出:[true,true,true,false,true] 
解释:
孩子 1 有 2 个糖果,如果他得到所有额外的糖果(3个),那么他总共有 5 个糖果,他将成为拥有最多糖果的孩子。
孩子 2 有 3 个糖果,如果他得到至少 2 个额外糖果,那么他将成为拥有最多糖果的孩子。
孩子 3 有 5 个糖果,他已经是拥有最多糖果的孩子。
孩子 4 有 1 个糖果,即使他得到所有额外的糖果,他也只有 4 个糖果,无法成为拥有糖果最多的孩子。
孩子 5 有 3 个糖果,如果他得到至少 2 个额外糖果,那么他将成为拥有最多糖果的孩子。

示例 2:

输入:candies = [4,2,1,1,2], extraCandies = 1
输出:[true,false,false,false,false] 
解释:只有 1 个额外糖果,所以不管额外糖果给谁,只有孩子 1 可以成为拥有糖果最多的孩子。

示例 3:

输入:candies = [12,1,12], extraCandies = 10
输出:[true,false,true]

提示:

2 <= candies.length <= 100
1 <= candies[i] <= 100
1 <= extraCandies <= 50

  • 分析:

首先,需要知道未加额外糖果时的最大值。

接着,遍历每个孩子,将额外的糖果加到每个孩子的原有糖果上,判断是否比最大值大。

class Solution {
public:
    vector<bool> kidsWithCandies(vector<int>& candies, int extraCandies) {

        vector<bool> ans;
        int maxVal=INT_MIN;
        for(int i=0;i<candies.size();i++)
        {
            maxVal=max(maxVal,candies[i]);
        }
        for(int i=0;i<candies.size();i++)
        {
            if(candies[i]+extraCandies>=maxVal)
                ans.push_back(true);
            else
                ans.push_back(false);
        }
        return ans;
    }
};

3.最佳观光组合(leetcode 1014

  • 题目描述:

给定正整数数组 A,A[i] 表示第 i 个观光景点的评分,并且两个景点 i 和 j 之间的距离为 j - i。

一对景点(i < j)组成的观光组合的得分为(A[i] + A[j] + i - j):景点的评分之和减去它们两者之间的距离。

返回一对观光景点能取得的最高分。

示例:

输入:[8,1,5,2,6]
输出:11
解释:i = 0, j = 2, A[i] + A[j] + i - j = 8 + 5 + 0 - 2 = 11

提示:

2 <= A.length <= 50000
1 <= A[i] <= 1000 

  • 分析:

方法一:从前往后遍历整个A数组,内层循环在(i<j)范围内寻找一个旅游景点,判断景点组合按照评分规则是否最高,此时算法复杂度为O(n^2);

方法二:从评分规则(A[i] + A[j] + i - j)可以看出,当固定A[i],内层循环遍历(j>i)的范围,当前观光景点评分比前面的范围内的景点评分小,那么它一定不能成为最大,所以可以直接跳过最高分的判断。

方法三:参考Leetcode官方题解。

方法一: 

class Solution {
public:
    int maxScoreSightseeingPair(vector<int>& A) {
        int maxScore=INT_MIN;
        for(int i=1;i<A.size();i++)
        {
            for(int j=i-1;j>=0;j--)
            {
                int score=A[i]+A[j]+j-i;
                maxScore=max(maxScore,score);
            }
        }
        return maxScore;
    }
};

 方法二:

class Solution {
public:
    int maxScoreSightseeingPair(vector<int>& A) {
        int maxScore=INT_MIN;
        for(int i=0;i<A.size()-1;i++)
        {
            int cur=A[i+1];
            for(int j=i+1;j<A.size();j++)
            {
                if(A[j]<cur)
                    continue;
                cur=max(cur,A[j]);
                int score=A[i]+A[j]+i-j;
                maxScore=max(maxScore,score);
            }
        }
        return maxScore;
    }
};

方法三: 

class Solution {
public:
    int maxScoreSightseeingPair(vector<int>& A) {
        int maxScore=0,Val=A[0]+0;
        for(int j=1;j<A.size();j++)
        {
            maxScore=max(maxScore,A[j]-j+Val);
            Val=max(Val,A[j]+j);  
        }
        return maxScore;

    }
};

 4.跳水板(leetcode 面试题16.11

  • 题目描述:

你正在使用一堆木板建造跳水板。有两种类型的木板,其中长度较短的木板长度为shorter,长度较长的木板长度为longer。你必须正好使用k块木板。编写一个方法,生成跳水板所有可能的长度。

返回的长度需要从小到大排列。

示例:

输入:
shorter = 1
longer = 2
k = 3
输出: {3,4,5,6}

提示:

0 < shorter <= longer
0 <= k <= 100000

  • 分析:

题目要求我们得到所有的可能,而且所有可能没有重复,首先想到了回溯的方法使用递归,然后用一个set保存结果,因为set内元素不能重复,且进行了排序,但是时间复杂度不满足。

使用公式,首先对于短的短板,尽可能的选多点,然后依次减少短板的数量,而长板的数量在不断的增加,这样就完成了从小到达排序。

方法一: 

class Solution {
public:
    set<int> ans;
    void helper(int k,int *a,int sum)
    {
        if (k==0)
        {
            ans.insert(sum);
            return;
        }
        for(int i=0;i<2;i++)
        {
            helper(k-1,a,sum+a[i]);
        }
        return;
    }
    vector<int> divingBoard(int shorter, int longer, int k) {
        vector<int>res;
        if(k==0)
            return res;
        int a[2]={shorter,longer};
        helper(k,a,0);
        res.insert(res.end(),ans.begin(),ans.end());
        return res;

    }

};

 方法二:

class Solution {
public:
     vector<int> divingBoard(int shorter, int longer, int k)
     {
        vector<int> ans;
        if(k==0)
            return ans;
        if(shorter==longer)
            return vector<int>(1,shorter*k);
        for(int i=0;i<=k;i++)
            ans.push_back((k-i)*shorter+i*longer);
        return ans;
        
     }
};

2.区间合并类型

区间合并是一个用来处理区间重叠问题的很高效的技术。通常需要判断是否有重叠,如何合并区间。

1.合并区间(leetcode 56

  • 题目描述:

给出一个区间的集合,请合并所有重叠的区间。

示例 1:

输入: [[1,3],[2,6],[8,10],[15,18]]
输出: [[1,6],[8,10],[15,18]]
解释: 区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].

示例 2:

输入: [[1,4],[4,5]]
输出: [[1,5]]
解释: 区间 [1,4] 和 [4,5] 可被视为重叠区间。

  • 分析:

首先,一个区间存在一个起点start,一个终点end,我们要区间合并就必须先判断区间是否存在重叠 ,区间重叠存在着6种情况。

 如果我们在无序的情况下去比较两个区间的起点和终点,判断他们是否符合这些情况是比较复杂的。因此我们可以通过先对区间进行排序固定起点,然后比较两个区间的终点的情况,这样就简便许多。

  • 第一种情况:两个区间没有重叠

  • 第二种情况 :两个区间重叠了部分

  • 第三种情况:其中一个区间为另外一个区间的子集

class Solution {
public:
    vector<vector<int>> merge(vector<vector<int>>& intervals) {
        vector<vector<int>> res;
        if(intervals.empty())
            return res;
        sort(intervals.begin(),intervals.end());
        
        auto temp=intervals[0];
        // cout<<"a: "<<temp[0]<<" b: "<<temp[1]<<endl;
        for(int i=1;i<intervals.size();i++)
        {
            //第一种情况
            if(intervals[i][0]>temp[1])
            {
                res.push_back(temp);
                temp=intervals[i];
            }
            else
            {
                //第二种情况
                if(intervals[i][1]>temp[1])
                    temp[1]=intervals[i][1];
            }
        }
        res.push_back(temp);
        return res;
        
    }
};

3.双指针类型

1.删除排序数组中的重复项(leetcode 26

  • 日期:2020/4/18
  • 题目描述:

给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。

不要使用额外的数组空间,你必须在原地修改输入数组并在使用O(1)额外空间的条件下完成。

示例 1:

给定数组 nums = [1,1,2], 

函数应该返回新的长度 2, 并且原数组 nums 的前两个元素被修改为 1, 2。 

你不需要考虑数组中超出新长度后面的元素。


示例 2:

给定 nums = [0,0,1,1,1,2,2,3,3,4],

函数应该返回新的长度 5, 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4。

你不需要考虑数组中超出新长度后面的元素。
 

说明:

为什么返回数值是整数,但输出的答案是数组呢?

请注意,输入数组是以「引用」方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。

你可以想象内部操作如下:

// nums 是以“引用”方式传递的。也就是说,不对实参做任何拷贝
int len = removeDuplicates(nums);

// 在函数里修改输入数组对于调用者是可见的。
// 根据你的函数返回的长度, 它会打印出数组中该长度范围内的所有元素。
for (int i = 0; i < len; i++) {
    print(nums[i]);
}

  • 分析:

要去除重复项,实质上就是两个元素相互比较,如果相同,则为重复,否则就不重复,我们定义两个指针,快指针用来遍历数组,慢指针指着当前元素。

  • 如果慢指针和快指针相同,则快指针+1;
  • 如果慢指针和快指针不同,则慢指针+1,将快指针给慢指针,快指针+1;
class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
        if(nums.empty())
            return 0;
        int slow=0;
        for(int i=1;i<nums.size();i++)
        {
            if(nums[i]!=nums[slow])
            {
                nums[++slow]=nums[i];
            }
        }
        return slow+1;
        
    }
};

2.盛最多水的容器(leetcode 11

  • 日期:2020/4/18
  • 题目描述

给你 n 个非负整数 a1,a2,...,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0)。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。

说明:你不能倾斜容器,且 n 的值至少为 2。

示例:

输入:[1,8,6,2,5,4,8,3,7]
输出:49
  • 分析

盛水有短板效应,最短的那个板决定了水的高度,其次容量和底板的长度有关,可以通过设置双指针分别指向容器的两个板,容量=短板长度*底板长度。(注意:这个容器只和两边的长短板和底有关,在计算对应的容量时,中间那些柱子不考虑)

class Solution {
public:
    int maxArea(vector<int>& height) {
        int i=0;
        int j=height.size()-1;
        int max_res=INT_MIN;

        while(i<j)
        {
            if (height[i] < height[j])
            {
                max_res = max(max_res, height[i] * (j - i));
                i += 1;
            }
            else
            {
                max_res = max(max_res, height[j] * (j - i));
                j -= 1;
            }
        }
        return max_res;
        
    }
};

3.移动零(leetcode 283

  • 日期:2020/4/19
  • 题目描述:

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。

示例:

输入: [0,1,0,3,12]
输出: [1,3,12,0,0]

说明:

必须在原数组上操作,不能拷贝额外的数组。
尽量减少操作次数。

  • 分析:

首先,我们的目的是移动零,我们定义两个指针,一个指针指向原数组,起遍历的作用,另一个指针指向新数组的索引位置。

  • 如果当前元素不是零元素,将其拷贝到新数组索引的位置。
  • 如果当前元素是零元素,则跳过。
  • 最后对后面空缺的位置进行补零。
class Solution {
public:
    void moveZeroes(vector<int>& nums) {
        int j=0;
        for(int i=0;i<nums.size();i++)
        {
            if(nums[i]!=0)
                nums[j++]=nums[i];
        }
        for(int k=j;k<nums.size();k++)
            nums[k]=0;
    }
};

4.移除元素(leetcode 27

  • 日期:2020/4/19
  • 题目描述:

给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。

不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。

元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

 

示例 1:

给定 nums = [3,2,2,3], val = 3,

函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。

你不需要考虑数组中超出新长度后面的元素。


示例 2:

给定 nums = [0,1,2,2,3,0,4,2], val = 2,

函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。

注意这五个元素可为任意顺序。

你不需要考虑数组中超出新长度后面的元素。

  • 分析:

这道题和移动零元素其实是一样的做法,只是移除后不需要在末尾补充元素了。

class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int j=0;
        for(int i=0;i<nums.size();i++)
        {
            if(nums[i]!=val)
                nums[j++]=nums[i];
        }
        return j;       
    }
};

5.统计【优美子数组】(leetcode 1248

  • 日期:2020/4/21
  • 题目描述:

给你一个整数数组 nums 和一个整数 k。

如果某个 连续 子数组中恰好有 k 个奇数数字,我们就认为这个子数组是「优美子数组」。

请返回这个数组中「优美子数组」的数目。

示例 1:

输入:nums = [1,1,2,1,1], k = 3
输出:2
解释:包含 3 个奇数的子数组是 [1,1,2,1] 和 [1,2,1,1] 。


示例 2:

输入:nums = [2,4,6], k = 1
输出:0
解释:数列中不包含任何奇数,所以不存在优美子数组。


示例 3:

输入:nums = [2,2,2,1,2,2,1,2,2,2], k = 2
输出:16
 

提示:

1 <= nums.length <= 50000
1 <= nums[i] <= 10^5
1 <= k <= nums.length

  • 分析:

对于寻找子数组,可以通过双指针实现,一个指针指向子数组的起点,一个指针指向子数组的终点。

  1. 首先,我们从数组的起点开始进行遍历,左右两个指针分别指向0,-1位置,右指针指向-1位置是因为需要判断第一个元素是否为奇数,是否需要被包含。
  2. 移动右指针,直到左右指针指向的子数组中包含k个奇数,或者已经把整个数组都已经遍历完了。
  3. 这时候我们需要判断右指针是否越界,因为如果整个数组中的奇数个数小于k,那么把整个数组遍历完后右指针就越界了,这时候我们直接结束循环。
  4. 如果没有越界,那么保存右指针的值(flag),接着右指针继续往右移动,直到遇到新的奇数或者数组的末尾
  5. 接着,我们需要移动左指针了,需要一直维持左指针和右指针的区域右K个奇数,而且左指针移动一步,相当于有:r-flag+1种结果,因为从保存的位置到右指针的位置,和左指针构成的区域是连续的递增1,所以我们只需要在左指针移动的时候每次都增加r-flag+1。
  6. 当左指针移动结束后,这时候区间的奇数数量少于k,如果右指针的下一个元素为奇数,且没有到达末尾,重复上述步骤。
class Solution {
public:
    int numberOfSubarrays(vector<int>& nums, int k){
        int n=nums.size();
        int l=0,r=-1,count=0;
        int ans=0;
        while(r+1<n)
        {
            //首先,移动右指针,找到刚好满足k个奇数的位置
            while(r+1<n&&count<k)
            {
                count+=nums[++r]&1;
            }
            if(r>n)
                break;
            //记录当前右区间
            int flag=r;
            // cout<<"flag: "<<flag<<endl;

            //向右移动,直到末尾或者下一个数为奇数
            while(r+1<n&&!(nums[r+1]&1)) 
                r++;
            // cout<<"r: "<<r<<endl;
            //移动左指针,每次加r-flag+1,如果为奇数,奇数的数量-1;
            while(count==k&&l<=r)
            {
                ans+=r-flag+1;
                count-=nums[l++]&1;
            }
            // cout<<"l: "<<l<<endl;
        }
        return ans;
    }
};

6.快乐数(leetcode 202

  • 题目描述:

编写一个算法来判断一个数 n 是不是快乐数。

「快乐数」定义为:对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和,然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。如果 可以变为  1,那么这个数就是快乐数。

如果 n 是快乐数就返回 True ;不是,则返回 False 。

示例:

  • 分析:

这道题确实挺快乐的(题外话),从题目和示例中,我们首先需要将这个整数进行拆分,然后对每位进行平方求和,最后循环操作,判断是否能到1。前面的步骤比较容易想到,主要的问题是无限循环,我们可以通过数据结构集合set来记录每次的循环数,set也有不重复性,这样就可以用来解决无限循环操作。 

当然,这里也可以用快慢指针,防止开辟额外的内存空间,我们快指针跑两步,慢指针跑一步,如果无限循环,那么快指针最后会反超慢指针一圈,这样就结束了无限循环。 

  • set集合 
class Solution {
public:
    int helper(int n)
    {
        int sum=0;
        while(n)
        {
            sum+=(n%10)*(n%10);
            n=n/10;
        }
        return sum;
    }
    bool isHappy(int n) {
        set<int> s;
        s.insert(n);
       while(n!=1)
       {
            // cout<<"n:  "<<n<<endl;
            n=helper(n);
            if(s.count(n)!=0)
                return false;
            else
                s.insert(n);
           
       }
       return true; 
    }
};
  •  快慢指针
class Solution {
public:
    int helper(int n)
    {
        int sum=0;
        while(n)
        {
            sum+=(n%10)*(n%10);
            n=n/10;
        }
        return sum;
    }
    bool isHappy(int n) {
        int slow=n;
        int fast=n;
        do
        {
            slow=helper(slow);
            fast=helper(fast);
            fast=helper(fast);
        }while(slow!=fast);
        if(fast==1)
            return true;
        else
            return false;
    }
};

7. 接雨水(leetcode 42

  • 题目描述:

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。 感谢 Marcos 贡献此图。

示例:

输入: [0,1,0,2,1,0,1,3,2,1,2,1]
输出: 6

  • 分析:

 

4.位运算操作

1.数组中数字出现的次数(leetcode 面试题56

  • 题目描述:

一个整型数组 nums 里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)

示例 1:

输入:nums = [4,1,4,6]
输出:[1,6] 或 [6,1]


示例 2:

输入:nums = [1,2,10,4,1,4,3,3]
输出:[2,10] 或 [10,2]

限制:

2 <= nums <= 10000

  • 分析:

这道题如果没有限制条件:时间复杂度是O(n),空间复杂度是O(1),其实方法还是很多的,比如:查表,双指针等等,我就采用的双指针,但是时间复杂度不达标呀。这道主要采用位运算操作:异或。首先看下异或的性质。

那么如果我们将所有数字都异或起来:

根据异或的交换律、 结合律 相同的数字优先两两进行异或运算。此时根据归零率 ,每两个相同的数字都变成了 000,再根据 恒等率 ,把式子里所有的 000 去了,此时就只剩下只出现一次的那个数了!

但是,两个数字异或在一起,我们没办法分辨出是哪两个数字组合在一起的。这时,可以采用分组异或,保证:

  1. 两个只出现一次的数字在不同的组中;
  2. 相同的数字会被分到相同的组中。

那么,如何分组呢?其实实质就是异或的定义:相同为0,不同为1,将所有数字一起异或后,相同的都抵消掉了,就剩下这两个数,那么可以根据两个数的二进制中不同的位(也就是异或中的1)来区分,进而达到分组的目的。【下面是leetcode官方的分析】

  • 双指针+快排(不符合题意) 
class Solution {
public:
    vector<int> singleNumbers(vector<int>& nums) {
        vector<int> ans;
        if(nums.empty())
            return ans;
        sort(nums.begin(),nums.end());
        int j=0;
        int count=0;
        for(int i=0;i<nums.size();i++)
        {
            if(nums[i]==nums[j])
            {
                count++;
            }
            else
            {
                if(count==1)
                {
                    ans.push_back(nums[j]);
                }
                j=i;
                count=1;
            }
        }
        if(count==1)
        {
            ans.push_back(nums[j]);
        }
        if(ans.size()==1)
        {
            ans.push_back(0);
        }
        return ans;

    }
};
  • 位操作 
class Solution {
public:
    vector<int> singleNumbers(vector<int>& nums) {
        vector<int> ans;
        int d=0;
        int a=0;
        int b=0;
        //所有数进行异或操作
        for(int num:nums)
            d^=num;
        int p=1;
        //找到最近的不同位(1)
        while((d&p)==0)
            p=p<<1;
        //分组
        for(int num:nums)
        {
            if(num&p)
                a^=num;
            else
                b^=num;
        }
        ans.push_back(a);
        ans.push_back(b);
        return ans;

    }
};

2.只出现一次的数字(leetcode 136

  • 题目描述:

给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。

说明:

你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?

示例 1:

输入: [2,2,1]
输出: 1


示例 2:

输入: [4,1,2,1,2]
输出: 4

  • 分析:

首先,查找只出现一次的数,可以使用hash表对出现的数进行统计,然后找到数量为1对应的数,但是需要开辟额外的空间。

为了满足线性时间复杂度和不使用额外的空间来实现,可以通过异或运算进行实现,异或的性质:

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int res=0;
        for(int i=0;i<nums.size();i++)
        {
            res^=nums[i];
        }
        return res;
    }
};

5. hash表

1.和为K的子数组(leetcode 560

  • 题目描述【中等难度】:

给定一个整数数组和一个整数 k,你需要找到该数组中和为 k 的连续的子数组的个数。

示例 1 :

输入:nums = [1,1,1], k = 2
输出: 2 , [1,1] 与 [1,1] 为两种不同的情况。


说明 :

数组的长度为 [1, 20,000]。
数组中元素的范围是 [-1000, 1000] ,且整数 k 的范围是 [-1e7, 1e7]。

  • 暴力法分析:

首先,因为是找子数组,所以我们需要两个变量,指向子数组的起始位置和结束位置。

接着,固定起始位置,然后移动结束位置的指针,使得区间元素的和为k。

最后,依次移动起始位置的指针,重复步骤2. 

这样时间复杂度为O(N^2)

class Solution {
public:
    int subarraySum(vector<int>& nums, int k) {
        if(nums.empty())
            return 0;
        int ans=0;
        for(int i=0;i<nums.size();i++)
        {
            int sum=0;
            for(int j=i;j<nums.size();j++)
            {
                sum+=nums[j];
                if(sum==k)
                    ans++;             
            }
        }
        return ans;

    }
};
  •  前缀+hash表

 

class Solution {
public:
    int subarraySum(vector<int>& nums, int k) {
        unordered_map<int,int> map;
        map[0]=1;
        int pre=0;
        int count=0;
        for(int i=0;i<nums.size();i++)
        {
            //前i项和
            pre+=nums[i];
            //查找是否存在pre[j]=pre[i]-k的前j项和
            if(map.count(pre-k)!=0)
                count+=map[pre-k];
            //保存前j项和
            map[pre]++;
        }
        return count;

    }
};

2. 两数之和(leetcode 1

  • 题目描述:

给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。

示例:

给定 nums = [2, 7, 11, 15], target = 9

因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]

  • 分析:

暴力方法:

通过两次遍历nums,判断nums[i]+nums[j]是否等于target,如果等,直接返回。

hash表:

因为在nums数组中,有两个数等于target,可以通过一次遍历查找结果,在遍历的过程中,保存target和当前的值的差值(target-nums[i]),以及其对应的索引(i),在后续的遍历过程中,如果有一个元素能够在hash表中查找到,那么就是最终的结果。

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        unordered_map<int,int> map;
        vector<int> ans;
        if(nums.empty())
            return ans;
        for(int i=0;i<nums.size();i++)
        {
            if(map.count(nums[i])!=0)
            {
                ans.push_back(map[nums[i]]);
                ans.push_back(i); 
            }
            else
                map[target-nums[i]]=i;

        }
        return ans;
    }
};

3.缺失的第一个正数(leetcode 41

  • 题目描述:

给你一个未排序的整数数组,请你找出其中没有出现的最小的正整数。

示例 1:

输入: [1,2,0]
输出: 3

示例 2:

输入: [3,4,-1,1]
输出: 2

示例 3:

输入: [7,8,9,11,12]
输出: 1

提示:

你的算法的时间复杂度应为O(n),并且只能使用常数级别的额外空间。

  • 分析:
  1. 方法1:为了寻找未出现的最小正整数,可以先使用排序,然后对数组进行二分查找实现。
  2. 方法2:寻找未出现的最小正整数,那么可以将原数组保存到一个hash表中,然后从1到len依次查找是否在hash表中存在,如果不在hash表中,那么直接返回。
  3. 方法3:将原数组视为一个hash表,将内部元素交换到属于他的位置,然后从1到len依次查找。

 

class Solution {
public:
    int firstMissingPositive(vector<int>& nums) {
        unordered_set<int> set;
        for(auto num:nums)
        {
            if(num>0&&set.find(num)==set.end())
                set.insert(num);
        }
        int start=1;
        while(1)
        {
            if(set.find(start)==set.end())
                return start;
            start++;
        }
        return start;

    }
};

 

class Solution {
public:
    int firstMissingPositive(vector<int>& nums) {
        int len=nums.size();
        for(int i=0;i<len;i++)
        {
            while(nums[i]>=1&&nums[i]<=len&&nums[nums[i]-1]!=nums[i])
                swap(nums,i,nums[i]-1);
        }
        for(int i=0;i<len;i++)
        {
            if(nums[i]!=i+1)
                return i+1;
        }
        return len+1;

    }
    void swap(vector<int> &nums,int index1,int index2)
    {
        int temp=nums[index1];
        nums[index1]=nums[index2];
        nums[index2]=temp;
    }
};

4. 两数之和II-(输入有序数据)(leetcode 167

  • 题目描述:

给定一个已按照升序排列 的有序数组,找到两个数使得它们相加之和等于目标数。

函数应该返回这两个下标值 index1 和 index2,其中 index1 必须小于 index2。

说明:

返回的下标值(index1 和 index2)不是从零开始的。
你可以假设每个输入只对应唯一的答案,而且你不可以重复使用相同的元素。

示例:

输入: numbers = [2, 7, 11, 15], target = 9
输出: [1,2]
解释: 2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。

  • 分析:

因为给定数组为有序数组,因此可以通过依次遍历整个数组,通过二分查找在剩余的部分中寻找目标值和当前值的差值

class Solution {
public:
    int helper(vector<int>&nums,int left,int right,int target)
    {
        int mid;
        while(left<=right)
        {
            mid=left+(right-left)/2;
            if(nums[mid]==target)
                return mid;
            else if(nums[mid]>target)
                right=mid-1;
            else
                left=mid+1;
        }
        return -1;

    }
    vector<int> twoSum(vector<int>& numbers, int target) {
        vector<int> ans;
        if(numbers.empty())
            return ans;
        for(int i=0;i<numbers.size();i++)
        {
            int index=helper(numbers,i+1,numbers.size()-1,target-numbers[i]);
            if(index!=-1)
            {
                ans.push_back(i+1);
                ans.push_back(index+1);
                return ans;
            }
        }
        return ans;
    }
};

 

6.单调栈

单调栈:栈内元素都是单调递减或者单调递增。

时间复杂度:O(n)

空间复杂度:O(n)

当新元素进栈时,为了维护栈的单调性,需要弹出大于等于新元素的所有元素。

主要应用:

对于数组中的元素,可以找到左侧/右侧第一个比它大/小的元素。

寻找某一子数组,使得子数组中的最小值乘以子数组的长度最大。

寻找某一子数组,使得子数组中的最小值乘以子数组所有元素和最大。

1.柱状图中最大矩形(leetcode 84

  • 题目描述:

给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。求在该柱状图中,能够勾勒出来的矩形的最大面积。

以上是柱状图的示例,其中每个柱子的宽度为 1,给定的高度为 [2,1,5,6,2,3]。

图中阴影部分为所能勾勒出的最大矩形面积,其面积为 10 个单位。

示例:

输入: [2,1,5,6,2,3]
输出: 10

  • 分析:
  • 暴力解法(O(n^2))
  1. 首先固定起点,然后移动终点(遍历整个数组)。
  2. 在遍历的过程中,判断当前终点的高度是否为最低。
  3. 根据移动的步数乘以最低高度得到面积,再判断面积是否为最大。
  4. 当终点遍历完后,移动起点,重复上述步骤。
class Solution {
public:
    int largestRectangleArea(vector<int>& heights) {
        int maxArea=0;
        for(int i=0;i<heights.size();i++)
        {
            int height=heights[i];
            for(int j=i;j<heights.size();j++)
            {
                height=min(height,heights[j]);
                int area=(j-i+1)*height;
                // cout<<"i: "<<i<<" j: "<<j<<" height: "<<height<<" area: "<<area<<std::endl;
                maxArea=max(maxArea,area); 
            }
        }
        return maxArea;
    }
};

图片.png

class Solution {
public:
    int largestRectangleArea(vector<int>& heights) {
        int ans=0;
        heights.push_back(0);
        stack<int> s;
        for(int i=0;i<heights.size();i++)
        {
            while(!s.empty()&&heights[i]<heights[s.top()])
            {
                auto temp=s.top();
                s.pop();
                ans=max(ans,heights[temp]*(s.empty()?i:i-s.top()-1));
            }
            s.push(i);
        }
        return ans;
    }
};

2.每日温度(leetcode 739

  • 题目描述:

根据每日 气温 列表,请重新生成一个列表,对应位置的输出是需要再等待多久温度才会升高超过该日的天数。如果之后都不会升高,请在该位置用 0 来代替。

例如,给定一个列表 temperatures = [73, 74, 75, 71, 69, 72, 76, 73],你的输出应该是 [1, 1, 4, 2, 1, 1, 0, 0]。

提示:气温 列表长度的范围是 [1, 30000]。每个气温的值的均为华氏度,都是在 [30, 100] 范围内的整数。

  • 分析:

暴力解法:遍历每日的温度,然后在当日以后的温度中寻找第一个最大值,如果找到就为j-i,否则为0。

class Solution {
public:
    vector<int> dailyTemperatures(vector<int>& T) {
        vector<int> ans(T.size());
        for(int i=0;i<T.size()-1;i++)
        {
            for(int j=i+1;j<T.size();j++)
            {
                if(T[j]>T[i])
                {
                    ans[i]=j-i;
                    break;
                }
                    
            }
        }
        return ans;
    }
};

单调栈: 参考

class Solution {
public:
    vector<int> dailyTemperatures(vector<int>& T) {
        vector<int> ans(T.size());
        stack<int> s;
        for(int i=0;i<T.size();i++)
        {
            while(!s.empty()&&T[i]>T[s.top()])
            {
                ans[s.top()]=i-s.top();
                s.pop();
            }
            s.push(i);
        }
        return ans;
    }
};

7.二分查找

1.转变数组后最接近目标值的数组和(leetcode 1300

  • 题目描述:

给你一个整数数组 arr 和一个目标值 target ,请你返回一个整数 value ,使得将数组中所有大于 value 的值变成 value 后,数组的和最接近  target (最接近表示两者之差的绝对值最小)。

如果有多种使得和最接近 target 的方案,请你返回这些整数中的最小值。

请注意,答案不一定是 arr 中的数字。

示例 1:

输入:arr = [4,9,3], target = 10
输出:3
解释:当选择 value 为 3 时,数组会变成 [3, 3, 3],和为 9 ,这是最接近 target 的方案。


示例 2:

输入:arr = [2,3,5], target = 10
输出:5


示例 3:

输入:arr = [60864,25176,27249,21296,20204], target = 56803
输出:11361

提示:

1 <= arr.length <= 10^4
1 <= arr[i], target <= 10^5

  • 分析:

二分查找:

class Solution {
public:
    int findBestValue(vector<int>& arr, int target) {
        int n = arr.size();
        int minCha = INT_MAX;
        sort(arr.begin(),arr.end());
        double lowTemp = (1.0)*target/n;
        if(lowTemp <= arr[0]){
            if(lowTemp * 2 - 1 >=0 && (int)(lowTemp * 2 - 1)/2 == (int)(lowTemp)){
                return (int)ceil(lowTemp);
            }else return (int)lowTemp;
        }
        int l = arr[0], r = arr[n-1];
        int ans = 0;
        while(l <= r){
            int mid = (l+r) >> 1;
            int sum = 0;
            for(int i = 0; i < n; i++){
                sum += arr[i] < mid ? arr[i] : mid;
            }
            int cha = target - sum;
            if(abs(cha) <= minCha){
                minCha = abs(cha);
                ans = mid;
            }
            if(cha == 0) return mid;
            else if(cha > 0) l = mid+1;
            else if(cha < 0) r = mid-1;
        }
        return ans;
    }
};

 参考:https://labuladong.gitbook.io/algo/suan-fa-si-wei-xi-lie/qu-jian-tiao-du-wen-ti-zhi-qu-jian-he-bing

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

火柴的初心

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值