https://leetcode-cn.com/problems/er-cha-sou-suo-shu-de-hou-xu-bian-li-xu-lie-lcof/
由于工作了,好久没看树方面的知识,二叉搜索树的定义有点忘了,面试后搜索定义如下:
二叉查找树(Binary Search Tree),(又:二叉搜索树,二叉排序树)它或者是一棵空树,或者是具有下列性质的二叉树: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 它的左、右子树也分别为二叉排序树。
解法一
后序遍历的最后一个数字一定是根节点,我们可以在数组中确定根节点的位置。
上面这棵树的后序遍历:
[3,5,4,10,12,9],
后续遍历的最后一个数字一定是根节点,所以数组中最后一个数字9就是根节点,我们从前往后找到第一个比9大的数字10,那么10后面的[10,12](除了9)都是9的右子节点,10前面的[3,5,4]都是9的左子节点,10以及10后面的需要判断一下,如果有小于9的,说明不是二叉搜索树,直接返回false。然后再以递归的方式判断左右子树。
代码:
public boolean verifyPostorder(int[] postorder) {
return helper(postorder, 0, postorder.length - 1);
}
boolean helper(int[] nums, int left, int right) {
if (left >= right) {
return true;
}
int leftIndex = left;
// 数组中的最后一个数字是二叉搜索树的根节点
int root = nums[right];
while (nums[leftIndex] < root) {
leftIndex++;
}
int rightIndex = leftIndex;
while (rightIndex < right) {
if (nums[rightIndex++] < root) {
return false;
}
}
return helper(nums, left, leftIndex - 1) && helper(nums, leftIndex, right - 1);
}
两者的主要区别:
详细的属性动画的驱动原理可以看下面的博客,博主介绍的很详细。
看了博客之后,可以将属性动画的运行原理大体概括为下面的流程。
参考:https://www.jianshu.com/p/46f48f1b98a9
Android应用性能优化最佳实践 2.7节动画性能中,对属性动画和补件动画的性能进行了对比,对比显示属性动画的性能更高。同时动画过程如果只涉及scale,alpha等非布局全量刷新的场景时,可以将硬件加速打开,提升动画流畅性,同时可以使用setLayerType设置layer的类型,比如设置为HARD_WARE_LAYER类型或者SOFTWARE_LAYER。
使用CountDownLatch来实现,先简单介绍CountDownLatch的用法和作用。
相关接口:
//参数count为计数值
public CountDownLatch(int count)
//调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行
public void await() throws InterruptedException {};
//和await()类似,只不过等待一定的时间后count值还没变为0的话就会继续执行
public boolean await(long timeout, TimeUnit unit) throws InterruptedException { };
//将count值减1
public void countDown() { };
代码如下:
/**
* 有三个线程,线程A等待线程B执行结果,
* 线程B等待线程C的执行结果
*/
public class ThreadABC {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatchAB = new CountDownLatch(1);
CountDownLatch countDownLatchBC = new CountDownLatch(1);
Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
try {
countDownLatchAB.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
Thread.sleep(new java.util.Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}
}, "Thread-A");
Thread threadB = new Thread(new Runnable() {
@Override
public void run() {
try {
countDownLatchBC.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
Thread.sleep(new java.util.Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
countDownLatchAB.countDown();
}
}, "Thread-B");
Thread threadC = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
countDownLatchBC.countDown();
}
}, "Thread-C");
threadA.start();
threadB.start();
threadC.start();
}
}
下面的代码线程B会获取lock锁,然后进入await状态,然后线程d会等待1000ms后去尝试获取锁:
/**
* 有三个线程,线程A等待线程B执行结果,
* 线程B等待线程C的执行结果
*/
public class ThreadABC {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatchAB = new CountDownLatch(1);
CountDownLatch countDownLatchBC = new CountDownLatch(2);
Lock lock = new ReentrantLock();
Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
try {
countDownLatchAB.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
Thread.sleep(new java.util.Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}
}, "Thread-A");
Thread threadB = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("thread b try lock");
lock.lock();
System.out.println("thread b lock");
try {
countDownLatchBC.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
Thread.sleep(new java.util.Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
countDownLatchAB.countDown();
lock.unlock();
}
}, "Thread-B");
Thread threadC = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
countDownLatchBC.countDown();
}
}, "Thread-C");
threadA.start();
threadB.start();
threadC.start();
Thread.sleep(1000);
Thread threadD = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("thread d try lock");
lock.lock();
System.out.println(Thread.currentThread().getName());
countDownLatchBC.countDown();
lock.unlock();
}
}, "Thread-D");
threadD.start();
}
}
thread b try lock
thread b lock
Thread-C
thread d try lock
上面的代码执行结果日志如上,可以看到d县城尝试去lock并没有获取到锁,所以标名await()使线程进入到阻塞状态,但是并没有释放自己持有的锁。
IntentService是继承于Service并异步处理请求的一个类,在IntentService内部有一个工作线程来处理耗时任务,当任务执行完时IntentService会自动停止。在执行任务的时候,每一个耗时任务会在onHandleIntent回调方法中执行,并且每次只会执行一个任务,第一个执行完了再执行第二个。
使用IntentService的优点:
IntentService代码
public class DemoIntentService extends IntentService {
private static final String TAG = "DemoIntentService";
public DemoIntentService() {
super("DemoIntentService");
}
@Override
public void onCreate() {
super.onCreate();
Log.i(TAG, "onCreate: ");
}
@Override
public void onStart(@Nullable Intent intent, int startId) {
super.onStart(intent, startId);
Log.i(TAG, "onStart: ");
}
@Override
public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
Log.i(TAG, "onStartCommand: ");
return super.onStartCommand(intent, flags, startId);
}
@Override
protected void onHandleIntent(@Nullable Intent intent) {
Log.i(TAG, "onHandleIntent: " + intent.toString() + " current thread is " + Thread.currentThread());
}
@Override
public void onDestroy() {
super.onDestroy();
Log.i(TAG, "onDestroy: ");
}
}
activity中启动IntentService的代码:
//可以启动多次,每启动一次,就会新建一个work thread,但IntentService的实例始终只有一个
//Operation 1
Intent startServiceIntent = new Intent();
startServiceIntent.setClassName("com.example.recyclerviewlearn", "com.example.recyclerviewlearn.DemoIntentService");
Bundle bundle = new Bundle();
bundle.putString("param", "oper1");
startServiceIntent.putExtras(bundle);
startService(startServiceIntent);
//Operation 2
Intent startServiceIntent2 = new Intent();
startServiceIntent2.setClassName("com.example.recyclerviewlearn", "com.example.recyclerviewlearn.DemoIntentService");
Bundle bundle2 = new Bundle();
bundle2.putString("param", "oper2");
startServiceIntent2.putExtras(bundle2);
startService(startServiceIntent2);
两次启动IntentService运行的日志如下:
2021-05-18 20:53:01.244 21393-21393/com.example.recyclerviewlearn I/DemoIntentService: onCreate:
2021-05-18 20:53:01.244 21393-21393/com.example.recyclerviewlearn I/DemoIntentService: onStartCommand:
2021-05-18 20:53:01.244 21393-21393/com.example.recyclerviewlearn I/DemoIntentService: onStart:
2021-05-18 20:53:01.245 21393-22982/com.example.recyclerviewlearn I/DemoIntentService: onHandleIntent: Intent { cmp=com.example.recyclerviewlearn/.DemoIntentService (has extras) } current thread is Thread[IntentService[DemoIntentService],5,main]
2021-05-18 20:53:01.245 21393-21393/com.example.recyclerviewlearn I/DemoIntentService: onStartCommand:
2021-05-18 20:53:01.245 21393-21393/com.example.recyclerviewlearn I/DemoIntentService: onStart:
2021-05-18 20:53:01.246 21393-22982/com.example.recyclerviewlearn I/DemoIntentService: onHandleIntent: Intent { cmp=com.example.recyclerviewlearn/.DemoIntentService (has extras) } current thread is Thread[IntentService[DemoIntentService],5,main]
2021-05-18 20:53:01.258 21393-21393/com.example.recyclerviewlearn I/DemoIntentService: onDestroy:
可以看到onHandleIntent方法执行的时候,线程id变成了22982,任务再子线程中执行了。同时每次startService都会调用onStartCommand,onStart,最终调用onHandleIntent方法,任务执行结束后onDestory自动结束生命。
onCreate执行的时候会创建一个HandlerThread对象,并启动线程,紧接着创建ServiceHandler对象,ServiceHandler继承自Handler,用来处理消息。ServiceHandler将获取HandlerThread的Looper就可以开始正常工作了。
@Override
public void onCreate() {
// TODO: It would be nice to have an option to hold a partial wakelock
// during processing, and to have a static startService(Context, Intent)
// method that would launch the service & hand off a wakelock.
super.onCreate();
HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
thread.start();
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}
每一次启动onStart方法,会把数据和消息发送给mServiceHandler,然后发送给onHandleIntent方法。
public IBinder onBind(Intent intent) {
return null;
}
IntentService的onBind方法返回为null。不建议使用bind方法绑定,如果需要使用bind方法绑定的话,这个时候IntentService就变为了Service。
实现一个子线程的Handler我们可以使用下面的方式:
创建当前线程的Looper
创建当前线程的Handler
调用当前线程Looper对象的loop方法
代码如下:
public class ChildThreadHandlerActivity extends Activity {
private MyThread childThread;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler);
childThread = new MyThread();
childThread.start();
Handler childHandler = new Handler(childThread.childLooper){//这样之后,childHandler和childLooper就关联起来了。
public void handleMessage(Message msg) {
};
};
}
private class MyThread extends Thread{
public Looper childLooper;
@Override
public void run() {
Looper.prepare();//创建与当前线程相关的Looper
childLooper = Looper.myLooper();//获取当前线程的Looper对象
Looper.loop();//调用此方法,消息才会循环处理
}
}
}
但是上面的代码会存在空指针的问题,因为我们是在子线程的run方法中创建Looper的,有可能主线程中获取looper的时候子线程的run方法还没有得到执行,就会有空指针问题。
那如何解决上面的问题呢,我们可以使用HandlerThread创建得到一个子线程的Looper:
HandlerThread handlerThread = new HandlerThread("HandlerThread");
handlerThread.start();
使用HandlerThread是如何避免空指针的呢?查看HandlerThread的实现:
public Looper getLooper() {
if (!isAlive()) {
return null;
}
// If the thread has been started, wait until the looper has been created.
synchronized (this) {
while (isAlive() && mLooper == null) {
try {
wait();
} catch (InterruptedException e) {
}
}
}
return mLooper;
}
getLooper的时候回去判断,当前的Looper如果为空的话,就会让线程wait(),那么唤醒的地方在哪里呢?
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
HandlerThread的run方法在执行的时候,先会去创建Looper,创建了Looper以后会去调用notifyAll(),这样就将等待的线程唤醒了,从而避免了空指针。
工厂模式:
对创建对象的接口进行封装,只根据名称获取对应的具体实现类。
优点:获取产品的时候只用知道名称就可以,扩展的时候增加具体的实现类就可以了。
缺点:同一个接口中扩展产品困难。扩展产品只能增加工厂,增加了复杂度。
参考:https://www.runoob.com/design-pattern/factory-pattern.html
抽象工厂模式:
是围绕一个超级工厂创建其他工厂。该超级工厂又称为其它工厂的工厂。简单理解为有多个接口,根据需要选择实现对应的工厂去创建对应的产品。
优点:将不同产品的接口隔离开来,代码封装较好,
缺点:产品族扩展比较麻烦,得改多处代码。
参考:https://www.runoob.com/design-pattern/abstract-factory-pattern.html
相同点:
当不需要返回值的时候优先使用apply,因为apply是异步的,当需要返回值的时候还是需要使用commit的。
面试的时候被问到手动实现一个阻塞队列,回答得并不好,自己下来在网上搜了下,发现其实也没有那么难,其实就是对wait和notify机制需要掌握。
首先wait和notfy需要在同步块中调用,不然会抛出IllegalMonitorStateException异常,
在实现阻塞队列的时候我们主要实现两个接口,take和put,take用于从阻塞队列中获取元素,put用于向阻塞队列中放置元素,实现代码如下:
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
public class BlockQueue {
public static final int CAPASITY = 20;
private final Object mLock = new Object();
private static final List<Integer> mQueue = new ArrayList<>();
public void add(int num) {
synchronized (mLock) {
// 这里使用while循环,防止当线程被唤醒的时候但是这个时候条件还是不满足的,
// 就直接往队列中添加元素,比如一个线程添加元素后队列满,如果没有while,唤醒另一个生产者线程
// 就会直接再次想list中添加元素
while (mQueue.size() == CAPASITY) {
System.out.println("Queue 满,生产者开始wait" + Thread.currentThread().getName());
try {
mLock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
mQueue.add(mQueue.size(), num);
System.out.println("Queue produce :[" + num + "] 当前线程为:" + Thread.currentThread().getName());
// 唤醒在这个锁上面等待的所有线程,线程之间通过竞争获取锁
mLock.notifyAll();
}
}
public void take() {
synchronized (mLock) {
// 当队列为空的时候停止消费者线程获取元素
while (mQueue.size() == 0) {
try {
System.out.println("队列为空,停止消费者");
mLock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
int num = mQueue.remove(0);
System.out.println("Queue Cosumer :[" + num + "] 当前线程为:" + Thread.currentThread().getName());
// 唤醒这个锁上面等待的所有线程
mLock.notifyAll();
}
}
public static void main(String[] args) {
BlockQueue blockQueue = new BlockQueue();
new Thread(() -> {
for (; ; ) {
blockQueue.add(ThreadLocalRandom.current().nextInt());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "生产者线程").start();
// 消费者sleep的时长大于生产者sleep的时长,所以队列会被生产满,
new Thread(() -> {
for (; ; ) {
blockQueue.take();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "消费者线程1").start();
// 消费者sleep的时长大于生产者sleep的时长,所以队列会被生产满,
new Thread(() -> {
for (; ; ) {
blockQueue.take();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "消费者线程2").start();
}
}
字节的面试对知识点扣的很细,因此面试的时候对基础要求很高,希望大家平常多准备,光靠面试的时候那几天准备是来不及的。