522. 最长特殊序列 II【中等题】【每日一题】
思路:【枚举】
过程写在代码注释里,参考自官方题解。但我的执行耗时是2ms~
代码:
class Solution {
public int findLUSlength(String[] strs) {
/**
* 对字符串数组中某一字符串 strs[i] 来说 如果它的一个子序列 sub 是特殊序列,即不为数组中其他字符串的子序列
* 那么这个字符串 strs[i]也是一个特殊序列
* 证明:反证法
* 假设 sub 为特殊序列,那么 sub 在其他字符串中均不会以子序列形式出现
* 那么给 sub 添加任意个字符,它依然不会以子序列形式出现在其他字符串中
* 由于题目要求寻找最长特殊序列,因此我们只需枚举数组中每个字符串,筛选出长度最长的符合特殊序列的字符串,即为我们要寻找的最长特殊序列
* 于是现在问题简化为两步:
* 1.遍历每个字符串,判断其是否特殊序列
* 2.如果字符串满足特殊序列要求,更新最长特殊序列长度 max,函数最后返回 max
* 对于第1步来说,可以转化为,判断当前字符串是否是数组中除自己之外的其他字符串的子序列,是 则说明当前字符串是特殊序列,否 则说明不是特殊序列
*/
int max = -1,n = strs.length;
//双循环遍历字符串数组,筛选strs[i]是否是特殊序列
for (int i = 0; i < n; i++) {
boolean flag = true;
for (int j = 0; j < n; j++) {
if (i == j){
continue;
}
String s1 = strs[i],s2 = strs[j];
int n1 = s1.length(),n2 = s2.length();
//s1的长度比s2还长,s1不可能是s2的子序列,不用接着判断了
if (n1 > n2){
continue;
}else if (n1 == n2){
//s1的长度与s2相等
//如果s1与s2不等,则s1不是s2的子序列
//如果s1与s2相等,则s1是s2的子序列,标志位置为flase,内层for循环退出
if (s1.equals(s2)){
flag = false;
break;
}else {
continue;
}
}
//判断s1是否是s2的子序列(此时 n1 < n2)
int p1 = 0,p2 = 0;
while (p1 < n1 && p2 < n2){
//如果p1字符与p2字符相等,则p1与p2均右移
if (s1.charAt(p1) == s2.charAt(p2)){
p1++;
p2++;
}else {
//否则,只将p2右移
p2++;
}
}
//如果s1正常遍历结束,说明s1是s2的子序列,标志位置为false,内层for循环退出
if (p1 == n1){
flag = false;
break;
}
}
//经过筛选,strs[i]经受住了考验,是特殊序列,则更新最长特殊序列的长度
if (flag){
max = Math.max(max,strs[i].length());
}
}
return max;
}
}
剑指 Offer II 101. 分割等和子集【简单题】
思路:【动态规划】
一阶dp和二阶dp,均看答案写~,这是个动态规划题我是真没想到。
代码:【二阶dp】
class Solution {
public boolean canPartition(int[] nums) {
//要分成两部分,那么数组长度至少为2,
int n = nums.length;
if (n < 2){
return false;
}
//要分成等和的两部分,那每一部分的和肯定是总和的一半,所以应该先求出数组的总和sum,这样就找到了目标和
int sum = 0,max = 0;
for (int num : nums) {
max = Math.max(max,num);
sum += num;
}
//如果sum是奇数,那么数组肯定不可能被分为元素和相等的两部分
if(sum % 2 != 0){
return false;
}
int target = sum / 2;
//max > target,不可能将数组分为元素和相等的两部分
if (max > target){
return false;
}
//max == target 一定可以分为两部分,max为一部分,其余元素为另一部分
if (max == target){
return true;
}
//定义dp数组 dp[i][j]表示数组下标 [0,i) 范围内,选取若干个元素是否能使元素和达到j 那么我们只需要求出dp[n-1][target]是否为true即可
boolean[][] dp = new boolean[n][target+1];
//边界条件: j=0时,dp[i][0] = true ; i=0时,dp[0][nums[0]] = true。
dp[0][nums[0]] = true;
for (int i = 0; i < n; i++) {
dp[i][0] = true;
}
for (int i = 1; i < n; i++) {
for (int j = 1; j <= target; j++) {
if (j >= nums[i]){
//不选择nums[i]元素,则dp[i][j]取决于 dp[i-1][j]
//选择 nums[i]元素,则dp[i][j]取决于 dp[i-1][j-nums[i]]
dp[i][j] = dp[i-1][j] || dp[i-1][j-nums[i]];
}else {
//由于 j < nums[i] 不可能选择nums[i]元素
dp[i][j] = dp[i-1][j];
}
}
}
return dp[n-1][target];
}
}
代码:【一阶dp】
class Solution {
public boolean canPartition(int[] nums) {
//要分成两部分,那么数组长度至少为2,
int n = nums.length;
if (n < 2){
return false;
}
//要分成等和的两部分,那每一部分的和肯定是总和的一半,所以应该先求出数组的总和sum,这样就找到了目标和
int sum = 0,max = 0;
for (int num : nums) {
max = Math.max(max,num);
sum += num;
}
//如果sum是奇数,那么数组肯定不可能被分为元素和相等的两部分
if(sum % 2 != 0){
return false;
}
int target = sum / 2;
//max > target,不可能将数组分为元素和相等的两部分
if (max > target){
return false;
}
//max == target 一定可以分为两部分,max为一部分,其余元素为另一部分
if (max == target){
return true;
}
//定义dp数组 dp[j]表示当前数组可选范围内,选择若干个元素是否能使元素和达到j,那么我们求出当可选范围为整个数组时,dp[target]是否为true即可
boolean[] dp = new boolean[target+1];
//边界条件:j = 0 时,必为 true
dp[0] = true;
for (int num : nums) {
for (int j = target; j >= num; j--) {
/*if (j >= nums[i]){
//选择nums[i]元素,则dp[j]取决于dp[j-nums[i]]
//不选择nums[i]元素,则dp[j]不动
dp[j] = dp[j] || dp[j-nums[i]];
}else {
//j < nums[i]时,不可能选择nums[i] 于是 dp[j]保持不动
dp[j] = dp[j];
}
于是转移方程整理归纳如下
*/
dp[j] = dp[j] || dp[j - num];
}
}
return dp[target];
}
}