不打算充钱
第一次用java写,有点不熟悉。。。还是用c+stl爽。
没写完,不定期更新。在忙八股,先发出来吧,万一有人需要呢
先更数论和动态规划
目录
动态规划篇
一眼斐波那契数列。想更进一步可以找一下矩阵写法。
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;
}
}
一眼数字三角形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;
}
}
简单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;
}
}
能优化为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;
}
}
第一次做没读懂题,写着写着就发现不对
由于他不是每次只能跳到下一个,因此可以由前面多种状态转移过来,所以必然为二维
而且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;
}
太生草了
第一眼懵了一下,然后思索了一下列了式子。一眼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],这样开大点保证不越界。实际使用直接需要多少开多少就行。
板子题,我校算法课实验考了三四次。。。
可以翻翻之前的。。。算了我粘贴过来吧。本质就是个最长上升子序列模型。原题是字符串,这里就是数。
那么有一种简单的状态表示方案,即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橙名红名等大爹级别之前,不急,先提升自己。提前掌握用不上的算法只是在浪费时间罢了。
暴力就能做的题。
类似的题是一道最大子矩阵和,那道题则涉及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;
}
}
单调栈?没想到,没做过类似的,回来再看看。先往后写。
第一眼:好眼熟,区间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;
}
}
数论篇
这不一眼欧拉筛。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;
}
有规律的,甚至常数可以优化,遍历一半就行,因数除了平方数都是偶数个,因此记录前一半,直接算后一半即可。
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);
}
}
}
明摆着提示你写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;
}
}
首先要组合,肯定分解质因数。分解完质因数之后就能看出是求组合数。
但问题是计算公式是什么?即我知道这道题需要求组合数,但是,具体怎么求?
我们假设将一个数已经完全分解为了若干个质因子的若干次幂相乘
假设已经处理完了前面所有结果,只剩最后一个
那么这一个能带来的变化是,从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;
}
确实难,但难不在如何想到,而是学没学过知识点,做没做过题。。
以及,如何确定相邻不同质因数之间的关系。
一眼并查集,思考了一下,既然要因数,直接分解质因数,带集合数量维护并查集。
然后写着写着发现了问题,不能直接维护集合。
如果直接维护集合,比如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];
}
}
八股要重新整理一下,稍微准备准备别的。有人看最好,没人看算了。过两天把那些准备好再刷刷。
二编:已找到大厂实习,所以先不更了
拒了考虑另一条路,或者不拒秋招大厂,再说吧