资料来源于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