web前端常见面试题

1. var、let 和 const 的区别是什么?

var、let 和 const 是 JavaScript 中声明变量的三种方式,它们的区别如下:

1. var 声明的变量可以重复声明,而 let 和 const 不可以。
2. var 的作用域是函数作用域(function scope),而 let 和 const 的作用域是块作用域(block scope)。
3. let 和 const 声明的变量必须先声明后使用,而 var 声明的变量可以先使用后声明。
4. const 声明的变量不能被重新赋值,而 let 和 var 可以。

总之,建议使用 let 和 const 来声明变量,因为它们更加安全,也更符合现代 JavaScript 的开发规范。

Var 是声明的变量既是全局变量,也是顶层变量,有变量提升,后声明的会覆盖前面声明的

Let 是声明块级作用域,不存在变量提升,不允许在相同作用域中重复声明

Const 是声明一个只读的常量,一旦声明,常量的值就不能改变,声明一个只读的常量,一旦声明,常量的值就不能改变

2. promise有哪几种状态,分别是如何变化的?

Promise 有三种状态:Pending(进行中)、Fulfilled(已成功)和Rejected(已失败)。

当一个 Promise 对象被创建时,初始状态是 Pending,此时可以调用 Promise 的 then() 和 catch() 方法来添加回调函数。

如果异步操作成功完成,Promise 的状态会从 Pending 变为 Fulfilled,此时触发 then() 方法绑定的回调函数。

如果异步操作失败,Promise 的状态会从 Pending 变为 Rejected,此时触发 catch() 方法绑定的回调函数。

在一个 Promise 对象的状态变化后,它的状态和值都不会再次改变。如果 Promise 状态变为 Fulfilled,后面再调用 then() 方法也会立即触发回调函数。如果 Promise 状态变为 Rejected,后面再调用 catch() 方法也会立即触发回调函数。

pending准备 fulfilled成功 rejected失败

pengding->fulfilled/pending->rejected

变化是不可逆的

pending准备, 不会触发then和catch

fulfilled成功, 会触发后续的then回调函数,rejected失败,会触发后续的catch回调函数

3. 请列举3个以上 HTTP常见的请求头,并说明其作用。

1. User-Agent:该请求头是用来通知服务器客户端的软件环境和版本信息,服务器根据用户代理头可以识别客户端的类型和版本信息,从而根据不同类型的客户端返回不同的内容或进行不同的处理。
2. Accept:该请求头是用来告知服务器客户端可以接受的响应内容类型,服务器可以根据 Accept 请求头返回客户端需要的文档类型,从而提高响应效率。
3. Referer:该请求头是用来告知服务器请求的来源,即当前请求是由哪个页面跳转过来的,这个信息可以帮助服务器进行流量分析以及对盗链或CSRF攻击进行防范。
4. Authorization:该请求头是用来携带认证信息,服务器可以根据这个头部信息来判断是否有访问权限。比如,在请求受保护的资源时需要搭配使用 Authorization 请求头来进行用户身份验证。
5. Cookie:该请求头是用来携带 cookie 信息的,服务器可以根据客户端发送过来的 cookie 信息来判断客户端的登录状态或者其他信息。

4. JavaScript 中 0.1 + 0.2 === 0.3 吗?为什么?

JavaScript 中 0.1 + 0.2 不等于 0.3。

具体原因是,JavaScript 中的数字类型采用的是 IEEE 754 双精度浮点数标准,该标准定义了数字类型的二进制表示方法,但对于某些十进制数,可能无法准确地表示为二进制数,进而可能导致舍入误差。

在 JavaScript 中,0.1 和 0.2 都无法准确地表示为二进制数。因此,它们相加得到的结果可能是一个近似值,而不是精确的 0.3。 

具体来说,0.1 的二进制表示是 0.0001100110011001100110011001100110011001100110011......,无限循环,因此在计算机中表示时会存在舍入误差。同样,0.2 的二进制表示是 0.001100110011001100110011001100110011001100110011......,也存在舍入误差。

如果需要高精度计算,可以使用第三方库,比如 big.js、decimal.js 等。

不等于0.3,等于0.30000000000000004

因为浮点数运算的精度问题。在计算机运行过程中,需要将数据转化成二进制,然后再进行计算。

js中的Number类型遵循IEEE754标准,在IEEE754标准的64位浮点数相加,因为浮点数自身小数位数的限制而截断的二进制在转化为十进制,就变成0.30000000000000004,所以在计算时会产生误差

5. 请解释一下typeof 与 instanceof 区别。

typeof 和 instanceof 都是用来判断值的类型的 JavaScript 操作符,它们之间的区别如下:

1. typeof 是一个一元操作符,它的返回值是一个字符串,表示表达式的数据类型。例如:typeof undefined 会返回字符串 "undefined",typeof 'hello' 会返回字符串 "string"。

2. instanceof 是一个二元操作符,它的左操作数是一个对象,右操作数是一个表示对象类型的构造函数。它的返回值是一个布尔值,表示左侧对象是否为右侧构造函数的实例。例如:'hello' instanceof String 会返回布尔值 true,因为 'hello' 是一个 String 类型的实例,而 Object instanceof String 会返回布尔值 false,因为 Object 不是一个 String 类型的实例。

因此,typeof 用于判断原始数据类型,而 instanceof 用于判断对象类型。

typeof会返回一个变量的基本类型,instanceof返回的是一个布尔值

instanceof 可以准确地判断复杂引用数据类型,但是不能正确判断基础数据类型

typeof 也存在弊端,它虽然可以判断基础数据类型(null 除外),但是引用数据类型中,除了function 类型以外,其他的也无法判断

6. 请解释一下浏览器事件触发的流程。

浏览器事件触发的流程如下:

1. 事件的发生:当用户进行某些操作,如点击按钮、滚动页面等,或者浏览器执行某些操作,如页面加载等,就会产生事件。

2. 捕获阶段:事件首先在文档根节点开始被捕获,逐级向下经过各个元素,直到到达触发事件的元素。在这个过程中,如果有父元素的事件被触发,则会先处理父元素的事件,再处理子元素的事件。

3. 目标阶段:事件到达目标节点(触发事件的元素)时,触发目标节点的事件处理程序。

4. 冒泡阶段:事件从目标节点开始向上冒泡,逐级经过各个元素,直到到达文档根节点。在这个过程中,如果有子元素的事件被触发,则会先处理子元素的事件,再处理父元素的事件。

5. 默认行为:如果事件触发后没有被取消,浏览器会执行默认行为,如点击链接跳转页面等。

可以通过addEventListener()方法绑定事件处理程序,其中第三个参数可选,可以指定事件是否在捕获阶段处理。默认值为false,即在冒泡阶段处理事件。

捕获阶段 概念: 事件从根节点流向目标节点,途中流经各个DOM节点,在各个节点上触发捕获事件,直到达到目标节点。

目标阶段 概念: 事件到达目标节点时,就到了目标阶段,事件在目标节点上被触发

冒泡阶段 概念: 事件在目标节点上触发后,不会终止,一层层向上冒,回溯到根节点

7. 请列举一些 JavaScript 中会出现内存泄漏的场景。

JavaScript 中会出现内存泄漏的场景有:

1. 意外的全局变量:如果一个变量被意外地定义为全局变量,即使在函数中使用它,也会占用全局作用域,导致内存泄漏。

2. 未清理的计时器和回调函数:如果一个计时器或回调函数没有被清理,它们就会一直占用内存,导致内存泄漏。

3. 循环引用:如果一个对象引用另一个对象,而后者又引用前者,就会形成循环引用。如果这些对象没有被及时清理,就会导致内存泄漏。

4. 闭包:如果一个闭包存在于一个函数的作用域中,并引用外部函数的变量,在外部函数执行完毕后,这些变量仍然在内存中存在,导致内存泄漏。

5. DOM 操作:如果经常创建和删除 DOM 元素,但没有清理相应的事件处理函数和其他占用内存的数据,就会导致内存泄漏。

6. AJAX 请求:如果经常发送 AJAX 请求,但没有及时清理相关的对象和事件处理函数,就会导致内存泄漏。

7. 资源没有释放:如果使用了一些需要手动释放资源的 API,例如 canvas,但忘记释放相关资源,就会导致内存泄漏。

全局变量造成的内存泄漏、忘记释放的计时器、多处引用、闭包

angualr

1. 请列举出 3 个以上 angular 中使用了 rxjs 的 api。

1. HttpClient:Angular 中的 HttpClient 会返回一个可观察对象(Observable),其订阅者可以使用 RxJS 操作符对返回的数据进行处理。
2. ActivatedRoute:用于在组件中访问路由参数的服务,它使用 RxJS 的 Observable 对象来传递参数变化事件。
3. Forms:Angular 中的表单处理使用了 RxJS 中的一些操作符,如 debounceTime 和 distinctUntilChanged,用于在输入框的值发生变化时触发响应函数。
4. NgRx:一个基于 RxJS 的状态管理库,用于帮助管理 Angular 应用的状态。它使用了 RxJS 中的一些操作符,如 map、switchMap 和 mergeMap 等,用于处理状态变化事件。
5. EventEmitters:Angular 中的 EventEmitters 也使用了 RxJS。它允许组件通过发出事件来通知其他组件或服务,其他组件或服务则可以使用 Observable 对象来订阅这些事件。

2. 请解释一下 angualr 中出现 ExpressionChangedAfterItHasBeenCheckedError 错误 的原因。

在 Angular 的变更检测机制中,当数据发生变化时,Angular 会遍历所有组件的视图层级,并检查视图中的表达式是否有变化。如果检测到有变化,Angular 会更新该视图,并再次检测所有子组件的表达式,以此类推。这个过程称为变更检测。

ExpressionChangedAfterItHasBeenCheckedError 错误发生的原因是,当在 Angular 的生命周期钩子中(如 ngOnInit、ngAfterViewInit 等)或者 Angular 模板中更新了组件的属性或者模板绑定时,Angular 在第一轮变更检测之后又执行了一轮变更检测,发现这个组件的某个表达式值发生了变化。这时 Angular 会抛出 ExpressionChangedAfterItHasBeenCheckedError 错误,因为此时再去更新视图已经来不及了。

解决该错误的方法有如下几种:

1.将组件变量的更新操作放到 ngAfterViewInit 以后的生命周期钩子函数中进行。

2.使用 ChangeDetectorRef.markForCheck() 或者 NgZone.run() 方法来手动触发变更检测。

3.使用 setTimeout 函数将变更操作放到下一次事件循环中进行,这样可以让 Angular 先完成上一轮变更检测后再执行更新操作。

3. 请说明一下 angular 组件 ChangeDetectionStrategy 的 OnPush 和 Default 模式 之间的区别。

Angular 组件在渲染过程中会依赖变化检测系统来监测数据变化并更新视图。变化检测系统会根据特定的策略来判断组件是否需要进行检测,其中就包括 `ChangeDetectionStrategy`。

Angular 中有两种 `ChangeDetectionStrategy`:`OnPush` 和 `Default`。它们之间的区别主要体现在如何触发变化检测。

在 `Default` 模式下,每次 Angular 的事件循环都会触发变化检测,即使组件的输入属性没有发生变化也会触发。这就会导致一些不必要的性能开销。

而在 `OnPush` 模式下,只有当组件的输入属性发生变化时才会触发变化检测。这样可以避免不必要的检测和重渲染,提高了组件的性能。

在使用 `OnPush` 模式时,还需要注意以下几点:

1. 需要使用 `@Input()` 装饰器来明确标识输入属性,以便能够监测到它们的变化。
2. 如果组件依赖了除了输入属性以外的数据源,需要手动调用 `detectChanges()` 方法来触发变化检测。
3. 对于数组类型的输入属性,需要使用不可变对象来保证变化检测的正确性。

总之,`OnPush` 模式在开发大型应用时可以提高组件的性能和响应速度,但使用时需要注意一些细节。

vue

1. 请说明 vue 的组合式 api 与选项式 api 相比具有哪些优点?

Vue 3.0 引入了全新的组合式 API,相较之前的选项式 API,具有以下优点:

1. 更好的代码组织和复用:选项式 API 是基于选项的,通过选项来定义组件的状态和行为,但是在复杂的组件中,选项的数量会不断增加,导致代码难以维护。组合式 API 将代码按照功能分离到不同的函数中,更好的组织和复用代码。

2. 更灵活的逻辑复用:选项式 API 中的 mixin 是一种逻辑复用方式,但是 mixin 会引入命名冲突和响应式数据保持一致的问题。组合式 API 中的函数和组件可以在不同组件之间共享,避免了这些问题。

3. 更好的类型推断和 IDE 支持:组合式 API 使用 TypeScript 实现,可以更好的类型推断和 IDE 支持。选项式 API 使用对象字面量来定义组件,类型推断更困难,IDE 支持也不够友好。

4. 更好的可测试性:组合式 API 中的函数和组件可以更容易被单独测试,而选项式 API 中的组件往往需要整体渲染才能测试。

综上所述,组合式 API 相较于选项式 API,拥有更优秀的代码组织和复用、型推断和 IDE 支持、可测试性等方面的优点。

选项式 API,可以用包含多个选项的对象来描述组件的逻辑,例如 data、methods 和 mounted。选项所定义的属性都会暴露在函数内部的 this 上,它会指向当前的组件实例。 优点: 1.上手简单,位置固定 缺点: 一个功能往往需要在不同的vue配置项中定义属性和方法,比较分散,项目小还好,清晰明了,但是项目大了后,一个methods中可能包含很多个方法,往往分不清哪个方法对应着哪个功能,而且当你想要新增一个功能的时候你可能需要在 data,methods,computed,watch中都要写一些东西,但是这个时候每个选项里面的内容很多你需要上下来回的翻滚,特别影响效率。

组合式 API,可以使用导入的 API 函数来描述组件逻辑,是一系列 API 的集合,可以使用函数的方式书写 Vue 组件。 组合式 API 的风格是基于函数的组合,但组合式 API 并不是函数式编程。组合式 API 是以 Vue 中数据可变的、细粒度的响应性系统为基础的,而函数式编程通常强调数据不可变。

2. 请说明 computed 和 watch 的区别和运用的场景。

computed 和 watch 都是 Vue.js 的核心特性,都是用于监控数据变化并对视图进行更新。

computed是计算属性,它的值会根据依赖(比如data 或 prop)自动计算,只要它的依赖不改变,它的值就不会改变。computed属性是具有缓存功能的,因为computed属性只在它的依赖发生改变时才会重新计算。computed 方法不会改变原始数据,它只是派生出一个新的属性。

watch 是一个观察者,当被监控的数据发生变化时,会触发函数。watch 可以监听一个或多个数据的变化,当数据变化时会执行一些特定的操作,比如更新视图或执行异步操作。watch 监听器可以非常灵活地响应数据的变化,可以处理任何类型的复杂业务逻辑。

区别:

1. computed 是计算属性,watch 是侦听属性。
2. computed 是有缓存的,只有在依赖的值发生改变时才会重新计算,而 watch 执行的函数参数是新值和旧值,可以做一些复杂的逻辑操作。

运用的场景:

1. computed:适合处理一些需要计算的属性,比如格式化日期、处理字符串等。
2. watch:适合响应式监听某个数据的变化而做一些复杂的异步操作或处理非常复杂的逻辑。比如页面滚动、异步请求等。

watch

watch是监听一个值的变化,然后执行对应的回调;

watch中的函数不需要调用;

watch有两个参数;

immediate:组件加载立即触发回调函数执行,

deep: 深度监听,为了发现对象内部值的变化,复杂类型的数据时使用,例如数组中的对象内容的改变

watch中的函数名称必须要和data中的属性名一致,watch依赖于data中的属性,data中的属性发生变化的时候,watch中的函数就会发生变化。

watch不支持缓存;

computed

computed是计算属性,通过属性计算得来的属性。

computed中的函数直接调用,不用()。

computed中的函数必须用return返回。

computed是依赖data中的属性,data中属性发生改变的时候,当前函数才会执行,data中属性没有改变的时候,当前函数不会执行。

computed中不能对data中的属性进行赋值操作,如果对data中的属性进行赋值,data中的属性发生变化,从而触发computed中的函数,就会形成死循环。

computed属性的结果会被缓存,除非依赖的属性发生变化才会重新计算。

使用场景

watch的使用场景:一个数据影响多个数据,需要在数据变化时执行异步操作或者开销较大的操作时使用。

例如:搜索数据

computed:一个数据受多个数据影响,处理复杂的逻辑或多个属性影响一个属性的变化时使用。

例如:购物车商品结算的时候

3. Vue 为什么没有类似于 React 中 shouldComponentUpdate 的生命周期?

Vue 在处理组件更新时,使用了一种称为“响应式”的机制,它能够追踪数据变化,并在数据变化时自动更新视图。这种机制可以在没有显式声明生命周期方法的情况下,实现高效的组件更新。

在 Vue 中,组件的更新会经过以下三个阶段:

1. 更新前:在这个阶段,Vue 会比较新旧虚拟 DOM 树的差异,并尝试复用已经存在的 DOM 元素,从而减少真实 DOM 操作的次数。这个过程不需要开发者介入。
2. 更新中:在这个阶段,Vue 会执行一系列的更新操作,包括 props 数据的更新、computed 属性的重新计算、监听器的执行等,如果数据发生变化,组件的视图也会自动更新。这个过程同样不需要开发者介入。
3. 更新后:在这个阶段,Vue 组件已经完成了更新,开发者可以通过 `updated` 生命周期钩子来执行一些额外的操作。

因此,Vue 并不需要像 React 一样通过 shouldComponentUpdate 来手动控制组件的更新,而是通过响应式机制自动进行更新。不过,Vue 也提供了一些类似于 shouldComponentUpdate 的优化方式,例如使用 `v-if` 和 `v-show` 来控制组件的渲染,以及使用 `computed` 计算属性来缓存计算结果等。

react

1. 说一下对 React Hook 的理解,它的实现原理,和生命周期有哪些区别。

React Hook 是 React 16.8.0 版本引入的新功能,它允许我们在函数式组件中使用状态管理和其他 React 特性,使得函数组件也能够拥有类组件同样的能力。因此,代码变得更加简洁易读,且可以减少组件之间的耦合度。

React Hook 的实现原理是利用了闭包和内部函数的特性来保存状态和管理函数组件的生命周期,从而取代了类组件中的 this 概念和生命周期函数的执行。

React Hook 和生命周期的主要区别是,生命周期函数是类组件的特定方法,而 Hook 可以在函数组件中使用。同时,生命周期函数只能在类组件中按照特定的顺序执行,而 Hook 则可以随意调用且每个 Hook 都是独立的。React Hook 主要包括 useState、useEffect、useContext、useReducer、useCallback、useMemo、useRef 和 useImperativeHandle 等。

2. 请列举出两种以上的 React 兄弟组件的通信方式。

1. 父组件通过 props 将数据传递给子组件,子组件通过回调函数将数据传递给父组件。
2. 使用 React Context 在一组件树中共享数据,允许多个组件访问相同的数据。
3. 使用第三方库,比如 Redux 或 MobX,来在整个应用程序中管理状态。可以使用这些库来跨组件传递和共享数据。
4. 使用 Event Bus 模式,创建一个事件总线组件,其他组件可以向此组件注册事件,并且此组件可以广播事件给所有已注册的组件。
5. 使用发布/订阅模式,通过为组件绑定事件监听器,从而实现组件之间的通信。

3. 虚拟(Virtual)DOM 的工作原理是什么?

虚拟 DOM 的工作原理如下:

1. 当页面数据发生变化时,框架会先对这些数据进行处理,生成新的虚拟 DOM 树。该树与之前的虚拟 DOM 树进行比较。

2. 在比较过程中,框架会先比较树的根节点是否相同。如果相同,则继续比较子节点;如果不同,则直接删除旧节点,插入新节点。

3. 对于子节点,框架会按照节点类型、属性等信息进行比较。如果发现节点类型不同,则直接删除旧节点,插入新节点;如果类型相同,则比较属性差异,并更新需要更新的节点。

4. 最后,框架会将新的虚拟 DOM 树转换成真实的 DOM 树,并进行渲染。

虚拟 DOM 的工作原理通过将需要更新的节点挂载在虚拟 DOM 树上进行比较,然后批量进行更新,减少了真实 DOM 树的操作,从而提高了性能和效率。

编程题1 给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出和为目标值 target 的那两个整数,并返回它们的数组下标。 你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出 现。 示例: 输入:nums = [2,7,11,15], target = 9 输出:[0,1] 解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。

let twoSum = function (nums, target) {

    for (let i = 0; i < nums.length; i++) {

        let iItem = nums[i]

        console.log(iItem)

        for (let j = 0; j < nums.length; j++) {

            if (j == i) {

                continue;

            }

            let jItem = nums[j]

            console.log(jItem)

            if (jItem + iItem == target) {

                return [i, j]

            }

        }

    }

};

//运行结果 [0,1]

编程题2 给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。 有效字符串需满足: 左括号必须用相同类型的右括号闭合。 左括号必须以正确的顺序闭合。 每个右括号都有一个对应的相同类型的左括号。 示例: 输入:s = "()[]{}" 输出:true

思路:使用栈来判断括号是否匹配。遍历字符串,如果是左括号就入栈,如果是右括号就判断栈顶元素是否与其匹配,如果匹配则弹出栈顶,如果不匹配则返回 false。最后如果栈为空,说明括号匹配。

具体代码如下:

function isValid(s) {
  const stack = [];
  const map = {
    '(': ')',
    '{': '}',
    '[': ']',
  };
  for (let i = 0; i < s.length; i++) {
    if (map[s[i]]) {
      // 左括号入栈
      stack.push(s[i]);
    } else {
      // 右括号与栈顶元素匹配则弹出栈顶,否则返回 false
      if (map[stack.pop()] !== s[i]) {
        return false;
      }
    }
  }
  // 栈为空则括号匹配
  return stack.length === 0;
}

测试:

console.log(isValid('()[]{}')); // true
console.log(isValid('([)]')); // false

var isValid = function (s) {
  const stack = [];
  for (let i = 0; i < s.length; i++) {
    let c = s[i];
    switch (c) {
      case '(':
        stack.push(')');
        break;
      case '[':
        stack.push(']');
        break;
      case '{':
        stack.push('}');
        break;
      default:
        if (c !== stack.pop()) {
          return false;
        }
    }
  }
  return stack.length === 0;
};
// 简化版本
var isValid = function(s) {
    const stack = [], 
        map = {
            "(":")",
            "{":"}",
            "[":"]"
        };
    for(const x of s) {
        if(x in map) {
            stack.push(x);
            continue;
        };
        if(map[stack.pop()] !== x) return false;
    }
    return !stack.length;
};

逻辑题 

5个海盗抢得100枚金币,他们决定按抽签的顺序依次提方案:首先由1号提出分配方案, 然后剩余的所有人表决,投票数超过半数同意方案即被通过并按分配方案执行,如未通 过则将被扔入大海喂鲨鱼,然后由2号提出分配方案,依此类推。假定每个海盗都是绝对 聪明且绝对理性的人,每个海盗都是在首先保证自己活命的前提下尽可能的分到更多的 金币,在自己利益最大的时候如果能再看着别的海盗被鲨鱼吃掉对他们来说是更好的事 情。那么最终哪个海盗能存下来呢,最终的分配方案是什么呢?

假设海盗编号依次为1,2,3,4,5,他们依次提出的分配方案如下:

1号海盗:
100枚金币中,他自己拿99枚,其他海盗各拿0枚,得票情况为1票赞成(他自己),4票反对(其他海盗)。方案未通过,1号海盗被扔入大海。

2号海盗:
100枚金币中,他自己拿1枚,3号海盗拿99枚,其他海盗各拿0枚,得票情况为2票赞成(2号、3号海盗),2票反对(1号、5号海盗)。方案通过,按照分配方案执行,2号海盗拿到1枚金币,3号海盗拿到99枚金币。

3号海盗:
100枚金币中,他自己拿0枚,4号海盗拿99枚,其他海盗各拿0枚,得票情况为2票赞成(3号、4号海盗),2票反对(2号、5号海盗)。方案未通过,3号海盗被扔入大海。

4号海盗:
100枚金币中,他自己拿99枚,5号海盗拿1枚,其他海盗各拿0枚,得票情况为2票赞成(4号、5号海盗),2票反对(1号、2号海盗)。方案通过,按照分配方案执行,4号海盗拿到99枚金币,5号海盗拿到1枚金币。

5号海盗:
100枚金币中,他自己拿0枚,其他海盗各拿33枚,得票情况为3票赞成(2号、4号、5号海盗),1票反对(1号海盗)。方案通过,按照分配方案执行,每个海盗都拿到了33枚金币。

因此,最终存活的是5号海盗,金币分配方案为每个海盗都拿到了33枚。

我们从后向前的模拟每个人的思考:

  • 5号:如果1 2 3号都被鲨鱼吃了,那么那100个金币我肯定得了,因为4号无论怎么投票都会被喂鲨鱼。(超过半数)如果没到4号选择,那么我就选给我金币最多的那个人。
  • 4号:因为如果1 2 3号都被鲨鱼吃掉,自己必死无疑,所以我要支持前面的人来活命,并且金币越多越好。
  • 3号:由于知道4号要活命,一定会投我,所以如果1号,2号死了以后,我的分配为(100 0 0),一定可以以2:1获胜。如果没有死,那我就选金币最多的情况。
  • 2号:我可以推理出3号的想法,他一定会阻止我,不过没关系,我只要让后面几个人支持我就行,所以我的分配为(98 0 1 1)就行。这样我就可以以3:2鹰下比赛。
  • 1号:我可以推理出2号的想法,他一定会阻止我,不过没关系,我只要给后面人多一点钱就行,我的分配是:(97 0 1 2 0)或者(97 0 1 0 2)就可以通过3:2赢下比赛。

没错,只要1号并将提出(97,0,1,2,0)(97,0,1,0,2)的方案,即放弃2号,而给3号一枚金币,同时给4号(或5号)2枚金币。由于1号的这一方案对于3号和4号(或5号)来说,相比2号分配时更优,他们将投1号的赞成票,再加上1号自己的票,1号的方案可获通过,97枚金币可轻松落入囊中。这无疑是1号能够获取最大收益的方案了!答案是:1号强盗分给3号1枚金币,分给4号或5号强盗2枚,自己独得97枚。分配方案可写成(97,0,1,2,0)或(97,0,1,0,2)。

posted @ 2023-06-13 21:00  JackieDYH  阅读(6)  评论(0编辑  收藏  举报  来源