一些面试笔试题_2017校招

随笔,Android 2016-10-08

1.音视频不同步的原因:

一般是客户端的问题。

由于音视频包都会带上时间戳,出现不同步的根本原因是客户端取出当前数据包解码放入缓冲队列,进行渲染时(在这做同步)不能从缓冲队列中同时找到当前时间戳的视频解码数据和音频解码数据,所以只能先取其中一个拿个渲染。

(注:拿去渲染后,如果时间戳同步,渲染一般不会出现不同步的情况,硬件处理相对成熟);

渲染不同步,可以说是缓冲队列设置太小,没有考虑网络不好数据包传输慢或者需要丢包重传的情况。 当然还有可能是解码的问题,音视频其中之一的解码效率太慢,而缓冲队列设置合适,稍微不好一点的网络情况导致解码数据包(音频or视频)来不及放入缓冲队列。

如果是服务端的问题,比较不好处理。

音视频线程数据采集速度不同步(和采集卡有关),封装协议时就已经出现同一时间戳的音视频包实际是不同步的情况。

2. synchronized 实例方法与静态方法的区别

先上结论:

1.同一个实例,所有非静态同步方法会加锁同步,锁的对象是类实例;即先等待其中一个实例方法运行结束才能运行下一个实例方法(不管实例方法是否是同一个)

2.同步实例方法的锁对象是类实例,所以不同类实例之间可以并行的运行实例方法

3.同步静态方法的锁对象是Class类本身,所以静态方法间的运行是串行的

4.类的静态方法和类实例的实例方法同时运行,由于锁对象不同,运行是并行的

public class TestSynchronized {
    public synchronized static void synstaMethod1(){
        System.out.println("i am synstaMethod1 start");
        timeTask();
        System.out.println("i am synstaMethod1 end");
    }

    public synchronized static void synstaMethod2()  {
        System.out.println("i am synstaMethod2 start");
        timeTask();
        System.out.println("i am synstaMethod2 end");
    }

    public synchronized void synMethod1()  {
        System.out.println("i am synMethod1 start");
        timeTask();
        System.out.println("i am synMethod1 end");
    }

    public void synMethod2()  {
        synchronized (this) {
            System.out.println("i am synMethod2 start");
            timeTask();
            System.out.println("i am synMethod2 end");
        }
    }

    public static void timeTask() {
        double i = 0;
        while (true) {
            i += 1;
            if (i > 1000000000)
                break;
        }
    }
    public static void timeTask2() {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    //下面这方法不好测试 会另开线程
    public static void timeTask3(String methodName) {
        Timer timer = new Timer();
        // 3s后运行该任务
        timer.schedule(new TimerTask() {
            public void run() {
                System.out.printf("i am %s end\n",methodName);
                timer.cancel();
            }
        }, 1000);
    }
}

测试方法:

package com.france;

//话说JUnit做多线程测试有诸多不便..还是用Main来测试把..
public class Main {
    static TestSynchronized obj1 = new TestSynchronized();
    static TestSynchronized obj2 = new TestSynchronized();

    // 测试:同一个类实例的实例方法
    // 结论:同一个实例,所有非静态同步方法会加锁同步,锁的对象是类实例;即先等待其中一个实例方法运行结束才能运行下一个实例方法(不管实例方法是否是同一个)
    // output:
    // i am synMethod1 start
    // i am synMethod1 end
    // i am synMethod2 start
    // i am synMethod2 end
    // i am synMethod2 start
    // i am synMethod2 end
    @org.junit.Test
    public static void testSameObjectUnstatic() {
        // 先运行完其中的一个实例方法完再运行下一个实例方法
        new Thread(new Runnable() {
            @Override
            public void run() {
                // TODO Auto-generated method stub
                obj1.synMethod1();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                // TODO Auto-generated method stub
                obj1.synMethod2();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                // TODO Auto-generated method stub
                obj1.synMethod2();
            }
        }).start();
    }

    // 测试:不同类实例的实例方法
    // 结论:同步实例方法的锁对象是类实例,所以不同类实例之间可以并行的运行实例方法
    // output:
    // i am synMethod1 start
    // i am synMethod2 start
    // i am synMethod2 end
    // i am synMethod1 end
    @org.junit.Test
    public static void testDifferentObjectUnstatic() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                // TODO Auto-generated method stub
                obj2.synMethod1();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                // TODO Auto-generated method stub
                obj1.synMethod2();
            }
        }).start();
    }

    // 测试:类的静态方法
    // 结论:同步静态方法的锁对象是Class类本身,所以静态方法间的运行是串行的
    // 注意与该方法的调用者(Class或者类实例)无关
    // output:
    // i am synstaMethod1 start
    // i am synstaMethod1 end
    // i am synstaMethod2 start
    // i am synstaMethod2 end
    @org.junit.Test
    public static void testClassStatic() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                // TODO Auto-generated method stub
                TestSynchronized.synstaMethod1();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                // TODO Auto-generated method stub
                TestSynchronized.synstaMethod2();
            }
        }).start();
    }

    // 测试:类的静态方法和类实例的实例方法
    // 结论:类的静态方法和类实例的实例方法同时运行,由于锁对象不同,运行是并行的
    // 注意与该方法的调用者(Class或者类实例)无关
    // output:
    // i am synstaMethod1 start
    // i am synMethod2 start
    // i am synstaMethod1 end
    // i am synMethod2 end
    @org.junit.Test
    public static void testClassAndObject() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                // TODO Auto-generated method stub
                TestSynchronized.synstaMethod1();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                // TODO Auto-generated method stub
                obj1.synMethod2();
            }
        }).start();
    }

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        testClassAndObject();
    }

}

3.Android 事件分发

参考:(荐)http://blog.csdn.net/duo2005duo/article/details/51604119 http://www.jianshu.com/p/6ebdb78f579e

1.一次Touch有UP,MOVE(>=0次),UP/Cancel,事件至上而下传递

ViewGroup的dispatchTouchEvent方法分析

public boolean dispatchTouchEvent(MotionEvent ev) {
    final int action = ev.getAction();
    final float xf = ev.getX();
    final float yf = ev.getY();
    final float scrolledXFloat = xf + mScrollX;
    final float scrolledYFloat = yf + mScrollY;
    final Rect frame = mTempRect;

    boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;

    if (action == MotionEvent.ACTION_DOWN) {
        //1.只有在非拦截的情况的下寻找target
        if (disallowIntercept || !onInterceptTouchEvent(ev)) {
            // 防止onInterceptTouchEvent()的时候改变Action
            ev.setAction(MotionEvent.ACTION_DOWN);
            // 遍历子View,第一个消费这个事件的子View的为Target
            final int scrolledXInt = (int) scrolledXFloat;
            final int scrolledYInt = (int) scrolledYFloat;
            final View[] children = mChildren;
            final int count = mChildrenCount;
            for (int i = count - 1; i >= 0; i--) {
                final View child = children[i];
                //当然只遍历可见的,并且没有在进行动画的。
                if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
                        || child.getAnimation() != null) {
                    child.getHitRect(frame);
                    if (frame.contains(scrolledXInt, scrolledYInt)) {
                        // offset the event to the view's coordinate system
                        final float xc = scrolledXFloat - child.mLeft;
                        final float yc = scrolledYFloat - child.mTop;
                        ev.setLocation(xc, yc);
                        if (child.dispatchTouchEvent(ev))  {
                            // Event handled, we have a target now.
                            mMotionTarget = child;
                            return true;
                        }
                                            }
                }
            }
        }
    }

    boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||
            (action == MotionEvent.ACTION_CANCEL);
    //up或者cancel的时候清空DisallowIntercept
    if (isUpOrCancel) {
        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
    }

    // 如果没有target,则把自己当成View,向自己派发事件
    final View target = mMotionTarget;
    if (target == null) {
        // We don't have a target, this means we're handling the
        // event as a regular view.
        ev.setLocation(xf, yf);
        return super.dispatchTouchEvent(ev);
    }

    // 如果有Target,拦截了,则对Target发送Cancel,并且清空Target
    if (!disallowIntercept && onInterceptTouchEvent(ev)) {
        final float xc = scrolledXFloat - (float) target.mLeft;
        final float yc = scrolledYFloat - (float) target.mTop;
        ev.setAction(MotionEvent.ACTION_CANCEL);
        ev.setLocation(xc, yc);
        if (!target.dispatchTouchEvent(ev)) {
            // target didn't handle ACTION_CANCEL. not much we can do
            // but they should have.
        }
        // clear the target
        mMotionTarget = null;
        // Don't dispatch this event to our own view, because we already
        // saw it when intercepting; we just want to give the following
        // event to the normal onTouchEvent().
        return true;
    }
    //up 或者 cancel清空Target
    if (isUpOrCancel) {
        mMotionTarget = null;
    }

    //如果有Target,并且没有拦截,则向Target派发事件,这个事件会转化成Target的坐标系
    final float xc = scrolledXFloat - (float) target.mLeft;
    final float yc = scrolledYFloat - (float) target.mTop;
    ev.setLocation(xc, yc);

    return target.dispatchTouchEvent(ev);
}

2.点击某个View控件,系统计算位置然后找到根ViewGroup; ACTION_DOWN且ViewGroup非拦截向下分发:如果ViewGroup的某个子View消费掉(View的dispatchTouchEvent返回true),则不再分发并且进行向上传递:ViewGroup遍历子View,将第一个消费这个事件的子View置为Target 3.如果ViewGroup没有target,则把自己当成View,向自己派发事件:向下分发时没有子View消费 4.如果ViewGroup有Target,并且拦截了,则对Target发送Cancel,并且清空Target。需要分析onInterceptTouchEvent方法 5.如果ViewGroup有Target,并且没有拦截,则向Target派发事件。比如DOWN时设置子View为Target 然后现在ACTION_MOVE,则向该子VIEW派发该事件

View的dispatchTouchEvent方法

public boolean dispatchTouchEvent(MotionEvent event) {
    boolean result = false;
    //1.停止嵌套滑动
    final int actionMasked = event.getActionMasked();
    if (actionMasked == MotionEvent.ACTION_DOWN) {
        stopNestedScroll();
    }
    //2.安全监测
    if (onFilterTouchEventForSecurity(event)) {
        //noinspection SimplifiableIfStatement
        ListenerInfo li = mListenerInfo;

        //3.如果当前View使能(setEnabled(true)),则调用Touch监听器    
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {
            result = true;
        }
        //4.如果Touch监听器返回false或者没有调用Touch监听器,则返回调用onTouchEvent()
        if (!result && onTouchEvent(event)) {
            result = true;
        }
    }
    //停止嵌套滑动
    if (actionMasked == MotionEvent.ACTION_UP ||
            actionMasked == MotionEvent.ACTION_CANCEL ||
            (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
        stopNestedScroll();
    }

    return result;
}

分析3. 第一个if语句要同时满足li != null 、 li.mOnTouchListener != null 、 (mViewFlags & ENABLED_MASK) == ENABLED 、 li.mOnTouchListener.onTouch(this, event); 第一个一般不会为null,所以走第二个条件,刚刚上面说到mOnTouchListener是我们在setOnTouchListener的时候传递过来的,所以只要设置了TouchListener,这个条件也会成立; 第三个条件是判断控件是否是enable的,一般控件默认都是enable的,除非你明确的设置成disable; 接着判断第四个条件,就是回调mOnTouchListener的onTouch方法,这个方法是我们自己重写的,如果我们返回false,这个条件就不成立,否则我们返回true,该条件就成立,然后执行result=true

如果第一个if不满足就会执行onTouchEvent方法

public boolean onTouchEvent(MotionEvent event) {    

...
if (((viewFlags & CLICKABLE) == CLICKABLE ||
            (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
            (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
        switch (action) {
            case MotionEvent.ACTION_UP://抬起事件
                //巴拉巴拉巴拉....
                if (mPerformClick == null) {
                    mPerformClick = new PerformClick();
                }
                if (!post(mPerformClick)) {
                    performClick();//去调用onClick方法
                }
                ......
                break;
            case MotionEvent.ACTION_DOWN://按下事件
                //巴拉巴拉巴拉....
                break;
            case MotionEvent.ACTION_CANCEL://取消事件
                //巴拉巴拉巴拉....
                break;
            case MotionEvent.ACTION_MOVE://移动事件
                //巴拉巴拉巴拉....
                break;
        }//switch语句执行完毕

    return true;//如果控件可以点击,也就是说可以进入到这个if语句里面,那么总会返回true
}//if语句结束
return false;//如果控件不可以点击,就进不到上面的if语句里面,那么总会返回false

} 这个条件是判断控件是否可以支持点击事件,如果支持点击事件进入语句体,开始执行switch语句,可以看到switch执行完毕之后总是会返回true onTouchEvent方法总结: 1.不管View使能与否,只要clickable或者longclickable,就一定消费事件(返回true) 2.如果View不使能,并且clickable或者longclick,就只会消费事件但不做其他任何操作 3.如果View使能,先看看TouchDelegate消费与否,如果不消费再给自己消费 4.处理包括focus,press,click,longclick

Demo:

tv.setOnTouchListener(new View.OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    Log.i(TAG, "onTouch: ACTION_DOWN");
                    break;

                case MotionEvent.ACTION_MOVE:
                    Log.i(TAG, "onTouch: ACTION_MOVE");
                    break;

                case MotionEvent.ACTION_UP:
                    Log.i(TAG, "onTouch: ACTION_UP");
                    break;
            }
            return false;
        }
    });

点击TextView,log如下:

06-04 04:38:46.835 15690-15690/cn.wang.permissiondemo I/ThirdActivity: onTouch: ACTION_DOWN

执行流程(TextView的CLICKABLE=false): 1.ACTION_DOWN:ViewGroup向下分发,找到某个View尝试进行消费 2.该View执行TouchListener.onTouch 处理了ACTION_DOWN,但是返回false,继续运行该View的onTouchEvent发现CLICKABLE是false然后方法返回false. 3.遍历子View后,此时ViewGroup没有找到子View可以消费事件,将自己当成View进行派发事件(super.dispatchTouchEvent),该dispatchTouchEvent默认返回false,就这样不断向上传递 4.ACTION_MOVE:ViewGroup没有Target,故还是自己当成View进行派发事件,并返回false 总的说,打印了ACTION_DOWN只是一开始View进行尝试消费时运行的..

4.内存泄漏检测工具

先说下经常出现内存泄漏的几种情况及解决

1.Activity的Context传给单例->改为传ApplicationContext

2.在某个类(Activity)中创建了一个非静态内部类(默认会持有外部类(Activity)的引用),并且创建了该类的单例(全局静态变量),该变量在某个方法中被实例化;

注:该变量生命周期与应用一致.也就是说,在应用生命周期中,该变量会持有Activity的引用导致不能进行回收
改进->将该非静态内部类该为静态内部类或单独提出封装成一个单例

3.通过这样创建的Handler handler=new Handler(){...}非静态匿名内部类持有Activity的引用. 当Activity销毁 而Handler里面有消息未完全处理完时,无法正常回收Activity.

改进->覆盖Handler类被静态实现被弱引用Context,被注意移除消息队列中消息(否则虽避免Activity内存泄漏但是Handler)

    public class MainActivity extends AppCompatActivity {
  private MyHandler mHandler = new MyHandler(this);
  private TextView mTextView ;
  private static class MyHandler extends Handler {
    private WeakReference<Context> reference;
    public MyHandler(Context context) {
      reference = new WeakReference<>(context);
    }
    @Override
    public void handleMessage(Message msg) {
      MainActivity activity = (MainActivity) reference.get();
      if(activity != null){
        activity.mTextView.setText("");
      }
    }
  }
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    mTextView = (TextView)findViewById(R.id.textview);
    loadData();
  }
  private void loadData() {
    //...request
    Message message = Message.obtain();
    mHandler.sendMessage(message);
  }
  @Override
  protected void onDestroy() {
    super.onDestroy();
    mHandler.removeCallbacksAndMessages(null);
  }
}

4.资源未关闭造成的内存泄漏

BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap,注册的系统监听器等资源的使用,应该在Activity销毁时及时关闭或者注销

使用完Bitmap后先调用recycle(),再赋为null

5.线程造成的内存泄漏

将异步任务和Runnable写成非静态匿名内部类,那么他们持有Activity的引用 如果Activity销毁时任务还没完成将导致Activity的资源无法正常回收 正确的做法还是使用静态内部类的方式

检测工具

MAT(比较复杂,面试的时候就提到有安装该插件 但是具体没用过) LeakCanary(onCreate里加一句代码,然后出现泄漏会发通知,点通知跳转到具体原因的页面,面试就讲这个) ndk内存泄漏检测工具?Valgrind, 有点复杂

5.View绘制过程

1.自上而下,先设置父视图,再循环设置子视图; 2.先通过measure计算视图大小,再通过layout计算视图位置,再调用draw绘制; 3.子视图的measure由父子视图共同决定,layout方法是传入四个位置的坐标,draw时先绘制background,然后绘制当前view的内容,再遍历view; 总过程:measure(viewgroup->childview)->layout(viewgroup->childview)->draw(viewgroup->childview);

6.Android的两种广播注册方式

代码中注册,与程序生命周期有关 Manifest.XML中注册,在系统中常驻:始终处于活动状态 耗电

7.ThreadLocal 的用法

提供线程内部的局部变量,本线程随时可取,隔离其他线程

8.Java的几种引用

软引用对象在内存不足会被回收;
弱引用只要进行GC就会被回收;
虚引用对象任何时候都会被回收(不仅是GC);
强引用对象不会被回收,一般不用的时候需要代码显示将其置null,不然宁愿OOM也不会回收;

另外讲下GC分主GC和次GC,次GC比较平滑,主GC满足一定条件才会调用 System.gc():调用该方法,JVM底层建议进行主GC,大部分情况会进行非一定,增加了间接性停顿的次数..

9.Android数据库升级

传入的数据库版本比之前的大,会调用SQLiteOpenHelper#onUpgrade()方法,我们在该方法做如下操作:

1.将要修改的表A重命名为A_temp
2.先建表A,并把A_temp的数据传入A
3.删除A_temp

10.HandlerThread

另开线程,封装了创建Looper,Handle去处理MessageQueue的操作

11.onSaveInstanceState和onRestoreInstanceState

onSaveInstanceState触发时机:存在非用户主动销毁Activity的情况下会触发,如:按HOME,切换程序,转屏,进ActivityB,关屏幕等,系统在内存不足时会销毁Activity(转屏是一定会销毁如果没设置的话)

onRestoreInstanceState调用时机:Activity确实被销毁了

12.Hashtable HashSet HashMap 细节

HashSet里面维护了一个HashMap map变量,增删改查都是对map对象的key进行操作,map的key即set的value HashMap里面定义了静态内部类Node<K,V>transient Node<K,V>[] table,K由key和hash(key)确定;非并发操作 Hashtable 维护了private transient Entry<?,?>[] table,其上的操作synchronized->并发,允许null值

13.java8 ConcurrentHashMap源码 分段锁是什么

insert链表头是采用CAS汇编指令集判断数据一致性,每个链表头加锁 链表>8才则tree化,桶数量>64也tree化

14.bitmap压缩

inJustDecodeBounds设置为true 不会分配bitmap内存 但是可以获取宽高 然后设置inSampleSize为我们想要的压缩比(和获取的宽高有关,取小的比例?)

源300,200 目标100,100 一般是压缩2 变为 150,100 而不是100,67..不过这也要看应用

再设置 inJustDecodeBounds为false 然后return BitmapFactory.decodeResource(res, resId, options);

15.Android中的几种设计模式(待补充)

工厂

BitmapFactory.decodeResource.. 生产出bitmap

适配器

不同的数据提供者通过同一接口提供给同一客户:Adapter

创建者模式

复杂对象的构建和表现分离

抽象工厂

MediaPlayerFactory:ijk项目的player有好几种实现,后面具体生成哪个Player就看用户选择了

原型

Bitmap.copy(Config.RGB_xxx)方法修改部分数据然后进行对象的clone AlertDialog.Builder

16.[笔试]扔硬币正反概率都是0.5,直到连续的2次都为正面即结束,问结束次数的期望

解:这题如果一直穷举,列数列,找规律 难度很大,会在其中陷入循环.... 所以我们设法得到一个等式。设次数的期望为E.我们现在开始抛第一枚硬币,有以下情况:

  1. 第一枚硬币为负,那么需要重新扔,所占比例的期望为0.5*(1+E)
  2. 第一枚硬币为正,第二枚为负,那么需要重新扔,所占比例的期望为0.25*(2+E)
  3. 一正二正,结束,0.25*2

    得到 E= 0.5*(1+E)+ 0.25*(2+E)+ 0.25*2 解得E=6

也可以写代码跑下,随着样例越大答案会趋近于6

改为 负正 即结束 问期望

情况为以下,注:当出现连续的-时我们只考虑下次是否为+即可,否则说明为-我们继续判断下下次

  1. +:0.5*(E+1)
  2. -+:0.25*2
  3. --+:0.125*3 ...
  4. (-...-)[n个]+(0.5)^(n+1)*(n+1)

    E=0.5E+0.5+0.252+...+(0.5)^(n+1)(n+1) 高中知识:错位相减法 0.5E=0.51+0.252+...+(0.5)^(n+1)(n+1) 0.25E=0.251+0.1252+...+(0.5)^(n+2)(n+1) 两式相减: 0.25E=0.5+0.25+0.125+...+0.5^(n+1)-(0.5)^(n+2)(n+1) 等比数列求和:0.5+0.25+0.125+...+0.5^(n+1) a1=0.5 q=0.5 等比数列前n项和(其实不用算也能得到是趋于1的无穷...E=4): Sn=0.5(1-0.5^n)/(1-0.5)=1-0.5^n 故0.25E=1-0.5^n-0.5^(n+2)(n+1) E=4(1-0.5^n-0.5^(n+2)*(n+1))

n→∞ 得E=4

改成 出现+-甲赢,--乙赢,问甲赢的概率

只要投硬币出现+说明后面甲一定赢了,乙赢的情况必须是一开始就是-- 很容易求得甲赢的概率为0.5+0.5*0.5=0.75

改成 出现--+说明甲赢 出现-++说明乙赢,问甲赢的概率

~~ 思路1:分别求--+和-++的期望 ~~

[不可行 例:-+和--期望分别是4 6 但是两人赢的概率是一样的] 说明两个事件是相关的,不能分别求。

~~ 思路2:那么分别求不出现-++情况条件下出现--+的期望和不出现--+情况条件下出现-++的期望呢,条件概率。~~

思路3:直接算

顺便复习可计算理论 求 表示不含-++且以--+结尾的-+字符串的`正则表达式 先画DFA

1

DFA转正则 2.png

最后的正则表达式如下 \+*-(\+-|--)*-\+

所以不含-++且以--+结尾的概率为 +*- :概率为1 (\+-|--)*-\+ :以下概率的和

先找几项
-+:0.5^2
---+:0.5^2*0.5^2
+--+:0.5^2*0.5^2
-----+:0.5^4*0.5^2
--+--+: 0.5^3 以--+-开头的不用再往后搜索了
+-+--+:0.5^4*0.5^2
+----+:0.5^4*0.5^2
...

把图画出来就好做了: 3.png

a:--(--)*-+:0.5^2*(0.5^2+0.5^4+...+0.5^2n) =0.25*(0.25(1-0.25^n)/0.75)=
b:--(--)*+-:0.5*(0.5^2+0.5^4+...+0.5^2n)=0.5*(0.25(1-0.25^n)/0.75)
s=a+b 表示以--开头 =0.75*(0.25(1-0.25^n)/0.75)
c:+-(+-)*-+:0.5^2*(0.5^2+0.5^4+...+0.5^2n)=0.25*(0.25(1-0.25^n)/0.75)
d:+-(+-)*s:(0.5^2+0.5^4+...+0.5^2n)*s=(0.25(1-0.25^n)/0.75)* 0.75*(0.25(1-0.25^n)/0.75)
e:-+:0.5^2=0.25

所得概率=a+b+c+d+e=0.25+(0.25(1-0.25^n)/0.75)*(1+ 0.75*(0.25(1-0.25^n)/0.75)) =0.25+1/3*(1+3/4*1/3)=2/3

故甲赢概率是2/3 乙赢概率是1/3


本文由 GaHingZ 创作,采用 知识共享署名 3.0,可自由转载、引用,但需署名作者且注明文章出处。

如果对您有用,您的支持将鼓励我继续创作!