LCR 121寻找目标值

LCR 121寻找目标值

我的思路

我的思路,很混乱。因为有两个方向,向下向右都增大,我就不知道该向下还是向右了。然后设计了很多情况,最后发现条件都理不顺。哭了

转变成树的思路

把数组旋转45度,他就变成了树,节点的左节点比根小,右节点比根大。问题就得到了解决

图 0

解题思路如下:

  • 确定根节点,以右上角为根节点,设置索引i=0,j=plants[0].length
  • plants[i][j] > target那么找左子节点,即j--
  • plants[i][j] < target那么找右子节点,即i++
  • plants[i][j] = target return true
  • 若行索引或列索引越界,表示没找到目标值,返回false
  • 最后补充注意点,测试时发现有的用例是空数组,所以要在函数中判断这种情况,遇到直接return false
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var findTargetIn2DPlants = function (plants, target) {
//特殊情况判断,空数组,return false
if (plants.length === 0) return false;

const r = plants.length;
const c = plants[0].length;
// 通过i,j确定根节点为右上角
let i = 0;
let j = c - 1;
// 从右上角开始查找
while (i < r && j >= 0) {
if (plants[i][j] === target) {
return true;
} else if (plants[i][j] > target) {
j--;
} else {
i++;
}
}
return false;
};

LCR 128.库存管理

库存管理(寻找旋转排序数组最小值)

题目

图 0
图 1

解题

我的思路

我知道最小值的位置的特点是,该位置的数小于其前一个位置的数,当然这里存在一个特殊点是当这个位置为0时,没有前一个数字。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//没写出来
var findMin = function(nums) {
// 特点在于,目标数的前一位大于目标数
//特殊情况是目标数的下表为0,说明旋转了0次
let left = 0,right = nums.length
while(left<right){
let mid = left + Math.floor((right - left)/2)
if(mid === 0) return nums[mid]
else{
if(nums[mid] < nums [mid-1]) return nums[mid]
else if()
}

}
};

正确思路

我们可以认为这个数组是由重复值的。那么一个经过旋转之后的数组可视化后如下图所示。数组中最后一个元素的大小为x,如图中虚线所示。

它具如下的特点:

  • 最小值右边的元素均小于等于x
  • 最小值左边的元素均大于等于最小值

    基于上述特性才使得本题可以使用二分查找法解决。

图 3

二分查找的情况可以分为3类
在二分查找的每一步中,左边界为 low,右边界为 high,区间的中点为 pivot,最小值就在该区间内。我们将中轴元素 nums[pivot]与右边界元素 nums[high]进行比较,可能会有以下的三种情况:

1、nums[pivot] > nums[high],说明最小值在pivothigh之间。low = pivot + 1

图 4

2、nums[pivot] < nums[high],说明最小值不在在pivothigh之间。high = pivot - 1

图 5

3、nums[pivot] === nums[high],这是由于存在重复值。high=high-1

图 6

当二分查找结束时,我们就得到了最小值所在的位置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var stockManagement = function(stock) {
let low = 0;
let high = stock.length - 1;
while (low < high) {
const pivot = low + Math.floor((high - low) / 2);
if (stock[pivot] < stock[high]) {
high = pivot;
} else if (stock[pivot] > stock[high]) {
low = pivot + 1;
} else {
high -= 1;
}
}
return stock[low];
};

LCR 127.跳跃训练

跳跃训练

题目

图 0

我的求解

这道题我很有印象,和爬楼梯是同一个意思。就是要用到动态规划。逻辑在于我要到达第n个格子的方法,等于我要到达第n-1个格子的方法加到达n-2个格子的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var trainWays = function(num) {
//和爬楼梯很像,是动态规划的题目
//dp[i]表示跳到第i个台阶的方法数
let dp = new Array(num + 1).fill(0)
dp[0] = 1
dp[1] = 2
//这里有一些特殊情况,第一种是在程序提交不同过的例子中发现的,
if (num === 0) return dp[0]
//第二种就是只有一个格子
if (num === 1) return dp[0]
//第三种只有两个格子的情况。
if (num === 2) return dp[1]
else {
for (let i = 2; i < num; i++) {
//别忘了题目要求的取模
dp[i] = (dp[i - 1] + dp[i - 2]) % 1000000007
}
return dp[num - 1]
}
};

合并两个有序数组

合并两个有序数组

题目

给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。

算法的时间复杂度应该为 O(log (m+n)) 。

示例 1:

输入:nums1 = [1,3], nums2 = [2]
输出:2.00000
解释:合并数组 = [1,2,3] ,中位数 2

示例 2:

输入:nums1 = [1,2], nums2 = [3,4]
输出:2.50000
解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5

求解

https://leetcode.cn/problems/median-of-two-sorted-arrays/solutions/258842/xun-zhao-liang-ge-you-xu-shu-zu-de-zhong-wei-s-114/

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
/**
* @param {number[]} nums1
* @param {number[]} nums2
* @return {number}
*/
var findMedianSortedArrays = function(nums1, nums2) {
// 保证nums1的较短数组
if (nums1.length > nums2.length) {
[nums1, nums2] = [nums2, nums1];
}
//m为较短数组的长度,n为较长数组的长度
let m = nums1.length; // Add missing variable declaration for m
let n = nums2.length; // Add missing variable declaration for n
// 左边元素的个数
let totalLeft = Math.floor((m + n + 1) / 2);
// 在nums1的[0,m]中找合适的分割线
// 使得nums1[i-1] <= nums2[j] && nums2[j-1] <= nums1[i]
let left = 0, right = m;
// 二分查找
while(left < right){ // Fix the loop condition
let i = left + Math.floor((right - left + 1) / 2);
let j = totalLeft - i;
if(nums1[i-1] > nums2[j]){
//下一轮搜索区间在[left,i-1]
right = i-1;
}
else{
//下一轮搜索区间在[i,right],注意当这个区间只有两个元素时,会陷入死循环,因此在二分的时候要+1
left = i
}
}
//退出循环后找到了想要的分割线
let i = left;
let j = totalLeft - i;
// 定义左右两边的四个值,还是为了适应特殊情况
// 如果 i 等于 0,说明 nums1 的左侧没有元素,所以左侧最大值设为负无穷 -Infinity。否则,左侧最大值就是 nums1[i-1]。
//左侧找最大值,右侧找最小值
let nums1LeftMax = i === 0 ? -Infinity : nums1[i-1];
let nums1RightMin = i === m ? Infinity : nums1[i];
let nums2LeftMax = j === 0 ? -Infinity : nums2[j-1];
let nums2RightMin = j === n ? Infinity : nums2[j];
// 如果总长度是奇数
if((m + n) % 2 === 1){
return Math.max(nums1LeftMax, nums2LeftMax);
}
else{
return (Math.max(nums1LeftMax, nums2LeftMax) + Math.min(nums1RightMin, nums2RightMin)) / 2;
}
};

二分查找

一看就会,一写就废。问题出在边界无法掌握,一不小心就死循环了,因此要掌握二分查找的边界
https://blog.csdn.net/qq_45978890/article/details/116094046

二分查找的条件

  • 有序
  • 只找一个

二分查找的边界

  • 1、[左闭,右闭]

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    const search1 = (arr, target) => {
    let left = 0, right = arr.length - 1;
    // 二分查找,闭区间
    while (left <= right) { // 因为是闭区间,所以left 要小于等于right
    let mid = left + Math.floor((right - left) / 2);
    if (arr[mid] < target) { // 目标值在右边
    left = mid + 1; //应为是左闭右闭,所以不用担心,边界mid+1后mid+1不能被比较
    }
    else if (arr[mid] > target) { // 目标值在左边
    right = mid - 1;
    } else {
    // 找到目标值
    return mid;
    }
    }
    return -1;
    }
    //犯个错误,第二个else if,我用了if,然后下面用了else,导致第二个if和最后的else成了组合,不再能表示所有情况。
    console.log(search([1,2,3,4,5,6,7,8,9], 5)) // 4
  • 2、[左闭,右开)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    const search = (arr, target) => {
    let left = 0, right = arr.length; // 左闭右开区间
    while (left < right) { //终止条件
    let mid = left + Math.floor((right - left) / 2);
    if (arr[mid] < target) { // 目标值在右边
    left = mid + 1; // 更新左边界
    } else if (arr[mid] > target) { // 目标值在左边
    right = mid; // 更新右边界
    } else {
    return mid;
    }
    }
    }
  • 3、(左开,右闭]

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    //左开右闭区间
    const search = (arr, target) => {
    let left = -1, right = arr.length - 1; // 左开右闭区间
    while (left < right) { //终止条件
    let mid = left + Math.floor((right - left + 1) / 2);
    if (arr[mid] < target) { // 目标值在右边
    left = mid; // 更新左边界
    } else if (arr[mid] > target) { // 目标值在左边
    right = mid - 1; // 更新右边界
    } else {
    return mid;
    }
    }
    }
  • 4、开区间写法

const search4 = (arr, target) => {
  let left = -1, right = arr.length; // 开区间
  while (left + 1 < right) { //终止条件
    let mid = left + Math.floor((right - left) / 2);
    if (arr[mid] < target) { // 目标值在右边
      left = mid; // 更新左边界
    } else if (arr[mid] > target) {  // 目标值在左边
      right = mid; // 更新右边界
    } else {
      return mid;
    }
  }
}```

ref和reactive的区别

ref和reactive的区别

1、ref适合监听基本数据类型,reactive监听复杂数据类型,如果让reactive监听基本数据类型,需要将其写对象模式,浪费空间。
2、reactive通过Proxy返回一个代理对象实现响应,ref通过defineProperty实现。

vue2中ref和vue3中ref的区别

在vue2中,ref主要是用来标识结点和组件的,相当于dom里面的id
在vue3中,vue团队重写了ref,主要通过ref来创建响应式元素,并且继承了vue2中的标识作用

在Vue 2中,ref的主要作用是在模板中获取DOM元素或组件实例。在Vue 3中,除了可以获取DOM元素或组件实例,还可以将一个基本类型的变量转换成响应式的数据,并且不用再通过复杂的步骤来访问响应式数据。Vue 3的ref还支持对象属性和数组索引来访问组件属性或DOM元素。

在Vue 2中,ref是一个帮助我们在模板中访问DOM元素或组件实例的API。例如,我们可以使用ref来访问一个表单输入框的值或组件实例的方法。在Vue 2中,ref还可以用于在父子组件之间进行通信,通过在父组件中使用ref为子组件创建引用来访问子组件实例。

在Vue 3中,ref的用途和Vue 2中一样,但它还有一些重要的新功能。在Vue 3中,ref可以包含更多类型的值,例如普通的Javascript变量、响应式的数据和一个函数。此外,Vue 3中的ref还可以用作类似于reactive函数的入口,将一个基本数据类型转换为响应式数据。而且Vue 3中的ref在访问响应式数据时,会自动进行解包和提取值,免去了Vue 2中通过$refs访问的繁琐步骤。最后,Vue 3中的ref还可以通过对象属性和数组索引来访问组件的属性或组件内的DOM元素。

原文链接:https://blog.csdn.net/qq_68805187/article/details/130856718

defineProperty和proxy的区别

defineProperty和proxy的区别

响应式的目的

当对象属性被读取后,被修改后,我要介入进行一些操作。
属性值的读和修改变成函数

defineProperty

他是针对属性的监听,所以对obj下的每个属性(深度遍历)进行监听。
天生缺陷:深度遍历效率缺失;由于遍历,新增属性他是没有被监听的;

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
const obj = {
a:1,
b:2,
c:{
d:3,
e:4
},
}
function _isObject(v){
return typeof v==='object' && v != null
}
function observe(obj){
for(const k in obj){
let v = obj[k]
if (_isObject(v)){
observe(v)
}
Object.defineproperty(obj,'a',{
get(){
console.log('a','读取')
return v
},
set(newval){
if(v != newval){
v = newval
console.log('a','更改')
}
}
})
}
}

proxy监听整个对象

Proxy返回一个新的代理对象,不动原数据
监听的是整个对象,所以不需要遍历,新增时可以收到通知

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
const obj = {
a:1,
b:2,
c:{
d:3,
e:4
},
}
function _isObject(v){
return typeof v==='object' && v != null
}

function observe(obj){
const proxy = new Proxy(obj,{
get(target,k){
let v = target[k]
if(_isObject(v)){ //这里是有递归,但只有v被读到才会触发。
observe(v)
}
console.log(k,'读取')
return v
},
set(target,k,val){
if(target[k] != val){
target[k] = val
}
}
})
return proxy
}

const proxy = new Proxy(obj,{
get(target,k){
let v = target[k]
console.log(k,'读取')
return v
},
set(target,k,val){
if(target[k] != val){
target[k] = val
}
}
})

26.删除有序数组中的重复项

删除有序数组中的重复项

题目:https://leetcode.cn/problems/remove-duplicates-from-sorted-array/description/

图 0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 方法一:遍历数组,splice删除重复项
var removeDuplicates = function(nums) {
for(let i = 0; i<nums.length; i++){
let j = i+1
while(nums[i] == nums[j]){
nums.splice(i, 1) //我一直再写j++,其实没必要,因为数据已经被删除了
}
}
};

//方法二:双指针
var removeDuplicates = function(nums) {
let p=0,q=1 //前后指针
while(q<nums.length){
if(nums[p] != nums[q]){
nums[p+1] = nums[q]
p++
}
q++
}
return p+1
};

Mock的使用

Mock的使用

Mock是什么

Mock(模拟)是一种常用的测试手段。Mocking 是在测试过程中,对于某些不容易构造或者不容易获取的对象,用一个虚假的对象来创建以便测试的方法。

你可能有一个函数,它需要从数据库中获取数据。在测试这个函数时,你可能不想真的去连接数据库,因为这会使测试变得复杂并且慢。这时,你可以创建一个 Mock 对象来模拟数据库的行为。你可以预设这个 Mock 对象的行为,让它返回你想要的数据,然后在测试中使用这个 Mock 对象,而不是真正的数据库。

Mockjs官网 https://github.com/nuysoft/Mock/wiki/Getting-Started

Mock安装与配置

安装
1
pnpm install -D vite-plugin-mock mockjs
配置vite.config.js文件
1
2
3
4
5
6
7
8
9
10
11
12
13
import { UserConfigExport, ConfigEnv } from 'vite'
import { viteMockServe } from 'vite-plugin-mock'
import vue from '@vitejs/plugin-vue'
export default ({ command })=> {
return {
plugins: [
vue(),
viteMockServe({ //保证开发期间可以使用访问mock提供的数据
localEnabled: command === 'serve',
}),
],
}
}

在整个项目的根路径新建一个mock文件夹,在此文件夹中新建一个用户文件user.ts

Mock模拟数据和接口

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
69
70
71
72
//用户信息数据
//createUserList函数执行会返回一个数组,数组里面包含两个用户信息
function createUserList() {
return [
{
userId: 1,
avatar:
'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
username: 'admin',
password: '111111',
desc: '平台管理员',
roles: ['平台管理员'],
buttons: ['cuser.detail'],
routes: ['home'],
token: 'Admin Token',
},
{
userId: 2,
avatar:
'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
username: 'system',
password: '111111',
desc: '系统管理员',
roles: ['系统管理员'],
buttons: ['cuser.detail', 'cuser.user'],
routes: ['home'],
token: 'System Token',
},
]
}

export default [
// 用户登录接口
{
url: '/api/user/login',//请求地址
method: 'post',//请求方式
response: ({ body }) => {
//获取请求体携带过来的用户名与密码
const { username, password } = body;
//调用获取用户信息函数,用于判断是否有此用户
const checkUser = createUserList().find(
(item) => item.username === username && item.password === password,
)
//没有用户返回失败信息
if (!checkUser) {
return { code: 201, data: { message: '账号或者密码不正确' } }
}
//如果有返回成功信息
const { token } = checkUser
return { code: 200, data: { token } }
},
},
//对外暴露一个数组,
//数组中包含登录假接口
//还有一个获取用户信息得到假的接口
{
url: '/api/user/info',
method: 'get',
response: (request) => {
//获取请求头携带token
const token = request.headers.token;
//查看用户信息是否包含有次token用户
const checkUser = createUserList().find((item) => item.token === token)
//没有返回失败的信息
if (!checkUser) {
return { code: 201, data: { message: '获取用户信息失败' } }
}
//如果有返回成功信息
return { code: 200, data: {checkUser} }
},
},
]

vue 的响应式框架 (1.0)

vue 的响应式框架 (1.0)

思路:属性发生变化时,调用依赖这些属性的函数

一、实现的过程需要解决以下一些问题

  • 1.如何追踪属性发生了变化
  • 2.如何知道该属性被哪些依赖使用
  • 3.如何执行依赖的函数
  • 4.如何避免重复执行依赖的函数

二、基础知识

object.defineProperty(obj, prop, descriptor)方法介绍

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//定义一个对象
var obj = {
name: '张三',
age : 20
}

object.defineProperty(obj,name){
get:function(){
console.log('有人读取了name属性')
return obj.name
}
set function(val){ //obj中的name发生那个变化后,会调用set函数。因此可在这里调用其依赖函数
obj.name = val
console.log('有个家伙在给name属性赋值')
}
}

去重

1
2
3
4
5
6
7
//方法一:Set集合,可以判断,如果当前的值和之前的值相同,则不执行依赖函数
let funcs = new Set()
//方法二:ES6中的includes方法,判断当前的值是否在数组中,如果在,则不执行依赖函数
let funcs = []
if (!funcs.includes(func)) {
funcs.push(func)
}

三、难点

此时我们已经解决了问题1和问题4,也知道了依赖函数应该在set中执行。那么如何知道属性被哪些依赖使用呢?

1
2
3
4
5
// 不直接运行函数,而是使用定义一个全局变量,这样只要使用这个统一变量的名称运行函数,就能拿到函数了
window.__func = func1
func1()
window.__func = null
//这样只需在get中添加window.__func到数组即可

四、实现

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
//首先新建一个js,在这个新的js中实现上述代码
//定义一个观察者,监视对象的属性
function Observer(obj) {
for (const key in obj){
let intervalValue = obj[key]
let observers = new Set() //用来保存相关依赖
//使用Object.defineProperty()方法,给obj的key属性添加get和set方法
defineProperty(obj, key, obj[key]){
get: function(){
if (window.__func){ //存在说明有函数在运行,是哪个函数呢,那个函数名使用window.__func可以访问到
observers.add(window.__func)
}
return intervalValue
},
set: function(val){
console.log('有人修改了' + key + '属性,我要去通知我的订阅者了')
obj[key] = val
//通知所有的订阅者,重新运行
observers.forEach(observer => observer())
}
}
}
}

// 定义一个统一执行函数的函数
function autoRun(fn){
window.__func = fn
fn()
window.__func = null
}

//函数调用时用autoRun不要直接运行
autoRun(func1())
autoRun(func2())

git多人合作

git实现多人合作

整体思路

  • 1、首先需要新建一个组织,组织中新建一个仓库,组织中新建一个team。
  • 2、队员们frok组织中的仓库,此时队员自身账号中的仓库是fork仓库,而组织中的仓库为原仓库
  • 3、队员们clone自己的fork仓库到本地目录
  • 4、在本地目录git remote add upstream <组织仓库名>
  • 5、队员修改文件后,通过git add git commit git push到自己的仓库
  • 6、队员申请pull request
  • 7、仓库合并队员的分支
  • 8、其他队员git pull upstream main,将仓库中的最新文件合并到本地目录

详细过程

https://www.cnblogs.com/thousfeet/p/7840932.html
https://www.cnblogs.com/eyunhua/p/13215936.html

原理

图 0