Leetcode普通数组-day5、6

Leetcode普通数组-day5/6

记录自己刷力扣备战秋招的刷题笔记❤️

​ ------wosz

普通数组

普通数组没什么需要说的,其实最简单的办法就是遍历,因为普通数组它是连续的,因此不会涉及到很复杂的算法。

因为是遍历嘛,我们就可以用到我们前面学习过的一些方法如:

  • 双指针,去减少遍历次数
  • 滑动窗口,去实习相对应的数组处理

最大子数组和

给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

子数组是数组中的一个连续部分。

**输入:**nums = [-2,1,-3,4,-1,2,1,-5,4]

**输出:**6

**解释:**连续子数组 [4,-1,2,1] 的和最大,为 6 。

自己题解

cpp 复制代码
class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int num_size=nums.size();
        int cur = 0;        // 当前前缀和
        int min_prevalue = 0;    // 到当前位置之前最小的前缀和
        int tag = nums[0];
        
        for(int i=0;i<num_size;i++)
        {
            cur+=nums[i];  //计算当前前缀和
            if(tag<cur-min_prevalue)    //当前前缀和减去之前最小前缀和
            {
                tag=cur-min_prevalue;
            }
            if(cur<min_prevalue)
            {
                min_prevalue=cur;
            }
        }
        return tag;
    }
};

我的思路受到前面做到一个关于子串的题是用的前缀和的方式,这道题我想了一下感觉可以使用前缀和 来解决。我们用当前前缀 和减去之前最小前缀和,就表示以当前位置结尾的最大子数组和。遍历一遍后取最大值即可。(我一开始还想错了直接使用最大减去最小,还去进行分段讨论呢🤣)

官方题解

cpp 复制代码
class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int pre = 0, maxAns = nums[0];
        for (const auto &x: nums) {
            pre = max(pre + x, x);
            maxAns = max(maxAns, pre);
        }
        return maxAns;
    }
};

官方我选取的是采用动态规划的题解,因为我本人也没有学习过动态规划就趁机再学习一下动态规划。

dp[i] 表示:以 nums[i] 结尾的连续子数组的最大和。

此时我们的考虑就是两种一种是:1.前面的dp[i-1]+nums[i]就是此时的dp 2.从自己开始num[i] (前面的和可能是负贡献)

txt 复制代码
dp[i] = max(dp[i-1] + nums[i], nums[i])

补充:动态规划

因为后面还会再次学所以这里我就大概简单先了解一下方便理解本题的动态规划解法:

动态规划:从初始状态 出发到,经过一系列的状态转移 到达目标状态的最优解。同时具有条件:

  • 状态转移必须有方向,同时整体不能成环状
  • 状态的个数必须要在可接收的范围内

解题的几个重点:

  • dp数组就是表示状态,要知道具体的下标是什么意思
  • 递推公式
  • dp数组初始化
  • 遍历的一个顺序

合并区间

以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间

**输入:**intervals = [[1,3],[2,6],[8,10],[15,18]]
输出: [[1,6],[8,10],[15,18]]

**解释:**区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6]

自己题解

cpp 复制代码
class Solution {
public:
    vector<vector<int>> merge(vector<vector<int>>& intervals) {
        int num_size=intervals.size();
        sort(intervals.begin(), intervals.end());   //先排序
        vector<vector<int>>tag;
        int temp_end=intervals[0][1];
        int temp_start=intervals[0][0]; 
        int index1=0,index2=1;
        while(index2<num_size)
        {
            //判断
            if(intervals[index2][0]<=temp_end)   //必定出现重合
            {
                if(temp_end<intervals[index2][1])
                {
                    temp_end=intervals[index2][1];
                }
                if(temp_start>intervals[index2][0])
                {
                    temp_start=intervals[index2][0];
                }
            }else
            {
                //出现不重合,此时直接添加到结果,同时移动index1
                tag.push_back({temp_start,temp_end});
                index1=index2;
                temp_end=intervals[index1][1];
                temp_start=intervals[index1][0];
            }
            //扩张
            index2++;
        }
        tag.push_back({temp_start, temp_end});  //er:最后一个没有放
        return tag;
    }
};

我的思路其实我就准备使用双指针 来解决,先进行排序,然后让 index1 去指向合并区间的起点,让 index2 去不断扩张,然后同时用值去记录当前和合并区间的末尾方便判断。之后如果遇到不可以可以合并的就将已经合并的添加到结果中去,同时让 index1 直接移动到 index2 处,让index2继续扩张持续。

官方题解

cpp 复制代码
class Solution {
public:
    vector<vector<int>> merge(vector<vector<int>>& intervals) {
        if (intervals.size() == 0) {
            return {};
        }
        sort(intervals.begin(), intervals.end());
        vector<vector<int>> merged;
        for (int i = 0; i < intervals.size(); ++i) {
            int L = intervals[i][0], R = intervals[i][1];
            if (!merged.size() || merged.back()[1] < L) {
                merged.push_back({L, R});
            }
            else {
                merged.back()[1] = max(merged.back()[1], R);
            }
        }
        return merged;
    }
};

思路是一致的都是先对数组进行排序,然后比较首尾进行判断合并区间。


轮转数组

给定一个整数数组 nums,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。

输入: nums = [1,2,3,4,5,6,7], k = 3
输出: [5,6,7,1,2,3,4]
解释:

向右轮转 1 步: [7,1,2,3,4,5,6]

向右轮转 2 步: [6,7,1,2,3,4,5]

向右轮转 3 步: [5,6,7,1,2,3,4]

自己题解

cpp 复制代码
class Solution {
public:
    void rotate(vector<int>& nums, int k) {
        int num_size=nums.size();
        vector<int>new_num(num_size,0);
        for(int i=0;i<num_size;i++)
        {
            int temp=(i+k)%num_size;
            new_num[temp]=nums[i];
        }
        //再来赋值
        for(int i=0;i<num_size;i++)
        {
            nums[i]=new_num[i];
        }
    }
};

我的思路:我感觉我有点苯,我的想法就是新开一个数组,然后直接进行位移进去就行,我就是用求余的形式来进行按照 index+k-1%size 来进行位移计算就可以了。最后再来一次遍历就行,确实感觉有点蠢了,但是胜在简单啊。

官方题解

cpp 复制代码
class Solution {
public:
    void reverse(vector<int>& nums, int start, int end) {
        while (start < end) {
            swap(nums[start], nums[end]);
            start += 1;
            end -= 1;
        }
    }

    void rotate(vector<int>& nums, int k) {
        k %= nums.size();
        reverse(nums, 0, nums.size() - 1);
        reverse(nums, 0, k - 1);
        reverse(nums, k, nums.size() - 1);
    }
};

官方的方法我就学习一下翻转法,这个翻转法的意思就是多次翻转就可以实现数组的轮转,大致的思路就是下面这样:


除了自身以外的数组乘积

给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除了 nums[i] 之外其余各元素的乘积 。

题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。

请 **不要使用除法,**且在 O(n) 时间复杂度内完成此题。

输入: nums = [1,2,3,4]
输出: [24,12,8,6]

自己题解

cpp 复制代码
class Solution {
public:
    vector<int> productExceptSelf(vector<int>& nums) {
        int num_size=nums.size();
        vector<int>my_v(num_size,1);
        int pre=1;  //前缀积
        //先计算前缀积
        for(int i=0;i<num_size;i++)
        {
            my_v[i]=pre;
            pre*=nums[i];
        }
        int last=1; //后缀积
        //计算后缀积
        for(int i=num_size-1;i>=0;i--)
        {
            my_v[i]*=last;
            last*=nums[i];
        }
        return my_v;
    }
};

我的思路:拿到这道题之后自然而然的想到了两种方法:1.直接所有乘起来,然后依次除以每一个元素(当然这道题不能用) 2.直接暴力for循环,进行判断一下这个下标条件即可(当然不出意外我们这个的复杂度不满足,会超出时间限制)

让AI给我提供了一下新思路就是前缀积后缀积

txt 复制代码
tag[i]=前缀积*后缀积

官方的题解

cpp 复制代码
class Solution {
public:
    vector<int> productExceptSelf(vector<int>& nums) {
        int length = nums.size();

        // L 和 R 分别表示左右两侧的乘积列表
        vector<int> L(length, 0), R(length, 0);

        vector<int> answer(length);

        // L[i] 为索引 i 左侧所有元素的乘积
        // 对于索引为 '0' 的元素,因为左侧没有元素,所以 L[0] = 1
        L[0] = 1;
        for (int i = 1; i < length; i++) {
            L[i] = nums[i - 1] * L[i - 1];
        }

        // R[i] 为索引 i 右侧所有元素的乘积
        // 对于索引为 'length-1' 的元素,因为右侧没有元素,所以 R[length-1] = 1
        R[length - 1] = 1;
        for (int i = length - 2; i >= 0; i--) {
            R[i] = nums[i + 1] * R[i + 1];
        }

        // 对于索引 i,除 nums[i] 之外其余各元素的乘积就是左侧所有元素的乘积乘以右侧所有元素的乘积
        for (int i = 0; i < length; i++) {
            answer[i] = L[i] * R[i];
        }

        return answer;
    }
};

最经典的解法就是前缀积和后缀积,这道题官方给出的也是这个解法进行的前缀积和后缀积的形式。


缺失的第一个正数

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

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

**输入:**nums = [1,2,0]

**输出:**3

**解释:**范围 [1,2] 中的数字都在数组中。

我的题解

cpp 复制代码
class Solution {
public:
    int firstMissingPositive(vector<int>& nums) {
        int num_size=nums.size();
        unordered_map<int,int>map;
        int tag=1;
        int flag=0;
        for(int i=0;i<num_size;i++)
        {
            map[nums[i]]=i;
        }
        //查找
        while(flag==0)
        {
            if(map.find(tag)!=map.end())
            {
                tag++;
            }else
            {
                flag=1;
            }
        }
        return tag;
    }
};

我看到这个题的时候首先想到的就是哈希表查询 ,然后时间复杂度应该是满足的,但是空间不是常数级别。还想到一个就是先对数组进行排序之后,利用双指针进行滑动应该也可以的。

然后让AI大哥帮我修改了一下,就用

cpp 复制代码
class Solution {
public:
    int firstMissingPositive(vector<int>& nums) {
        int n = nums.size();

        // 第一步:把无关数字变成 n+1
        // 因为我们只关心 1~n
        for (int i = 0; i < n; i++) {
            if (nums[i] <= 0 || nums[i] > n) {
                nums[i] = n + 1;
            }
        }

        // 第二步:用下标做标记
        // 如果数字 x 出现过,就把 nums[x-1] 标记成负数
        for (int i = 0; i < n; i++) {
            int x = abs(nums[i]);
            if (x >= 1 && x <= n) {
                nums[x - 1] = -abs(nums[x - 1]);
            }
        }

        // 第三步:找第一个没被标记的位置
        for (int i = 0; i < n; i++) {
            if (nums[i] > 0) {
                return i + 1;
            }
        }

        return n + 1;
    }
};

因为我们要找到的是一个 1~n 之间,所以直接把无关的数组变为 n+1 ,之后就在原地实现桶的功能,不用新开哈希表。

官方题解

cpp 复制代码
class Solution {
public:
    int firstMissingPositive(vector<int>& nums) {
        int n = nums.size();
        for (int& num: nums) {
            if (num <= 0) {
                num = n + 1;
            }
        }
        for (int i = 0; i < n; ++i) {
            int num = abs(nums[i]);
            if (num <= n) {
                nums[num - 1] = -abs(nums[num - 1]);
            }
        }
        for (int i = 0; i < n; ++i) {
            if (nums[i] > 0) {
                return i + 1;
            }
        }
        return n + 1;
    }
};

这个官方的题解就是主要采用的原地桶查找,和我们上面让AI修改的大致是一样的思路。

我们对数组进行遍历,对于遍历到的数 x,如果它在 [1,N] 的范围内,那么就将数组中的第 x−1 个位置(注意:数组下标从 0 开始)打上「标记」。在遍历结束之后,如果所有的位置都被打上了标记,那么答案是 N+1,否则答案是最小的没有打上标记的位置加 1。

就是先将无关的数组去掉,然后利用 nums充当标记即可。

相关推荐
y = xⁿ2 小时前
【LeetCode】双指针:同向快慢针
算法·leetcode
啊哦呃咦唔鱼2 小时前
LeetCode hot100-105从前序与中序遍历序列构造二叉树
算法
favour_you___2 小时前
2026_4_8算法练习题
数据结构·c++·算法
汀、人工智能2 小时前
[特殊字符] 第57课:搜索旋转排序数组
数据结构·算法·数据库架构·图论·bfs·搜索旋转排序数组
倦王2 小时前
力扣日刷47
算法·leetcode·职场和发展
MicroTech20252 小时前
突破量子数据加载瓶颈,MLGO微算法科技推出面向大规模量子计算的分治态制备技术
科技·算法·量子计算
码王吴彦祖2 小时前
顶象 AC 纯算法迁移实战:从补环境到纯算的完整拆解
java·前端·算法
SccTsAxR2 小时前
算法基石:手撕离散化、递归与分治
c++·经验分享·笔记·算法
wuweijianlove2 小时前
算法测试中的数据规模与时间复杂度匹配的技术4
算法