34. Find First and Last Position of Element in Sorted Array
题意:找到非递减序列中目标的开头和结尾
我的思路
用二分法把每一个数字都找到,最后返回首尾两个数
代码 Runtime12 ms Beats 33.23% Memory14 MB Beats 5.16%
class Solution {
public:
void bins(int l,int r,int tar,vector<int>& nums,vector<int>& ans){
while(l<=r){
int mid=l+(r-l)/2;
if(nums[mid]>tar) r=mid-1;
else if(nums[mid]<tar)l=mid+1;
else{
ans.push_back(mid);
bins(l,mid-1,tar,nums,ans);
bins(mid+1,r,tar,nums,ans);
return ;
}
}
}
vector<int> searchRange(vector<int>& nums, int target) {
int n=nums.size();
vector<int> ans;
bins(0,n-1,target,nums,ans);
if (ans.empty())return {-1,-1};
else sort(ans.begin(),ans.end());
return {ans[0],ans[ans.size()-1]};
}
};
标答
可以直接找到序列的头部数字,和数字加一的序列的头部数字
代码 Runtime 3 ms Beats 96.59% Memory13.7 MB Beats 11.32%
class Solution {
public:
pair<int,int> bins(vector<int>& nums,int tar,int l,int r){
int ans=-1;
while(l<=r){
int mid=(l+r)/2;
if(tar>nums[mid]) l=mid+1;
else if(tar<nums[mid]) r=mid-1;
else{//如果等于的话
ans=mid;r=mid-1;
}
}
if(ans==-1)return {l,0};
return {ans,1};
}
vector<int> searchRange(vector<int>& nums, int target) {
int n=(int)nums.size()-1;
pair<int,int> st; pair<int,int> en;
st =bins(nums,target,0,n);
en =bins(nums,target+1,0,n);
if(!st.second) return {-1,-1};
return {st.first,en.first-1};
}
};
35. Search Insert Position
题意:给定一个有序数组和一个元素,找到给序号,没找到返回他应该插在哪里
我的思路
二分查找,我记得二分查找应该是有upper_bound和lower_bound直接用的
注意:lower_bound函数是返回指针,用于在指定区域内查找不小于目标值的第一个元素,也就是说[1,2,3,5]数组中,target为4的情况下返回的是序号为3;[1,2,3,4]数组中,target为4的情况下返回的是序号为3
upper_bound为大于目标元素的第一个数/位置,[1,2,3,5]数组中,target为4的情况下返回的是序号为3;[1,2,3,4]数组中,target为4的情况下返回的是序号为4
代码 3ms Beats 88.42% 9.63mb Beats 47.66%
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
return lower_bound(nums.begin(), nums.end(), target) - nums.begin();
}
};
39. Combination Sum
题意:给定一个不同整数数组和一个目标,返回一个单独的集合,使得集合内部和等于target;同一个数字可以用多次;
我的思路
好像是动态规划做的?和背包问题差不多,但是我忘记了orz
标答 动态规划
首先创建一个dp的vector,每个dp的内容是答案的格式,也就是vector<vector<int>>,dp表示的是从第一个数开始到target的每个数为target时的答案
状态转移方程为 dp[j]=dp[j-i]+i (这里的+时集合加入的意思)
代码 Runtime 18 ms Beats 34.7% Memory14.3 MB Beats 40.40%
class Solution {
public:
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
vector <vector <vector<int>>> dp(target+1);//首先要开辟空间,不然会runtime error
dp[0]={{}}; //初始化
for(int &i:candidates){//对于每一个在候选名单里的
for(int j=i;j<=target;j++){//对于每个在数组里的i
for(auto v:dp[j-i]){//v是每一个答案中的集合
v.push_back(i);
dp[j].push_back(v);//要把更新的集合放回dp[j]中
}
}
}
return dp[target];
}
};
标答 递归
递归的方法好像速度更快,首先将数组排序,之后递归,递归的引入参数为num;next是nums的索引,也就是遍历了nums的每一个数字;psol是每一个集合,表示拿或者不拿的可能性;target是目标;result是最后的答案
注意:在一开始要排序,这么做是为了剪枝操作
因为 if(target<0)return; 这一操作同样可以退出递归,但是如果target-candidates[next]<0 的时候推出递归,那么可以剪枝,大大提升速度;如果要达成这target-candidates[next]<0退出的操作,那么就必须要nums排序,这一才能加快速度
注意:psol传入时要引用,不然会拖慢时间
代码 Runtime2 ms Beats 91.39% Memory10.7 MB Beats 84.74%
class Solution {
public:
void search(vector<int>& candidates, int next, int target, vector<int>& pol, vector<vector<int>> &result){
if(target==0){//说明结束了
result.push_back(pol);
return;
}
if(next==candidates.size()||target-candidates[next]<0) return;
pol.push_back(candidates[next]);//这是拿了,注意拿了还可以接着拿
search(candidates,next,target-candidates[next],pol,result);
pol.pop_back();
search(candidates,next+1,target,pol,result);//这是没拿,没拿就走了
}
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
sort(candidates.begin(),candidates.end());
vector<int> pol;vector<vector<int>> result;//初始化
search(candidates,0,target,pol,result);
return result;
}
};
41. First Missing Positive
题意:给出未排序的nums,返回未在这些数字中的正整数
我的思路
好像博弈论中有这个算法,还涉及位运算?但是忘记了【好吧,答案中没有位运算】
num的长度只有1e5,所以只能写一个O n的算法了
代码 Runtime 38 ms Beats 98.63% Memory41.3 MB Beats 34.99%
class Solution {
public:
int firstMissingPositive(vector<int>& nums) {
bool vis[100005]={0};int n=nums.size();
for(int i=0;i<n;i++){
if(nums[i]>100000||nums[i]<=0)continue;
vis[nums[i]]=1;
}
for(int i=1;i<=100001;i++){
if(vis[i]==0)return i;
}
return 0;
}
};
另一道题 涉及位运算
给定一个包含 0, 1, 2, ..., n 中 n 个数的序列,找出 0 .. n 中没有出现在序列中的那个数。
输入: [3,0,1]
输出: 2
输入: [9,6,4,2,3,5,7,0,1]
输出: 8
运用位运算来完成,思路为temp ^ nums[ i ] ^ (i + 1),这里的temp值从0开始,因为nums 的值是从0开始,而(i + 1)则是为了取到自然数1,2,3,4,5...等。运算规则如下:假如目标数组nums 值 为 0,1,2,4 自然数对应为1,2,3,4
0 ^ 1 ^ 0 = 1
1 ^ 2 ^ 1 = 2
2 ^ 3 ^ 2 = 3
3 ^ 4 ^ 4 = 3
返回值为3 缺失的数字即为3
class Solution {
public:
int firstMissingPositive(vector<int>& nums) {
int temp = 0;
for(int i = 0;i < nums.size();i++){
temp = temp ^ (i + 1) ^ (nums[i]);
}
return temp;
}
};
42. Trapping Rain Water
题意:给一组正整数数组,表示宽度为1的砖块的高度,问能存多少水
我的思路
费案1
单调栈,同时要记录下每个砖块的距离,所以栈里面存的是序号,一层一层的扫描?首先是y等于1之间的砖块,之后是y=2之间的砖块
假设是9 6 2 0 3 0 2 5 ,就是先9和6和3和0放入栈中;【如果一开始是0,就不放进去了】
height【i】=3来了把0弹出来,那么就直接弹出,之后栈顶是2,在计算0的那一个步骤,ans加上min(目前栈顶=2,height【i】=3)的数值,ans+=2;
目前栈顶是2,发现小于等于height【i】=3,那么就直接弹出,之后栈顶是6,在计算2的那一个步骤,ans+=id的差=2 * (height【i】=3 - 上一个弹出的数=2)的数值,ans+=2;当前栈为9 6 3
height【i】=0,不弹出;
height【i】=2,把0弹出来,直接弹出,之后栈顶是3,在计算0的那一个步骤,ans+=id的差=1 * (height【i】=2 - 上一个弹出的数=0),ans+=2,目前栈为9 6 3 2
height【i】=5,目前栈顶是2,发现小于等于height【i】=5,直接弹出,之后栈顶是3,在计算0的那一个步骤所以ans=ans+(i=7 - top的序号=6-1 )*(height【i】 - 上一个弹出的值=2),ans+=0,当前栈为9 6 3 ans不对
费案2
用减法,先减去砖块占用水的体积
例如1 0 2 0 3 0 2 5 ,从右向左,mx=5,扫完一遍了,假设-1的位置有无限高的墙,
所以ans=【7-(-1)-1】*5-所有砖块的体积,不对
标答 双指针法 竖着加的
和上面的减法的思路差不多,但他是一个个竖着加起来
lmax代表最左边的墙,rmax代表最右边的墙前两个if表示的是目前的lmax和rmax都不是最高的,应该要更新;第三个条件如果rmax大于lmax,ans加上lmax-h[lpos];第四个条件如果lmax大于rmax,ans加上rmax-h[rpos];为什么那么做?因为如果整个盆子左边高于右边,那么右侧的水肯定能装住,如果整个盆子右边高于左边,那么左侧的水肯定能装住。
代码 Runtime6 ms Beats 97.75% Memory19.8 MB Beats 53.37%
注意 lpos<=rpos的等号
class Solution {
public:
int trap(vector<int>& h) {
int n=h.size()-1;
int lmax=h[0],rmax=h[n];
int lpos=1,rpos=n-1,ans=0;
while(lpos<=rpos){ //等号
if(rmax<=h[rpos]){
rmax=h[rpos];rpos--;
}
else if(lmax<=h[lpos]){
lmax=h[lpos];lpos++;
}
else if(rmax>=lmax){
ans+=(lmax-h[lpos]);lpos++;
}
else{
ans+=(rmax-h[rpos]);rpos--;
}
}
return ans;
}
};
标答 前缀和 竖着加的
dp[i]表示从左到右,以左边的边作为最高的边,每个横坐标 i 能装多少水;之后从右到左,以右边的边作为最高的边,res在每个横坐标 i 上加上min ( 当前的最高 - dp[i] )
代码 Runtime 12 ms Beats 78.67% Memory19.8 MB Beats 53.37%
class Solution {
public:
int trap(vector<int>& h) {
int n=h.size(),dp[20004]={0};
int curmax=-1,ans=0;
for(int i=0;i<n;i++){
curmax=max(curmax,h[i]);
dp[i]=curmax-h[i];
}
curmax=-1;
for(int i=n-1;i>=0;i--){
curmax=max(curmax,h[i]);
ans+=min(dp[i],curmax-h[i]);
}
return ans;
}
};
标答 单调栈 横着加的
和费案1的思路相仿。栈中的数字越来越小,如果来了一个大的数,把这个数弹出,设cur是这个弹出的数,diff的序号的差,ans+=(min(当前栈顶的数,height[i] ) - 上一个弹出的数)*序号的差
为什么想费案1的时候没有想出来呢?
height【i】=5,目前栈顶是2,发现小于等于height【i】=5,直接弹出,之后栈顶是3,在计算0的那一个步骤所以ans=ans+(i=7 - top的序号=6-1 )*(height【i】 - 上一个弹出的值=2),ans+=0,当前栈为9 6 3
因为从最上面的图中可以看出,之前的步骤都是ans+=(min(当前栈顶的数,height[i] ) - 上一个弹出的数),但是当h[i]=5时,ans+=(当前栈顶的数 上一个弹出的数),没有办法把公式统一起来,所以没做出来,明明只要加上取最小就可以了
为什么是取最小呢?
原理和标答1的原理是差不多的,因为答案是和两侧最小的那一条有关
补充:
1. 费案1中的栈根本不需要pair结构,因为有序号有数组就知道了数值了
2. if(st.empty())break;这条语句是指左边没有边可以作为盘子的左边了,装不下水了,所以break了
代码 Runtime 12 ms Beats 78.67% Memory19.8 MB Beats 53.37%
class Solution {
public:
int trap(vector<int>& h) {
stack<int> st;int n=h.size(),ans=0;
for(int i=0;i<n;i++){
while(!st.empty()&&h[st.top()]<h[i]){//弹出操作
int temp=st.top();
st.pop();
if(st.empty())break;//注意这句话
int cha=i-st.top()-1;//注意这里是-1!!!!!!!
ans+=(min(h[i],h[st.top()])-h[temp])*cha;
}
st.push(i);
}
return ans;
}
};
45. Jump Game II
题意:有n个序号从0开始的数组,你可以从num[i],向右跳到【num[i],num[i+j]】之间
我的思路
动态规划,dp表示需要最少的次数,一开始初始化为无穷,dp[n-1]=0,最外层循环是i从n-1到0,第二层循环是j从1到num[i],找到循环中最小的dp[i+j],dp[i]=min( dp[ i + j ] + 1 ),返回答案dp[0]
但不幸的是n是1e4,nums是1e3,时间复杂度1e7
代码 Runtime128 ms Beats 39.88% Memory16.8 MB Beats 41.3%
class Solution {
public:
int jump(vector<int>& nums) {
int n=nums.size();
int dp[10004];
for(int i=0;i<n;i++)dp[i]=9999;
dp[n-1]=0;
for(int i=n-2;i>=0;i--){
int minn=9999;
for(int j=1;j<=nums[i] && i+j<n;j++) minn=min(minn,dp[i+j]);
dp[i]=minn+1;
}
return dp[0];
}
};
要优化的话肯定是优化第二个循环,快速找到某个范围内的最小值,想到树状数组,去看了看树状数组的板子,找到的树状数组代码维护的是【1,i】范围的最小值,网上说只有树状数组套树状数组或者线段树才可以达到动态维护区间最小值
但是线段树要从1开始建立,同时修改的时候不是单纯的增减,而是直接替换,所以线段树也不适合
标答 贪心
为什么可以用贪心算法呢?
乍一看想到动态规划去了,才发现忽略了最大步长的信息(没有的话就是DP了),因此不需要考虑跳多了的情况,而且题面能够保证绝对能到达,所以不用担心0步长的情形(跳不出去啦!)。
因此本题可以直接使用贪心算法:向后遍历,找出能够跳的最远的选择。
贪心最麻烦的地方就是全局最优解的证明,我这里给出一个简单的思路:从同一个起点出发,如果当前选择不是贪心选择的,那么下一个选择必然比贪心方案跳的更近。完成一次跳跃后有两种情形:贪心方案的第二次选择的位置是否存在于当前方案的第二次选择中。存在,则最多选择这个方案,此时当前方案会比贪心方案段(第二次选择一样,但是第一次选择短了);不存在,那么肯定是会短的(两次选择都短)。
参考链接:https://blog.csdn.net/cary_leo/article/details/115451900
简单来说就是比谁跳的远
代码 Runtime 8 ms Beats 95.22% Memory16.6 MB Beats 64.1%
class Solution {
public:
int jump(vector<int>& nums) {
int n=nums.size(),ans=0;
for(int i=1;i<n;i++){//要么选择在这一格跳走,要么就留在这里
nums[i]=max(nums[i-1],nums[i]+i);//nums代表在i位置可以到达的最远的位置
}
int st=0;
while(st<n-1){
ans++; st=nums[st];
}
return ans;
}
};
46. Permutations
题意:有一个数组,里面的数字各不相同,返回所有排列
我的思路
用dfs做,没写出来;我好像还记得C++库中有个直接全排列的调用
标答 dfs 回溯法
照着我写坏的和表达对照一下,我用了两次dfs,一次是没取走数字,一次是取走了数字;不需要没取走数字的那一组,因为他会因为个数不够而不能加入最终答案的vector,所以只要取走的那一次dfs就可以了
代码 Runtime 6 ms Beats 18.86% Memory8.2 MB Beats 11.7%
class Solution {
public:
void dfs(vector<int>& nums,vector<int>& pol,vector<vector<int>>& ans,vector<int> vis){
if(pol.size()==nums.size()){ans.push_back(pol);return;}
for(int i=0;i<nums.size();i++){
if(vis[i]) continue;
vis[i]=1;
pol.push_back(nums[i]);
dfs(nums,pol,ans,vis);
pol.pop_back();
vis[i]=0;
}
}
vector<vector<int>> permute(vector<int>& nums) {
vector<int> pol; vector<vector<int>> ans;
vector<int> vis((int)nums.size(),0);
dfs(nums,pol,ans,vis);
return ans;
}
};
标答 dfs 交换
确实,不断交换就可以排序完成了,还不会有多余的没到个数的pol(和上一个做法不同)
代码 Runtime 5 ms Beats 29.21% Memory 7.5 MB Beats 81.96%
class Solution {
public:
vector<vector<int>> result;
void permutation(vector<int> &nums,int i,int n){
if(i==n){
result.push_back(nums);
return ;
}
for(int j=i;j<=n;j++){
swap( nums[i],nums[j]);
permutation(nums,i+1,n);//注意这里是i+1
swap( nums[i],nums[j]);
}
}
vector<vector<int>> permute(vector<int>& nums) {
permutation(nums,0,nums.size()-1);
return result;
}
};
48. Rotate Image
题意:将整个矩阵顺时针旋转90度,注意不要新开一个矩阵
我的思路
按照这个顺序,一个圈层只要3组交换就可以了,实际操作的时候注意 i 和 j 的范围!!!!!
例子1:初始
1 2 3
4 5 6
7 8 9
第一次
7 2 3
8 5 6
9 4 1
第二次
7 2 1
8 5 4
9 6 3
第三次
7 4 1
8 5 2
9 6 3
例子2:初始
4 8
3 6
第一次
3 8
6 4
第二次
3 4
6 8
第三次
循环直接结束了
代码 Runtime 0 ms Beats 100% Memory 7.2 MB Beats 14.63%
class Solution {
public:
void rotate(vector<vector<int>>& matrix) {
int n=matrix.size();
for(int i=0;i<n/2;i++){//圈层
for(int j=i;j<n-i;j++){
swap(matrix[j][i],matrix[n-1-i][j]);
}
for(int j=i+1;j<n-i;j++){
swap(matrix[n-1-i][j],matrix[n-1-j][n-1-i]);
}
for(int j=i+1;j<n-1-i;j++){
swap(matrix[i][j],matrix[j][n-1-i]);
}
}
}
};
标答 思维
先对角反转,之后逆向
例子1:初始
1 2 3
4 5 6
7 8 9
第一次
1 4 7
2 5 8
3 6 9
第二次
7 4 1
8 5 2
9 6 3
代码 Runtime 0 ms Beats 100% Memory7.3 MB Beats14.63%
class Solution {
public:
void rotate(vector<vector<int>>& matrix) {
int n = matrix.size();
int m = matrix[0].size();
vector<vector<int>> temp(n, vector<int>(m, 0));
for(int i=0; i<n; i++){
for(int j=0; j<i; j++){
swap(matrix[i][j], matrix[j][i]);
}
}
for(int i=0; i<n; i++){
reverse(matrix[i].begin(), matrix[i].end());
}
}
};
49. Group Anagrams
题意:给一组字符串,把字母相同的字符串放在一组里
我的思路
只能想到死做,就是map映射,key是字典序排列,value是vector<string>,之后遍历map,组成答案
代码 Runtime 30 ms Beats83.8% Memory20.8 MB Beats 30.73%
class Solution {
public:
vector<vector<string>> groupAnagrams(vector<string>& strs) {
map<string,vector<string> > mp;
int n=strs.size();
for(int i=0;i<n;i++){
string tmp=strs[i];
sort(tmp.begin(),tmp.end());
mp[tmp].push_back(strs[i]);
}
vector< vector<string> > ans;
for(auto q:mp)
ans.push_back(q.second);
return ans;
}
};
优化
看了标答,确实是这样做的,但是sort和map和遍历可以优化,map改成unorder_map,for(auto q:mp)加上引用【这一步突然时间少一半】
代码 Runtime 15 ms Beats 99.87% Memory19.5 MB Beats 84.31%
class Solution {
public:
vector<vector<string>> groupAnagrams(vector<string>& strs) {
unordered_map<string,vector<string> > mp;
int n=strs.size();
for(int i=0;i<n;i++){
string tmp=strs[i];
sort(tmp.begin(),tmp.end());
mp[tmp].push_back(strs[i]);
}
vector< vector<string> > ans;
for(auto &q:mp)
ans.push_back(q.second);
return ans;
}
};
51. N-Queens
题意:皇后攻击的范围是所属的横线、竖线、斜线,在n*n的棋盘上摆放皇后,使得n个皇后不会互相攻击
我的思路
递归
代码 Runtime4 ms Beats 78.35% Memory7.8 MB Beats 36.39%
class Solution {
public:
bool check(int n,int id, int j, vector<int>&pol){
for(int i=0;i<pol.size();i++)
if(pol[i]==j) return 0;
for(int i=0;i<pol.size();i++){
if(pol[i]-i ==j-id)return 0;
}
for(int i=0;i<pol.size();i++){
if(pol[i]+i ==j+id)return 0;
}
return 1;
}
void se(int n,int id, vector<int>&pol, vector<vector<string>> & result) {
if(pol.size()==n){
vector<string> mp;
for(int j=0;j<n;j++){//列
string s (n, '.');
s[pol[j]]='Q';
mp.push_back(s);
}
result.push_back(mp);
return;
}
for(int j=0;j<n;j++){//列
if(!check(n,id,j,pol))//判断这一列上有无其他数字,判断斜线上有无其他数字;如果不可以放在这里,就跳过
continue;
pol.push_back(j);
se(n,id+1,pol,result);
pol.pop_back();
}
}
vector<vector<string>> solveNQueens(int n) {
vector<int> pol;
vector<vector<string>> result;//假设返回的vector<int>表示每一行的皇后放在第n列
se(n,0,pol,result);//id代表在第几行
return result;
}
};
优化 位运算 没看懂
10394 用位运算速解 n 皇后问题 - 知乎 (zhihu.com)
class Solution {
public:
vector <vector <string>> ans;
void solve(int n, int row, int colMask, int leftDiagMask, int rightDiagMask, vector <string> &board) {
if (row == n) {
ans.push_back(board);
return;
}
int rowState = (colMask | leftDiagMask | rightDiagMask) & ((1 << n) - 1);
int safePos = rowState ^ ((1 << n) - 1);
while (safePos) {
int queenPos = safePos & (-safePos);
safePos -= queenPos;
if (!(queenPos & rowState)) {
board[row][log2(queenPos)] = 'Q';
solve(n, row + 1, colMask | queenPos, (leftDiagMask | queenPos) << 1, (rightDiagMask | queenPos) >> 1, board);
board[row][log2(queenPos)] = '.';
}
}
}
vector<vector<string>> solveNQueens(int n) {
vector <string> board(n, string (n, '.'));
solve(n, 0, 0, 0, 0, board);
return ans;
}
};