leetcode 2024春招冲刺百题计划——动态规划+数论

不打算充钱

第一次用java写,有点不熟悉。。。还是用c+stl爽。

没写完,不定期更新。在忙八股,先发出来吧,万一有人需要呢

先更数论和动态规划

目录

动态规划篇

数论篇


动态规划篇

70. 爬楼梯

一眼斐波那契数列。想更进一步可以找一下矩阵写法。

class Solution {
    public int climbStairs(int n) {
        if(n==1) return 1;
        else if(n==2) return 2;
        int sum=0,f1=1,f2=2;
        for(int i=3;i<=n;i++){
            sum=f1+f2;
            f1=f2;
            f2=sum;
        }
        return sum;
    }
}

118. 杨辉三角

一眼数字三角形dp,入门dp,感觉不用多说。。。

这道题对我来说难在哪里了。。。难在,怎么转换为固定答案需要的格式了。。。。。。。我以前做算法题哪里受过这气,无论牛客洛谷还是cf不都直接打印输出就行。。。。。。让我用c++写分分钟,让我用java写,我还得回忆一下,java二维数组怎么转list来着。。。

思考了半天函数,最后还是选择了遍历,简单粗暴。

class Solution {
    public List<List<Integer>> generate(int numRows) {
        int [][]array = new int[50][50];
        array[0][0] = 1;
        List<List<Integer>> list = new ArrayList<>();
        List<Integer> first = new ArrayList<>();
        first.add(1);
        list.add(first);
        for (int i=1;i<numRows;i++){
            List<Integer> row = new ArrayList<>();
            array[i][0]=1;
            row.add(1);
            for (int j=1;j<i;j++){
                array[i][j]=array[i-1][j-1]+array[i-1][j];
                row.add(array[i][j]);
            }
            array[i][i]=1;
            row.add(1);
            list.add(row);
        }
        return list;
    }
}

198. 打家劫舍

简单dp,稍微理一下思路就能看出来和第一道题没什么大差别。每个数要么从前一个数转移过来,自身不能选;要么从前两个数转移过来,能带上自身。

怎么想到的?我也不知道,但是dp做一部分后,看到这道题脑子里自动生成了答案。

class Solution {
    public int rob(int[] nums) {
        int n=nums.length;
        if (n==1) {
            return nums[0];
        }
        int []dp = new int[300];
        dp[0]=nums[0];
        dp[1]=nums[1];
        dp[1]=max(nums[0],nums[1]);
        for(int i=2;i<n;i++){
            dp[i]=max(dp[i-2]+nums[i],dp[i-1]);
        }
        return dp[n-1];
    }
    public int max(int a,int b){
        if(a>=b) return a;
        else return b;
    }
}

329. 矩阵中的最长递增路径

能优化为dp,拓扑排序我确实没想到。

以后记一下这种题,还可以拓扑排序写。

我的思路是一眼dfs,但是数量太多,所以一定要优化。剪枝策略+记忆化搜索。

记忆化的难点在什么?在于你如何让每个结点记录他的新的值。和以往的回溯法不同,简单的回溯大多数情况下不需要额外记录当前结点状态。而记忆化搜索的难点(个人认为)在于如何记录当前结点状态。

因此在搜索的时候每次都当成一个新结点搜索,将当前的值保存下来即可。实现不难,但是想到比较难。。。

优化dp没看,但和记忆化思路应该差不多。

class Solution {
    //首先要知道只要搜过的路确认是这里,就不需要再搜了。从这里必然不可能是新的开始地方。
    //或者说每个地方可以自己标记自己的最大值,遇到时若有直接返回
    //而且比起dp,我更喜欢用dfs来看这道题
    int [][]mp = new int[250][250];
    private int m;
    private int n;
    public int longestIncreasingPath(int[][] matrix) {
        m = matrix.length;
        n = matrix[0].length;
        boolean [][]acc = new boolean[250][250];
        int mx=0;
        for (int i=0;i<m;i++){
            for (int j=0;j<n;j++){
                mx=max(mx,dfs(i,j,matrix,1));
            }
        }
        return mx;
    }
    public int dfs(int x,int y,int[][] matrix,int cnt){
        int []dx={0,1,0,-1};
        int []dy={1,0,-1,0};
        if (mp[x][y]!=0) return mp[x][y];
        int ans = 1;
        for(int i=0;i<4;i++){
            int tx=x+dx[i],ty=y+dy[i];
            if (tx<0||tx>=m||ty<0||ty>=n) continue;
            if(matrix[tx][ty]<=matrix[x][y]) continue;
            cnt++;
            ans = max(ans,dfs(tx,ty,matrix,cnt)+1);
            cnt--;
        }
        mp[x][y]=ans;
        return ans;
    }
    public int max(int a,int b){
        if (a>b) return a;
        else return b;
    }
}

403. 青蛙过河

第一次做没读懂题,写着写着就发现不对

由于他不是每次只能跳到下一个,因此可以由前面多种状态转移过来,所以必然为二维

而且k每次也要记录,而每个k又不一样,如果单纯保留下来,则需要又开一维空间。

这个时候就想到了搜索,或者说写着写着就写成了搜索,dfs的话1e3否决。完全没考虑过记忆化,果然是记忆化没练过容易漏。。

思索了一下bfs,觉得可行。但是动态规划是弱项,因此还是想了想动态规划怎么写。

n3必然不过的,2e3的数据;必须考虑如何优化

没想出来怎么优化到n2,看了答案。原来让k每次重新计算即可。

即dp[i][k]表示当前在i时,可跳k步,是否可达(目前这个状态是否可被达到)

则存在j<i,使dp[i][k]=dp[j][k-1]||dp[j][k]||dp[j][k+1]

k如何计算?这就是这道题的难点,其实相比记录所有的可能的k,不如直接计算i和j之前需要的k,以此来进行判断是否可达。

n2的复杂度

class Solution {
    boolean [][]dp = new boolean[2500][2500];
    public static HashMap<Integer, Boolean> map = new HashMap<>();
    //做了做后发现一维肯定不行
    //首先,他不是一个个跳的,意味着他可以一次跳过多个
    //其次,k伴随每个的状态,需要记录
    //dp[i][k]表示当前在i时,可跳k步,能否到达。
    //那么存在j<i,使dp[i][k]=dp[j][k-1]||dp[j][k]||dp[j][k+1]
    public boolean canCross(int[] stones) {
        int n = stones.length;
        if(stones[1]>1) return false;
        dp[1][1]=true;
        for (int i=2;i<n;i++){
            for(int j=1;j<i;j++){
                int k = stones[i]-stones[j];
                if(k<=j+1) dp[i][k]=dp[j][k-1]||dp[j][k]||dp[j][k+1];
            }
        }
        boolean res = false;
        for (int i=1;i<n;i++) res=res||dp[n-1][i];
        return res;
    }

}

bfs写的话,直接搜就行。因为必然递增,所以我们只需要判断最后下标到达了最终点没

以bfs的视角来看的话,似乎是做过这道题。。。

但是这道题卡了我一个上午,你猜为什么?

因为我写完后换bfs写的时候,习惯性开了static的hashmap放在成员变量的位置。

就这一个问题,导致样例一直过不去,模拟多少次都不知道问题在哪里。

草了,太草了。。。c写习惯了,全放外面,不觉得会影响啥。。。

太生草了

bfs写法

    boolean [][]vis = new boolean[2500][2500];
    public Map<Integer, Integer> map = new HashMap<>();
    public boolean canCross(int[] stones) {
        int n = stones.length;
        if(stones[1]>1) return false;
        for (int i=0;i<n;i++) {
            map.put(stones[i],i);
        }
        Queue<int[]> q = new ArrayDeque<>();
        q.add(new int[]{1,1});
        vis[1][1]=true;
        while(!q.isEmpty()){
            int []t = q.element();
            q.remove();
            int idx = t[0],k=t[1];
            if(idx == n-1) return true;
            for(int i=-1;i<=1;i++){
                int dis = k + i;
                if (dis<=0) continue;
                int next = stones[idx] + dis;
                if (map.containsKey(next)) {
                    int nidx = map.get(next);
                    if (nidx==n-1) return true;
                    if(!vis[nidx][dis]){
                        vis[nidx][dis] = true;
                        q.add(new int[]{nidx,dis});
                    }
                }
            }
        }
        return false;
    }

太生草了

416. 分割等和子集

第一眼懵了一下,然后思索了一下列了式子。一眼01背包,这也能叫中等题?

class Solution {
    public boolean canPartition(int[] nums) {
        //本质就是找若干元素和为sum/2
        int sum = 0;
        int n = nums.length;
        for (int num : nums) {
            sum+=num;
        }
        if(sum%2==1) return false;
        else {
            sum/=2;
            boolean [][]dp = new boolean[250][25000];
            //然后就是经典01背包问题了,考虑前i个数,容量为j时,是否可以
            dp[0][0]=true;
            for(int i=1;i<=n;i++){
                for(int j=0;j<=sum;j++){
                    dp[i][j]=dp[i][j]||dp[i-1][j];
                    if(j>=nums[i-1]) dp[i][j]=dp[i][j]||dp[i-1][j-nums[i-1]];
                }

            }
            return dp[n-1][sum];
        }
        
    }
}

然后发现二维太大,涉及优化。中间体的滚动数组优化略了,直接优化到最后就行。

class Solution {
    public boolean canPartition(int[] nums) {
        //本质就是找若干元素和为sum/2
        int sum = 0;
        int n = nums.length;
        for (int num : nums) {
            sum+=num;
        }
        if(sum%2==1) return false;
        else {
            sum/=2;
            boolean []dp = new boolean[25000];
            //然后就是经典01背包问题了,考虑前i个数,容量为j时,是否可以
            dp[0]=true;
            for(int i=1;i<=n;i++){
                for(int j=sum;j>=nums[i-1];j--){
                    dp[j]=dp[j]||dp[j-nums[i-1]];
                }

            }
            return dp[sum];
        }

    }
}

很奇怪的是理论上优化只优化了空间,时间复杂度是没变的。但运行结果快了不少

那个里面的25000常数是我c的做题习惯,c+STL的话一般习惯性const int N=2e5+10,然后开数组int a[N],这样开大点保证不越界。实际使用直接需要多少开多少就行。

300. 最长递增子序列

板子题,我校算法课实验考了三四次。。。

可以翻翻之前的。。。算了我粘贴过来吧。本质就是个最长上升子序列模型。原题是字符串,这里就是数。

那么有一种简单的状态表示方案,即dp[i]是以第i个字母结尾的最长上升子序列的长度

以结尾作为位置划分,仅有一种。

我们考虑前i个字符串,即其中以i结尾的长度为在i之前所有满足小于a[i]的所有位置j的dp[j]的最大值+1

有点绕 

就是,前i个字符串中,所有小于a[i]的,都可以构成一个上升子序列。而其中,最大的那个dp[j]+1,就是我们要找的最长上升子序列。

于是便有了代码。

class Solution {
    public int lengthOfLIS(int[] nums) {
        //直接就找最长上升子序列即可
        //dp[i]表示以i结尾的最长上升子序列长度
        int []dp = new int[3000];
        int n=nums.length;
        for(int i=0;i<n;i++) dp[i] = 1;
        for (int i=0;i<n;i++){
            int ans = 0;
            for(int j=0;j<i;j++){
                if(nums[i]>nums[j]) ans = max(dp[j],ans);
            }
            dp[i] = ans+1;
        }
        int res = 0;
        for (int i=0;i<n;i++) res = max(res,dp[i]);
        return res;
    }

    public int max(int a,int b){
        if (a>b) return a;
        else return b;
    }
}

优化是二分优化,是经典题目导弹拦截的优化。一道NOI题,但是优化。。。我不会,不太懂这个思路怎么出来的。。

饭要一口一口吃,路要一步一步走,没达到cf橙名红名等大爹级别之前,不急,先提升自己。提前掌握用不上的算法只是在浪费时间罢了。

85. 最大矩形

暴力就能做的题。

类似的题是一道最大子矩阵和,那道题则涉及dp,不过是n^2的暴力判定ij和O(n)的最大连续上升子序列的和。

class Solution {
    public int maximalRectangle(char[][] matrix) {
        /*
            第一眼,搜?嗯?再看一眼,嗯,不好搜。
            看一眼,只要矩阵。那二维前缀和,然后直接遍历就行。
            思考一下复杂度,不行,四次方的复杂度过不了。
            思考一下以前做过的n3内解决的题,还真有类似的。
            我们假设已确定i行到j行,那么剩下的列只一次O(n)确定哪些列即可
            那么i行到j行直接遍历,就是n3的复杂度。
            殊途同归了,那道题是求,最大矩阵和。那道题需要二维前缀和优化+最长连续子序列和
            
            而这道题,确定i行到j行后,只需要确定连续相等且不为0的n个数的最大个数即可
         */
        int m = matrix.length,n=matrix[0].length;
        int [][]sum = new int[m+1][n+1];
        for(int i=0;i<m;i++){
            //前缀和处理,将前i行压缩到一行
            //我是真讨厌这种输入下我不能从1开始啊,这前缀和写的要多麻烦有多麻烦
            for(int j=0;j<n;j++) {
                sum[i][j]+=matrix[i][j]-'0';
                if (i-1>=0) sum[i][j]+=sum[i-1][j];
                //System.out.print(sum[i][j]+" - ");调试用的
            }
            //System.out.println();
        }
        int res = 0;
        //n^2遍历i和j
        for(int i=0;i<m;i++){
            for(int j=i;j<m;j++){
                //取出i行到j行元素,他们之间的值期望为j-i+1
                //否则围不成矩形,可以更小
                int count = 0,mx=0;
                for(int k=0;k<n;k++){
                    int bef = 0;
                    if (i-1>=0) bef = sum[i-1][k];
                    if(sum[j][k]-bef==j-i+1){
                        count++;
                        mx=max(count,mx);
                    }else {
                        count=0;
                    }
                }
                //此时i行到j行的最大值是count列
                res=max(res,(j-i+1)*mx);
            }
        }
        return res;
    }

    public int max(int a,int b){
        if (a>b) return a;
        else return b;
    }
}

单调栈?没想到,没做过类似的,回来再看看。先往后写。

312. 戳气球

第一眼:好眼熟,区间dp?和石子合并很像,就拿区间dp的方法写了。

然而树上dp开始我都没怎么刷过了,,,,,,凭感觉做的题。

结果没过,思考了一下,状态转移应该是对的。但跑不出来答案,多半是有细节没处理到。

然后调了一下,发现这道题以以往的合并石子的闭区间思路做不好做。

重点是闭区间,区间dp的处理思路是没问题的。

比如假设我现在是f[l][r]=f[l][k-1]+f[k+1][r]+sum

请问,如果l=r,即len为1时,k为1

但两个状态,一个变成了f[1][0],一个变成了f[2][1]

这两个状态不应该出现,是错误

因此闭区间不好做,得开区间,重点就是开区间的状态如何表示了

我们假设要打点1的最小区间,则其应该是f[0,2],仅留1可选

这时候l=0,r=2,len起步就变成了2,k从l+1出发,最大为r-1

整个区间所求就变成了f[0,n+1]

注意全是开区间

class Solution {
    public int maxCoins(int[] nums) {
        //一眼区间dp典题,看眼复杂度,没跑了应该。另一道题叫石子合并
        //f[l][r]表示l-r之间的最大值。
        //k为划分点,f[l][r]=f[l][k]+f[k][r]+sum
        //开区间做,闭区间有状态不好表示,容易出问题
        int n = nums.length;
        int []a = new int[n+10];
        a[0]=1;
        for (int i=0;i<n;i++) a[i+1]=nums[i];
        a[n+1]=1;

        int [][]f = new int[n+10][n+10];

        for (int len = 2;len <= n+2 ;len++ ){
            for (int l=0;l+len<=n+1;l++){
                int r = len+l;
                for(int k=l+1;k<=r-1;k++){
                    int sum = a[l]*a[k]*a[r];
                    f[l][r]=max(f[l][k]+f[k][r]+sum,f[l][r]);
                }
            }
        }
        return f[0][n+1];
    }


    public int max(int a,int b){
        if (a>b) return a;
        else return b;
    }
}

数论篇

204. 计数质数

这不一眼欧拉筛。5e6的数量级,O(n)欧拉筛呗

板子题,小于等于n换小于n即可

public int countPrimes(int n) {
        int []primes = new int[n+1];
        boolean []st = new boolean[n+1];
        int cnt = 0;
        for (int i=2;i<n;i++){
            if(!st[i]) primes[++cnt] = i;
            for (int j=1;i*primes[j]<=n;j++){
                st[primes[j]*i]=true;
                if(i%primes[j]==0) break;
            }
        }
        return cnt;
    }

1492. n 的第 k 个因子

有规律的,甚至常数可以优化,遍历一半就行,因数除了平方数都是偶数个,因此记录前一半,直接算后一半即可。

class Solution {
    public int kthFactor(int n, int k) {
        //除非平方数,否则因数都是偶数个的
        int here = 0;
        ArrayList<Integer> list = new ArrayList<>();
        int all=0;
        int res=0;

        for (int i=1;i*i<=n;i++){
            if(n%i==0){
                all+=2;
                //另一个可以算出来的
                list.add(i);
                if(i*i==n){
                    all--;
                    break;
                }
            }
        }
        if(k>all) return -1;
        else {
            if(k>list.size()){
                all=all+1-k;
                return n/list.get(all-1);
            }else return list.get(k-1);
        }
    }
}

1979. 找出数组的最大公约数

明摆着提示你写gcd了,这个板子如果还不会需要查,我直接紫菜吧。。。

class Solution {
    public int findGCD(int[] nums) {
        int mx=nums[0],mi=nums[0];
        for (int num : nums) {
            mx=Math.max(mx,num);
            mi=Math.min(mi,num);
        }
        return gcd(mx,mi);
    }
    
    public int gcd(int a,int b){
        return b!=0?gcd(b,a%b):a;
    }
}

1735. 生成乘积数组的方案数

首先要组合,肯定分解质因数。分解完质因数之后就能看出是求组合数。

但问题是计算公式是什么?即我知道这道题需要求组合数,但是,具体怎么求?

我们假设将一个数已经完全分解为了若干个质因子的若干次幂相乘

假设已经处理完了前面所有结果,只剩最后一个

那么这一个能带来的变化是,从m个(所有能放的)里选n个(最大次幂)的个数,其他的可以填放之前弄好的。对于仅1个质因子,他就是1。对于若干个,那就是他和之前所有的组合,也就是乘积。

那么就是相乘的关系。

static long [][]c = new long[11000][50];
    //第一眼,分解质因数肯定是需要的。思索一下,然后根据个数,求组合数即可。
    //本质就是分解质因数+求组合数啊
    //不如抽象一个场景,然后抽象出来这个模型,cf最喜欢考思维。
    public int[] waysToFillArray(int[][] queries) {
        //预处理,组合数的一种求法
        //1e4的选取个数最大为log 2 1e4,10即可;而1e9的范围内约数最大1500多个
        //因此预处理一下即可,开1e3应该没问题
        long mod = 1_000_000_007;
        for (int i = 0; i < 11000; i ++ )
            for (int j = 0; j <= 20&&j<=i; j ++ )
                if (j==0) c[i][j] = 1;
                else c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % mod;

        int[] res = new int[queries.length];
        int count = 0;
        for (int[] query : queries) {
            int k=query[0],n=query[1];
            long all=1;
            for (int i=2;i*i<=n;i++){
                if(n%i==0){
                    int cnt=0;
                    while(n%i==0){
                        n/=i;
                        cnt++;
                    }
                    all = (all * c[k+cnt-1][cnt])%mod;
                }
            }
            if(n>1) all = (all * k) % mod;
            res[count++] = (int) all;
        }
        return res;
    }

确实难,但难不在如何想到,而是学没学过知识点,做没做过题。。

以及,如何确定相邻不同质因数之间的关系。

952. 按公因数计算最大组件大小

一眼并查集,思考了一下,既然要因数,直接分解质因数,带集合数量维护并查集。

然后写着写着发现了问题,不能直接维护集合。

如果直接维护集合,比如6会被同时拆分到2和3,此时会出现一个问题,即6被计算了两次。

嗯?但也不是不可以,我们何不先将所有数分解质因数,将每个数的质因数连通,连通后遍历一遍数组,将其所在的连通图上++。

(突发奇想写了,过了。其实就是想不出来在题解这里一边敲一边思索突然想出来的

首先,由于存在重复计算一个数的情况,因此先将质因子连通,最终计算的时候遍历每个数,取这个数的任意一个质因子++即可在这个连通图里++。

然后WA了,按样例调试了一下,发现问题在于,存在两个质因数比如2,13;3,13,先后顺序下,p[13]=p[2]和p[13]=p[3],3没有归到2里

因此,每次进行合并时,先将其祖宗合并,即:

p[p[list.get(k)]] = find(p[p[list.get(0)]]);

这一句没意识到卡了我半天,调试了好久。(当然可能是挂着星穹铁道的原因让我静不下心(你这辈子就被mhy给害了))

其他的就是标准的并查集+分解质因数,将分解出来的质因数连通后,遍历数组++即可。

class Solution {
    private static final int N = 100050;
    int []p = new int[N];
    int []cal = new int[N];
    int []si = new int[N];
    //同时维护一个map,每个map存放质因子-出现过的数
    //分解质因子时同时将质因子连通起来,将map合并,最后直接取最大集合数量即可

    public int largestComponentSize(int[] nums) {
        //只要求集合,不要求里面的顺序关系,应该是并查集
        //看眼长度,每个数分解质因数,有相同质因数的合并为一个集合即可
        //先将所有质因数连通
        int n = nums.length;
        int mx = 0;
        for (int num : nums) {
            mx = Math.max(mx,num);
        }
        for (int i=1;i<=mx;i++) p[i]=i;
        for (int i=0;i<n;i++){
            int init = nums[i];
            ArrayList<Integer> list = new ArrayList<>();
            for (int j=2;j*j<=init;j++){
                if(init%j==0){
                    while(init%j==0) {
                        init /= j;
                    }
                    list.add(j);
                    cal[nums[i]]=j;
                }
            }
            if(init>1){
                //主要是防止2这个特殊值,上来不进循环,所以在这里也cal记录一下
                list.add(init);
                cal[nums[i]]=init;
            }
            if (list.size()>1) {
                for (int k=1;k<list.size();k++){
                    p[list.get(k)] = find(p[list.get(k)]);
                    p[list.get(0)] = find(p[list.get(0)]);
                    p[p[list.get(k)]] = find(p[p[list.get(0)]]);
                    p[list.get(k)] = find(p[list.get(0)]);
                }
            }
        }
        for (int i=2;i<=mx;i++) p[i] = find(p[i]);
        //此时所有质因数已经连通了
        for (int num : nums) {
            int now = find(p[cal[num]]);
            si[now]++;
        }
        int res = 1;
        for (int i=0;i<=mx;i++){
            res = Math.max(res,si[i]);
        }
        return res;
    }

    public int find(int x){
        if(p[x]!=x) p[x]=find(p[x]);
        return p[x];
    }
}

八股要重新整理一下,稍微准备准备别的。有人看最好,没人看算了。过两天把那些准备好再刷刷。

二编:已找到大厂实习,所以先不更了

拒了考虑另一条路,或者不拒秋招大厂,再说吧

  • 49
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值