20、难度简单:通过率45%
方法一:原创:失败的暴力:仅用于我自己日后审视,这里面有我一开始没想到但后续出现的各种情况以及相应的应对
原理:正常判断当前项和下一项是否为一堆括号,并添加左括号群体(如"{{{}}}")的应对措施
失败点:如情况"(([]){})"下面代码会将true判断为false
HashMap相关知识网站:https://m.runoob.com/java/java-hashmap.html?ivk_sa=1024320u
class Solution {
public boolean isValid(String s) {
int len = s.length();
if(len<=1){
// 如情况"{"
return false;
}
// 创建哈希表,注意:哈希表不能用char等原始类型,需要用Character等封装类
Map<Character, Character> yes = new HashMap<Character, Character>();
yes.put('{', '}');
yes.put('[', ']');
yes.put('(', ')');
// 以下部分作用在for循环内有描述
int left = 0;
int right = 0;
int end = 0;
boolean bool = true;
boolean boolStart = false;
// 每轮循环结束后的i自增判断据情况定义,这里不写
for(int i = 0; i < len;){
// 如情况"][]"、"()][]"
if(!yes.containsKey(s.charAt(i))){
return false;
}
// 若左括号群体事件开始、,如"{[]}"那么在i=1时会经过下面if判断i+=2并且continue
// 直接开启下一轮循环,此时还会再经历一次该if,这时i=len-1,调用s.charAt(i+1)溢出了
// 所以前面加入 i+1<len &&,这样在溢出前就会结束当前if
if(i+1<len && yes.get(s.charAt(i)) == s.charAt(i+1)){
i+=2;
// 若出现匹配括号,有两种可能:
// 1、前面无左括号群体:如({[]})
// 2、前面有左括号群体:如()[]{}、({[]}){}
if(!bool){
// 若前面有左括号群体
// 那就意味着可以开始左括号群的匹配了
boolStart = true;
// 右括号群体起始下标
right = i;
// 用于终止左右括号群体匹配的下标,也就是当前成对括号的左括号下标
end = i-2;
} else {
// 如情况 (){[]}[],当 i=5时也就是正式到达右括号群体第一个右括号时
// 若continue不是写在else里,那么会直接进入下一层循环,
// 进入该if判断 } 和 [ 是否匹对
// 但hashmap中没有键},这就会报错,所以在进入右括号群体后我们应立跳过continue
// 进入下面if(boolStart){里的for循环
continue;
}
// 如情况"([]){"
if(i+1 == len-1){
return false;
}
// 应对情况如"{[]}",与上面if原理同理
}else if(i+1<len && !yes.containsKey(s.charAt(i+1))){
// 如果当前元素和下一个元素不构成括号
// 且下一个元素不是 {、(、[ 这类的左括号那就返回false
return false;
}else if(bool){
// 以上情况可以应对诸如 "()[]{}"、"(]" 等情况
// 以下为针对 "{[]}" 的情况
// 可以发现,这种情况的特点就是出现一个或多个左括号后(标记开始的左括号)
// 必然会有一个成对括号出现
// 该括号一经出现,后续第一个必为和开始的左括号匹配的右括号
// 在结束当前左括号群事件前关闭该if保护left值不变
bool = false;
left = i;
}
if(boolStart){
for(; left < end; left++,right++){
if(right == len){
return false;
}
if(!(yes.get(s.charAt(left)) == s.charAt(right))){
return false;
}
}
// 左括号群体和右括号群体配对完成,可以再次开启针对左括号群体的if
bool = true;
// 同时因为右括号群体也检测完了,所以i的下标要直接跳向最右括号的下一项
if(right == len-1){
return true;
} else {
i = right;
}
} else {
// 当左括号群体事件开始,需要一个 i 自增语句
i++;
}
}
if(!bool){
// 如情况"((",也就是左循环群体还未解决就跳出for循环了(若解决了cool应为true)
return false;
}else{
return true;
}
}
}
方法二:原创:栈:
原理:栈是先进后出,这里建议举例画图辅助理解
栈Stack能进行的操作可以在https://www.runoob.com/java/java-stack-class.html得到
遍历字符串,有两种处理当前项的方式:
(1)、若当前项和下一项不是一对括号,且这两项都属于左括号,那就将这两项压入栈中,下标+2。若是一对括号,下标+2。
(2)、若当前项属于右括号,也就是 }、]、) 那就从栈里取值,若取出的值和该项不组成一对括号,就返回false;若属于左括号,重复(1)
class Solution {
public static boolean isValid(String s) {
int len = s.length();
if(len<=1){
return false;
}
// 创建哈希表
Map<Character, Character> yes = new HashMap<Character, Character>();
yes.put('{', '}');
yes.put('[', ']');
yes.put('(', ')');
// 创建栈
Stack <Character> left = new Stack<Character>();
// 奇数项必为false
if (n % 2 == 1) {
return false;
}
// 第一项为右括号
if(s.charAt(i) != yes.get(left.pop())){
return false;
} else if (yes.containsKey(s.charAt(len-1))){
// 最后一项为左括号
return false;
}
// 写成i<len-1:如情况 [[[ 会报错
// 进入if(i+1<len && yes.containsKey(s.charAt(i+1)))后
// i+2=0+2=2,由于i+1<len &&导致下一轮循环哪个 if 都进不去 i 永远不自增无限循环
// 存在的问题:如情况"{[]}",下标到达不了 } 的位置(也就是最后一项),导致left无法清空把true的判断为false
// 解决措施:在for循环外判断字符串最后一项是否和栈里的能组成一队
for(int i = 0; i < len - 1;){
// 当前项为右括号
if(!yes.containsKey(s.charAt(i))){
if(left.empty()){
return false;
}
if(s.charAt(i) != yes.get(left.pop())){
return false;
}
i++;
continue;
}
// 当前项为左括号
// i+1<len &&用于防溢出,若溢出了,那执行s.charAt(i+1)会报错
if(i+1<len && yes.get(s.charAt(i)) == s.charAt(i+1)){
// 当前项和下一项构成一对括号
i+=2;
}else if(i+1<len && yes.containsKey(s.charAt(i+1))){
// 当前项和下一项都是左括号
left.push(s.charAt(i));
left.push(s.charAt(i+1));
i+=2;
}else if(i+1<len && !yes.containsKey(s.charAt(i+1))){
// 下一项为右括号
return false;
}
}
// 解决下标 i 无法到达字符串最后一项的问题:
// 先执行left为空判断,若为空情况下调用left.pop()会报错。
// 成功后执行 && 右侧部分,判断是否构成括号
if(!left.empty() && s.charAt(len-1) == yes.get(left.peek())){
// 存在情况如"{}{}{}{}{} { (( [[ ]] )) ]",若在上面if判断里直接用pop方法
// 尽管 { 和 ] 并不匹配但仍会将left清空
// 这会让该该嵌套if外的if(left.empty())判断失去它的作用
// 所以上面我们用peek,若通过了这里再pop
left.pop();
// 存在情况如"([]",若只是经历了上述if判断就直接返回true,会将false的判断为true
// 所以这里再加入if判断
if(left.empty()){
return true;
}
}
if(left.empty()){
// 如情况"[[[["
return true;
}
return false;
}
}
方法三:官方题解给的栈:
class Solution {
public boolean isValid(String s) {
int n = s.length();
// 奇数个数必为false
if (n % 2 == 1) {
return false;
}
Map<Character, Character> pairs = new HashMap<Character, Character>() {{
put(')', '(');
put(']', '[');
put('}', '{');
}};
Deque<Character> stack = new LinkedList<Character>();
for (int i = 0; i < n; i++) {
char ch = s.charAt(i);
// 若为右括号
if (pairs.containsKey(ch)) {
// 此时栈不为空(无左括号群)或(栈顶元素和当前右括号不配对)
if (stack.isEmpty() || stack.peek() != pairs.get(ch)) {
return false;
}
// 没发生上述if,说明配对成功,取出栈顶元素
stack.pop();
} else {
// 只要当前项不是右括号就存入栈中
stack.push(ch);
}
}
return stack.isEmpty();
}
}
该方法在哈希表中采取右对应左,且遇到一个左括号就存入栈中,因此相对于我的方法二大大化简
118、难度简单:
方法一:原创:暴力解法:
理解:不看题给的三角形动态图,以返回的数组角度观察,可以得到规律为:
数组第一项(也是杨辉三角第一行)必为 [1],然后生成第二行时我们给第一行左右处额外添加两个 0 值,得到 [0,1,0],
此时该数组第一项+第二项=0+1=1作为第二行的第一个元素,第二项+第三项作为第二个元素,此时第三项是 [0,1,0] 数组的最后一个元素所以我们已经完成了第二行的布置,也就是 [1,1]
生成第三行同理,额外添 0 得 [0,1,1,0],然后重复当两两相加得一个元素,直至所有元素被利用完。
同时我们可以发现每行的元素数量等于该行的行数
所以我们要做的就是根据题传行数,生成各行的数组,每生成完一行数组,就将其添加到结果数组中。
代码实现:这里有一处错误,在下面的代码解释部分有修正说明
class Solution {
public static List<List<Integer>> generate(int numRows) {
List<List<Integer>> nums = new ArrayList<List<Integer>>();
List<Integer> pre = new ArrayList<Integer>();
int in = 0;
pre.add(1);
nums.add(pre);
for(int i = 1; i<numRows; i++){
List<Integer> now = new ArrayList<Integer>();
for(int j = 0; j<=i;j++){
if(j == 0 || j == i){
in = pre.get(0);
}else{
in = pre.get(j-1) + pre.get(j);
}
now.add(in);
}
nums.add(now);
pre.add(0);
Collections.copy(pre, now);
}
return nums;
}
}
代码部分解释:
这里涉及到了数组中各元素是不同长度数组的问题:普通数组和二维数组都无法满足要求,所以我们利用泛型,将一个数组的元素类型设置为数组即可。
List<List<Integer>> nums = new ArrayList<List<Integer>>();
同时我们要注意,题目要求我们返回的类型为 List<List>,也就是我们上面设置的数组类型 List<List>,这说明元素数组必须是 List 类型。这么设置完后我们最终输出的结果就和题目示例中的的输出格式相同。
List<Integer> pre = new ArrayList<Integer>();
该版本代码存在一个问题,假设输入 3 最终输出为 [[1, 2, 1], [1, 1], [1, 2, 1]]:
虽然我将被当作元素的数组now的创建写在了for循环中,这样可以避免nums的每一项元素数组都是同一个地址导致值全都相同(如果将now写在了第一层for循环里的第一行,那就不能在第二层for循环的末尾写now.clean()清空now里的值,否则nums里由now赋值的元素全变为空)
但我给nums的第一个元素使用数组pre复制的,而这个pre我又在for循环里进行了 Collections.copy(pre, now); 操作,导致值改变了,变为了杨辉三角第二行的值。
// 解决措施:在开头位置另添加一个one,作为第一项元素的值
List<List<Integer>> nums = new ArrayList<List<Integer>>();
List<Integer> one = new ArrayList<Integer>();
List<Integer> pre = new ArrayList<Integer>();
int in = 0;
one.add(1);
pre.add(1);
nums.add(one);
for循环里的部分:
首先,三角的第一行已经被我写死了,所以我们从第二行开始添加,所以第一层for循环初始为 int i=1;(0是第一行已添加,用0开始是便于从数组中取值)
考虑到数组下标溢出问题,第二层for循环,作用是为了根据前一行的元素进行对当前行的推算添加。前一行的元素数由第一层for循环的 i 来表示,所以当前行的元素数就是 i+1。如上述代码那样写在当前行为3行时(也就是当前行有3个元素时)必然能循环3次。
if 判断语句是为了实现当前项若为当前行的第一个数值或最后一个数值,那就直接添加当前值(由于第一个和最后一个值相同,直接用get(0)代表这两种情况的应对取值)else则是正常的两两相加
当第二层的for循环结束时,now数组就具备了当前行的所有数值,就可以加入到nums里了。同时将now的值克隆到pre作为前一行。
为什么要写 pre.add(0); 这行代码:因为pre的数据个数比now少一个,这导致在运行copy方法时会报错,因为二者元素个数不同。
所以给pre随便添加一个数值。
方法二:数学:时间复杂度O(numRows2) 空间复杂度O(1):
class Solution {
public List<List<Integer>> generate(int numRows) {
List<List<Integer>> ret = new ArrayList<List<Integer>>();
for (int i = 0; i < numRows; ++i) {
List<Integer> row = new ArrayList<Integer>();
for (int j = 0; j <= i; ++j) {
if (j == 0 || j == i) {
row.add(1);
} else {
// 直接将返回结果当做要调用前一行数据的数组,避免了方法一中多个数组的创建以及衍生问题
row.add(ret.get(i - 1).get(j - 1) + ret.get(i - 1).get(j));
}
}
ret.add(row);
}
return ret;
}
}
基本原理和方法一一致,结果我的代码又乱又长:
绝望