1.最长有效括号
这道题我们首先可以用栈来解决,当“(”进入时,将其压入,当到“)”的时候,我们检查栈是否为空,如果不为空,那说明里面有"(",那我们将其弹出进行匹配,然后再进行判断,如果是空的,那说明全部匹配完成,我们直接更新最大的ans有效括号长度,如果不为空,那么说明这里还不是最大有效匹配位置呗,就再更新它目前的位置就好了,当我们压入“)”,发现是空,那我们start更新到这个位置,继续往后判断是否存在更长的有效括号。
class Solution {
public int longestValidParentheses(String s) {
Stack<Integer> st =new Stack<Integer>();
int ans=0;
for(int i =0,start=0;i<s.length();i++){
if(s.charAt(i)=='('){
st.push(i);
}else{
if(!st.isEmpty()){
st.pop();
if(st.isEmpty()){
ans=Math.max(ans,i-start+1);
}
else{
ans = Math.max(ans,i - st.peek());
}
}else start =i+1;
}
}
return ans;
}
}
这道题目主要还是想介绍用动态规划来写,首先动态规划的思想是,分成子问题来求解,那么首先我们先推导出:
s[i]=‘)’ 且 s[i−1]=‘(’,也就是字符串形如 “……()”,我们可以推出:
dp[i]=dp[i−2]+2
上面是第一个子问题,接下来我们放大情况:
类似于这种……((……)),我们看一下下面的图来看下,所以我们要判断第i -1- dp[i - 1]个字符是否是"(",如果是,那么递推公式是dp[i]=dp[i - 1] + 2 + dp[i - dp[i - 1] - 2],这里dp[i - dp[i - 1] - 2]是第一个省略号构成的有效括号,这个不要忘了
class Solution {
public int longestValidParentheses(String s) {
int maxans =0;
int[] dp=new int[s.length()];
for(int i =1;i<s.length();i++){
if(s.charAt(i)==')'){
if(s.charAt(i-1)=='('){
dp[i]=(i>=2?dp[i-2]:0)+2;
}else if(i-dp[i-1]>0&&s.charAt(i-dp[i-1]-1)=='('){
dp[i]=dp[i-1]+((i-dp[i-1])>=2?dp[i-dp[i-1]-2]:0)+2;
}
maxans=Math.max(maxans,dp[i]);
}
}
return maxans;
}
}
2.接雨水
这道题的话,我们首先可以用双指针的方法来求解,我们定义两个指针left和right,都只能往中间的方向靠拢,leftmax和rightmax分别用来记录目前遇到的left和right经过的位置下最大高度。
我们要理解为什么能接到雨水,是因为有凹字形的中间小缺口才能装到水,那这需要我们双指针在遍历高度时,能找到这样的高度差,left和right只要形成了高度差,我们不用管中间是个什么情况,我们只要将当前已知单方向上知道的最大高度减去当前高度,求出目前单位的水位差就OK了。下面是整体的步骤:
维护两个指针 left 和 right,以及两个变量 leftMax 和 rightMax,初始时
left=0,right=n−1,leftMax=0,rightMax=0。
指针 left 只会向右移动,指针 right 只会向左移动,在移动指针的过程中维护两个变量 leftMax 和 rightMax 的值。
- 使用 height[left] 和 height[right] 的值更新 leftMax 和 rightMax 的值;
- 如果 height[left]<height[right],则必有 leftMax<rightMax,下标 left 处能接的雨水量等于 leftMax−height[left],将下标 left 处能接的雨水量加到能接的雨水总量,然后将 left 加 1(即向右移动一位);
- 如果 height[left]≥height[right],则必有 leftMax≥rightMax,下标 right 处能接的雨水量等于 rightMax−height[right],将下标 right 处能接的雨水量加到能接的雨水总量,然后将 right 减 1(即向左移动一位)。
class Solution {
public int trap(int[] height) {
int ans =0;
int left =0,right =height.length-1;
int leftMax=0,rightMax =0;
while(left<right){
leftMax=Math.max(leftMax,height[left]);
rightMax=Math.max(rightMax,height[right]);
if(height[left]<height[right]){
ans+=leftMax-height[left];
left++;
}else{
ans +=rightMax-height[right];
right--;
}
}
return ans;
}
}
常规方法讲完,让我们来看看动态规划的解法:
其实思路是差不多的,也是看左右两边遍历的到的高度,取最小的值,然后减去当前的高度。
leftMax[0]=height[0],rightMax[n−1]=height[n−1]。两个数组的其余元素的计算如下:
- 当 1≤i≤n−1 时,leftMax[i]=max(leftMax[i−1],height[i]);
- 当 0≤i≤n−2 时,rightMax[i]=max(rightMax[i+1],height[i])。
因此可以正向遍历数组 height 得到数组 leftMax 的每个元素值,反向遍历数组 height 得到数组 rightMax 的每个元素值。
在得到数组 leftMax 和 rightMax 的每个元素值之后,对于 0≤i<n,下标 i 处能接的雨水量等于 min(leftMax[i],rightMax[i])−height[i]。遍历每个下标位置即可得到能接的雨水总量。
class Solution {
public int trap(int[] height) {
int n= height.length;
if(n==0){
return 0;
}
int[] leftMax =new int[n];
leftMax[0]=height[0];
for(int i=1;i<n;i++){
leftMax[i]=Math.max(leftMax[i-1],height[i]);
}
int[] rightMax =new int[n];
rightMax[n-1]=height[n-1];
for(int i =n-2;i>=0;i--){
rightMax[i]=Math.max(rightMax[i+1],height[i]);
}
int ans =0;
for(int i =0;i<n;i++){
ans+=Math.min(leftMax[i],rightMax[i])-height[i];
}
return ans;
}
}
3.编辑距离
看评论区说这道题是很多大厂的笔试题,像字节,滴滴等一些公司都考到了,还是要认真对待这个动态规划的题目的。
思路:
动态规划
定义 dp[i][j],dp[i][j] 代表 word1 中前 i 个字符,变换到 word2 中前 j 个字符,最短需要操作的次数
需要考虑 word1 或 word2 一个字母都没有,即全增加/删除的情况,所以预留 dp[0][j] 和 dp[i][0]
状态转移
增,dp[i][j] = dp[i][j - 1] + 1删,dp[i][j] = dp[i - 1][j] + 1
改,dp[i][j] = dp[i - 1][j - 1] + 1
按顺序计算,当计算 dp[i][j] 时,dp[i - 1][j] , dp[i][j - 1] , dp[i - 1][j - 1] 均已经确定了
配合增删改这三种操作,需要对应的 dp 把操作次数加一,取三种的最小
如果刚好这两个字母相同 word1[i - 1] = word2[j - 1] ,那么可以直接参考 dp[i - 1][j - 1] ,操作不用加一
class Solution {
public int minDistance(String word1, String word2) {
int n1 = word1.length();
int n2 = word2.length();
int[][] dp = new int[n1 + 1][n2 + 1];
// 第一行
for (int j = 1; j <= n2; j++) dp[0][j] = dp[0][j - 1] + 1;
// 第一列
for (int i = 1; i <= n1; i++) dp[i][0] = dp[i - 1][0] + 1;
for (int i = 1; i <= n1; i++) {
for (int j = 1; j <= n2; j++) {
if (word1.charAt(i - 1) == word2.charAt(j - 1)) dp[i][j] = dp[i - 1][j - 1];
else dp[i][j] = Math.min(Math.min(dp[i - 1][j - 1], dp[i][j - 1]), dp[i - 1][j]) + 1;
}
}
return dp[n1][n2];
}
}