剑指offer学习笔记(一)
- 老板太苛刻
- 同时太难相处
- 加班太频繁
- 工资太低
synchronized
同步块大家都比较熟悉,通过 synchronized 关键字来实现,所有加上synchronized 和 块语句,在多线程访问的时候,同一时刻只能有一个线程能够用
synchronized 修饰的方法 或者 代码块。
volatile
用volatile修饰的变量,线程在每次使用变量的时候,都会读取变量修改后的最新的值。volatile很容易被误用,用来进行原子性操作。
懒汉式单例
- //懒汉式单例类.在第一次调用的时候实例化自己
- public class Singleton {
- private Singleton() {}
- private static Singleton single=null;
- //静态工厂方法
- public static Singleton getInstance() {
- if (single == null) {
- single = new Singleton();
- }
- return single;
- }
- }
Singleton通过将构造方法限定为private避免了类在外部被实例化,在同一个虚拟机范围内,Singleton的唯一实例只能通过getInstance()方法访问。
1、在getInstance方法上加同步
- public static synchronized Singleton getInstance() {
- if (single == null) {
- single = new Singleton();
- }
- return single;
- }
2、双重检查锁定
- public static Singleton getInstance() {
- if (singleton == null) {
- synchronized (Singleton.class) { //synchronized 锁定的关键字
- if (singleton == null) {
- singleton = new Singleton();
- }
- }
- }
- return singleton;
- }
3、静态内部类
- public class Singleton {
- private static class LazyHolder {
- private static final Singleton INSTANCE = new Singleton();
- }
- private Singleton (){}
- public static final Singleton getInstance() {
- return LazyHolder.INSTANCE;
- }
- }
这种比上面1、2都好一些,既实现了线程安全,又避免了同步带来的性能影响。
二、饿汉式单例
- //饿汉式单例类.在类初始化时,已经自行实例化
- public class Singleton1 {
- private Singleton1() {}
- private static final Singleton1 single = new Singleton1(); //静态成员加载初始化
- //静态工厂方法
- public static Singleton1 getInstance() {
- return single;
- }
- }
饿汉式在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以天生是线程安全的。
饿汉式和懒汉式区别
从名字上来说,饿汉和懒汉,
饿汉就是类一旦加载,就把单例初始化完成,保证getInstance的时候,单例是已经存在的了,
而懒汉比较懒,只有当调用getInstance的时候,才回去初始化这个单例。
另外从以下两点再区分以下这两种方式:
1、线程安全:
饿汉式天生就是线程安全的,可以直接用于多线程而不会出现问题,
懒汉式本身是非线程安全的,为了实现线程安全有几种写法,分别是上面的1、2、3,这三种实现在资源加载和性能方面有些区别。
2、资源加载和性能:
饿汉式在类创建的同时就实例化一个静态对象出来,不管之后会不会使用这个单例,都会占据一定的内存,但是相应的,在第一次调用时速度也会更快,因为其资源已经初始化完成,
而懒汉式顾名思义,会延迟加载,在第一次使用该单例的时候才会实例化对象出来,第一次调用时要做初始化,如果要做的工作比较多,性能上会有些延迟,之后就和饿汉式一样了。
至于1、2、3这三种实现又有些区别,
- 第1种,在方法调用上加了同步,虽然线程安全了,但是每次都要同步,会影响性能,毕竟99%的情况下是不需要同步的;
- 第2种,在getInstance中做了两次null检查,确保了只有第一次调用单例的时候才会做同步,这样也是线程安全的,同时避免了每次都同步的性能损耗;
- 第3种,利用了classloader的机制来保证初始化instance时只有一个线程,所以也是线程安全的,同时没有性能损耗,所以一般我倾向于使用这一种。
什么是线程安全?
如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
或者说:一个类或者程序所提供的接口对于线程来说是原子操作,或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题,那就是线程安全的。
应用以下是一个单例类使用的例子,以懒汉式为例,这里为了保证线程安全,使用了双重检查锁定的方式:
- public class TestSingleton {
- String name = null;
- private TestSingleton() {
- }
- private static volatile TestSingleton instance = null;
-
- public static TestSingleton getInstance() {
- //双重检查锁定
- if (instance == null) {
- synchronized (TestSingleton.class) {
- if (singleton == null) {
- singleton = new TestSingleton();
- }
- }
- }
- return instance;
- }
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- public void printInfo() {
- System.out.println("the name is " + name);
- }
- }
可以看到里面加了volatile关键字来声明单例对象,既然synchronized已经起到了多线程下原子性、有序性、可见性的作用,为什么还要加volatile呢?
- public class TMain {
- public static void main(String[] args){
- TestStream ts1 = TestSingleton.getInstance();
- ts1.setName("jason");
- TestStream ts2 = TestSingleton.getInstance();
- ts2.setName("0539");
- ts1.printInfo();
- ts2.printInfo();
- if(ts1 == ts2){
- System.out.println("创建的是同一个实例");
- }else{
- System.out.println("创建的不是同一个实例");
- }
- }
- }
结论:由结果可以得知单例模式为一个面向对象的应用程序提供了对象惟一的访问点,不管它实现何种功能,整个应用程序都会同享一个实例对象。对于单例模式的几种实现方式,知道饿汉式和懒汉式的区别,线程安全,资源加载的时机,还有懒汉式为了实现线程安全的3种方式的细微差别。
public String replaceSpace(StringBuffer str) {
//str.reverse();
// str.charAt(1);
// str.length();
// str.replace(i, i+1, "");
char[] cs = str.toString().toCharArray();
//System.out.println(cs.length);
str=new StringBuffer("");
for(int i=0;i<cs.length;i++){
if(cs[i]!=' '){
str.append(cs[i]);
}else{
str.append("%20");
}
}
returnstr.toString();
}
/**
* 递归的方式处理
* @param listNode
* @return
*/
public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
ArrayList<Integer> arrayList = new ArrayList<Integer>();
if (listNode != null) {
if (listNode.next != null){
//将arrayList引用指向最后的结点然后递归的添加内容到arrayList
arrayList = printListFromTailToHead(listNode.next);
}
arrayList.add(listNode.val);
}
// System.out.println(listNode.val);
return arrayList;
}
/**
* 栈的方式处理
* @param
*/
public ArrayList<Integer> printListFromTailToHeadStack(ListNode listNode) {
ArrayList<Integer> arrayList = new ArrayList<Integer>();
Stack<ListNode> s = new Stack<ListNode>();
if(listNode != null){
while(listNode.next!=null){
s.push(listNode);
listNode=listNode.next;
}
s.push(listNode);
while(!s.isEmpty()){
arrayList.add(s.pop().val);
}
}
return arrayList;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
|
/** *
重建二叉树 * *
@param pre *
@param in *
@return */ public TreeNode
reConstructBinaryTree( int []
pre, int []
in) { if (pre.length
== 0 ||
pre == null ||
in == null || pre.length
!= in.length) { return null ; } else { return constructorBinaryTreeCore(pre,
in, 0 ,
pre.length - 1 , 0 , in.length
- 1 ); } } private TreeNode
constructorBinaryTreeCore( int []
pre, int []
in, int preStart, int preEnd, int inStart, int inEnd)
{ //
前序遍历的第一个数字就是根节点 int rootValue
= pre[preStart]; TreeNode root = new TreeNode(rootValue); root.left = null ; root.right = null ; if (preStart
== preEnd) { if (inStart
== inEnd && pre[preStart] == in[inStart]) { return root; } else { try { throw new Exception( "Invalid
Input!" ); } catch (Exception
e) { e.printStackTrace(); } } } //
中序遍历找到根结点的位置 int rootInorder
= inStart; while (rootInorder
<= inEnd && in[rootInorder] != rootValue) { rootInorder += 1 ; } if (rootInorder
== inEnd && in[rootInorder] != rootValue) { try { throw new Exception( "Invalid
Input!" ); } catch (Exception
e) { e.printStackTrace(); } } int leftLen
= rootInorder - inStart; int splitPre
= preStart + leftLen; if (leftLen
> 0 )
{ //
从前序和中序各自的分类位置继续递归重建二叉树 root.left = constructorBinaryTreeCore(pre,
in, preStart + 1 , splitPre,
inStart, rootInorder - 1 ); } if (leftLen
< inEnd - inStart) { root.right = constructorBinaryTreeCore(pre,
in, splitPre + 1 , preEnd, rootInorder
+ 1 ,
inEnd); } return root; } |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
import java.util.Stack; public class Solution
{ Stack<Integer>
stack1 = new Stack<Integer>(); Stack<Integer>
stack2 = new Stack<Integer>(); public void push( int node)
{ stack1.push(node); } public int pop()
{ if (stack2.isEmpty()){ while (!stack1.isEmpty()){ stack2.push(stack1.pop()); } } if (stack2.isEmpty()){ try { throw new Exception( "Queue
is Empty" ); } catch (Exception
e) { e.printStackTrace(); } } Integer
top = stack2.pop(); return top; } } |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
import java.util.ArrayList; public class Solution
{ public int minNumberInRotateArray( int []
array) { int len=array.length; if (array== null ||len== 0 ){ return 0 ; } int rear= 0 ; int tail=len- 1 ; int aimIndex=rear; while (array[rear]>=array[tail]){ if (tail-rear== 1 ){ aimIndex=tail; break ; } aimIndex=(rear+tail)/ 2 ; //如果三个数相等那么只能顺序查找 if (array[rear]==array[tail]&&array[rear]==array[aimIndex]){ return minInOrder(rear,tail,array); } if (array[rear]<=array[aimIndex]){ rear=aimIndex; } else if (array[tail]>array[aimIndex]){ tail=aimIndex; } } return array[aimIndex]; } /** *
出现特殊情况 求解到的中间值和两端的值相等 *
@param rear *
@param tail *
@param array *
@return */ private int minInOrder( int rear, int tail, int []
array) { int result=array[rear]; for ( int i=rear+ 1 ;i<=tail;i++){ if (result>array[i]){ result=array[i]; } } return result; } } |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
public class Solution
{ public int Fibonacci( int n)
{ int f1
= 1 ; int f2
= 1 ; int result
= 0 ; if (n<= 0 ){ return 0 ; } if (n
<= 2 )
{ return 1 ; } for ( int i
= 3 ;
i <= n; i++) { result
= f1 + f2; f1
= f2; f2
= result; } return result; } } |
题目描述:矩形覆盖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
public class Solution
{ public int RectCover( int target)
{ if (target<= 0 ){ return 1 ; } if (target<= 2 ){ return target; } int result= 0 ; int f1= 1 ; int f2= 2 ; for ( int i= 3 ;i<=target;i++){ result=f1+f2; f1=f2; f2=result; } return result; } } |
1
2
3
4
5
6
7
8
9
10
11
|
public class Solution
{ public int NumberOf1( int n)
{ int count
= 0 ; while (n!= 0 ){ count++; n
= n & (n - 1 ); } return count; } } |
1
2
3
4
5
6
7
8
9
10
11
|
public class Solution
{ public int NumberOf1( int n)
{ int count
= 0 ; while (n!= 0 ){ count++; n
= n & (n - 1 ); } return count; } } |