LeetCode算法题个人笔记【数组】【简单1-5】【c++】

资料来源于leetcode官网
记得多看评论!
听从大佬建议从同一类型题目开始做,首先决定做数组!

前面还有三道简单题已经做过了。共47道简单题

**

第一题:搜索插入位置

**

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。

你可以假设数组中无重复元素。

看到有序数组就可以考虑二分法

条件总结一下就是在数组里找到第一个大于等于目标值的索引,

此外注意,只剩两个相邻数时,二分永远等于小的那个,比如 (1+2)/2=1,(2+3)/2=2。

那么剩下的就是边界的判断,这点可以自己动手画图,光想有点难想

官方:

class Solution {
public:    
	int searchInsert(vector<int>& nums,int target)    
	{        
		int n=nums.size();        
		int left=0,right=n-1,ans=n;        
		while(left<=right)        
		{            
			int mid=((right-left)>>1)+left;            
			if(target<=nums[mid])            
			{                
				ans=mid;                
				right=mid-1;            
			}            
			else            
			{                
				left=mid+1;            
			}        
		}        
		return ans;    
	}
};

(>>,<<)二进制运算符,先将数转为二进制,再移动相应位数,这里确实能起到 /2 的效果

第一:首先能确定的是,二分:当目标值大于mid时,将左边界右移,小于mid时,将右边界左移,两个操作同时进行,这就是二分
比如0,1,2,3,4,5 到0,1,2 到1,2到1,(这里是索引)
第二:就像递归一样,什么时候跳出二分?无论如何,最后肯定两个数的时候再二分就等于小的那个,也就是二分道最后就是一个数了

时间复杂度:
O(logn),其中 n为数组的长度。二分查找所需的时间复杂度为 O(logn)。
(想想看,我们每次二分都缩短一半的查找范围),用二分的目的就是为了节省时间

空间复杂度:
O(1)我们只需要常数空间存放若干变量。

果然是这样,记得小时候会玩的一种游戏吗,告诉别人一个数字范围,让他猜一个数,大家第一反应都是先猜中间的那个,然后我们会说是大了还是小了,缩短范围,然后不断如此,最后得到答案

class Solution {
public:    
	int searchInsert(vector<int>& nums,int target)        
	{                
		int n=nums.size(); 
        	int left=0,right=n-1;                  
        	while(left<right)        
        	{            
        		int mid=(right+left)/2;
            		if(nums[mid]==target)//如果等于mid就直接返回了            
            		{                
            			return mid;            
            		}            
            		else if(target<nums[mid])//否则移动边界            
            		{                
            			right=mid;            
            		}            
            		else             
            		{                
            			left=mid;            
            		}        
            	}             
            	if(nums[left]<target)//二分到最后一个数仍不相等       
            	{            
            		return left-1;        
            	}        
            	else         
            	{            
            		return left+1;       
            	}    
            }
         };

这个方法会超时

在这里插入图片描述

实际上有错误,(0,1)想要右移左边界的话,left只会不断等于mid=0.
我们这个right=mid没错,可以求到一个元素,但是因为mid只会等于两个数中小的(左边的那个)所以,如果此时想要移动左边界left,通过left=mid是不行的,必须是left=mid+1

关键问题就在这里,两个数理论上说没有中间值,但是mid就是会返回他们中的小的值
或者说偶数的情况下,它会返回中间两个左边的那个

同时关键的一点在于,数组插入一个值到某值的前面,位置不是当前索引减一,而是等于当前索引,并将之后索引加一
然而插入当前位置,(在本题就是返回当前索引)和插入之后(当前index+1)都符合常规思维逻辑,上当了。。。

在这里插入图片描述

第一,将 left<right 改为 left<=right 这样相当于在还剩一个元素的时候再判断一次
省去最后的if else

第二,如何知道应该返回left=mid+1还是返回right=mid-1呢?(看上面)(不返回right的,right=mid-1其实是为了跳出循环)

class Solution {
public:        
	int searchInsert(vector<int>& nums,int target)            
	{                        
		int n=nums.size();         
		int left=0,right=n-1;                          
		while(left<=right)                
		{                        
			int mid=(right+left)/2;
            		if(nums[mid]==target)                                        
            		{                
            			return mid;                        
            		}                        
            		else if(target<nums[mid])//否则移动边界                       
            		{                                
            			right=mid-1;                        
            		}                       
            		else                         
            		{                                
            			left=mid+1;                       
            		}        
            	}        
            	return left;    
            }                     
     }; 

顺利通过,时间和内存都基本一样

最后一个问题,为什么是target<=nums[mid]

假如我们先把=放到一边,先只考虑用<,>二分,如果刚好mid=target,就会卡住,既不大于也不小于,(啊,就不能此时直接返回吗草)

先只考虑二分的话,可以首先确定我们一定是返回left,这个就可作为判断条件了,

另外二分是在不断的缩短范围,target必然在过程中某一次等于mid,target是不会跑到范围外的,只有在target=mid而后right=mid+1后才会,不过这时只会左边界不断右移最后返回left=mid+1了,还有两种情况就是在二分过程中刚好成为新的左边界或右边界,要么本来就是

在这里插入图片描述

我悟了,跳出的条件(left>=right)这个是必然的,因为肯定是左边界往右移,右边界往左移,

在这里插入图片描述

如果要想返回right,条件改成nums[i]<=target,会发现不行,因为会发现不满足不等的情况,因为跳出条件是left>=right,那么必然在left=right=mid时,right向左移小于left ,或者 left向右移大于right, 而这时返回right并不对

评论区:

class Solution {
public:
    int searchInsert(vector<int>& nums, int target) {
        int n = nums.size();
        int l=0,r=n-1;
        while(l<=r){
            int mid=l+(r-l)/2;
            if(nums[mid]<target)
                l=mid+1;
            else r=mid-1;//注意这里隐含的是nums[mid]>=target
        }
        return l;
    }
};

那么终于差不多弄清楚了,那么就是不断二分,跳出循环二分的条件,以及我们所要求的值和条件的关系,

我们那个好理解好多,但是只是一个小于等于的条件就折腾了这么久。。

**

第二题:最大子序和

**

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

输入: [-2,1,-3,4,-1,2,1,-5,4]
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。

解法一:动态规划

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;    
	}
};

在这里插入图片描述

上面有个小错误,问题不大,这都能算歪我也是服了

确实是这样的,好神奇,

是这样的,注意条件是 最大连续子数组 。不要求返回数组。不是要你全部累加起来,

For(auto x : str)是利用x生成str中每一个值的复制,对x的赋值不会影响到原容器。
For(auto &x : str)是利用x生成str中每一个值的引用,对x的操作会影响到原容器。

pre=max(pre+x,x) 意味着pre要么等于 一段累加,要么等于数组某个大于当前累加的元素,
pre不是最大子数组,maxAns才是
如果没有大的元素,很顺利一直加下去,(负数减法也是相同)那就顺利地通过maxAns=mas(maxAns,pre)得到最大累加值,

但是如果,比如从头开始加,居然有一个元素本身就比我们累加值还大,

想不明白,先放一边,之后还有一个贼复杂的分治方法,也看不懂

在这里插入图片描述

代码就几句话,不过要知道到底是怎么实现的。
关键在于有正有负时,max一定在正子串里,不管你怎么加,是哪个就不知道了
更新放到ans里,

其他的解

暴力法:

class Solution {
public:     
	int maxSubArray(vector<int>& nums)    
	{        
		int max=INT_MIN;类似寻找最大最小值的题目,初始值一定要定义成理论上的最小最大值
        	for(int i=0;i<nums.size();i++)        
        	{            
        		int sum=0;            
        		for(int j=i;j<nums.size();j++)            
        		{                
        			sum+=nums[j];                
        			if(sum>max)                
        			{                    
        				max=sum;                
        			}            
        		}        
        	}        
        	return max;    
        }
};

第一个元素从头累加到尾,这个时候得到的最大值有一个问题,就是必须是从index=0的元素累加起,显然中间有可能有更大的,
所以再从第二个元素开始加,遍历完所有元素,就查看了所有情况。

耗时巨长。

**

第三题 : 加一

**

给定一个由整数组成的非空数组所表示的非负整数,在该数的基础上加一。
最高位数字存放在数组的首位, 数组中每个元素只存储单个数字。
你可以假设除了整数 0 之外,这个整数不会以零开头。

题目有点难懂,就是数组 [1,2,3,4] 代表数字 1234,

输入: [1,2,3]
输出: [1,2,4]
解释: 输入数组表示数字 123。

begin()首部,insert(begin(),5)在首部插入5, insert(begin(),2,5)在首部插入两个5

class Solution {
public:    
	vector<int> plusOne(vector<int>& digits)    
	{        
		for(int i=digits.size()-1;i>=0;i--)        
		{            
			digits[i]++;            
			if(digits[i]==10) digits[i]=0;            
			else                
				return digits;        
		}        
		digits.insert(digits.begin(),1);//这里是为了数字9开头的时候,在开头加上一个1
		return digits;    
	}   
};

**

第四题:合并两个有序数组

**

给你两个有序整数数组 nums1 和 nums2,请你将 nums2 合并到 nums1 中,使 nums1 成为一个有序数组。

要求:
初始化 nums1 和 nums2 的元素数量分别为 m 和 n 。
你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素。

方法一:插入然后用sort方法排序。。

class Solution {
public:       
	void merge(vector<int>& nums1,int m,vector<int>& nums2,int n)    
	{        
		for(int x:nums2)        
		{            
			nums1[m++]=x;        
		}        
		sort(nums1.begin(),nums1.end());    
	}
};

方法二:双指针法/从前往后

class Solution{
public:
	void merge(vector<int>& nums1,int m,vector<int>& nums2.int n)
	{
		//Make a copy of nums1;
		vector<int> tmp(nums1);

		//Two get pointers for tmp and nums2
		int p1=0;
		int p2=0;

		//Set pointer for nums1
		int p=0;

		while(p1<m&&p2<n)
		{
			nums1[p++]=(tmp[p1]<nums2[p2])?tmp[p1++]:nums2[p2++];
		}
		while(p1<m)//tmp没遍历完,nums[2]遍历完了,p=n+p1
		{
			//nums1[n+p1]=tmp[p1++];   错误!
			nums1[n+p1]=tmp[p1];
			p1++;
		}
		while(p2<n)
		{
			//nums1[m+p2]=nums2[p2++];
			nums1[m+p2]=nums2[p2];
			p2++;
		}

	}
};

注意我们不需要返回值

为什么自增运算符不对?先不管了

方法三:双指针/从后往前

方法二已经取得了最优的时间复杂度O(n+m),但需要使用额外空间。这是由于在从头改变nums1的值时,需要把nums1中的元素存放在其他位置。

如果我们从结尾开始改写 nums1 的值又会如何呢?这里没有信息,因此不需要额外空间。

class Solution {
public:    
	void merge(vector<int>& nums1,int m,vector<int>& nums2,int n)    
	{        
		//two get pointers for nums1 and nums2        
		int p1=m-1;        int p2=n-1;
		
	        //set pointer for nums1        
	        int p=m+n-1;
	        
	        //while there are still elements to compare        
	
		while((p1>=0)&&(p2>=0))        
		{            
			//compare two element from nums1 and nums2            
			//and add the largest one in nums1
	            	nums1[p--]=(nums1[p1]<=nums2[p2])?nums2[p2--]:nums1[p1--];
	        }        
	        while(p2>=0)        
	        {            
	        	nums1[p--]=nums2[p2--];        
	        }    
	    }
};

这里注意,如果num1有剩,则已经是排好的,如果是num2有剩,则把他继续插入num1前面。

**

第五题:杨辉三角:

**

给定一个非负整数 numRows,生成杨辉三角的前 numRows 行。
在这里插入图片描述

在杨辉三角中,每个数是它左上方和右上方的数的和。

又是动态规划:

注释里都是举例子理解

class Solution{
public:
	vector<vector<int>> generate(int numRows)
	{
		vector<vector<int>> ans(numRows);//初始化数组ans,numRows个元素,numRows个数组,numRows行
		for(int i=0;i<numRows;i++)//就和常规数组遍历一样
		{
			ans[i]=vector<int>(i+1,0);//第i个元素,第i+1个数组,第i+1行的数组,初始化为一个有i+1个值,索引为0-(i+1-1),值为0的数组,比如2
			ans[i][0]=1;//第2个元素,第3个数组的第0个元素
			ans[i][i]=1;//第2个元素,第3个数组的第2个元素,就是末尾
		}
		if(numRows<=2) return ans;//i<=1,一行和两行的时候
		for(int i=2;i<numRows;i++)//从i=2,也就是第三行开始,从i=2的索引元素开始
		{
			for(int j=1;j<ans[i].size()-1;j++)//具体到当前行的数组,从index=1开始,从第二个元素到倒数第二个元素<size()-1
			{
				ans[i][j]=ans[i-1][j-1]+ans[i-1][j];//动态规划
			}
			
		}
		return ans;
	}
};

如果能够知道一行杨辉三角,我们就可以根据每对相邻的值轻松地计算出它的下一行。
虽然这一算法非常简单,但用于构造杨辉三角的迭代方法可以归类为动态规划,因为我们需要基于前一行来构造每一行。

在这里插入图片描述

记得string数组也可以直接像这样用,像二维数组一样
vector < int > b(2,0); 即定义一个b,里b面有2个元素,分别初始化为0;

class Solution{
public:
	vector<vector<int>> generate(int numRows)
	{
		vector<vector<int>> ans;
		for(int i=1;i<=numRows;i++)
		{
			ans.push_back(vector<int>(i,1);
		}
		for(int i=2;i<numRows;i++)
		{
			for(int j=1;j<i;j++)
			{
				ans[i][j]=a[i-1][j-1]+a[i-1][j];
			}
		}
		return ans;
	}
};

不用想太多,3行就行(0,1,2) (0,1,2,3) 随便写几个就清楚了

第I行的数组有i+1个元素,索引刚好和大数组一样是0—i

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值