数组相关题目

目录

560. 和为 K 的子数组

581. 最短无序连续子数组

41. 缺失的第一个正数

169. 多数元素

128. 最长连续序列


前缀和相关

前缀和相关题目主要出现在要求解满足某种条件的连续子数组个数,同时在二叉树中也有437. 路径总和 III,满足某种条件的路径的数目,根据数组的前缀和,很容易快速得到某个子数组的和,同时,在这里还有一个小技巧在于,用哈希表存储前缀和时,使用第一项为sum、第二项为sum的个数时可以更加快速求解出满足条件的子数组的个数<sum,  num>

560. 和为 K 的子数组

给你一个整数数组 nums 和一个整数 k ,请你统计并返回 该数组中和为 k 的子数组的个数 。

示例 1:

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

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

class Solution {
public:
    int subarraySum(vector<int>& nums, int k) {
        /*前缀和+哈希表的思想:
        记录数组nums[i]的前缀和pre_sum[i]
        假设数组[i...j]和刚好为k,则pre_sum[j] - pre_sum[i-1] = k
        使用hash表来维护前缀和为sum的个数
        */
        unordered_map<int, int> pre_sum;
        pre_sum[0] = 1;//表示可以一个元素也不选择,前缀和为0的子数组个数为1,为了满足从0---i的前缀和刚好为k的情况
        int sum = 0;
        int num = 0;
        for(int i = 0; i < nums.size(); i++){
            sum += nums[i];
            if(pre_sum.find(sum-k) != pre_sum.end()){
                num += pre_sum[sum-k];
            }
            pre_sum[sum] += 1;
        }
        return num;
    }
};

581. 最短无序连续子数组

给你一个整数数组 nums ,你需要找出一个 连续子数组 ,如果对这个子数组进行升序排序,那么整个数组都会变为升序排序。

请你找出符合题意的 最短 子数组,并输出它的长度。

示例 1:

输入:nums = [2,6,4,8,10,9,15]
输出:5
解释:你只需要对 [6, 4, 8, 10, 9] 进行升序排序,那么整个表都会变为升序排序。
示例 2:

输入:nums = [1,2,3,4]
输出:0
示例 3:

输入:nums = [1]
输出:0

class Solution {
public:
    int findUnsortedSubarray(vector<int>& nums) {
        //A ----B-----C
        //核心点还是在找中间B区间的最小值和最大值的边界
        //min(B,C) >= A_max,找到左边界:从右往左扫描,当前值小于最小值,则更新最小值,否则则更新左边界
        //max(A,B) >= C_min,找到右边界,从左到右扫描,当前值大于最大值,则更新最大值,否则则更新右边界
        //分别从左到右扫描到不满足大小关系的转折点位置,
        int l = 1, h = nums.size()-1;
        while(l < nums.size() && nums[l] >= nums[l-1]){
            l++;
        }
        while(h >= 1 && nums[h] >= nums[h-1]){
            h--;
        }
        if(l == nums.size() || h == 0)
            return 0;
        //找出中间区间的最小值与最大值
        int min_val = 10001, max_val = -10001;
        for(int i = l-1; i <= h+1 && i >= 0 && i < nums.size(); i++){
            //解释为何要从l-1和h+1开始
            min_val = min(min_val, nums[i]);
            max_val = max(max_val, nums[i]);
        }
        int new_l=0, new_h=nums.size()-1;
        //找出中间区间最小值应该在前面哪一段的位置
        while(new_l < nums.size() && nums[new_l] <= min_val){
            new_l++;
        }
        //找出中间区间最大值应该在前面哪一段的位置
        while(new_h >= 0 && nums[new_h] >= max_val){
            new_h--;
        }
        return new_h - new_l + 1;
    }
};

官方题解的简单思路代码实现:

class Solution {
public:
    int findUnsortedSubarray(vector<int>& nums) {
        int n = nums.size();
        int maxn = INT_MIN, right = -1;
        int minn = INT_MAX, left = -1;
        for (int i = 0; i < n; i++) {
            if (maxn > nums[i]) {
                right = i;
            } else {
                maxn = nums[i];
            }
            if (minn < nums[n - i - 1]) {
                left = n - i - 1;
            } else {
                minn = nums[n - i - 1];
            }
        }
        return right == -1 ? 0 : right - left + 1;
    }
};

41. 缺失的第一个正数

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

请你实现时间复杂度为 O(n) 并且只使用常数级别额外空间的解决方案。
 

示例 1:

输入:nums = [1,2,0]
输出:3
示例 2:

输入:nums = [3,4,-1,1]
输出:2
示例 3:

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

#include "bits/stdc++.h"

using namespace std;

class Solution {
public:
    int firstMissingPositive(vector<int>& nums) {
        /*假设数组的长度为N,那么缺失的第一个整数在[1, N+1]
        分析,如果[1,N]全部出现在原数组中,那么结果为N+1;如果[1,N]有元素未出现,那么结果肯定在[1, N]中
        普通思路就是枚举[1,N+1]是否在nums数组中出现过,未出现说明就是结果,时间复杂度:O(N^2)
        另外思路:使用哈希表,哈希表中元素是否在nums数组中出现过,时间复杂度:O(N)
        但由于不能使用额外的空间,所以只能对原数组进行操作当作哈希表:
        对于元素x(1 <= x <=N),标记对应的下标[x-1],最后扫描数组,未被标记的就是我们想要的结果
        对于不在[1,N]之间的元素,可以标记为其他元素:N+1
        */
        int n = nums.size();
        //对不满足条件的元素进行标记
        for(auto& a: nums){
            if(a <= 0 || a > n){
                a = n + 1;
            }
        }
        //对元素进行哈希操作
        for(auto& a: nums){
            if(abs(a) > 0 && abs(a) <= n){
                //标记当前元素a对应的下标为a-1的元素为其负值
                nums[abs(a) - 1] = -1 * abs(nums[abs(a) - 1]);
            }
        }
        int ans = n + 1;
        //寻找未被标记的下标,即为结果
        for(int i = 0; i < nums.size(); i++){
            if(nums[i] > 0){
                ans = i + 1;
                return ans;
            }
        }
        return ans;
    }
    int firstMissingPositive_swap(vector<int>& nums) {
        /*
        置换的思路:与刚刚的哈希思路一致:也是想将元素x通过交换刚好放到下标为x-1处
        swap(nums[i], nums[nums[i]-1]),有可能nums[i] = nums[nums[i]-1] = x需要跳出循环
        */
        int n = nums.size();
        for(int i = 0; i < n; i++){
            while(nums[i] >= 1 && nums[i] <= n && nums[i] != nums[nums[i] - 1]){
                swap(nums[i], nums[nums[i]-1]);
            }
        }
        for(int i = 0; i < n; i++){
            if(nums[i] != i+1)
                return i+1;
        }
        return n+1;
    }
};

169. 多数元素

给定一个大小为 n 的数组 nums ,返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。

你可以假设数组是非空的,并且给定的数组总是存在多数元素。

示例 1:

输入:nums = [3,2,3]
输出:3
示例 2:

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

class Solution {
public:
    int majorityElement(vector<int>& nums) {
        //多数元素,引入计数器count被认为是众数的数出现的次数与其他数的次数的差值
        int count = 0, ans;
        for(auto &n : nums){
            if(count == 0){
                ans = n;
                count++;
            }else if(ans == n){
                //记录的出现最多的数和当前扫描数一样
                count++;
            }else{
                //记录出现最多的数和当前扫描数不一样
                count--;
            }
        }
        return ans;
    }
};

128. 最长连续序列

 

给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。

请你设计并实现时间复杂度为 O(n) 的算法解决此问题。

示例 1:

输入:nums = [100,4,200,1,3,2]
输出:4
解释:最长数字连续序列是 [1, 2, 3, 4]。它的长度为 4。
示例 2:

输入:nums = [0,3,7,2,5,8,4,6,0,1]
输出:9

 

class Solution {
public:
    int longestConsecutive(vector<int>& nums) {
        //使用Hash表记录所有的元素,起到去重功能
        //朴素的思路:扫描每个元素x,往后枚举x+1,...,x+y,判断其是否存在于nums中,每次维持记录一个最大长度,这样的复杂度为O(n^3)
        //进阶的思路:判断枚举的元素x+i在不在nums中,需要O(n)复杂度,在这里我们引入一个哈希表unordered_set,即可降低复杂度到O(1)
        //最终的进阶:在扫描元素时,比如x:我们会依次枚举x+1,...,x+y是否在哈希表中,在这里我们只需要判断x前驱x-1在不在其中判断是否需要进行枚举
        if (nums.size() == 0)
            return 0;
        unordered_set<int> num_set;
        for(int i = 0; i < nums.size(); i++){
            num_set.insert(nums[i]);
        }

        int len = 1;
        for(const auto&num: num_set){
            int cur = num;
            //当cur-1不在哈希表中,需要对当前的cur进行扫描遍历
            if(!num_set.count(cur-1)){
                while(num_set.count(cur)){
                    cur += 1;
                }
                int now_len = cur - num;
                len = len > now_len ? len: now_len;
            }
        }
        return len;
    }
    //思路2:哈希表记录右边界。在上诉思路中,对cur枚举时,需要逐个+1枚举到达cur的右界,在这里,有一个进阶思路,维持一个Hash表,记录每个元素num能够到达的右边界。
    int longestConsecutive_2(vector<int>& nums){
        map<int, int> num_map;
        for(const auto&num: nums){
            //初始化map,每个元素num的右边界为自身
            num_map.insert(pair<int, int>(num, num));
        }
        int len = 0;
        for(const auto&num: nums){
            int right = num_map(cur);
            while(num_map.count(right+1)){
                right = num_map[right+1];
            }
            //更新右边界
            num_map[num] = right;
            len = len > right - num + 1 ? len : right - num + 1;
        }
        return len;
    }
    //思路3:动态规划。与思路2类似,该思路也是为了减少逐个+1枚举的时间,与思路2的对比在于,该思路对于元素num同时向左边界和右边界进行扩充
    int longestConsecutive_3(vector<int>& nums){
        map<int, int> num_map;
        int len = 0;
        for(const auto&num: nums){
            //当map中不包含num时,即num第一次出现
            if(!num_map.count(num)){
                //left为num-1所在连续区间的长度,进一步理解为左连续区间的长度,若不存在,则取0
                int left = num_map[num-1];
                //right为num+1所在连续区间的长度,进一步理解为右连续区间的长度,若不存在,则取0
                int right = num_map[num+1];
                int cur_len = left + right + 1;
                len = len > cur_len ? len : cur_len;
                //将num加入到map中,表示其已经被遍历过
                num_map[num] = -1;
                num_map[num - left] = cur_len;
                num_map[num + right] = cur_len;
            }
        }
        return len;
    }
    //思路4:并查集。与思路2类似,也是用来记录右边界,所有在同一个连续区间内的元素都会在一个连通分量中,且这些元素的根节点都为最远的右边界元素
    //具体思路如下:
    //1.遍历所有元素num,如果num+1存在,则将其加入到num+1所在的连通分量中
    //2.遍历所有元素num,通过find函数找到num所在分量的根节点,也就是最右边界,从而求得连续区间的长度
    int longestConsecutive_4(vector<int>& nums){
        
    }


};

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值