Algorithm
leetcode301(https://leetcode.com/problems/remove-invalid-parentheses/),删除最少数量字符使字符串有效,返回所有可能结果。有效判定左括号'('和右括号')'匹配。
- Input:"()())()" Output:["()()()", "(())()"]
- Input:"(a)())()" Output:["(a)()()", "(a())()"]
- Input:")(" Output: [""]
看到这个问题第一眼想到的是栈,类似判定字符串是否回文,但是返回的结果只有一个显然是不符合结果的。后面经过考虑之后决定通过栈都方式返回1个结果,再做进一步计算。
最终大致思路就是对所有对可能情况通过递归查找,最后排除错误结果。同时,为了减少递归分支,通过增加对最终结果无效的情况提前判断终止递归。
code
public List removeInvalidParentheses(String s) {
// 求出1个结果
String result = remove(s);
// 计算最小删减无效字符数量
int strLeft = 0, strRight = 0, resultLeft = 0, resultRight = 0;
char c;
for (int i = 0; i < s.length(); i++) {
c = s.charAt(i);
if (c == '(') {
strLeft++;
} else if (c == ')') {
strRight++;
}
}
for (int i = 0; i < result.length(); i++) {
c = result.charAt(i);
if (c == '(') {
resultLeft++;
} else if (c == ')') {
resultRight++;
}
}
// 找出可能结果
List list = new ArrayList<>();
recursion(list, s, "", 0, 0, 0, strLeft - resultLeft, strRight - resultRight);
// 排除结果中无效的字符串
Iterator it = list.iterator();
String item;
while (it.hasNext()) {
item = it.next();
if (remove(item).length() != item.length()) {
it.remove();
}
}
return list;
}
/**
* 删除无效的字符串(返回1个结果,明确最少删除字符数)
```java)
*/
private String remove(String s) {
if (s.length() <= 0) {
return "";
}
Stack stack = new Stack<>();
StringBuilder ret = new StringBuilder();
Character c;
for (int i = 0; i < s.length(); i++) {
c = s.charAt(i);
if (stack.size() == 0) {
if (c == '(') {
stack.push(c);
} else if (c == ')') {
} else {
ret.append(c);
}
} else {
if (c == '(') {
stack.push(c);
} else if (c == ')') {
Character temp;
StringBuilder tempS = new StringBuilder();
while (stack.size() > 0) {
temp = stack.pop();
if (temp == '(') {
tempS.insert(0, temp).append(c);
break;
} else {
tempS.insert(0, temp);
}
}
ret.append(tempS);
} else {
Character temp = stack.peek();
if (temp == ')') {
ret.append(c);
} else {
stack.push(c);
}
}
}
}
Character temp;
StringBuilder tempS = new StringBuilder();
while (stack.size() > 0) {
temp = stack.pop();
if (temp != '(' && temp != ')') {
tempS.insert(0, temp);
}
}
ret.append(tempS);
return ret.toString();
}
/**
* 可能结果
*
* @param list 结果
* @param s 原字符串
* @param result 递归中结果
* @param index index
* @param deleteLeft 左括号删除数
* @param deleteRight 右括号删除数
* @param minDeleteLeft 左括号最小删除数
* @param minDeleteRight 右括号最小删除数
*/
private void recursion(List list, String s, String result, int index, int deleteLeft, int deleteRight, int minDeleteLeft, int minDeleteRight) {
if (deleteLeft > minDeleteLeft) return;
if (deleteRight > minDeleteRight) return;
// 剩余字符串小于删除数
if (minDeleteLeft + minDeleteRight - deleteLeft - deleteRight > s.length() - index) return;
if (index >= s.length()) {
if (deleteLeft == minDeleteLeft && deleteRight == minDeleteRight) {
if (!list.contains(result)) {
list.add(result);
}
}
} else {
Character c;
int i;
for (i = index; i < s.length(); i++) {
c = s.charAt(i);
if (c == '(') {
recursion(list, s, result + c, i + 1, deleteLeft, deleteRight, minDeleteLeft, minDeleteRight);
deleteLeft++;
//recursion(list, s, result, i + 1, deleteLeft + 1, deleteRight, minDeleteLeft, minDeleteRight);
//break;
} else if (c == ')') {
recursion(list, s, result + c, i + 1, deleteLeft, deleteRight, minDeleteLeft, minDeleteRight);
deleteRight++;
//recursion(list, s, result, i + 1, deleteLeft, deleteRight + 1, minDeleteLeft, minDeleteRight);
//break;
} else {
result = result + c;
}
}
if (i == s.length() && deleteLeft == minDeleteLeft && deleteRight == minDeleteRight) {
if (!list.contains(result)) {
list.add(result);
}
}
}
}
代码效率不是特别高,还能继续优化,优化方向一致。
最后个人总结,只能深度搜索所有可能才能找到结果的情况,可以通过各种方式,提前结束子搜索,类似树分叉提前砍断子树,来提高效率。
Review
作为一个java程序员,一定是用过spring,在springboot配置配置数据库连接池默认使用 HikariCP,HikariCP有关于数据库连接池说明About-Pool-Sizing。
按照我之前的想法连接池连接数越多,肯定程序越快,文中指出连接池数量在不考虑磁盘开销、网络开销等情况,连接池数和cpu核数相同性能最好,如果连接数远大于cpu核数,cpu上下文切换会带来额外开销。
在考虑磁盘开销和网络开销情况,ssd是会减少磁盘开销,同时带宽更高会减少网络开销,一个系统数据库连接数应当考虑磁盘、带宽和cpu核数来配置适当的连接池连接数,不然不计后果增加连接数会适得其反。
(文中的数据库连接池连接数提供来一个公式,没细看,觉得只能作为参考)
再分享一篇文章java in flames,讲java中结合-XX:+PreserveFramePointer参数,对java程序包括底层以及系统进行可视化分析。
Tip
自学docker遇到需要写shell脚本。
linux命令 ps -ef|grep xxxxx | grep -v grep|awk '{print $2}'
ps -ef|grep用来找pid。grep -v 反向grep即排除有指定字符的结果,$2第二列就是pid。写shell脚本找到pid,然后kill -9 pid。
java包启动-classpath参数后jar包,linux使用:分开jar,windows使用;分开jar
分享个git常用命令忽略文件修改
git update-index --assume-unchanged FILENAME
git update-index --no-assume-unchanged FILENAME
Share
结合前段时间刚跟学的mysql对自己影响做个思考总结,对比自己一个java程序员思考
1.mysql中server层和innodb引擎之间同步,redolog先写入处于prepare状态,binlog再写完之后提交讲redolog改成commit,即使出现异常(如:binlog未写完mysql异常重启,binlog恢复数据没未保存的数据;binlog写完mysql异常重启,binlog恢复数据后发现redolog处于prepare状态后会讲redolog改成commit,事物完成,两边一致)。在我的理解中和分布式事物很相似,保证事物完整性;
2.事物的隔离中有可重复读,事物启动时建立单独视图其他事物不可见,java中有个ThreadLocalMap中就是线程对对象对拷贝,每个线程有自己单独对对象;
3.mysql锁可以表锁和全局锁,对应不同粒度。在java开发中,同样可以锁一个整个方法也可以通过不同对对象java更加细粒度锁;
4.在mysql中有change_buffer可以对修改暂时在内存中进行,后面再写到磁盘中,同时写入顺序写可以减少开销。同样,在开发中,对数据库频繁跟新可以加一层缓存,暂时在缓存中更新,然后通过定时任务定时将缓存写入磁盘,为两保证缓存不会因为程序重启不丢失,可以使用redis来代替程序缓存;
5.mysql内存数据页,将数据加载在优先队列中,同时队列分两部分,前面一部分young属于优先队列原则,后面一部分old在数据页读如内存时放在old头,同时在读内存数据时,如果old数据2次访问间隔时间不超过1s,就将数据页移到young头,这样对于访问不频繁对数据很容易被淘汰。这样数据结构在开发中目前没有遇到过,但是很有借鉴意义。
6.mysql通过一些缓存将随机访问变成顺序访问就如数组这样对数据结构访问比链表更快一样。
目前就想到这样,后面有还会继续学习mysql有更深对理解会继续思考。
最后想说,各种语言在不断演变过程都有相互借鉴,虽然语言不同目的不同,但是思想是永远不变的。