5. 最长回文子串
思路分析:
先看一个成功的例子:abba,A[1]和A[4]匹配,A[2]和A[3]匹配,更符合实际来看,当我们不看A[4]时,abb,最长回文串为bb,当我们更新到abba时候,最长回文串更新到abba,所以整个的循环不变量是相应的最长回文串
j | i | ||
---|---|---|---|
a | b | b | a |
dp:
初始化:
0 | 0 | 0 | 0 |
---|---|---|---|
0 | 0 | 0 | 0 |
0 | 0 | 0 | 0 |
0 | 0 | 0 | 0 |
结果:
i\j | 0 | 1 | 2 | 3 |
---|---|---|---|---|
0 | 1 | |||
1 | 0 | 1 | ||
2 | 0 | 1 | 1 | |
3 | 1 | 0 | 0 | 1 |
递推方程:
f(i)(j):j到i是否形成了回文串
递推方程:
f
(
i
)
(
j
)
=
{
1
,
i
−
j
<
=
1
d
p
[
i
−
1
]
[
j
+
1
]
,
i
−
j
>
1
f(i)(j)=\begin{cases} 1,i-j<=1\\ dp[i-1][j+1], i-j>1\end{cases}
f(i)(j)={1,i−j<=1dp[i−1][j+1],i−j>1
代码实现:
class Solution {
public String longestPalindrome(String s) {
int n = s.length();
if(n<1){
return "";
}
int dp[][] = new int[n][n];
int start = 0;
int end = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j <= i; j++) {
if (s.charAt(i)==s.charAt(j)){//i,j位置是一样
if (i-j<=1 || dp[i-1][j+1]==1){
dp[i][j] = 1;
if (i-j>=end-start){
end = i;
start = j;
}
}
}
}
}
return s.substring(start,end+1);
}
}
对存储空间做出优化(下三角可以优化为一维数组):
dp[i] [j] -->dp[i(i+1)/2+j]
class Solution {
public String longestPalindrome(String s) {
int n = s.length();
if(n<=0){
return "";
}
int length = n*(n+1)/2;
int dp[] = new int[length];
int start = 0;
int end = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j <= i; j++) {
if (s.charAt(i)==s.charAt(j)){
if (i-j<=2 || dp[ (i-1)*i/2+j+1]==1){
dp[ i*(i+1)/2+j] = 1;
if (i-j>=end-start){
end = i;
start = j;
}
}
}
}
}
return s.substring(start,end+1);
}
}
32. 最长有效括号
示例:()(())
输出:6
示例:(()()
输出: 4
分析:以()()()为例,首先(单个是不能形成有效括号的,当我们遍历这个串的时候,()能形成配对,同理,()()也能形成配对。扩展一下,能形成配对的一定是 ) 结尾的,例如:(())
当子串以(结尾时,无论如何都是匹配到另一半的,因为)一定在其左边,都已经是结尾了,哪来的左边
确定了以)结尾之后,我们就考虑其前一个位置元素是什么,
-
前一个为 ( 直接配对
-
前一个为 ) ,我们需要找到 ) 的最大的形成的串是多长,然后在寻找其和结尾配对的位置是否是(
例如:
0 1 2 3 4 i ( ( ( ) ) 当i指向最后一个位置的时候,我们发现其前一个,也就是三号位是 ) 我们找到这个)匹配的最大配对括号,为2,然后判断4 - 2 - 1,也就是1的位置是否是(,如果是,则4位置的最大配对子串就是4了,当然了这还是有一个小问题的,我们再看另一个例子:
0 1 2 3 4 5 6 ( ) ( ( ) ) ) 当i指向5的时候,通过上一例子,不难判断,子串为4,但是,实际为6,因为01位置形成的串是可以和2345结合起来的,所以我们得出了以下的递推方程
dp[i]:表示[0,i]以s.charAt(i)结尾的形成的最大的配对子串
d p [ i ] = { d p [ i − 2 ] + 2 , 前 一 个 位 置 为 ( d p [ i − d p [ i − 1 ] − 2 ] + d p [ i − 1 ] + 2 , 前 一 个 位 置 为 ) dp[i] = \begin{cases} dp[i-2] + 2 ,前一个位置为( \\dp[i-dp[i-1]-2]+dp[i-1]+2 ,前一个位置为)\end{cases} dp[i]={dp[i−2]+2,前一个位置为(dp[i−dp[i−1]−2]+dp[i−1]+2,前一个位置为)
class Solution {
public static int longestValidParentheses(String s) {
//最后一位是)才行,
//1,前一位是(匹配
//2,前一位是),找到和)的匹配的(,其前一位是(,匹配成功,反则之
int max = 0;
int n = s.length();
int dp[] = new int[n];
for(int i = 1;i < n;i++) {
if((s.charAt(i)==')')) {
if((s.charAt(i-1) == ')')) {
if((i-1-dp[i-1]>=0) && (s.charAt(i-1-dp[i-1]) == '(')) {
dp[i] = i-dp[i-1]-2>=0?(dp[i-dp[i-1]-2])+dp[i-1]+2:dp[i-1]+2;
}
}else{
dp[i] = 2 + ((i-2)>=0?dp[i-2]:0);
}
max = Math.max(max, dp[i]);
}
}
return max;
}
}
53. 最大子序和
示例:
输入: [-2,1,-3,4,-1,2,1,-5,4],
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
分析:连续的子数组和最大,注意是连续的
-2 | 1 | -3 | 4 | -1 | 2 | 1 | -5 | 4 |
---|---|---|---|---|---|---|---|---|
-2 | 1 | -2 | 4 | 3 | 5 | 6 | 1 | 5 |
每当遍历到 i 位置的元素,其位置的元素我们给他设置为连续子数组的一部分,也就是说当前部分的最大值为nums[i]或者是nums[i]+dp[i-1],无论如何,当前位置的元素是包含在其内部的,也就是dp[i]的含义为在当前 i 位置形成的最大值(包含当前位置),然后在每个位置取最大,就得到了结果
f
(
i
)
=
{
M
a
x
(
d
p
[
i
−
1
]
+
n
u
m
s
[
i
]
,
n
u
m
s
[
i
]
)
f(i)=\begin{cases} Max(dp[i-1]+nums[i],nums[i])\end{cases}
f(i)={Max(dp[i−1]+nums[i],nums[i])
public int maxSubArray(int[] nums) {
int n = nums.length;
int dp[] = new int[n];
int res = nums[0];
for(int i = 0;i < n;i++){
dp[i] = i==0?nums[0]:Math.max(nums[i],dp[i-1]+nums[i]);
res = Math.max(res,dp[i]);
}
return res;
}
62. 不同路径
示例
输入: m = 3, n = 2
输出: 3
解释:
从左上角开始,总共有 3 条路径可以到达右下角。
1. 向右 -> 向右 -> 向下
2. 向右 -> 向下 -> 向右
3. 向下 -> 向右 -> 向右
起点 | 1 |
---|---|
1 | 2 |
1 | 终点(3) |
分析:当我们到达终点时,来源只有左和上,所以实际结果也就是dp[i] [j] = dp[i] [j-1] + dp[i-1] [j]
d
p
[
i
]
[
j
]
=
d
p
[
i
]
[
j
−
1
]
+
d
p
[
i
−
1
]
[
j
]
dp[i] [j] = dp[i] [j-1] + dp[i-1] [j]
dp[i][j]=dp[i][j−1]+dp[i−1][j]
public static int uniquePaths(int m, int n) {
int dp[][] = new int[n+1][m+1];
for(int i = 1;i <= n;i++) {
for(int j = 1;j <= m;j++) {
if(i==1&&j==1) {
dp[i][j] = 1;
continue;
}
dp[i][j] = dp[i-1][j] + dp[i][j-1];
}
}
return dp[n][m];
}
63. 不同路径 II
示例 1:
输入:
[
[0,0,0],
[0,1,0],
[0,0,0]
]
输出: 2
解释:
3x3 网格的正中间有一个障碍物。
从左上角到右下角一共有 2 条不同的路径:
1. 向右 -> 向右 -> 向下 -> 向下
2. 向下 -> 向下 -> 向右 -> 向右
这一题与上面一题的解法思路是一样的。在遇到障碍的时候,我们直接给其置为0即可
class Solution {
public static int uniquePathsWithObstacles(int[][] obstacleGrid) {
int m = obstacleGrid.length;
int n = obstacleGrid[0].length;
if (m == 1&&n==1&&obstacleGrid[0][0]==0){//只有一个
return 1;
}
if (obstacleGrid[m-1][n-1] == 1 || obstacleGrid[0][0] ==1){//出入口堵死了
return 0;
}
if (m == 1&&n==1 && obstacleGrid[0][0] == 1){//只有一个
return 0;
}
int dp[][] = new int[m+1][n+1];
if (m>=2) {
dp[2][0] = 1;
}
if (n>=2) {
dp[0][2] = 1;
}
for(int i = 1;i <= m;i++) {
for(int j = 1;j <= n;j++) {
if(obstacleGrid[i-1][j-1] == 1) {
dp[i][j] = 0;
continue;
}
dp[i][j] = dp[i-1][j] + dp[i][j-1];
}
}
return dp[m][n];
}
}
64. 最小路径和
示例:
输入:
[
[1,3,1],
[1,5,1],
[4,2,1]
]
输出: 7
解释: 因为路径 1→3→1→1→1 的总和最小。
分析:
1 | 3 | 1 |
---|---|---|
1 | 5 | 1 |
4 | 2 | 1 |
每在一个位置,其产生最小的值有两个来源,一个是其左边,一个是其上边,最小的产生为当前位置的值加上左,和当前位置加上上边的两者其中较小的一个
d
p
[
i
]
[
j
]
=
m
i
n
(
g
r
i
d
[
i
]
[
j
]
+
d
p
[
i
−
1
]
[
j
]
,
g
r
i
d
[
i
]
[
j
]
+
d
p
[
i
]
[
j
−
1
]
)
dp[i][j] = min(grid[i][j]+dp[i-1][j],grid[i][j]+dp[i][j-1])
dp[i][j]=min(grid[i][j]+dp[i−1][j],grid[i][j]+dp[i][j−1])
class Solution {
public static int minPathSum(int[][] grid) {
int m = grid.length;
int n = grid[0].length;
int dp[][] = new int[m+1][n+1];
for(int i = 1;i <= m;i++) {
for(int j = 1;j <= n;j++) {
if(i==1&&j==1) {
dp[i][j] = grid[i-1][j-1];
continue;
}
if(i==1) {
dp[i][j] = grid[i-1][j-1] + dp[i][j-1];//第一行不考虑上
}else if(j == 1) {
dp[i][j] = grid[i-1][j-1] + dp[i-1][j];//第一列不考虑左
}else {
dp[i][j] = Math.min(dp[i-1][j]+grid[i-1][j-1], dp[i][j-1]+grid[i-1][j-1]);
}
}
}
return dp[m][n];
}
}