73. 矩阵置零
O(1)额外空间写法
简单理解一下:
首先我们对于矩阵内所有\(\sum_{i=0}^{m}{\sum_{j=1}^{n}}0\),记录到第0行和第0列上
那么,我们只需要对于\(\sum_{i=1}^{m}{\sum_{j=1}^{n}}\),只需要根据第0行和第0列来置0即可
无论第0行和第0列原先是否是0,其对应的列/行,必定变为0
那么,我们考虑第0行对应的行
我们可以发现,它有\(m[0][0]\)来控制,只要是0,那么整行都是0。
我们再考虑第0列对应的列
我们可以发现,遍历每一行会对每一行的第0列进行更改,我们需要在遍历前,记录原先第\(i\)行第0列的是否为0,只要有一个\([i][0]\)是0,那么显然第0列全为0
class Solution {
public:
void setZeroes(vector<vector<int>> &matrix) {
int n, m;
m = matrix.size();
n = matrix[0].size();
bool rs=0;
for (int i = 0; i < m; i++) {
//每一行头部原本是否有0
//包含原本的[0,0]
if(matrix[i][0]==0)
rs=1;
//判断每列里是否有0
//第0行只对[0,0]有影响
for (int j = 1; j < n; j++)
if (matrix[i][j] == 0)
matrix[i][0] = matrix[0][j] = 0;
}
for(int i=1;i<m;i++)
for(int j=1;j<n;j++)
if(matrix[i][0]==0||matrix[0][j]==0)
matrix[i][j]=0;
if(matrix[0][0]==0)
for(int i=0;i<n;i++)
matrix[0][i]=0;
if(rs)
for(int i=0;i<m;i++)
matrix[i][0]=0;
}
};
2021年3月22日
191. 位1的个数
0ms代码,o(*≧▽≦)ツ笑
-n&n,其作用是返回n二进制最低位的1所对应的2的幂
学会这个可以去学一下树状数组了
原理是:
原码在最低位1之前都是0,取反码则是最低位0之前都是1
补码=反码+1,反码因为进位,最低位的1位置必然和原码相同。
class Solution {
public:
int hammingWeight(uint32_t n) {
int ans=0;
while(n){
n-=-n&n;
ans++;
}
return ans;
}
};
2021年3月23日
341. 扁平化嵌套列表迭代器
没有代码提示的我快死了
就是个遍历……只要知道函数和方法就没什么难度
注意尝试迭代,不要用递归
class NestedIterator {
private:
int curInteger;
stack<pair<vector<NestedInteger>::iterator, vector<NestedInteger>::iterator>> st;
public:
NestedIterator(vector<NestedInteger> &nestedList) {
st.emplace(nestedList.begin(),nestedList.end());
}
int next() {
return curInteger;
}
bool hasNext() {
while(!st.empty()){
auto &i=st.top();
if(i.first==i.second){
st.pop();
continue;
}
if(i.first->isInteger()){
curInteger=i.first->getInteger();
i.first++;
return true;
}
auto &lit=i.first->getList();
st.emplace(lit.begin(),lit.end());
i.first++;
}
return false;
}
};
2021年3月24日
456. 132模式
1e4的数据,我\(O(n^2)\)都能给你过了,就不能1e5的数据吗
单调栈经典例题(๑•̀ㅂ•́)و√
倒着遍历,维护一个递减的单调栈。
两个方法:
- 第一个方法
- 记录所有从栈里弹出的所有数的最大值\(maxx\),这个是2
- 栈顶就是3
- 将要进的值\(nums[i]\),如果\(<maxx\),那么这个值就是1
- 否则就进栈。
- 这样子可以一直保证,\(maxx\)是2,栈顶是3
class Solution {
private:
stack<int>q;
public:
bool find132pattern(vector<int>& nums) {
int maxx=INT_MIN;
int n=nums.size();
for(int i=n-1;i>=0;i--){
if(maxx>nums[i])
return true;
while(!q.empty()&&q.top()<nums[i])
maxx=max(maxx,q.top()),q.pop();
q.push(nums[i]);
}
return false;
}
};
- 第二个写法
- 首先先正着遍历,记录\(nums[i]\)前的最小值,记录到\(v[i]\)里。
- 依然是维护一个递减的单调栈
- 倒着遍历,这里注意的是,我们比较的是栈顶和\(v[i]\)的大小
- 如果栈顶小于等于\(v[i]\),显然栈顶不符合2的条件,故将其弹出
- 当栈顶大于\(v[i]\)时,栈顶符合2的条件
- 再比较即将进入的\(nums[i]\)和栈顶的大小,如果栈顶小,我们就找到了答案
class Solution {
private:
stack<int>q;
public:
bool find132pattern(vector<int>& nums) {
int maxx=INT_MIN;
int n=nums.size();
vector<int>v(n);
v[0]=nums[0];
for(int i=1;i<n;i++)
v[i]=min(v[i-1],nums[i-1]);
for(int i=n-1;i>=0;i--){
while(!q.empty()&&q.top()<=v[i])
q.pop();
if(!q.empty()&&q.top()<nums[i])
return true;
q.push(nums[i]);
}
return false;
}
};
2021年3月25日
82. 删除排序链表中的重复元素 II
数据量300,数据大小[-200,200]
题意很简单,就考验你指针的使用。
两种方法
-
桶排序暴力法
思路很简单,加个100的偏移量,然后全都存下来,再倒着存进链表里返回即可。
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
vector<int>v(201);
while(head){
v[head->val+100]++;
ListNode* tmp=head;
head=head->next;
delete(tmp);
}
for(int i=200;i>=0;i--)
if(v[i]==1){
ListNode* tmp=new ListNode(i-100,head);
head=tmp;
}
return head;
}
};
-
双(三)指针法
嗯,也没什么难理解的。
新建一个头指针指向head,然后从头到尾遍历。
对于[3,3,3]
我们在while里处理成[3]
再令
pre->next=p(3)->next,再给它叉掉就完事了
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
ListNode *pre=new ListNode(0,head);
ListNode *dump=pre;
ListNode *p=head;
while(p&&p->next){
if(p->val==p->next->val){
while(p->next&&p->val==p->next->val){
ListNode *tmp=p->next;
p->next=tmp->next;
delete(tmp);
}
pre->next=p->next;
delete(p);
}
else
pre=p;
p=pre->next;
}
head=dump->next;
delete(dump);
return head;
}
};
2021年3月26日
83. 删除排序链表中的重复元素
上面题目的简化版,while套while就行为了时间,指针都不删除吗?
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
ListNode* p=head;
while(p&&p->next){
while(p->next&&p->val==p->next->val){
ListNode* tmp=p->next;
p->next=p->next->next;
delete(tmp);
}
p=p->next;
}
return head;
}
};
2021年3月27日
61. 旋转链表
将链表每个节点向右移动 \(k\)个位置
首先,假设链表长度为\(len\)
- 当\(k<len\)时,相当于后\(k\)位移到前面
- 当\(k>len\)时,令\(k\%=len\),然后再移动即可
class Solution {
public:
ListNode* rotateRight(ListNode* head, int k) {
if(head==nullptr)
return head;
int len=0;
ListNode* tmp=nullptr;
for(ListNode* p=head;p;p=p->next){
len++;
if(p->next==nullptr)
tmp=p;
}
k%=len;
if(k==0)
return head;
int i=0;
for(ListNode* p=head;p;p=p->next,i++){
if(i==len-k-1){
tmp->next=head;
head=p->next;
p->next=nullptr;
break;
}
}
return head;
}
};
2021年3月28日
173. 二叉搜索树迭代器
让你实现二叉搜索树的迭代器,实现中序遍历
\(next()\)返回元素,并使迭代器下移一个
\(hasnext()\)返回是否存在
两种方法,非递归和递归
- 递归写法
没啥难度,就普通的遍历,将数值存入queue就是了
class BSTIterator {
private:
queue<int> q;
public:
BSTIterator(TreeNode *root) {
dfs(root);
}
void dfs(TreeNode *p) {
if (p) {
dfs(p->left);
q.push(p->val);
dfs(p->right);
}
}
int next() {
int ans = q.front();
q.pop();
return ans;
}
bool hasNext() {
return !q.empty();
}
};
- 非递归写法
方法差不多,把dfs拆了就是了。
class BSTIterator {
private:
queue<int> q;
public:
BSTIterator(TreeNode *root) {
stack<TreeNode *> st;
TreeNode *p = root;
while (p || !st.empty()) {
//优先左走
if (p) {
st.push(p);
p = p->left;
} else {
//走不动了,看一下右边
p = st.top();
st.pop();
q.push(p->val);
p = p->right;
}
}
}
int next() {
int ans = q.front();
q.pop();
return ans;
}
bool hasNext() {
return !q.empty();
}
};
2021年3月29日
190. 颠倒二进制位
两种方法,分治和-n&n
-
-n&n
关于这个方法,具体原理可看上方。
我们拿一个最大值,\(2^{31}\)
颠倒数位,观察一下,对于第\(k\)位,就相当于变成\(2^{31-k}\)
class Solution {
public:
uint32_t reverseBits(uint32_t n) {
uint32_t ans = 0;
uint32_t maxx = 1 << 31;
while (n) {
int x = -n & n;
ans += maxx / x;
n -= x;
}
return ans;
}
};
- 官方解法,分治\(O(1)\)
颠倒嘛
对32位进制进行分组,显然,颠倒就相当于左右互换
那么32->16->8->4->2的顺序分上去,再并回来,就是答案了。
class Solution {
private:
const uint32_t M1 = 0x55555555; // 01010101010101010101010101010101
const uint32_t M2 = 0x33333333; // 00110011001100110011001100110011
const uint32_t M4 = 0x0f0f0f0f; // 00001111000011110000111100001111
const uint32_t M8 = 0x00ff00ff; // 00000000111111110000000011111111
public:
uint32_t reverseBits(uint32_t n) {
//左移和右移针对奇偶组
n = n >> 1 & M1 | (n & M1) << 1;
n = n >> 2 & M2 | (n & M2) << 2;
n = n >> 4 & M4 | (n & M4) << 4;
n = n >> 8 & M8 | (n & M8) << 8;
return n >> 16 | n << 16;
}
};
2021年3月30日
74. 搜索二维矩阵
数据量小的可怜
我实现的是官方题解的方法二
嘛,和一没什么区别实话实说。
二分没什么可说的,理解一下就好。
class Solution {
public:
bool searchMatrix(vector<vector<int>>& matrix, int target) {
int n=matrix.size();
int m=matrix[0].size();
int l=0,r=n*m-1;
while(l<r){
int mid=(r+l)>>1;
if(matrix[mid/m][mid%m]<target)
l=mid+1;
else
r=mid;
}
return matrix[l/m][l%m]==target;
}
};
2021年3月31日
90. 子集 II
很有意思的一个题目。嗯,在一定程度上。
首先,你需要学会怎么遍历所有子集。
其实就是对数组里每一位进行选或不选。
之后,我们再考虑重复问题
首先我们先进行排序,然后进行简单的遍历,就可以得到下面的结果
1 2 2
1 2
1 2
2
2
我们观察上面遍历出来的几个子集
可以发现,怎样选才会重复。
对于在集合里重复出现的数,如果之前相同的数没有选。
那么显然,此次递归可以直接返回
即,当我们递归到:
1,2时
对于2我们在递归里是有两步操作的
-
选,那么答案添加一个1,2
-
不选,迭代到下一个2。我们可以发现,它先前重复出现的数字,我们没有选择。而如果选择此次的2,那么便和上面①重复。
由此我们便可以得到去重的方式了。
class Solution {
private:
bool vis[11];
vector<vector<int>>ans;
public:
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
sort(nums.begin(),nums.end());
dfs(nums,0,nums.size());
return ans;
}
void dfs(vector<int> v,int dep,int n){
if(dep==n){
vector<int>tmp;
for(int i=0;i<n;i++)
if(vis[i])
tmp.push_back(v[i]);
ans.push_back(tmp);
return ;
}
vis[dep]=0;
dfs(v,dep+1,n);
if(dep>0&&!vis[dep-1]&&v[dep-1]==v[dep])
return ;
vis[dep]=1;
dfs(v,dep+1,n);
}
};
2021年4月1日
1006. 笨阶乘
两个法,数学法和暴力法
阶乘就要有阶乘的样子
观察一下样例
我们可以对其简单的进行分组。最开始的\(10*9/8\)是一组,然后\(7\),\(-6*5/4\),然后又是\(3\),然后又是\(2*1\)
那么规律就很简单的出来了,定义一个\(x\),记录层数,偶数层就是\(-N*(N-1)/(N-2)\),奇数层就是\(N\)
然后在第\(0\)层和偶数层&&N=2时进行特判
能跑184ms的阶乘就写出来了§( ̄▽ ̄)§
class Solution {
public:
int fun(int x,int N){
if(N==1||N==0){
if(x%2||x==0)
return N;
return -N;
}
int ans=0;
if(x%2)
ans=N+fun(x+1,N-1);
else{
if(x==0)
ans= N*(N-1);
else
ans= -N*(N-1);
if(N!=2)
ans=ans/(N-2)+fun(x+1,N-3);
}
return ans;
}
int clumsy(int N) {
return fun(0,N);
}
};
数学永远滴神
我们对\(N*(N-1)/(N-2)\)进行整理
我们可以得到这么一个式子
我们将关注点放在\(\lfloor\frac{2}{N-2}\rfloor\)上
于是我们将重点放在\(N>4\)
对于笨阶乘,我们的公式如下
可以知道除了第一串,后面相当于两两相减
将上面的结论带到这里,就得到下面的式子
于是,我们只需要关注\((N-3)\bmod4\)所得到的值即可
class Solution {
public:
int clumsy(int N) {
if(N==4)
return 7;
if(N==3)
return 6;
if(N==2||N==1)
return N;
switch((N-3)%4){
case 0:
return N-1;
case 1:
return N+1;
default:
return N+2;
}
}
};
2021年4月2日
面试题 17.21. 直方图的水量
搭眼一看就是个单调栈的板子题。
然后写了半天没写出来XD
我们从左往右扫一遍,过程中维护一个单调递减的单调栈,其中保存下标。
我们考虑下面这张图
序列是\([3,2,1,4]\),首先,单调栈stack里存储的是[3,2,1]。
当我们扫到4时,发现4>1
那么我们的长就是4的下标-栈中1左边下标
高就是min(4,2)-1
我们便得到了红色矩形的面积
我们将1从栈中弹出,我们发现4>2
于是重复上面的操作
长度=4的下标4-左边的下标1
高度=min(4,3)-2
于是我们得到了蓝色矩阵的面积
将两个矩阵的面积相加,就是我们的结果。
然后对于高度3,我们发现它左边莫的东西,就直接将其弹出即可。
class Solution {
public:
int trap(vector<int>& height) {
stack<int>st;
int ans=0,n=height.size();
for(int i=0;i<n;i++){
while(!st.empty()&&height[st.top()]<height[i]){
int mid=st.top();
st.pop();
if(st.empty())
break;
int l=st.top();
int w=i-l-1;
int h=min(height[i],height[l])-height[mid];
ans+=h*w;
}
st.push(i);
}
return ans;
}
};
2021年4月3日
1143. 最长公共子序列
一个题目难的往往不是看题解,而是想解法
虽然有些题目,解法都看不懂
动态规划入门题,很好理解
对于两个字符串
-
当s[i]=t[j]的时候,我们的将这个字母加上,从上个步骤转移,dp[i][j]=dp[i-1][j-1]+1
-
当s[i]!=t[j]的时候,我们上一步只有两个,一个是dp[i][j-1],一个是dp[i-1][j]。
- 什么意思?
- 在这种情况下,影响我们答案的,只有两种方式,一种是在后面加上s的一个字符,一种是在后面加上t的一个字符。
手动模拟一下这张图就明白了
class Solution {
int dp[1000+10][1000+10];
public:
int longestCommonSubsequence(string text1, string text2) {
int len1=text1.length(),len2=text2.length();
for(int i=1;i<=len1;i++){
for(int j=1;j<=len2;j++){
if(text1[i-1]==text2[j-1])
dp[i][j]=dp[i-1][j-1]+1;
else
dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
}
}
return dp[len1][len2];
}
};
1044. 最长重复子串
做最长公共子序列的时候突然想到的题目,就一块贴上来了。
后缀数组跑一遍,然后取最大height即可。
const int MAXN=1e5+10;
class Solution {
private:
string s;
int rk[MAXN], sa[MAXN], tax[MAXN], hg[MAXN], n, m;
int tp[MAXN];
//tp第二关键字数组
//rk第一关键字第i位的排名,sa排名第i位的位置
void rsort(int a[], int b[]) { //a第一关键字数组,b第二关键字数组
for (int i = 0; i <= m; i++) //桶清零
tax[i] = 0;
for (int i = 1; i <= n; i++) //在第一关键字相同的情况下,按照第二关键字从小到大放入
tax[a[b[i]]]++;
for (int i = 1; i <= m; i++) //计算排名
tax[i] += tax[i - 1];
for (int i = n; i >= 1; i--) //在第一关键字相同的情况下,按照第二关键字从大到小取出
sa[tax[a[b[i]]]--] = b[i];
}
void suffix() {
for (int i = 1; i <= n; i++) //字母大小为第一关键字,位置为第二关键字
rk[i] = s[i], tp[i] = i;
rsort(rk, tp);
for (int l = 1, num = 0; num != n; l <<= 1) {
num = 0;
for (int i = n - l + 1; i <= n; i++) //在n-l+1后面的位置都无法找到l位后的后续,所以为0
tp[++num] = i; //所以先将位置存入
for (int i = 1; i <= n; i++) //sa[i]=排名i的后缀位置,如果>l,那么它就可以和sa[i]-l位置的第一关键字匹配
if (sa[i] > l)
tp[++num] = sa[i] - l;
//此刻tp数组内情况:[第二关键字为0的一组的起始位置......第二关键字按照从小到大的一组的起始位置]
rsort(rk, tp);
//此处分割,上述为k阶段的sa以及排序,下述为k<<1阶段的rk
swap(rk, tp); //用tp存储上一轮的rk,来为下一轮的rk做准备
rk[sa[num = 1]] = 1;
for (int i = 2; i <= n; i++) //排名相近的第一关键字和第二关键字是否相等
rk[sa[i]] = (tp[sa[i]] == tp[sa[i - 1]] && tp[sa[i] + l] == tp[sa[i - 1] + l]) ? num : ++num;
m = num;
}
return;
}
void get_height() {
for (int i = 1; i <= n; i++) //这句话其实可加可不加
rk[sa[i]] = i;
for (int i = 1, k = 0; i <= n; i++) {
if (k)
k--;
int j = sa[rk[i] - 1];
while (s[i + k] == s[j + k])
k++;
hg[rk[i]] = k;
}
}
public:
string longestDupSubstring(string S) {
s=" "+S;
n = s.length();
m = 122;
suffix();
get_height();
int idx=1,ans=0;
for (int i = 1; i <= n;i++)
if(hg[i]>ans){
ans=hg[i];
idx=sa[i];
}
return S.substr(idx-1,ans);
}
};
2021年4月4日
781. 森林中的兔子
兔兔能有什么坏心思呢?
兔兔要是有坏心思就吃掉好了。
题目要求输出最少个数,所以我们可以考虑,对于个数相同的并为一组,以减少增加。
例如,[1,1,2],那么我们将里面的[1,1]并为相同的颜色,那么这组最大就只有5个了。
那么,对于相同个数的,我们最少可以分成多少组呢?
设x是数字相同的个数,y是数字
那么,显然,相同数字并成一组就是y+1个
那么,分组数就是\(\lceil\frac{x}{y+1}\rceil\)
答案也就很显然了。
class Solution {
public:
int numRabbits(vector<int>& answers) {
unordered_map<int,int>cnt;
for(auto it:answers)
cnt[it]++;
int ans=0;
for(auto [y,x]:cnt)
// ans+=(x+y)/(y+1)*(y+1);
ans+=(y+1)*(x/(y+1)+(x%(y+1)!=0));
return ans;
}
};