leetcode第5~6题

简介: 我们可以看到,图形其实是有周期的,0,1,2 ... 7 总过 8 个,然后就又开始重复相同的路径。周期的计算就是 cycleLen = 2 × numRows - 2 = 2 × 5 - 2 = 8 个。我们发现第 0 行和最后一行一个周期内有一个字符,所以第一个字符下标是 0 ,第二个字符下标是 0 + cycleLen = 8,第三个字符下标是 8 + cycleLen = 16 。其他行都是两个字符。第 1 个字符和第 0 行的规律是一样的。第 2 个字符其实就是下一个周期的第 0 行的下标减去当前行。什么意思呢?我们求一下第 1 行第 1 个周期内的第 2 个字符

top5

给定一个字符串,输出最长的回文子串。回文串指的是正的读和反的读是一样的字符串,例如 "aba","ccbbcc"。

解法一 暴力破解

暴力求解,列举所有的子串,判断是否为回文串,保存最长的回文串。

publicbooleanisPalindromic(Strings) {
intlen=s.length();
for (inti=0; i<len/2; i++) {
if (s.charAt(i) !=s.charAt(len-i-1)) {
returnfalse;
            }
        }
returntrue;
    }
// 暴力解法publicStringlongestPalindrome(Strings) {
Stringans="";
intmax=0;
intlen=s.length();
for (inti=0; i<len; i++)
for (intj=i+1; j<=len; j++) {
Stringtest=s.substring(i, j);
if (isPalindromic(test) &&test.length() >max) {
ans=s.substring(i, j);
max=Math.max(max, ans.length());
            }
        }
returnans;
}

时间复杂度:两层 for 循环 O(n²),for 循环里边判断是否为回文,O(n),所以时间复杂度为 O(n³)。

空间复杂度:O(1),常数个变量。


解法二 最长公共子串

根据回文串的定义,正着和反着读一样,那我们是不是把原来的字符串倒置了,然后找最长的公共子串就可以了。例如,S = " caba",S' = " abac",最长公共子串是 "aba",所以原字符串的最长回文串就是 "aba"。

关于求最长公共子串(不是公共子序列),有很多方法,这里用动态规划的方法,可以先阅读下边的链接。

https://blog.csdn.net/u010397369/article/details/38979077

https://www.kancloud.cn/digest/pieces-algorithm/163624

整体思想就是,申请一个二维的数组初始化为 0,然后判断对应的字符是否相等,相等的话

arr [ i ][ j ] = arr [ i - 1 ][ j - 1] + 1 。

当 i = 0 或者 j = 0 的时候单独分析,字符相等的话 arr [ i ][ j ] 就赋为 1 。

arr [ i ][ j ] 保存的就是公共子串的长度。


publicStringlongestPalindrome(Strings) {
if (s.equals(""))
return"";
Stringorigin=s;
Stringreverse=newStringBuffer(s).reverse().toString(); //字符串倒置intlength=s.length();
int[][] arr=newint[length][length];
intmaxLen=0;
intmaxEnd=0;
for (inti=0; i<length; i++)
for (intj=0; j<length; j++) {
if (origin.charAt(i) ==reverse.charAt(j)) {
if (i==0||j==0) {
arr[i][j] =1;
                } else {
arr[i][j] =arr[i-1][j-1] +1;
                }
            }
if (arr[i][j] >maxLen) { 
maxLen=arr[i][j];
maxEnd=i; //以 i 位置结尾的字符            }
        }
    }
returns.substring(maxEnd-maxLen+1, maxEnd+1);
}

再看一个例子,S = "abc435cba",S’ = "abc534cba" ,最长公共子串是 "abc" 和 "cba" ,但很明显这两个字符串都不是回文串。

所以我们求出最长公共子串后,并不一定是回文串,我们还需要判断该字符串倒置前的下标和当前的字符串下标是不是匹配。

比如 S = " caba ",S' = " abac " ,S’ 中 aba 的下标是 0 1 2 ,倒置前是 3 2 1,和 S 中 aba 的下标符合,所以 aba 就是我们需要找的。当然我们不需要每个字符都判断,我们只需要判断末尾字符就可以。

image.png



首先 i ,j 始终指向子串的末尾字符。所以 j 指向的红色的 a 倒置前的下标是 beforeRev = length - 1 - j = 4 - 1 - 2 = 1,对应的是字符串首位的下标,我们还需要加上字符串的长度才是末尾字符的下标,也就是 beforeRev + arr[ i ] [ j ] - 1 = 1 + 3 - 1 = 3,因为 arr[ i ] [ j ] 保存的就是当前子串的长度,也就是图中的数字 3 。此时再和它与 i 比较,如果相等,则说明它是我们要找的回文串。

之前的 S = "abc435cba",S' = "abc534cba" ,可以看一下图示,为什么不符合。

image.png


当前 j 指向的 c ,倒置前的下标是 beforeRev = length - 1 - j = 9 - 1 - 2 = 6,对应的末尾下标是 beforeRev + arr[ i ] [ j ] - 1 = 6 + 3 - 1 = 8 ,而此时 i = 2 ,所以当前的子串不是回文串。

代码的话,在上边的基础上,保存 maxLen 前判断一下下标匹不匹配就可以了。


publicStringlongestPalindrome(Strings) {
if (s.equals(""))
return"";
Stringorigin=s;
Stringreverse=newStringBuffer(s).reverse().toString();
intlength=s.length();
int[][] arr=newint[length][length];
intmaxLen=0;
intmaxEnd=0;
for (inti=0; i<length; i++)
for (intj=0; j<length; j++) {
if (origin.charAt(i) ==reverse.charAt(j)) {
if (i==0||j==0) {
arr[i][j] =1;
                } else {
arr[i][j] =arr[i-1][j-1] +1;
                }
            }
/**********修改的地方*******************/if (arr[i][j] >maxLen) {
intbeforeRev=length-1-j;
if (beforeRev+arr[i][j] -1==i) { //判断下标是否对应maxLen=arr[i][j];
maxEnd=i;
                }
/*************************************/            }
        }
returns.substring(maxEnd-maxLen+1, maxEnd+1);
}

时间复杂度:两层循环,O(n²)。

空间复杂度:一个二维数组,O(n²)。


top6


就是给定一个字符串,然后按写竖着的 「z」的方式排列字符,就是下边的样子。

然后按行的方式输出每个字符,第 0 行,第 1 行,第 2 行 ....

解法一

按照写 Z 的过程,遍历每个字符,然后将字符存到对应的行中。用 goingDown 保存当前的遍历方向,如果遍历到两端,就改变方向。

publicStringconvert(Strings, intnumRows) {
if (numRows==1) returns;
List<StringBuilder>rows=newArrayList<>();
for (inti=0; i<Math.min(numRows, s.length()); i++)
rows.add(newStringBuilder());
intcurRow=0;
booleangoingDown=false;
for (charc : s.toCharArray()) {
rows.get(curRow).append(c);
if (curRow==0||curRow==numRows-1) goingDown=!goingDown; //遍历到两端,改变方向curRow+=goingDown?1 : -1;
        }
StringBuilderret=newStringBuilder();
for (StringBuilderrow : rows) ret.append(row);
returnret.toString();
    }

时间复杂度:O(n),n 是字符串的长度。

空间复杂度:O(n),保存每个字符需要的空间。

解法二

找出按 Z 形排列后字符的规律,然后直接保存起来。

我们可以看到,图形其实是有周期的,0,1,2 ... 7 总过 8 个,然后就又开始重复相同的路径。周期的计算就是 cycleLen = 2 × numRows - 2 = 2 × 5 - 2 = 8 个。

我们发现第 0 行和最后一行一个周期内有一个字符,所以第一个字符下标是 0 ,第二个字符下标是 0 + cycleLen = 8,第三个字符下标是 8 + cycleLen = 16 。

其他行都是两个字符。

第 1 个字符和第 0 行的规律是一样的。

第 2 个字符其实就是下一个周期的第 0 行的下标减去当前行。什么意思呢?

我们求一下第 1 行第 1 个周期内的第 2 个字符,下一个周期的第 0 行的下标是 8 ,减去当前行 1 ,就是 7 了。

我们求一下第 1 行第 2 个而周期内的第 2 个字符,下一个周期的第 0 行的下标是 16 ,减去当前行 1 ,就是 15 了。

我们求一下第 2 行第 1 个周期内的第 2 个字符,下一个周期的第 0 行的下标是 8 ,减去当前行 2 ,就是 6 了。

当然期间一定要保证下标小于 n ,防止越界。

可以写代码了。


publicStringconvert(Strings, intnumRows) {
if (numRows==1)
returns;
StringBuilderret=newStringBuilder();
intn=s.length();
intcycleLen=2*numRows-2;
for (inti=0; i<numRows; i++) {
for (intj=0; j+i<n; j+=cycleLen) { //每次加一个周期ret.append(s.charAt(j+i));
if (i!=0&&i!=numRows-1&&j+cycleLen-i<n) //除去第 0 行和最后一行ret.append(s.charAt(j+cycleLen-i));
        }
    }
returnret.toString();
}


时间复杂度:O(n),虽然是两层循环,但第二次循环每次加的是 cycleLen ,无非是把每个字符遍历了 1 次,所以两层循环内执行的次数肯定是字符串的长度。

空间复杂度:O(n),保存字符串。

相关文章
|
8月前
|
C++ Python
leetcode-283:移动零
leetcode-283:移动零
31 0
|
5月前
|
算法
LeetCode第66题加一
LeetCode第66题"加一"的解题方法,通过遍历数组从后向前处理每一位的加法,并考虑进位情况,最终实现给定数字加一的功能。
LeetCode第66题加一
|
8月前
leetcode-1219:黄金矿工
leetcode-1219:黄金矿工
90 0
顺手牵羊(LeetCode844.)
好多同学说这是双指针法,但是我认为叫它顺手牵羊法更合适
89 0
LeetCode 354. Russian Doll Envelopes
给定一些标记了宽度和高度的信封,宽度和高度以整数对形式 (w, h) 出现。当另一个信封的宽度和高度都比这个信封大的时候,这个信封就可以放进另一个信封里,如同俄罗斯套娃一样。 请计算最多能有多少个信封能组成一组“俄罗斯套娃”信封(即可以把一个信封放到另一个信封里面)。
87 0
LeetCode 354. Russian Doll Envelopes
|
算法 Java
一和零(LeetCode 474)
一和零(LeetCode 474)
104 0
|
存储 算法
leetcode第49题
时间复杂度:两层 for 循环,再加上比较字符串,如果字符串最长为 K,总的时间复杂度就是 O(n²K)。 空间复杂度:O(NK),用来存储结果。 解法一算是比较通用的解法,不管字符串里边是大写字母,小写字母,数字,都可以用这个算法解决。这道题的话,题目告诉我们字符串中只有小写字母,针对这个限制,我们可以再用一些针对性强的算法。 下边的算法本质是,我们只要把一类的字符串用某一种方法唯一的映射到同一个位置就可以。
191 0
leetcode第49题
|
算法
leetcode第34题
第二种思路,参考这里。 我们开始更新 start 的时候,是 mid + 1,如果剩两个元素,例如 2 4,target = 6 的话,此时 mid = 0,start = mid + 1 = 1,我们返回 start + 1 = 2。如果 mid 是右端点,那么 mid = 1,start = mid + 1 = 2,这样就可以直接返回 start 了,不需要在返回的时候加 1 了。
114 0
leetcode第34题
|
机器学习/深度学习
leetcode第50题
求幂次方,用最简单的想法,就是写一个 for 循环累乘。 至于求负幂次方,比如 2^{-10}2−10,可以先求出 2^{10}210,然后取倒数,1/2^{10}1/210 ,就可以了 double mul = 1; if (n > 0) { for (int i = 0; i < n; i++) { mul *= x; } } else { n = -n; for (int i = 0; i < n; i++) { mul *= x; } mul = 1 / mul; }
111 0
leetcode第50题