LeetCode 每日一题

每日一题

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

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

我们考虑下面这张图

1

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

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

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

高就是min(4,2)-1

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

2

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

于是重复上面的操作

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

高度=min(4,3)-2

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

3

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

然后对于高度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;
    }
};

2023年11月27日

907. 子数组的最小值之和

使用dp解决这个问题

  1. 首先考虑最简单的方式

    根据题目的要求,我们可以知道:

    \[假设:a[i][j]=min(a[i]...a[j]) \]

    我们让\(dp\)数组维护前\(i\)项最小值的和

    \[\begin{align} dp[i]&=\sum_{k=0}^i\sum_{j=0}^ka[j][k]\\ & =\sum_{j=0}^i{a[j][i]}+dp[i-1] \end{align} \]

    当然,\(O(n^2)\)这样子做肯定超时。

  2. 考虑找一下规律

    我们假设在\((i+1,j)\)区间,只有\(arr[i]<arr[j]\),这意味着\(arr[j]\)是这个区间内的最小值

    那么对于\(\sum_{k=i+1}^j{a[k][j]}\)

    我们可以得出一个很简单的结论:\(\sum_{k=i+1}^j{a[k][j]}=(j-i)*arr[j]\)

    那么我们重新思考上面的dp公式

    \[\begin{align} dp[i] & = \sum_{j=0}^i{a[j][i]}+dp[i-1]\\ &=\sum_{j=k+1}^i{a[j][i]}+dp[k]\\ &=(i-k)*arr[i]+dp[k] \end{align} \]

    那么如何寻找\(arr[i]\)左侧第一个小于等于它的呢?维护一个递增的单调栈即可。

代码

class Solution {

  public int sumSubarrayMins(int[] arr) {
    int len = arr.length;
    int mod = (int) 1e9 + 7;
    int dp[] = new int[len];
    int stack[] = new int[len];
    int ans = 0, cur = -1;
    for (int i = 0; i < len; i++) {
      while (cur != -1 && arr[i] < arr[stack[cur]]) {
        cur--;
      }
      if (cur == -1) {
        dp[i] = (i + 1) * arr[i] % mod;
      } else {
        dp[i] = dp[stack[cur]] + (i - stack[cur]) * arr[i] % mod;
      }
      cur++;
      stack[cur] = i;
      ans = (ans + dp[i]) % mod;
    }
    return ans;
  }
}

2023年11月28日

1670. 设计前中后队列

思路与解析

又不是比赛,不追求解题的效率,所以就想着直接模拟做了。

三个指针,分别指向队首、队尾和中间。每次插入都对长度判断,根据奇偶来移动中间的指针。

然后就是debug、debug和debug

代码

懒得优化了,就这样吧……

class FrontMiddleBackQueue {

  class Node {

    Integer val;
    Node next;
    Node pre;

    Node(int val) {
      this.next = null;
      this.pre = null;
      this.val = val;
    }
  }

  Node head, back, middle;
  int len = 0;

  public FrontMiddleBackQueue() {
    head = new Node(-1);
    back = new Node(-1);
    middle = new Node(-1);
    head.next = back;
    back.pre = head;
    middle = back;
  }

  public void pushFront(int val) {
    Node node = new Node(val);
    node.pre = head;
    node.next = head.next;
    head.next.pre = node;
    head.next = node;
    if (len == 0) {
      middle = head.next;
    } else if (len % 2 != 0) {
      middle = middle.pre;
    }
    len++;
  }

  public void pushMiddle(int val) {
    Node node = new Node(val);
    if (len == 0) {
      head.next = node;
      back.pre = node;
      node.pre = head;
      node.next = back;
      middle = node;
    } else if (len % 2 != 0) {
      node.next = middle;
      node.pre = middle.pre;
      middle.pre.next = node;
      middle.pre = node;
    } else {
      node.next = middle.next;
      node.pre = middle;
      middle.next.pre = node;
      middle.next = node;
    }
    middle = node;
    len++;
  }

  public void pushBack(int val) {
    Node node = new Node(val);
    node.next = back;
    node.pre = back.pre;
    back.pre.next = node;
    back.pre = node;
    if (len == 0) {
      middle = back.pre;
    } else if (len % 2 == 0) {
      middle = middle.next;
    }
    len++;
  }

  public int popFront() {
    if (len == 0) {
      return -1;
    }
    len--;
    if (len == 0 || len % 2 != 0) {
      middle = middle.next;
    }
    Node node = head.next;
    int val = node.val;
    node.next.pre = head;
    head.next = node.next;
    return val;
  }

  public int popMiddle() {
    if (len == 0) {
      return -1;
    }
    int val = middle.val;
    len--;
    middle.next.pre = middle.pre;
    middle.pre.next = middle.next;
    if (len == 0 || len % 2 != 0) {
      middle = middle.next;
    } else {
      middle = middle.pre;
    }
    return val;
  }

  public int popBack() {
    if (len == 0) {
      return -1;
    }
    len--;
    if (len == 0) {
      middle = middle.next;
    }
    if (len % 2 == 0) {
      middle = middle.pre;
    }
    Node node = back.pre;
    int val = node.val;
    node.pre.next = back;
    back.pre = node.pre;
    return val;
  }
}

2023年11月29日

2336. 无限集中的最小数字

水题,\(Set\)直接做可以秒。当然想要快的做法,请往下看。

思路

参考桶排序的思路。

看一下数据范围\(1000\),那么定义一个数组new int[1001],只要\(pop\)就将arr[i]=1。然后将minn++,再从\(minn\)开始往后找,找到第一个桶不为\(1\)的就可以了。

\(add\)就把arr[i]=0,如果i<minn就把\(minn\)更新为\(i\)

代码

class SmallestInfiniteSet {

  int arr[];
  int minn;

  public SmallestInfiniteSet() {
    arr = new int[1001];
    minn = 1;
  }

  public int popSmallest() {
    int val = minn;
    arr[val] = 1;
    minn++;
    for (int i = minn; i < 1001; i++) {
      if (arr[i] == 0) {
        minn = i;
        break;
      }
    }
    return val;
  }

  public void addBack(int num) {
    arr[num] = 0;
    minn = minn > num ? num : minn;
  }
}

2023年11月30日

1657. 确定两个字符串是否接近

思路

规律题

首先是word1word2中出现的字符是同一集合的,即\(S_1=S_2\)

其次是频次相同,即统计每个字符后,从小到大排序,word1word2的排序结果是一样的。

代码

class Solution {

  public boolean closeStrings(String word1, String word2) {
    if (word1.length() != word2.length()) {
      return false;
    }
    int[] count1 = new int[26];
    int[] count2 = new int[26];
    for (int i = 0; i < word1.length(); i++) {
      count1[word1.charAt(i) - 'a']++;
      count2[word2.charAt(i) - 'a']++;
    }
    for (int i = 0; i < 26; i++) {
      if ((count2[i] > 0) != (count1[i] > 0)) {
        return false;
      }
    }
    Arrays.sort(count1);
    Arrays.sort(count2);
    for (int i = 0; i < 26; i++) {
      if (count1[i] != count2[i]) {
        return false;
      }
    }
    return true;
  }
}
posted @ 2021-04-04 15:27  CrossAutomaton  阅读(62)  评论(0编辑  收藏  举报