代码随想录算法训练营:2/60

非科班学习算法day2 | LeetCode977: 有序数组的平方,Leetcode209: 长度最小的子数组,Leetcode88: 合并有序数组,Leetcode59:螺旋矩阵||

目录

介绍

一、基础概念补充:

1.c++中的vector的构造

2.++i和i++的区别

++i(前缀自增)

i++(后缀自增)

二、LeetCode题目

1.LeetCode977: 有序数组的平方

题目解析

优化思路

2.Leetcode209: 长度最小的子数组

题目解析

优化思路

3.Leetcode88:合并有序数组

题目解析

 4.Leetcode59:螺旋矩阵

题目解析

总结


介绍

包含LC的两道题目,还有相应概念的补充。

相关图解和更多版本:

代码随想录 (programmercarl.com)https://programmercarl.com/#%E6%9C%AC%E7%AB%99%E8%83%8C%E6%99%AF


一、基础概念补充:

1.c++中的vector的构造

默认构造函数:创建一个空的vector

std::vector<T> vec;

填充构造函数:创建一个包含n个复制自val的元素的vector

std::vector<T> vec(n, val);

范围构造函数:根据迭代器范围[first, last)创建一个vector

std::vector<T> vec(first, last);

拷贝构造函数:创建一个新的vector,作为现有vector的副本。

2.++ii++的区别

       ++ii++都是C语言中的自增运算符,它们的作用是使变量i的值增加1。然而,它们在使用上有所不同,具体表现在它们的值何时被更新以及如何影响表达式的计算结果。

++i(前缀自增)
  • ++i首先将i的值增加1,然后再将更新后的值用于表达式的计算。
  • 如果++i是一个独立的语句或者是一个表达式的一部分,它的值会立即更新。
i++(后缀自增)
  • i++首先将i的当前值用于表达式的计算,然后再将i的值增加1。
  • 如果i++是一个独立的语句或者是一个表达式的一部分,它的值在表达式计算完毕后才会更新。

二、LeetCode题目

1.LeetCode977: 有序数组的平方

题目链接:. - 力扣(LeetCode)

题目解析

        首先,根据题目给出的示例一可以想到:对数组元素进行平方操作,得到新数组,对新数组进行冒泡排序即可得到目标数组。

 C++代码如下:

class Solution {
public:
    vector<int> sortedSquares(vector<int>& nums) {
        for(int i = 0; i < nums.size();i++)
        {
            nums[i] *= nums[i];
        }
        
        //排序
        for (int i = 0; i < nums.size(); i++)
        {
            for(int j = i + 1; j < nums.size(); j++)
            {
                if (nums[j] < nums[i])
                {
                    int temp = nums[i];
                    nums[i] = nums[j];
                    nums[j] = temp; 
                }
            }
        }
        
        //返回目标数组
        return nums;
    }
};

时间复杂度分析:进行了双层的循环嵌套,按照最坏复杂度为O(n^2)

这段代码并不困难,可以在原有数组的基础上进行更新,但是时间复杂度爆炸,虽然在LC上能通过

优化思路

        继续观察题目要求:非递减顺序,平方。首先我们可以确定数组中的数字可能有负有正,但是取平方之后,最大的数不过就是出现在数组的两头。那么就可以借助两个指针(指向需求的位置:头部和尾部)进行比较,并且生成新的数组并且作为返回值。

注意点1:边界条件设置,为了能够遍历到每一个值,不排除最后两个指针重合的情况,所以这里采用left <= right 而不是left < right

注意点2:检查条件之后,进行左/右指针的移动。

注意点3:这里新建了一个vector,并且定义了临时指针指向该vector,left和right并不对prt有任何影响,最后返回的也是这个数组。

 调整之后C++代码如下:

class Solution {
public:
    vector<int> sortedSquares(vector<int>& nums) {
        //左右指针指向两端
        //检查状态
        //更新数组
        
        int left = 0;
        int right = nums.size() - 1;

        vector<int>result (nums.size());

        for(int prt = nums.size() - 1; prt >= 0 && left <= right ; prt--)
        {
            if(nums[left] * nums[left] < nums[right] * nums[right])
            {
                result[prt] = nums[right] * nums[right];
                right--;
            }
            else
            {
                result[prt] = nums[left] * nums[left];
                left++;
            }
        }
        return result;

    }
};

 时间复杂度分析:按照最坏复杂度为O(n),很有效降低了时间复杂度。

2.Leetcode209: 长度最小的子数组

题目链接:209. 长度最小的子数组 - 力扣(LeetCode)

题目解析

        遍历数组,生成目标数组的左边界;

        嵌套循环,生成目标数组的右边界;        

        对区间内的元素加和判断是否满足条件;

        返回有效数组长度

注意点1:外部循环条件设置:遍历每一个元素作为起始

注意点2:内部循环条件设置:遍历从外部循环锁定的元素[i]之后的所有元素,之后累计求和。

注意点3:这里设置minlen的原因是:除了像LC示例3这种不满足if判断的例子,还可能存在多个满足条件的子数组,这时候就需要满足最小的要求,所以在循环判断的过程中,设定一个当前最小长度的变量,有效防止仅出现一次满足大于target的数组出现就返回的现象。

注意点4:借助break,可以减少计算量。显而易见,在满足了当前搜索数组大于目标时,无需再将j向后检索。使用break跳出当前循环。

 C++代码如下: 

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int minlen = INT_MAX;
        int i = 0;
        while(i < nums.size())
        {
            int sum = 0;
            for(int j = i ;j < nums.size(); ++j)
            {
                sum += nums[j];
                if (sum >= target)
                {
                    minlen =  min(minlen,j - i + 1);
                    break;
                }
            }
            ++i;
        }
        return (minlen == INT_MAX) ? 0 : minlen;
    }
};

 时间复杂度分析:按照最坏复杂度为O(n^2) 

优化思路

       采用滑动窗口来进行:

       基本上还是基于左右两个边界,不过就像是一个区间版滑动变阻器,你的每次循环调试都像是测试电阻(当前值)是否满足目标电阻(条件)。

        先移动右边窗口;

        得到满足的窗口之后,移动左窗口;

        检查是否符合条件。

           

 调整之后C++代码如下:

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int left = 0;
        int right = 0;
        int sum = 0;
        int minlen = INT_MAX;

        //右窗口滑动
        for(; right < nums.size(); ++right)
        {
            sum += nums[right];
            while(sum >= target && left <= right)
            {
                minlen = min(minlen,right - left + 1);
                sum -= nums[left];
                ++left;
            }    
        }
        return (minlen == INT_MAX) ? 0 : minlen;

    }
};

 时间复杂度分析:按照最坏复杂度为O(n) 

3.Leetcode88:合并有序数组

题目链接:88. 合并两个有序数组 - 力扣(LeetCode)

题目解析

       以下思路借助于LC示例一给出:

        设置两个指针分别指向两个数组的起始位置;

        比对两个指针指向的大小;

        用小的数填充新建的m+n数组;

        遍历有效两个数组其中某一个的全部有效数字位置之后,检查另一个数组的剩余有效数字。

注意点1:在这里,我给出的快慢指针的说法并不准确,更好的应该是说1和2指针

注意点2:循环边界条件设置:不允许超出数组有效数字长度,这里有效数组长度指的分别是m和n

注意点3:在其中一个数组先全部填充到a(新建数组)后,要检查另一个数组的状态,将数组中剩余元素添加到新数组中。再次说明,新建数组之后,这三个指针相对独立,不会影响循环取值。

注意点4:根据题目要求,最后替换nums1.

C++代码如下:

class Solution {
public:
    void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {

        //借助双指针
        //定义快指针指向数组2
        //定义慢指针指向数组1
        int fast = 0;
        int slow = 0;
        int prt = 0;

        vector<int>a (m+n);

        while(slow < m && fast < n && prt < m + n)
        {
            if(nums2[fast] >= nums1[slow])
            {
                a[prt] = nums1[slow];
                slow++;
            }
            else
            {
                a[prt] = nums2[fast];
                fast++;
            }
            prt++;
        }
        while(slow < m)
        {
            a[prt] = nums1[slow];
            slow++;
            prt++;
        }
        while(fast < n)
        {
            a[prt] = nums2[fast];
            fast++;
            prt++;
        }

        for(int i = 0; i < m+n; i++)
        {
            nums1[i] = a[i];
        }
    }
};

 补充:这道题只要说新建空间其实并不算很困难,一开始我沉浸在正向遍历,而且还想不新建空间,最后发现问题的复杂度大大增加。我们假设在数组1中插入数组2,那就要检查数组2中的元素在数组1中元素的相对位置,然后将其插入,数组1的元素向后移动,时间复杂度也爆炸,我还想用数组2来交换数组1的元素,原谅我太菜了,无果。

下面给出一种后向遍历的方法,不用新开辟空间

c++代码如下:

class Solution {
public:
    void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
        int p1 = m - 1;
        int p2 = n - 1;
        int p = m + n - 1;

        while(p1 >= 0 && p2 >= 0 && p >= 0)
        {
            if (nums1[p1] > nums2[p2])
            {
                nums1[p] = nums1[p1];
                p1--;
            }
            else
            {
                nums1[p] = nums2[p2];
                p2--;
            }
            p--;
        }

        //处理剩余nums2,因为此时没有开辟新空间,极端情况是要么num2全部比num1小
        //此时相当于移动了整个num1;要么是nums2全部插入

        while(p2 >= 0)
        {
            nums1[p] = nums2[p2];
            p--;
            p2--;
        }

    }
};

以上代码运行之后发现示例3跑不过,即nums1 = [0],m = 0的情况, 头部添加检查:

        if(m == 0)
        {
            nums1 = nums2;
        }

 4.Leetcode59:螺旋矩阵

题目链接:59. 螺旋矩阵 II - 力扣(LeetCode)

题目解析

       以下思路借助于LC示例给出:

        首先给定循环的边界条件,四个边界;

        预先定义需要作为容器的矩阵matrix;

        按照左开右闭的原则遍历四条边;

        检查元素遗漏。

注意点1:vector里面是可以再放vector的,这也是C++常见的矩阵创建方式,详细见前面说的vector构造。

注意点2:最重要的无疑是边界条件的设置。设置了四个初始边界之后,我们要清楚,自己写的遍历,每一次不变的是什么,变的是什么。

比如在遍历第一行时候,明显变化的是纵坐标,也就是martix[~][i]里的i在变化,它的变化区间是什么?是左闭右开。这样子就可以一步一步确定遍历的条件。当然,外部循环的条件就是,边界不允许交叉。

注意点3:最后设置了一个检查,根据实例,不难发现,如果数字为奇数,比如3,那么最后(9)肯定会有一个单独的中心点,所以我们检查是否为奇数,如果是,那么就单独填补一下这个数字。

 C++代码如下:

class Solution {
public:
    vector<vector<int>> generateMatrix(int n) {
        //模拟螺旋顺序
        //坚持左闭右开原则

        //预分配
        vector<vector<int>> matrix(n, vector<int>(n, 0));
        //定义循环变量
        int left = 0;
        int right = n - 1;
        int top = 0;
        int bottom = n - 1;

        int mid = n / 2;
        int r = 0;
        while(left <= right && top <= bottom)
        {
            //遍历第一条边
            for(int i = left; i< right; i++)
            {
                matrix[top][i] = ++r;
            }
            top++;  //向下一行

            //遍历第二条边
            for(int j = top - 1; j < bottom; j++)
            {
                matrix[j][right] = ++r;
            }
            right--;

            //遍历第三条边
            for(int i = right + 1; i > left; i--)
            {
                matrix[bottom][i] = ++r;
            }
            bottom--;

            //遍历第四条边
            for(int j = bottom + 1; j > top - 1; j--)
            {
                matrix[j][left] = ++r;
            }
            left++;
        }
        if(n % 2)
        {
            matrix[mid][mid] = ++r;
        }
        return matrix;
    }
    
};

总结


打卡第二天,坚持!!!

  • 14
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
代码随想录算法训练营是一个优质的学习和讨论平台,提供了丰富的算法训练内容和讨论交流机会。在训练营中,学员们可以通过观看视频讲解来学习算法知识,并根据讲解内容进行刷题练习。此外,训练营还提供了刷题建议,例如先看视频、了解自己所使用的编程语言、使用日志等方法来提高刷题效果和语言掌握程度。 训练营中的讨论内容非常丰富,涵盖了各种算法知识点和解题方法。例如,在第14天的训练营中,讲解了二叉树的理论基础、递归遍历、迭代遍历和统一遍历的内容。此外,在讨论中还分享了相关的博客文章和配图,帮助学员更好地理解和掌握二叉树的遍历方法。 训练营还提供了每日的讨论知识点,例如在第15天的讨论中,介绍了层序遍历的方法和使用队列来模拟一层一层遍历的效果。在第16天的讨论中,重点讨论了如何进行调试(debug)的方法,认为掌握调试技巧可以帮助学员更好地解决问题和写出正确的算法代码。 总之,代码随想录算法训练营是一个提供优质学习和讨论环境的平台,可以帮助学员系统地学习算法知识,并提供了丰富的讨论内容和刷题建议来提高算法编程能力。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [代码随想录算法训练营每日精华](https://blog.csdn.net/weixin_38556197/article/details/128462133)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值