1.单词拆分
dp[i]表示s的前i个字符是否可以由字典里的字符组成。
dp[0]初始化为0,dp数组长度 len + 1
需要Hashset存储字典里的字符,方便判断。new HashSet<>(wordDict)即可,不需要一个一个加入。
转移方程:如果dp[j] && set.contains(s.substring(i, j)),那么dp[i] = true,然后break;
class Solution {
public boolean wordBreak(String s, List<String> wordDict) {
HashSet<String> set = new HashSet<>(wordDict);
int len = s.length();
boolean[] dp = new boolean[len + 1];
dp[0] = true;
for (int i = 1; i <= len; i++) {
for (int j = 0; j <= i; j++) {
if (dp[j] && (set.contains(s.substring(j, i)))) {
dp[i] = true;
break;
}
}
}
return dp[len];
}
}
注意i j的取值范围问题。
2.最长递增子序列
跟连续递增序列不同,这个题不依赖nums[i-1],而是要遍历前i-1个数
dp[i]表示前i个数字的最长递增子序列长度,初始化为1即可
转移方程:
j从0遍历到i - 1,dp[i] = Math.max(dp[i], dp[j] + 1))
每次内循环后,用res来更新最大的dp[i],最后返回res。
class Solution {
public int lengthOfLIS(int[] nums) {
int len = nums.length;
int[] dp = new int[len];
Arrays.fill(dp, 1);
int res = 1;
for (int i = 1; i < len; i++) {
for (int j = 0; j < i; j++) {
if (nums[i] > nums[j]) {
dp[i] = Math.max(dp[i], dp[j] + 1);
}
}
res = Math.max(res, dp[i]);
}
return res;
}
}
3.乘积最大子数组
res = nums[0] 用来存全局最大值
maxDp = nums[0] 用来存以当前数结尾的最大乘积
minDp = nums[0] 用来存以当前数结尾的最小乘积
从i = 1开始遍历,更新上面这三个值
maxDp要用temp存起来。
maxDp的更新:maxDp * nums[i]、minDp * nums[i]、nums[i]中取最大值
minDp的更新:temp * nums[i]、minDp * nums[i]、nums[i]中取最小值
res = Math,max(res, maxDp)
class Solution {
public int maxProduct(int[] nums) {
int len = nums.length;
int minDp = nums[0], maxDp = nums[0], res = nums[0];
for (int i = 1; i < len; i++) {
int tempMax = maxDp;
maxDp = Math.max(Math.max(maxDp * nums[i], minDp * nums[i]), nums[i]);
minDp = Math.min(Math.min(tempMax * nums[i], minDp * nums[i]), nums[i]);
res = Math.max(res, maxDp);
}
return res;
}
}
4.分割等和子集
先计算总和,如果总和是奇数直接返回false;
target = sum / 2;
dp[i]表示能否凑出数字i,dp[0] = true;
对于nums中的每一个num,倒着遍历dp[i]。
转移方程:要凑出i,手里有个num,不用num,则看原来的dp[i],用num,则看dp[i-num]
一定要倒着遍历,num才只用一次。否则num会使用多次。
二刷感悟:01背包固定公式:先遍历物品,再倒着遍历容量
class Solution {
public boolean canPartition(int[] nums) {
int sum = 0;
for (int num : nums) {
sum += num;
}
if (sum % 2 == 1) {
return false;
}
int target = sum / 2;
boolean[] dp = new boolean[target + 1];
dp[0] = true;
for (int num : nums) {
for (int j = target; j >= num; j--) {
dp[j] = dp[j] || dp[j - num];
}
}
return dp[target];
}
}
5.最长有效括号
5.1 用栈
用栈存下标,先存一个-1来当基准。
遇到'(',就push(i),如果是 ) 先pop()出来,如果栈空了,则需要把i push进去当新的基准。如果栈不空,则长度是i - st.peek(),更新全局最大值。
class Solution {
public int longestValidParentheses(String s) {
int len = s.length();
if (len == 0) return 0;
Deque<Integer> st = new LinkedList<>();
st.push(-1);
int res = 0;
for (int i = 0; i < len; i++) {
char c = s.charAt(i);
if (c == '(') {
st.push(i);
} else {
st.pop();
if (st.isEmpty()) {
st.push(i);
} else {
res = Math.max(res, i - st.peek());
}
}
}
return res;
}
}
5.2 dp
class Solution {
public int longestValidParentheses(String s) {
int n = s.length();
if (n == 0) return 0;
int[] dp = new int[n]; // dp[i] = 以i结尾的最长有效括号长度
int max = 0;
for (int i = 1; i < n; i++) {
// 只有遇到 ) 才可能形成有效结尾
if (s.charAt(i) == ')') {
// ======================================
// 情况1:... () 最后两位是 ()
// ======================================
if (s.charAt(i-1) == '(') {
// 直接凑一对 + 前面的有效长度
dp[i] = (i >= 2 ? dp[i-2] : 0) + 2;
}
// ======================================
// 情况2:... )) 最后两位是 ))
// ======================================
else {
// 找到和当前 ) 配对的 ( 的位置
int match = i - dp[i-1] - 1;
// 配对位置必须合法,且必须是 (
if (match >= 0 && s.charAt(match) == '(') {
// 长度 = 中间一段 + 新配对的2 + 配对位置前面的有效长度
dp[i] = dp[i-1] + 2;
// 加上前面的有效段(如果有的话)
if (match > 0) {
dp[i] += dp[match - 1];
}
}
}
}
// 更新全局最大值
max = Math.max(max, dp[i]);
}
return max;
}
}
二刷感悟:第一种情况很简单,dp[i] = dp[i-2] + 2;
第二种情况,要找到preindex = i - dp[i-1] - 1,也就是跟位置i配对的左括号
如果preindex >= 0 && s.charAt(preidex) == '(',那么dp[i] = dp[i-1] + 2;
更进一步,如果preindex - 1 >= 0,dp[i] += dp[preindex-1]。因为可能出现()((()))这样的情况,所以要加上左边界之前的已经有效的括号序列。

243

被折叠的 条评论
为什么被折叠?



