赞
踩
1.旋转词-模拟
2.旋转矩阵-模拟
3.数轴覆盖-贪心
4.1 完整字符串1(括号字符串的有效性)-栈
4.2 完整字符串2(缺失的括号)-栈
4.3 完整字符串3(最长合法括号子串)-栈
5.打包机器人-贪心
6.1容器装水-贪心
6.2地形盛水-贪心
7.牛牛找工作-有序表
8.安置路灯-贪心
2022.3.1-2022.3.6
牛客网题目连接
如果一个字符串为str,把字符串的前面任意部分挪到后面形成的字符串叫str的旋转词。比如str=“12345”,str的旋转串有“12345”、“45123”等等。给定两个字符串,判断是否为旋转词。
解析:
1.备注时间复杂度和空间复杂度为O(N),这就表示,我们不能用暴力法去求解了。
2.我们通过仔细阅读题目,不难发现,如果旋转词的长度和原来串的长度不一致,那么肯定是NO
3.我们使用的方法是,扩大串。将str= str + str
4.所以我们的算法的难度来到了KMP,这是一个经典的判断字串的算法
using System; namespace test{ class test{ static void Main(string[] args){ string lengthIn=Console.ReadLine(); string[] lengthSplit=lengthIn.Split(' '); int length1=Convert.ToInt32(lengthSplit[0]); int length2=Convert.ToInt32(lengthSplit[1]); if(length1!=length2){ Console.WriteLine("NO"); return; } string l1=Console.ReadLine(); string l2=Console.ReadLine(); //第二个字符串 //让字符串扩大两倍 string big=l1+l1; for(int i=0;i<big.Length;i++){ if(big[i]==l2[0]){ if(IsChild(i,big,l2)){ Console.WriteLine("YES"); return; } } } Console.WriteLine("NO"); } //KMP static bool IsChild(int begin,string father,string child){ int childindex=0; for(int i=begin;i<father.Length;i++){ if(father[i]!=child[childindex]){ return false; } childindex++; if(childindex>=child.Length)return true; } return true; } } }
总结:这里我们采用基地址的方法,以每个基地址作为处理对象,通过将每个基地址的情况进行计算,得到最优的答案。
局部->最优
牛客网链接
有一个NxN整数矩阵,请编写一个算法,将矩阵顺时针旋转90度。
给定一个NxN的矩阵,和矩阵的阶数N,请返回旋转后的NxN矩阵。
解析:
1.其实我们可以创新一个新的矩阵,然后将旧矩阵旋转之后,赋值给新矩阵,只需要找到对应点旋转后的关系即可。
2.但是,进阶要求是要我们去实现空间复杂度为O(1),意思就是不创建新的矩阵,就在原来的矩阵里面进行旋转
3.我们采取剥洋葱的方法,一层一层的去实现旋转。首先是最外面一层,我们的发现他们的规律,红色的组一,就是四个顶点,他们旋转的位置很容易发现规律。我们只需要用一个去覆盖另外一个即可。然后紧跟着的绿色组二,和组一位置的关系很紧密,看下面写的for循环就可以理解了。
4.for循环的一次循环,代表完成一组。比如i=0,执行完后,组1完成旋转。比如i=1,执行完成后,组2完成选择。所以,一个for循环,就可以解决一圈的数据
5.我们只需要让abcd的值,不断往里面缩。下一个圈,就是a+1,b+1,c-1,d-1
6.直到a>c了,就是都完成了
using System; class Solution { public int[][] rotateMatrix(int[][] mat, int n) { int a=0; int b=0; int c=n-1; int d=n-1; while(a<c){ RotateOne(mat,a++,b++,c--,d--); } PrintMat(mat,n); return mat; } public static void RotateOne(int[][] mat,int a,int b,int c,int d){ for(int i=0;i<d-b;i++){ int temp=mat[a][b+i]; mat[a][b+i]=mat[c-i][b]; mat[c-i][b]=mat[c][d-i]; mat[c][d-i]=mat[a+i][d]; mat[a+i][d]=temp; } } public static void PrintMat(int[][] mat,int n){ Console.Write('['); for(int i=0;i<n;i++){ Console.Write('['); for(int j=0;j<n;j++){ Console.Write(mat[i][j]); Console.Write(','); } Console.Write(']'); } Console.Write(']'); } }
总结:
这道题我们应该舍弃局部,而是从整体来进行处理。以整体去推局部,如果纠结于某个局部的值怎么样去转换, 那会很难!
如果我们分析整体,发现客观规律,那我们就可以轻松应对。
将局部代入整体
牛客网链接
分析:
1.这里使用的是3方法,滑动窗口法。以Left作为窗口左边,Right作为窗口右边。Right试着往右边无限扩大窗口,直到无法扩大,即让Left往前移动一格。每一次右边无法扩大的时候,即是Left位置窗口的最大值。此时应该刷新Max。
using System; namespace test{ class test{ static void Main(){ string[] putin=Console.ReadLine().Split(' '); int pointCount=Convert.ToInt32(putin[0]); int Length=Convert.ToInt32(putin[1]); string[] PointArrayIn=Console.ReadLine().Split(' '); int[] PointArray=new int[PointArrayIn.Length]; for(int i=0;i<PointArrayIn.Length;i++){ PointArray[i]=Convert.ToInt32(PointArrayIn[i]); } int Left=0; int Right=0; int Max=0; //滑动窗口,开始滑动,窗口左边不越界 while(Left<pointCount){ //窗口右边无限右扩,直到长度大于了绳子长度 while(Right<pointCount && PointArray[Right]-PointArray[Left]<Length){ Right++; } //右边已经扩到顶了,所以让更新Max,然后左边往右移一格 Max=GetMax(Max,Right-Left); Left++; } Console.WriteLine(Max); } static int GetMax(int a,int b){ if(a>b){ return a; }else{ return b; } } } }
总结:
这道题有三种解题方法,分别是基地址法o(N2),基地址加二分查找O(NlogN),滑动窗口O(N)
分析:我们如何判断括号是否匹配?
1.首先,我们应该从括号的数量下手。我们定义一个变量,Count。如果此时字符为 ‘(’ 我们应该让Count++。如果此时字符为 ‘)’ ,我们应该让Count–
2.那怎么判断呢?第一:任何时候,只要Count<0,就代表无法匹配了。为什么?因为Count<0就代表当前位置的 ‘)’ 比 '('要多,而多出来的 ')'此时已经无法进行匹配了。
例如(()))( 第三个 ‘)’ 是无法找到一个 ‘(’ 去和它匹配的
3.第二:如果结束的时候Count!=0,此时就说明,Count>0,即 '('比 ‘)’ 要多,自然不匹配。
using System; namespace test{ class test{ static void Main(){ string str=Console.ReadLine(); int Count=0; for(int i=0;i<str.Length;i++){ if(str[i]=='(')Count++; if(str[i]==')')Count--; if(Count<0) { break; } } if(Count==0)Console.WriteLine("YES"); else Console.WriteLine("NO"); } } }
总结:
我们应该通过对用例的分析来发现规律,匹配即左括号和右括号数目一样,并且右括号不能单独出现
牛客网链接
解析:
1.我们这里和4.1有区别的点就是在于,我们字符串本身就是不匹配的,需要让我们找出需要添加的括号数量,那么这里就有几种情况。一:需要添加左括号 二:需要添加右括号 三:两者都需要添加
2.我们定义一个变量,Count。如果此时字符为 ‘(’ 我们应该让Count++。如果此时字符为 ‘)’ ,我们应该让Count–
2.我们如何来确定是否需要添加左括号?我们定义一个变量为Need,如果Count==-1的时候,就是出现了 ‘)’ ,需要 ‘(’ 来对齐。那么这个时候,我们让Need++,相当于补充了一个 ‘(’ ,那么此时Count=0
3.最后,输出Need+Count,即代表,需要的左括号数目加上需要的右括号数目,为总的需要的数目。
using System; namespace test{ class test{ static void Main(){ string str=Console.ReadLine(); int Count=0; int Need=0; for(int i=0;i<str.Length;i++){ if(str[i]=='(')Count++; if(str[i]==')'){ Count--; if(Count==-1){ Need++; Count=0; } } } Console.WriteLine(Need+Count ); } } }
牛客网链接
分析:
1.对于字串问题,我们采用基地址的核心思想,以基地址去判断,以局部求最优
2.dp指的是当前位置的最长字串长度,对于每个基地址的dp值,我们应该考虑之前的相邻的dp值。
using System; namespace test{ class test{ static void Main(){ string str=Console.ReadLine(); //用于往前找'('的指针 int pre=0; //记录索引位置最长的字串 int[] dp=new int[str.Length]; //最长 int max=0; //从前往后遍历每一个点 for(int i=1;i<str.Length;i++){ if(str[i]=='(')continue; //只有遇到了')'我们才进行判断 //前面的'('的指针的位置,应该是根据i-1位置的dp值 //如果i-1位置不存在dp值,即i-1位置就是pre指向的位置 //如果i-1位置存在dp值,即i-1位置也是')',那么pre指向的位置一定在i-dp[i-1]-1位置 pre=i-dp[i-1]-1; //没有越界,并且pre指向的位置是'(',才能构成字串 if(pre>=0&&str[pre]=='('){ //如果构成字串了,那么i位置的dp值应该更新 dp[i]=dp[i-1]+2+(pre>0?dp[pre-1]:0); } //更新最大值 max=max>dp[i]?max:dp[i]; } Console.WriteLine(max); } } }
牛客网连接
解析:
1.我们对单独某个i进行分析,对其左边和右边进行分析,就可以得到以下结论
2.为什么?为什么要选出最大值作为最佳轮数?因为我们这里是求解出每个位置需要的轮数。当我们解决了最痛点的时候,自然其他点也会被解决
分析:
1.容器装水问题,有三种解决方式:1.求每个波谷,计算复杂 2.根据i求每个格子的水,然后累加起来。这里只需要求i左边最大高度,和i右边最大高度,然后两个最大高度与i的高度进行比较,就可以求出i的水量了 3.双指针法
2.双指针法就是利用左指针在左边,右指针在右边,然后一个Lmax表示左边最大的高度,Rmax表示右边最大的高度。我们每次只需要进行判断Lmax和Rmax谁大谁小,我们就去结算哪一边指针指向格子的水量。
using System; namespace test{ class test{ static void Main(){ string lengthin=Console.ReadLine(); int length=Convert.ToInt32(lengthin); string[] arrayin=Console.ReadLine().Split(' '); //长度小于3,无法装水 if(arrayin.Length<3){ Console.Write(0); return; } //初始化我们的数组 int[] Array=new int[arrayin.Length]; for(int i=0;i<arrayin.Length;i++){ Array[i]=Convert.ToInt32(arrayin[i]); } int Lmax=Array[0]; int Rmax=Array[Array.Length-1]; int Lindex=1; int Rindex=Array.Length-2; long Water=0; //我们左边指针不能大于右边指针 while(Lindex<=Rindex){ //如果左边比较小,则结算左边,同时要更新左边最大值 if(Lmax<=Rmax){ Lmax=Math.Max(Lmax, Array[Lindex]); Water+=Math.Max(0,Lmax-Array[Lindex]); Lindex++; //如果右边比较小,则结算右边,同时要更新右边最大值 }else{ Rmax=Math.Max(Rmax, Array[Rindex]); Water+=Math.Max(0,Rmax-Array[Rindex]); Rindex--; } } Console.WriteLine(Water); } } }
分析:
1.我们构造一个小根堆,都先将旁边一圈全部放入,用红色下划线表示
2.我们弹出最小的值,是3,然后我们更新max为3,即,我们盛水最大容量为3。然后我们试着将3上下左右四个点都放入,在放入时,产生水。比如3旁边的2,产生一格水。
3.然后弹出最小值,是2,无法更新max,我们将2旁边的3和1全部塞入,只有1产生水。产生两格水。
4.然后弹出最小值,是1,无法更新max,我们将1旁边的9和4塞入,发现无法产生水。
5.弹出最小值3,弹出最小值4,更新max为4
6.弹出最小值,右边的5,更新max为5,然后将5旁边的2放入。此时产生3格水。
7.弹出最小值,是2,无法更新max,然后将2旁边的3和0放入,产生2+5格水。
8.弹出0,弹出3,弹出5,都无法产生水,直到全部弹出,处理完毕!
using System; using System.Collections.Generic; namespace test{ class test{ public struct Node{ public int Value; public int Row; public int Line; } static void Main(){ string[] linein=Console.ReadLine().Split(' '); int R=Convert.ToInt32(linein[0]) ; int L=Convert.ToInt32(linein[1]) ; int[,] Map=new int[R,L]; //格子二维数组 bool[,] IsIn=new bool[R,L]; //标记是否进入的二维数组 for(int i=0;i<R;i++){ for(int j=0;j<L;j++){ IsIn[i,j]=false; } } //初始化格子 for(int i=0;i<R;i++){ string[] line=Console.ReadLine().Split(' '); for(int j=0;j<L;j++){ Map[i,j]=Convert.ToInt32(line[j]); } } List<Node> Sorted=new List<Node>(); //小根堆 //四个For循环完成将边框全部的都放入 for(int i=0;i<L;i++){ //第一行 Sorted=InsertByMin(Sorted,Map[0,i],0,i); IsIn[0,i]=true; //最后一行 Sorted=InsertByMin(Sorted,Map[R-1,i],R-1,i); IsIn[R-1,i]=true; } for(int i=0;i<R;i++){ //第一列 Sorted=InsertByMin(Sorted,Map[i,0],i,0); IsIn[i,0]=true; //最后一列 Sorted=InsertByMin(Sorted,Map[i,L-1],i,L-1); IsIn[i,L-1]=true; } int Water=0; int Max=0; //然后开始弹出 判断 while(Sorted.Count!=0){ //取出最小的格子 Node node =Sorted[0]; Sorted.RemoveAt(0); Max=Math.Max(Max,node.Value); int nodeR=node.Row; int nodeL=node.Line; //遍历弹出来格子的边上四个,上下左右, 因为上下左右会围成一个圈,可以装水 //上 存在上面的格子并且没有进入过 if(nodeR>0&&IsIn[nodeR-1,nodeL]==false){ //将格子创建为Node,然后放入堆 Node nodeIn=CreateNode(Map[nodeR-1,nodeL],nodeR-1,nodeL); IsIn[nodeR-1,nodeL]=true; Water+=Math.Max(0,Max-nodeIn.Value); Sorted=InsertByMin(Sorted,Map[nodeR-1,nodeL],nodeR-1,nodeL); } //下 存在下面的格子并且没有进入过 if(nodeR<R-1&&IsIn[nodeR+1,nodeL]==false){ //将格子创建为Node,然后放入堆 Node nodeIn=CreateNode(Map[nodeR+1,nodeL],nodeR+1,nodeL); IsIn[nodeR+1,nodeL]=true; Water+=Math.Max(0,Max-nodeIn.Value); Sorted=InsertByMin(Sorted,Map[nodeR+1,nodeL],nodeR+1,nodeL); } //左 存在左边的格子并且没有进入过 if(nodeL>0&&IsIn[nodeR,nodeL-1]==false){ //将格子创建为Node,然后放入堆 Node nodeIn=CreateNode(Map[nodeR,nodeL-1],nodeR,nodeL-1); IsIn[nodeR,nodeL-1]=true; Water+=Math.Max(0,Max-nodeIn.Value); Sorted=InsertByMin(Sorted,Map[nodeR,nodeL-1],nodeR,nodeL-1); } //右 存在右边的格子并且没有进入过 if(nodeL<L-1&&IsIn[nodeR,nodeL+1]==false){ //将格子创建为Node,然后放入堆 Node nodeIn=CreateNode(Map[nodeR,nodeL+1],nodeR,nodeL+1); IsIn[nodeR,nodeL+1]=true; Water+=Math.Max(0,Max-nodeIn.Value); Sorted=InsertByMin(Sorted,Map[nodeR,nodeL+1],nodeR,nodeL+1); } } Console.WriteLine(Water); } //构造小根堆 public static List<Node> InsertByMin(List<Node> Sorted,int n,int R,int L){ Node node=CreateNode(n,R,L); if(Sorted.Count==0){ Sorted.Add(node); return Sorted; } for(int i=0;i<Sorted.Count;i++){ if(Sorted[i].Value>node.Value){ Sorted.Insert(i, node); return Sorted; }else if(i==Sorted.Count-1){ Sorted.Add(node); return Sorted; } } return Sorted; } //创建新节点 public static Node CreateNode(int n,int R,int L){ Node node=new Node(); node.Value=n; node.Row=R; node.Line=L; return node; } } }
牛客网链接
分析:
使用有序表就能在规定时间内完成
TreeMap<Integer, Integer> map = new TreeMap<>();
但是在C#中没有JAVA里面的TreeMap,所以实现起来比较繁琐,这里我是将List进行修改,达到同样的效果。但是,超时了!
1.首先我们要清楚的知道,同一岗位要求,只需要留下薪水最高的就行了。
2.其次,如果岗位要求增加,薪水没有增加,则不需要考虑该岗位
3.将其按岗位要求排好序,然后看员工满足的岗位要求,即是最大报酬!
using System; using System.Collections; using System.Collections.Generic; namespace test { class test { public struct Job { public int Di; public int Pi; } static void Main() { //工作的集合 List<Job> JobList = new List<Job>(); string[] putin = Console.ReadLine().Split(' '); int JobCount = Convert.ToInt32(putin[0]); int ManCount = Convert.ToInt32(putin[1]); //将工作全部插入集合 for (int i = 0; i < JobCount; i++) { string[] put = Console.ReadLine().Split(' '); Job job = new Job { Di = Convert.ToInt32(put[0]), Pi = Convert.ToInt32(put[1]) }; JobList = InsertByMin(JobList, job); } //将工作能力大反而报酬小的工作去掉 JobList = SortList(JobList); //for循环尝试每个工作 string[] lastin = Console.ReadLine().Split(' '); for (int i = 0; i < ManCount; i++) { int temp = Convert.ToInt32(lastin[i]); int Big = 0; for (int j = JobList.Count-1; j >=0; j--) { if (temp >= JobList[j].Di) { Big =JobList[j].Pi; break; } } Console.WriteLine(Big); } } //构造小根堆 public static List<Job> InsertByMin(List<Job> Sorted, Job job) { if (Sorted.Count == 0) { Sorted.Add(job); return Sorted; } for (int i = 0; i < Sorted.Count; i++) { //如果要求相同,则换报酬高的 if (Sorted[i].Di == job.Di) { Job newjob = Sorted[i]; newjob.Pi = Math.Max(Sorted[i].Pi, job.Pi); Sorted.RemoveAt(i); Sorted.Insert(i, newjob); return Sorted; }//按从小到大的顺序排序 else if (Sorted[i].Di > job.Di) { Sorted.Insert(i, job); return Sorted; } else if (i == Sorted.Count - 1) { Sorted.Add(job); return Sorted; } } return Sorted; } //构造小根堆 public static List<Job> SortList(List<Job> Sorted) { for (int i = 0; i< Sorted.Count; i++) { if(i+1== Sorted.Count) return Sorted; //如果能力大反而报酬少,则删除 if (Sorted[i].Di < Sorted[i +1].Di&& Sorted[i].Pi > Sorted[i + 1].Pi) { Sorted.RemoveAt(i+1); i--; } } return Sorted; } } }
牛客网链接
分析:
1.路灯只能照亮自己和旁边的两个格子,所以,我们可以在草稿纸上将各种情况绘制出来。
2.如果i是’.’,i+1是’X’,则我们应该意识到,只能在i位置放一盏灯,然后i+2,跳过’X’
3.如果i是’.’,i+1是’.’,则我们应该意识到,放一盏灯在i+1然后,i+3
4.如果i是’X’,则i++,不放灯
using System; namespace test{ class test{ static void Main(){ int count=Convert.ToInt32(Console.ReadLine()); string array=Console.ReadLine(); int index=0; int light=0; while(index<array.Length){ if(array[index]=='.'){ light++; if(index+1==array.Length)break; if(array[index+1]=='X'){ index+=2; }else{ index+=3; } }else{ index++; } } Console.WriteLine(light); } } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。