代码算法训练营day2 | 977有序数组的平方、209长度最小的子数组、59螺旋矩阵Ⅱ

977有序数组的平方

题目链接
状态:用暴力算法做出来了,双指针法没有做出来
文档:programmercarl.com

(暴力算法)思路1:
这里所谓暴力,就是把数组中的每个数都做一个自身的平方,变成一个新的数。然后用一个sort全局函数把容器中的所有数做一个排序,就完成了。

代码:

class Solution {
public:
    vector<int> sortedSquares(vector<int>& nums) {
        //[-5,1,2,3] 平方之后,最大值可能是在两端,但不可能是在中间
        //暴力
        for(int i = 0;i<nums.size();i++)
        {
            nums[i] *= nums[i];
        }
        sort(nums.begin(),nums.end());  //快速排序 全局函数sort()
        return nums;
    }
};

(双指针)看过文档后的思路2: (看之前没有思路)
[-5,1,2,3] 平方之后,最大值可能是在两端,但不可能是在中间。所以这样可以确定双指针的起始位置。
i-指向头,j-指向尾,k-指向新数组的尾部。
i 和 j 谁的数大 谁就放到 k上,然后 k再–,进行前移,继续等待第二大的数放进来。

代码:

class Solution {
public:
    vector<int> sortedSquares(vector<int>& nums) {
    	//创建新数组,来放平方后的数据
        vector<int> newArr(nums.size(),0);
        int k = nums.size()-1;
        int i = 0,j = nums.size()-1;
        
        while(i <= j)
        {
            //j大
            if(nums[i] * nums[i] < nums[j] * nums[j])
            {
                //j代表的值赋给k 那i上的值还没有进行处理,所以i不动,只有j--
                newArr[k--] =  nums[j] * nums[j];
                j--;
            } 
            //i大
            //else if(nums[i] * nums[i] > nums[j] * nums[j])
            //其实并不用这样,因为不管i大 还是i和j一样大 都是要先移动被赋值的那个下标
            //那么不妨多比一次 不用直接一次就比较出j和i代表的数是一样的  
            //这样一个 if else 就解决了
            else //nums[i] * nums[i] >= nums[j] * nums[j]
            {
                //i代表的值赋给k 那j上的值还没有进行处理,所以j不动,只有i++
                newArr[k--] =  nums[i] * nums[i];
                i++;
            }   
        }
        return newArr;   
    }
};

实现时遇到的问题:
尝试用一个 if( j 大)-else if (i 大)-else(一样大) 去解决问题
但是会出现报错,代码如下:

class Solution {
public:
    vector<int> sortedSquares(vector<int>& nums) {
        vector<int> newArr(nums.size(),0);
        int k = nums.size()-1;
        int i = 0,j = nums.size()-1;
        while(i <= j)
        {
            //j大
            if(nums[i] * nums[i] < nums[j] * nums[j])
            {
                //j代表的值赋给k 那i上的值还没有进行处理,所以i不动,只有j--
                newArr[k--] =  nums[j] * nums[j];
                j--;
            } 
            //i大 
            else if(nums[i] * nums[i] > nums[j] * nums[j])
            {
                //i代表的值赋给k 那j上的值还没有进行处理,所以j不动,只有i++
                newArr[k--] =  nums[i] * nums[i];
                i++;
            }
            
            //错误的地方出现在下面这个else中
            else
            {
                //nums[i] * nums[i] == nums[j] * nums[j]
                //把i和j的值都赋给k,i j同时变化
                cout<<"赋值之前的k:"<<k<<endl;  //结果是0
                newArr[k--] = nums[i] * nums[i];
                cout<<"赋值之后的k:"<<k<<endl;  //结果是-1
                cout<<i<<endl;
                i++;
                cout<<i<<endl;

                newArr[k--] = nums[j] * nums[j];  //k已经==-1了,所以这里肯定运行不了了
                cout<<k<<endl;
                cout<<j<<endl;
                j--;
                cout<<j<<endl;   
            }
        }
        return newArr;
    }
};

报错原因:

Line 1037: Char 34: runtime error: addition of unsigned offset to 0x5030000000a0 overflowed to 0x50300000009c (stl_vector.h)
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior /usr/bin/…/lib/gcc/x86_64-linux-gnu/11/…/…/…/…/include/c++/11/bits/stl_vector.h:1046:34

分析: 查了一下,说是越界。和上面的 k == -1的情况是相对应的。
所以来分析一下为什么下标会越界:
【-3,-2,1,2】–原数组
①i =0 — 指向-3,j=3 — 指向2,i×i = 9,j×j = 4 (注意:i×i j×j 指的是nums[i]×nums[i],nums[j]×nums[j])
满足else if条件,所以k = 3时,被赋值为9,i++,k- -,j不动
②i =1 — 指向-2,j依旧=3 — 依旧指向2,i×i = 4,j×j = 4
此时满足else条件,两个指针指向的值都相等。
所以k = 2时,被赋值为 j 结果:4,k- -,j- -,(j- -结果是2)
k- -之后的结果是k = 1,被赋值为 i 的结果:4,k- -(k- -结果是0),i++(i++结果是2)
③好了 现在 i 和 j 都指向2这个位置,那么都不用算,就知道nums[i]*nums[i] ==nums[j]*nums[j]了,直接执行的是else条件,那么问题现在就来了,k已经是0了,可以在0的位置上把i或者j指向的所算出来的值赋过去,那么剩下的那个指针呢?赋值过去后k进行–操作,此时k就等于-1了,再按照代码中的操作那就是把剩下那个指针所代表的值放入到newArr[-1]的位置上,那显然是不可能的,因为越界了。这就是问题的根源。

总结: 因为 i 和 j 是可以有相等的情况,那么如果在算出的值相等的情况下,k 要被赋值两次,那么就相当于把 i 重复赋两次,那么新数组的长度一定不够,也就是说会发生上述提到的越界问题。


209长度最小的子数组

题目链接
状态:暴力算法ok,滑动窗口不ok
文档:programmercarl.com

【方法一:暴力算法】
思路:
所谓暴力解法,就是一个个的去加,直到加够了,算出长度。因为题目中要求找最短长度,所以还要对长度进行比较。这一点在实现过程中没有注意到,所以一直AC不了。
代码:

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        //暴力算法
        int comp = INT32_MAX;  //很大的一个正数 //注意点1
        int sum = 0; //计算元素的和
        int subLenth = 0; //子序列的长度
        for(int i=0;i<nums.size();i++)
        {
            //sum = nums[i];
            sum = 0;
            int j=i; //注意点2
            for(;j<nums.size();j++)
            {
                sum += nums[j]; //注意点3
                if(sum >= target)
                {
                    subLenth = j-i+1;
                    //找出最小的subLenth
                    comp = comp>subLenth?subLenth:comp;  //注意点4
                    break; //找到条件就跳出while循环,进行下一轮的for循环
                }
                j++;
            }
        }
        //如果comp没有被赋值的话,就输出0,
        //如果comp被赋值的话,说明找到了子序列,就输出子序列的长度即可
        return comp == INT32_MAX ?0:comp;
    }
};

实现过程中的问题:
上述代码中出现了几个注意点,现在做一下描述
①注意点1:int comp = INT32_MAX; 这是因为要和之后的subLenth作比较,要不断比出最小的subLenth,所以要把它先置为一个很大的数才行。
②注意点2:int j=i; 在实现过程中我一度把 j 设置成 j=i+1,我之前的想法是sum = nums[i];sum += nums[j],然后不断的+nums[j],直到满足条件,求出此时的长度。但是有一个问题就是:我的sum += nums[j]判断语句是放到while(j<nums.size())中的,那么就表明:如果我sum = nums[i]已经满足条件了,那我还要再加一个nums[j]才行,怎么改也改不了。所以问题就出现在了最开始,也就是j不能初始化为i+1,而是初始为i,即j = i;之后sum +=nums[j];放在while循环中,满足条件了就求长度,再比较长度即可。然后外循环用i=0,不断地去进行i++,变相的让j++,去不断更新第一个加数。
③注意点3:在②中已经有所描述。
④注意点4:让comp初始化一个很大的数,这样一旦有个子序列长度,肯定会赋值给comp,那么在进行最后的return中,很容易就能分出来到底有没有子序列以及子序列的长度为多少。
错误代码:

for(int i=0;i<nums.size();i++)
        {
            sum = nums[i];
            int j=i+1; //i确定了,因为是连续的,所以j在i后一个 错误点1
            while(j<nums.size())
            {
                sum += nums[j]; //错误点2
                if(sum >= target)
                {
                    subLenth = j-i+1;
                    //找出最小的subLenth
                    comp = comp>subLenth?subLenth:comp;
                    break; //找到条件就跳出while循环,进行下一轮的for循环
                }
                j++;
            }
        }

总结: 在最后return的时候,要注意return的是comp,而不是subLenth,因为comp才是最短的结果,subLenth只是当前满足条件的子序列的长度,并不一定是最短的。

【方法二:滑动窗口】
(看文档后)思路:
所谓滑动窗口,其实说白了就是双指针,只不过实现过程是在一个从 i 到 j 的范围内去操作,很像一个窗口,所以叫做滑动窗口更为贴切。i 表示起始位置,j 表示终止位置。只用一个for循环,如果循环起始位置,那么终止位置还是要一个个循环到末尾,和暴力无异,所以选择循环终止位置 j 。
还是老思想 sum += nums[j],直到sum >= target 时,开始对 i 进行操作。先把现在的从 i 到 j 的子序列长度记录下来,并去和comp去比较到底谁更短(comp存放子序列长度)。之后为了缩短这个长度,就让i++,也就是 i 进行后移,随之sum也要减去原来 i 所指向的数值,也就是sum -= nums[i]。当 i 后移后,不满足 sum >= target了,那就再进行下一轮的 j++,继续找符合sum >= target 这个条件。sum的值无需重新置空,因为sum存放的值再加上j后移的值,也就不用再重新遍历了。

代码:

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        //滑动窗口
        //i表示起始位置,j表示终止位置
        int comp = INT32_MAX;
        int subLenth = 0;
        int sum = 0;
        int i=0; //起始位置
        //循环终止位置
        for(int j = 0;j<nums.size();j++)
        {
            sum += nums[j];
            //满足 加数和比tar要大的时候,再让起始位置i进行后移操作,不断缩短子序列的距离
            while(sum >= target)
            {   
                subLenth = (j-i+1);
                comp = comp<subLenth?comp:subLenth; //选择最小子序列长度
                //开始缩短i和j的距离
                sum = sum-nums[i];
                i++;
            }
        }
        return comp == INT32_MAX? 0 : comp;
    }
};

59螺旋矩阵Ⅱ

题目链接
状态:没做出来
文档:programmercarl.com

思路: 为了能套用一个模式写代码,选择左开右闭的规则。
过程图解

代码:
思路已经注释好了

class Solution {
public:
    vector<vector<int>> generateMatrix(int n) {
        //确定区间规则 -- 左闭右开
        //坚持用一个规则来处理每一条边
        //规则:每条边的最后一个节点不处理

        //用循环来一圈圈遍历
        //首先确定遍历几圈 n/2圈
        //for循环中的起始位置是要变的,因为边要变,位置也肯定要变
        //终止位置(i,j)也是随着时间去改变的

        //先用vector定义一个二维数组
        vector<vector<int>> nums(n, vector<int>(n, 0));

        int start_I = 0; //横坐标初始值
        int start_J = 0; //纵坐标初始值
        int offset = 1; //偏移量
        int count = 1; //要添加的数字
        int i,j; //i 横坐标、j 纵坐标
        int loop = n/2;//循环次数
        int mid = n/2; //中间位置

        //循环n/2圈
        while(loop--)
        {
            //用第一个for循环来循环第一条边
            //(0,j) -- (0,0) (0,1) (0,2)
            for(j = start_J;j<n-offset;j++)
            {
                nums[start_I][j] = count++;
            }
            
            //for循环结束后,j = n-offset
            //再遍历第二条边
            //(i,j) -- j不变,(0,j) (1,j) (2,j)
            for(i = start_I;i<n-offset;i++)
            {
                nums[i][j] = count++;
            }
            
            //for循环结束后,i = n-offset
            //再遍历第三条边
            //(i,j)-- i不变,j-- (i,2) (i,1) (i,0)
            for(;j>start_J;j--)
            {
                nums[i][j] = count++;
            }
            
            //for循环结束后,j = 0
            //再遍历第四条边
            //(i,j)-- j不变,i-- (2,j) (1,j) (0,j)
            for(;i>start_I;i--)
            {
                nums[i][j] = count++;
            }
            //for循环走完后,i=0,j=0

            //走完四条边之后,一些数据要更新 因为外圈走完了 
            offset+=1;
            start_I++;
            start_J++;
        }

        //如果n是奇数
        if(n%2 != 0)
        {
            //也就是说最后一个数被放到了中间的位置
            nums[mid][mid] = count;
        }
        return nums;

    }
};

总结: 需要明白要遵循什么样的规则。寻找规律是解题的关键。

  • 18
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值