复杂逻辑题目的好帮手:注释和断言
前言
最近在刷题的时候,遇到一些逻辑比较复杂的题,往往会遇到困难,经常写不出来。即使在有debug帮助的时候,也往往会出现思虑不周,导致一错再错的情况,即好不容易debug通过一个测试用例,然后发现被另一个测试用例卡住。在周赛双周赛中,这意味着大量的罚时;在工作中,这意味着浪费大量的时间。因此需要有一个方法来辅助思考,完成正确的分类讨论。
注释与断言的作用
人脑的短时存储能力很差(内存/运存很小),因此在处理复杂问题时往往会顾此失彼,此时就需要工具来辅助记录思想(外存)。注释与断言就是起一个这样的作用。
如何使用注释
注释应该给出关键的信息,即一个循环/函数,当进入的时候,关键变量的值应该是什么。当离开的时候,关键变量的值应该是什么。
如何使用断言
即便使用了注释,也不能保证写出的代码在正确的地点有正确的值,此时就需要assert
来自动检验值是否正确。
什么时候使用注释与断言
在刷题/竞赛的时候,为了提高效率,可以采用以下策略:
- 当刷题时发现自己无法完成分类讨论的时候
- 当发现自己写的代码有bug的时候
而当工作或者开发项目的时候,由于越早发现bug代价越小,因此最好从一开始就使用。
例子
以今天的每日一题LC385.迷你语法分析器为例。
如果使用栈+迭代的方法,那么迭代变量就不会出问题,但如果使用递归的方法,那么迭代变量还是容易出问题的。这种情况下,使用注释和断言就可以帮助思考。
/**
* // This is the interface that allows for creating nested lists.
* // You should not implement it, or speculate about its implementation
* public interface NestedInteger {
* // Constructor initializes an empty nested list.
* public NestedInteger();
*
* // Constructor initializes a single integer.
* public NestedInteger(int value);
*
* // @return true if this NestedInteger holds a single integer, rather than a nested list.
* public boolean isInteger();
*
* // @return the single integer that this NestedInteger holds, if it holds a single integer
* // Return null if this NestedInteger holds a nested list
* public Integer getInteger();
*
* // Set this NestedInteger to hold a single integer.
* public void setInteger(int value);
*
* // Set this NestedInteger to hold a nested list and adds a nested integer to it.
* public void add(NestedInteger ni);
*
* // @return the nested list that this NestedInteger holds, if it holds a nested list
* // Return empty list if this NestedInteger holds a single integer
* public List<NestedInteger> getList();
* }
*/
class Solution {
public NestedInteger deserialize(String s) {
char[] cs = s.toCharArray();
return recur(cs, new int[1]);
}
private NestedInteger recur(char[] cs, int[] i) {
// i[0] must be the start of a token
if (cs[i[0]] == '[') {
// this means it's a list
NestedInteger result = new NestedInteger();
i[0]++;
while (cs[i[0]] != ']') {
// i[0] should be the start of a token
assert i[0] < cs.length;
if (cs[i[0]] == ',') {
i[0]++; // make sure i[0] is the start of next token
continue;
}
if (cs[i[0]] == '[' && cs[i[0] + 1] == ']') {
// it is a list whose length is 0
i[0] += 2; // make sure i[0] is the start of next token
result.add(new NestedInteger());
} else {
// it is a list with at least 1 element
result.add(recur(cs, i));
// the function will make sure i[0] is the start of next token
}
}
// in the end, cs[i[0]] must be ']'
assert cs[i[0]] == ']';
i[0]++; // make sure i[0] is the start of next token
return result;
}
// this is an integer
int val = 0;
boolean isNagtive = false;
if(cs[i[0]] == '-') {
isNagtive = true;
i[0]++;
}
while(i[0] < cs.length && cs[i[0]] != ',' && cs[i[0]] != ']') {
assert cs[i[0]] != '[';
val = val * 10 + cs[i[0]++] - 48;
}
// if (i[0] == cs.length) this is the start of next token;
// if (cs[i[0]] == ',') this is the start of next token;
// if (cs[i[0]] == ']') this is the end of this token, but should not be process here
val = isNagtive ? -val : val;
return new NestedInteger(val);
}
}