好久不刷题了,今天比较想划划水,所以闲来无事刷几道题。
(ps:以前我是顺序难度刷题,不过今天连着跳过了两道。题目晦涩难懂,而且看着就没啥意思。。)好了,直接开始上题目。
字符串解码
题目:给定一个经过编码的字符串,返回它解码后的字符串。编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k ,例如不会出现像 3a 或 2[4] 的输入。
思路:这个确确实实是个中等难度的题目,但是我觉得是蛮简单的。审完题的第一想法是用递归一层一层的解析出来。有点类似于之前做过的一道可能有数组可能有单独数字的解析的题目。我直接去试试代码实现。有什么坑再回来细说。
首先思路是没问题的,其次是这种递归的题目一直debug是真的恶心~我先直接附上代码:
class Solution {
int i = 0;
public String decodeString(String s) {
int n = 0;
StringBuffer temp = new StringBuffer();
StringBuffer res = new StringBuffer();
while(i<s.length()){
if(Character.isDigit(s.charAt(i))){//是数字说明是次数
n = n*10+(s.charAt(i)-48);
i++;
}
else if(s.charAt(i)=='['){//递归
i++;
temp.append(decodeString(s));
if(n!=0){
for(int j = 0;j<n;j++){
res.append(temp);
}
}
n = 0;
temp = new StringBuffer();
}
else if(s.charAt(i)==']'){
i++;
break;
}
else {//说明是重复的内容
res.append(s.charAt(i));
i++;
}
}
return res.toString();
}
}
其实以上代码的思路大部分借鉴于之前的语法解析器那道题。全部变量做下标。这道题的本质就是一次循环。然后处理起来每次递归完的结果直接处理了。我看了好久这个i的处理我觉得是可以更优雅,但是没想到要怎么做呢。。不过这个性能已经不错了,我去尝试下微调看能不能更进一步。
算了,暂时想不出优化方案,刚刚顺便看了下性能排行第一的代码。性能确实不错,但是我觉得写着没有我的简单。挺麻烦的每次递归都要记录开始下标,反正我不打算改了,下一题吧。
至少有K个重复字符的最长子串
题目:找到给定字符串(由小写字符组成)中的最长子串 T , 要求 T 中的每一字符出现次数都不少于 k 。输出 T 的长度。
思路:怎么说呢,审完题我就有个大胆的想法:找出整个字符串中没有出现k次的字符的下标。。。然后取这些下标中间最大的差值。我感觉实现是可以实现的。就是性能不一定很好,我先去试试再说。
好的吧,代码很容易撸出来了,但是思路有问题。。毕竟有可能这个子串中元素的个数不够。。我就说中等难度不至于这么送分。。我换个思路试试。
做出来了,别管性能多可怜。。我这里用了个递归。就是每个串判断有没有不合格的字符,有的话从这个不合格的开始前后判断,直到整个串合格。然后找出最长的子串。暂时处理的比较粗糙,所以性能不好。我觉得进步空间还是很大的。先附上第一版代码。
class Solution {
public int longestSubstring(String s, int k) {
return dfs(s,k,0,s.length());
}
public int dfs(String s, int k,int start,int end){
int[] ar = new int[26];
for(int i = start;i<end;i++){
ar[s.charAt(i)-'a']++;
}
for(int i = start;i<end;i++){
//当出现不合格的分开判断
if(ar[s.charAt(i)-'a']<k){
return Math.max(dfs(s,k,start,i),dfs(s,k,i+1,end));
}
}
//能走到这说明这个子串都合格
return end-start;
}
}
暂时的想法就是有些没必要的递归直接剪枝就行了。我去优化了。优化半天还是不对,我觉得这个题可能是我思路问题,先附上优化后的代码:
class Solution {
int max = 0;
public int longestSubstring(String s, int k) {
dfs(s,k,0,s.length());
return max;
}
public void dfs(String s, int k,int start,int end){
int[] ar = new int[26];
for(int i = start;i<end;i++){
ar[s.charAt(i)-'a']++;
}
for(int i = start;i<end;i++){
//当出现不合格的分开判断
if(ar[s.charAt(i)-'a']<k){
if(i-start>max) dfs(s,k,start,i);
if(end-i+1>max) dfs(s,k,i+1,end);
return;
}
}
//能走到这说明这个子串都合格
max = Math.max(max,end-start);
}
}
我也不自己钻研了,直接看性能排行第一的代码吧。
唔,怎么说呢,思路其实可以想到,感觉略有类似,但是处理的细节大不相同。我这里直接附上代码:
class Solution {
public int longestSubstring(String s, int k) {
char [] chars = s.toCharArray();
int left =0;
int right = chars.length-1;
if (chars.length ==0||k>chars.length)return 0;
if (k<2)return chars.length;
return helper(chars,k,left,right);
}
private int helper(char [] chars,int k,int left,int right){
//递归出口
if (right - left + 1 < k) return 0;
//统计次数
int [] times = new int [26];
for (int i=left;i<=right;i++){
times[chars[i]-'a']++;
}
//如果字符出现的次数小于K,同时字符子串长度K,要将坐标后移
while (times[chars[left]-'a']<k&&right-left+1>=k){
left++;
}
while (times[chars[right]-'a']<k&&right-left+1>=k){
right--;
}
//对子串再进行递归处理
for (int i=left;i<=right;i++){
//如果字符出现的次数小于K,对于子串进行递归处理
if (times[chars[i]-'a']<k){
return Math.max(helper(chars,k,left,i-1),helper(chars,k,i+1,right));
}
}
//递归出口之一,返回字符子串长度
return right-left+1;
}
}
这个答案因为能看懂,所以我就不多说了,直接下一题。
旋转函数
题目:给定一个长度为 n 的整数数组 A 。假设 Bk 是数组 A 顺时针旋转 k 个位置后的数组,我们定义 A 的“旋转函数” F 为:
F(k) = 0 * Bk[0] + 1 * Bk[1] + ... + (n-1) * Bk[n-1]。
计算F(0), F(1), ..., F(n-1)中的最大值。
注意:
可以认为 n 的值小于 105。
思路:怎么说呢,审完题我的想法就是暴力破解,i从0开始,一直到n-1.下标乘数值。然后再来一层大循环使得下标和数值之间的对应关系往后错位。反正实现是能实现,但是不知道会不会超时。我先去试试吧。
一个好消息,暴力破解没有超时,一个坏消息,性能可怜的不忍直视。我先贴代码:
class Solution {
public int maxRotateFunction(int[] A) {
int res = -2147483648;
int n = A.length;
if(n==0) return 0;
for(int i = 0;i<n;i++){
int sum = 0;
for(int j = 0;j<n;j++){
sum += A[j]*((j+i)%n);
}
res = Math.max(res,sum);
}
return res;
}
}
代码逻辑真的很简单,而且面向测试案例编程,关于最小值问题也是在测试案例报错的情况下修改的。但是性能,一千多ms,我真的觉得不超时是运气好。但是这个题真的没啥思路优化。。所以我直接去看性能排行第一的代码吧。
class Solution {
public int maxRotateFunction(int[] A) {
int l=A.length;
int sum=0;
for(int i=0;i<l;i++){
sum+=A[i];
}
int ans=0;
int a=0;
for(int j=0;j<l;j++){
ans+=j*A[j];
a=ans;
}
for(int k=0;k<l-1;k++){
a=a-sum+l*A[k];
ans=Math.max(ans,a);
}return ans;
}
}
最可悲的事情出现了,看了人家的代码,但是没看懂是为什么。。啧啧,刚刚去看了题解,然后明白了这个代码,我去画个图:
虽然图不好看,但是我觉得还是挺好理解的,重点就是这个规律。我看评论都说这个又是什么数学算法,看懂就好了~下一题。
整数替换
题目:给定一个正整数 n,你可以做如下操作:
1. 如果 n 是偶数,则用 n / 2替换 n。
2. 如果 n 是奇数,则可以用 n + 1或n - 1替换 n。
n 变为 1 所需的最小替换次数是多少?
思路:这是什么神仙题目啊,感觉这几道题目都比较简单,哈哈,目前的想法给定的例子没显出来,但是应该每个数是奇数的时候+1,-1是两个分支。最终选择路径最短的一个。我觉得这个题应该不难,我先去暴力试一下。还有的隐约的思路就是这个数是2的几次方什么的,一会儿再说。
哎,暴力法又成功了,略有兴奋啊,直接附上代码:
class Solution {
int res = Integer.MAX_VALUE;
public int integerReplacement(int n) {
dfs(n,0);
return res;
}
public void dfs(int n,int num){
if(n==1) {
res = Math.min(res,num);
return;
}
if((n&1) == 0){//偶数
dfs(n/2,num+1);
}else{
dfs(n/2,num+2);
dfs(n/2+1,num+2);
}
}
}
递归实现,如果是奇数则分支,而且+1.-1本身也算一次,所以次数+2.代码挺简单易懂的。代码性能4ms,不算好但是比之前的几道题好多了,起码能看了。剩下的我直接去看性能排行第一的代码了:
class Solution {
public int integerReplacement(int n) {
if (n == Integer.MAX_VALUE)
return 32;
if (n <= 3)
return n - 1;
if (n % 2 == 0)
return integerReplacement(n / 2) + 1;
else
return (n & 2) == 0 ? integerReplacement(n - 1) + 1 : integerReplacement(n + 1) + 1;
}
}
还是细节处理问题,毕竟4ms和2ms差别是不大的。这个题逻辑真的挺简单的。
今天的笔记就记到这里,现在刷题也没有每天都刷的精力了,只能说用碎片时间慢慢累计,到了几道题我觉得可以作为一篇文章的时候再一起发布。如果稍微帮到你了记得点个喜欢点个关注,另外也祝大家工作顺顺利利!java技术交流群:130031711,欢迎各位萌新大佬踊跃加入!