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的数据吗

单调栈经典例题(๑•̀ㅂ•́)و√

倒着遍历,维护一个递减的单调栈。

两个方法:

  1. 第一个方法
  • 记录所有从栈里弹出的所有数的最大值\(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;
    }
};
  1. 第二个写法
  • 首先先正着遍历,记录\(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]

​ 题意很简单,就考验你指针的使用。

​ 两种方法

  1. 桶排序暴力法

    思路很简单,加个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;
    }
};
  1. 双(三)指针法

    嗯,也没什么难理解的。

    新建一个头指针指向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\)

  1. \(k<len\)时,相当于后\(k\)位移到前面
  2. \(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()\)返回是否存在

两种方法,非递归和递归

  1. 递归写法

没啥难度,就普通的遍历,将数值存入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();
    }
};
  1. 非递归写法

方法差不多,把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

  1. -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;
    }
};
  1. 官方解法,分治\(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. 选,那么答案添加一个1,2

  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. 笨阶乘

两个法,数学法和暴力法

阶乘就要有阶乘的样子

观察一下样例

\[clumsy(10) = 10 * 9 / 8 + 7 - 6 * 5 / 4 + 3 - 2 * 1 \]

我们可以对其简单的进行分组。最开始的\(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)\)进行整理

我们可以得到这么一个式子

\[\frac{N\times(N-1)}{N-2} \\ =\frac{N^2-2N+N}{N-2}\\ =N+\frac{N-2+2}{N-2}\\ =N+1+\lfloor\frac{2}{N-2}\rfloor \]

我们将关注点放在\(\lfloor\frac{2}{N-2}\rfloor\)

\[\frac{2}{N-2}\ge1\\ 2\ge{N-2}\\ 4\ge{N} \]

于是我们将重点放在\(N>4\)

对于笨阶乘,我们的公式如下

\[N\times(N-1)/(N-2)+(N-3)-(N-4)\times(N-5)/(N-6)…… \]

可以知道除了第一串,后面相当于两两相减

将上面的结论带到这里,就得到下面的式子

\[N+1+(N-3)-(N-3)…… \]

于是,我们只需要关注\((N-3)\bmod4\)所得到的值即可

\[\begin{cases} N+1+……+4-3\times2/1=N-1 &, (N-3)\bmod4=0 \\ N+1+……+5-4\times3/2+1=N+1 &, (N-3)\bmod4=1 \\ N+1+……+2-1=N+2 &, (N-3)\bmod4=2 \\ N+1+……+3-2\times1=N+2 &, (N-3)\bmod4=3 \\ \end{cases} \]

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

我们从左往右扫一遍,过程中维护一个单调递减的单调栈,其中保存下标。

我们考虑下面这张图

leetcode每日一题

序列是\([3,2,1,4]\),首先,单调栈stack里存储的是[3,2,1]。

当我们扫到4时,发现4>1

那么我们的长就是4的下标-栈中1左边下标

高就是min(4,2)-1

我们便得到了红色矩形的面积

leetcode每日一题

我们将1从栈中弹出,我们发现4>2

于是重复上面的操作

长度=4的下标4-左边的下标1

高度=min(4,3)-2

于是我们得到了蓝色矩阵的面积

leetcode每日一题

将两个矩阵的面积相加,就是我们的结果。

然后对于高度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的一个字符。

    leetcode每日一题

    手动模拟一下这张图就明白了

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

相关文章: