文章目录

  • 34. 在排序数组中查找元素的第一个和最后一个位置
    • 描述
    • 示例 1:
    • 示例 2:
    • 示例 3:
    • 提示:
    • 解题思路
      • 算法分析
      • 问题本质分析
      • 双重二分查找详解
      • 左边界查找过程
      • 右边界查找过程
      • 算法流程图
      • 边界情况分析
      • 各种解法对比
      • 二分查找变种详解
      • 时间复杂度分析
      • 空间复杂度分析
      • 关键优化点
      • 实际应用场景
      • 二分查找模板
      • 算法扩展
      • 测试用例设计
      • 常见错误避免
      • 代码实现要点
      • 手工验证示例
    • 完整题解代码

34. 在排序数组中查找元素的第一个和最后一个位置

描述

给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。

如果数组中不存在目标值 target,返回 [-1, -1]。

你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。

示例 1:

输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]

示例 2:

输入:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1]

示例 3:

输入:nums = [], target = 0
输出:[-1,-1]

提示:

  • 0 <= nums.length <= 105
  • -10^9 <= nums[i] <= 10^9
  • nums 是一个非递减数组
  • -10^9 <= target <= 10^9

解题思路

算法分析

这道题是二分查找边界查找的经典应用。主要解法包括:

  1. 双重二分查找法:分别查找左边界和右边界
  2. 单次二分查找法:一次查找确定范围
  3. 线性查找法:暴力遍历(不满足时间复杂度要求)
  4. 库函数法:使用内置的查找函数

问题本质分析

排序数组范围查找
二分查找变种
左边界查找
右边界查找
范围确定
找到第一个>=target的位置
找到最后一个<=target的位置
合并两个边界结果
时间复杂度O_logn

双重二分查找详解

flowchart TDA[输入nums和target] --> B[查找左边界]B --> C[left_bound = findLeft]C --> D{左边界是否存在}D -->|不存在| E[返回[-1,-1]]D -->|存在| F[查找右边界]F --> G[right_bound = findRight]G --> H[返回[left_bound, right_bound]]B --> I[左边界二分查找]I --> J[寻找第一个>=target的位置]F --> K[右边界二分查找]K --> L[寻找最后一个<=target的位置]

左边界查找过程

flowchart TDA[左边界查找] --> B[初始化left=0, right=len]B --> C{left < right}C -->|否| D[返回left]C -->|是| E[计算mid = left + (right-left)/2]E --> F{nums[mid] < target}F -->|是| G[left = mid + 1]F -->|否| H[right = mid]G --> CH --> CD --> I[检查边界有效性]

右边界查找过程

flowchart TDA[右边界查找] --> B[初始化left=0, right=len]B --> C{left < right}C -->|否| D[返回left-1]C -->|是| E[计算mid = left + (right-left)/2]E --> F{nums[mid] <= target}F -->|是| G[left = mid + 1]F -->|否| H[right = mid]G --> CH --> CD --> I[检查边界有效性]

算法流程图

flowchart TDA[开始] --> B[输入验证]B --> C{数组为空}C -->|是| D[返回[-1,-1]]C -->|否| E[查找左边界]E --> F[二分查找第一个>=target]F --> G{找到有效位置}G -->|否| DG -->|是| H{nums[left]==target}H -->|否| DH -->|是| I[查找右边界]I --> J[二分查找最后一个<=target]J --> K[返回[left, right]]

边界情况分析

graph TDA[边界情况] --> B[空数组]A --> C[单元素数组]A --> D[目标不存在]A --> E[全部相同元素]A --> F[目标在首尾]B --> G[直接返回[-1,-1]]C --> H[判断唯一元素是否匹配]D --> I[两次二分都找不到]E --> J[返回[0, n-1]]F --> K[边界处理要准确]

各种解法对比

解法对比
双重二分查找
单次二分查找
线性查找
库函数法
时间O_logn空间O_1
时间O_logn空间O_1
时间O_n空间O_1
时间O_logn空间O_1
推荐解法清晰易懂
代码紧凑但复杂
不满足题目要求
语言特定实现

二分查找变种详解

graph TDA[二分查找变种] --> B[查找确切值]A --> C[查找左边界]A --> D[查找右边界]A --> E[查找插入位置]B --> F[nums[mid] == target时直接返回]C --> G[nums[mid] >= target时收缩右边界]D --> H[nums[mid] <= target时收缩左边界]E --> I[找到第一个>target的位置]F --> J[经典二分查找]G --> K[左边界二分]H --> L[右边界二分]I --> M[插入位置二分]

时间复杂度分析

  • 双重二分查找:O(log n),执行两次独立的二分查找
  • 单次二分查找:O(log n),一次遍历确定范围
  • 线性查找:O(n),不满足题目要求
  • 库函数查找:O(log n),依赖具体实现

空间复杂度分析

  • 双重二分查找:O(1),只使用常数额外空间
  • 单次二分查找:O(1),只使用常数额外空间
  • 线性查找:O(1),只使用常数额外空间
  • 库函数查找:O(1),通常只使用常数空间

关键优化点

优化策略
边界处理
提前退出
溢出防护
代码复用
准确定义开闭区间
空数组直接返回
mid计算防止溢出
统一二分查找模板
避免边界错误

实际应用场景

应用场景
数据库索引
搜索引擎
数据分析
算法竞赛
范围查询优化
相关性排序查找
时间序列数据范围
区间查找问题
核心算法组件

二分查找模板

flowchart TDA[二分查找模板选择] --> B[左闭右闭 [left, right]]A --> C[左闭右开 [left, right)]A --> D[左开右开 (left, right)]B --> E[right = len - 1]C --> F[right = len]D --> G[left = -1, right = len]E --> H[while left <= right]F --> I[while left < right]G --> IH --> J[经典二分模板]I --> K[边界查找模板]

算法扩展

算法扩展
查找峰值
旋转数组查找
二维矩阵查找
第K小元素
山峰数组问题
分治策略
行列有序矩阵
快速选择算法
二分查找家族

测试用例设计

测试用例
基础功能
边界情况
性能测试
目标存在
目标不存在
重复元素
空数组
单元素
全相同
大数组
最坏情况
验证正确性
验证性能

常见错误避免

常见错误
边界计算错误
无限循环
整数溢出
边界条件遗漏
左右边界定义不一致
left和right更新错误
mid计算溢出
特殊情况未处理
使用统一模板
保证循环收敛
使用安全计算
完善测试用例

代码实现要点

  1. 二分查找模板

    • 使用左闭右开区间避免边界错误
    • mid计算使用left + (right-left)/2防溢出
    • 明确left和right的更新规则
  2. 边界查找逻辑

    • 左边界:找第一个大于等于target的位置
    • 右边界:找最后一个小于等于target的位置
    • 验证找到的位置是否有效
  3. 特殊情况处理

    • 空数组直接返回[-1,-1]
    • 目标值不存在返回[-1,-1]
    • 单元素数组的边界情况
  4. 性能优化技巧

    • 提前判断边界情况
    • 使用位运算优化除法
    • 减少重复的边界检查

手工验证示例

数组[5,7,7,8,8,10], target=8
查找左边界
left=0, right=6
mid=3, nums[3]=8 >= 8, right=3
mid=1, nums[1]=7 < 8, left=2
mid=2, nums[2]=7 < 8, left=3
left=right=3, 左边界=3
查找右边界
left=0, right=6
mid=3, nums[3]=8 <= 8, left=4
mid=5, nums[5]=10 > 8, right=5
mid=4, nums[4]=8 <= 8, left=5
left=right=5, 右边界=4
返回[3,4]

这个问题的关键在于理解二分查找的边界处理掌握左右边界的查找技巧,通过两次二分查找分别确定目标值的起始和结束位置。

完整题解代码

package mainimport ("fmt""sort""strings""time"
)// 解法一:双重二分查找法(推荐解法)
// 时间复杂度:O(log n),空间复杂度:O(1)
func searchRange(nums []int, target int) []int {if len(nums) == 0 {return []int{-1, -1}}// 查找左边界leftBound := findLeftBound(nums, target)if leftBound == -1 {return []int{-1, -1}}// 查找右边界rightBound := findRightBound(nums, target)return []int{leftBound, rightBound}
}// 查找左边界:第一个大于等于target的位置
func findLeftBound(nums []int, target int) int {left, right := 0, len(nums)for left < right {mid := left + (right-left)/2if nums[mid] < target {left = mid + 1} else {right = mid}}// 检查找到的位置是否有效if left < len(nums) && nums[left] == target {return left}return -1
}// 查找右边界:最后一个小于等于target的位置
func findRightBound(nums []int, target int) int {left, right := 0, len(nums)for left < right {mid := left + (right-left)/2if nums[mid] <= target {left = mid + 1} else {right = mid}}// left-1是最后一个小于等于target的位置return left - 1
}// 解法二:优化的双重二分查找(更清晰的边界处理)
// 时间复杂度:O(log n),空间复杂度:O(1)
func searchRangeOptimized(nums []int, target int) []int {if len(nums) == 0 {return []int{-1, -1}}// 使用统一的二分查找模板leftBound := binarySearchLeft(nums, target)rightBound := binarySearchRight(nums, target)if leftBound <= rightBound {return []int{leftBound, rightBound}}return []int{-1, -1}
}// 二分查找左边界(左闭右闭区间)
func binarySearchLeft(nums []int, target int) int {left, right := 0, len(nums)-1for left <= right {mid := left + (right-left)/2if nums[mid] >= target {right = mid - 1} else {left = mid + 1}}// 检查边界和目标值if left < len(nums) && nums[left] == target {return left}return len(nums) // 表示未找到
}// 二分查找右边界(左闭右闭区间)
func binarySearchRight(nums []int, target int) int {left, right := 0, len(nums)-1for left <= right {mid := left + (right-left)/2if nums[mid] <= target {left = mid + 1} else {right = mid - 1}}// 检查边界和目标值if right >= 0 && nums[right] == target {return right}return -1 // 表示未找到
}// 解法三:单次二分查找法
// 时间复杂度:O(log n),空间复杂度:O(1)
func searchRangeSingle(nums []int, target int) []int {if len(nums) == 0 {return []int{-1, -1}}// 先用标准二分查找找到任意一个target位置pos := binarySearch(nums, target)if pos == -1 {return []int{-1, -1}}// 从找到的位置向两边扩展left, right := pos, pos// 向左扩展找到左边界for left > 0 && nums[left-1] == target {left--}// 向右扩展找到右边界for right < len(nums)-1 && nums[right+1] == target {right++}return []int{left, right}
}// 标准二分查找
func binarySearch(nums []int, target int) int {left, right := 0, len(nums)-1for left <= right {mid := left + (right-left)/2if nums[mid] == target {return mid} else if nums[mid] < target {left = mid + 1} else {right = mid - 1}}return -1
}// 解法四:使用Go标准库
// 时间复杂度:O(log n),空间复杂度:O(1)
func searchRangeStdLib(nums []int, target int) []int {if len(nums) == 0 {return []int{-1, -1}}// 使用sort.SearchInts查找左边界leftBound := sort.SearchInts(nums, target)// 检查是否找到目标值if leftBound >= len(nums) || nums[leftBound] != target {return []int{-1, -1}}// 查找右边界:第一个大于target的位置减1rightBound := sort.SearchInts(nums, target+1) - 1return []int{leftBound, rightBound}
}// 解法五:线性查找法(不满足时间复杂度要求,仅用于对比)
// 时间复杂度:O(n),空间复杂度:O(1)
func searchRangeLinear(nums []int, target int) []int {left, right := -1, -1// 从左到右找第一个目标值for i := 0; i < len(nums); i++ {if nums[i] == target {left = ibreak}}if left == -1 {return []int{-1, -1}}// 从右到左找最后一个目标值for i := len(nums) - 1; i >= 0; i-- {if nums[i] == target {right = ibreak}}return []int{left, right}
}// 解法六:递归二分查找
// 时间复杂度:O(log n),空间复杂度:O(log n)
func searchRangeRecursive(nums []int, target int) []int {if len(nums) == 0 {return []int{-1, -1}}left := findLeftBoundRecursive(nums, target, 0, len(nums)-1)if left == -1 {return []int{-1, -1}}right := findRightBoundRecursive(nums, target, 0, len(nums)-1)return []int{left, right}
}// 递归查找左边界
func findLeftBoundRecursive(nums []int, target, left, right int) int {if left > right {return -1}mid := left + (right-left)/2if nums[mid] == target {// 检查是否是最左边的if mid == 0 || nums[mid-1] != target {return mid}return findLeftBoundRecursive(nums, target, left, mid-1)} else if nums[mid] < target {return findLeftBoundRecursive(nums, target, mid+1, right)} else {return findLeftBoundRecursive(nums, target, left, mid-1)}
}// 递归查找右边界
func findRightBoundRecursive(nums []int, target, left, right int) int {if left > right {return -1}mid := left + (right-left)/2if nums[mid] == target {// 检查是否是最右边的if mid == len(nums)-1 || nums[mid+1] != target {return mid}return findRightBoundRecursive(nums, target, mid+1, right)} else if nums[mid] < target {return findRightBoundRecursive(nums, target, mid+1, right)} else {return findRightBoundRecursive(nums, target, left, mid-1)}
}// 测试函数
func testSearchRange() {testCases := []struct {nums     []inttarget   intexpected []intdesc     string}{{[]int{5, 7, 7, 8, 8, 10}, 8, []int{3, 4}, "示例1:目标存在多个"},{[]int{5, 7, 7, 8, 8, 10}, 6, []int{-1, -1}, "示例2:目标不存在"},{[]int{}, 0, []int{-1, -1}, "示例3:空数组"},{[]int{1}, 1, []int{0, 0}, "单元素匹配"},{[]int{1}, 2, []int{-1, -1}, "单元素不匹配"},{[]int{1, 1, 1, 1, 1}, 1, []int{0, 4}, "全部相同元素"},{[]int{1, 2, 3, 4, 5}, 1, []int{0, 0}, "目标在首位"},{[]int{1, 2, 3, 4, 5}, 5, []int{4, 4}, "目标在末位"},{[]int{1, 2, 3, 4, 5}, 3, []int{2, 2}, "目标在中间单个"},{[]int{1, 2, 2, 2, 3}, 2, []int{1, 3}, "目标在中间多个"},{[]int{1, 3, 5, 7, 9}, 4, []int{-1, -1}, "目标在间隙"},{[]int{1, 1, 2, 2, 3, 3}, 2, []int{2, 3}, "连续重复"},{[]int{-1, 0, 3, 5, 9, 12}, 9, []int{4, 4}, "包含负数"},{[]int{-3, -1, 0, 3, 5}, -1, []int{1, 1}, "负数目标"},{[]int{0, 0, 0, 1, 1, 1}, 0, []int{0, 2}, "零值连续"},}fmt.Println("=== 查找元素范围测试 ===\n")for i, tc := range testCases {// 测试主要解法result1 := searchRange(tc.nums, tc.target)result2 := searchRangeOptimized(tc.nums, tc.target)result3 := searchRangeStdLib(tc.nums, tc.target)status := "✅"if !equalSlices(result1, tc.expected) {status = "❌"}fmt.Printf("测试 %d: %s\n", i+1, tc.desc)fmt.Printf("输入: nums=%v, target=%d\n", tc.nums, tc.target)fmt.Printf("期望: %v\n", tc.expected)fmt.Printf("双重二分: %v\n", result1)fmt.Printf("优化二分: %v\n", result2)fmt.Printf("标准库法: %v\n", result3)fmt.Printf("结果: %s\n", status)fmt.Println(strings.Repeat("-", 40))}
}// 辅助函数:比较两个切片是否相等
func equalSlices(a, b []int) bool {if len(a) != len(b) {return false}for i := range a {if a[i] != b[i] {return false}}return true
}// 性能测试
func benchmarkSearchRange() {fmt.Println("\n=== 性能测试 ===\n")// 构造测试数据testData := []struct {nums   []inttarget intdesc   string}{{generateSortedArray(1000, 5), 5, "1000元素数组"},{generateSortedArray(10000, 50), 50, "10000元素数组"},{generateSortedArray(100000, 500), 500, "100000元素数组"},{generateRepeatedArray(50000, 42), 42, "50000重复元素"},}algorithms := []struct {name stringfn   func([]int, int) []int}{{"双重二分", searchRange},{"优化二分", searchRangeOptimized},{"标准库法", searchRangeStdLib},{"递归二分", searchRangeRecursive},{"线性查找", searchRangeLinear},}for _, data := range testData {fmt.Printf("%s:\n", data.desc)for _, algo := range algorithms {start := time.Now()result := algo.fn(data.nums, data.target)duration := time.Since(start)fmt.Printf("  %s: %v, 耗时: %v\n", algo.name, result, duration)}fmt.Println()}
}// 生成有序数组(包含重复元素)
func generateSortedArray(size, targetCount int) []int {nums := make([]int, size)target := size / 2for i := 0; i < size; i++ {if i >= target && i < target+targetCount {nums[i] = target} else if i < target {nums[i] = i} else {nums[i] = i - targetCount + 1}}return nums
}// 生成重复元素数组
func generateRepeatedArray(size, value int) []int {nums := make([]int, size)for i := range nums {if i < size/3 {nums[i] = value - 1} else if i < 2*size/3 {nums[i] = value} else {nums[i] = value + 1}}return nums
}// 演示二分查找过程
func demonstrateBinarySearch() {fmt.Println("\n=== 二分查找过程演示 ===")nums := []int{5, 7, 7, 8, 8, 10}target := 8fmt.Printf("数组: %v, 目标: %d\n", nums, target)fmt.Println("\n查找左边界过程:")demonstrateLeftBound(nums, target)fmt.Println("\n查找右边界过程:")demonstrateRightBound(nums, target)result := searchRange(nums, target)fmt.Printf("\n最终结果: %v\n", result)
}func demonstrateLeftBound(nums []int, target int) {left, right := 0, len(nums)step := 1fmt.Printf("初始: left=%d, right=%d\n", left, right)for left < right {mid := left + (right-left)/2fmt.Printf("步骤%d: left=%d, right=%d, mid=%d, nums[%d]=%d\n",step, left, right, mid, mid, nums[mid])if nums[mid] < target {left = mid + 1fmt.Printf("       nums[%d]=%d < %d, left=%d\n", mid, nums[mid], target, left)} else {right = midfmt.Printf("       nums[%d]=%d >= %d, right=%d\n", mid, nums[mid], target, right)}step++}if left < len(nums) && nums[left] == target {fmt.Printf("找到左边界: %d\n", left)} else {fmt.Println("未找到目标值")}
}func demonstrateRightBound(nums []int, target int) {left, right := 0, len(nums)step := 1fmt.Printf("初始: left=%d, right=%d\n", left, right)for left < right {mid := left + (right-left)/2fmt.Printf("步骤%d: left=%d, right=%d, mid=%d, nums[%d]=%d\n",step, left, right, mid, mid, nums[mid])if nums[mid] <= target {left = mid + 1fmt.Printf("       nums[%d]=%d <= %d, left=%d\n", mid, nums[mid], target, left)} else {right = midfmt.Printf("       nums[%d]=%d > %d, right=%d\n", mid, nums[mid], target, right)}step++}rightBound := left - 1fmt.Printf("找到右边界: %d\n", rightBound)
}func main() {fmt.Println("34. 在排序数组中查找元素的第一个和最后一个位置")fmt.Println("================================================")// 基础功能测试testSearchRange()// 性能对比测试benchmarkSearchRange()// 二分查找过程演示demonstrateBinarySearch()// 展示算法特点fmt.Println("\n=== 算法特点分析 ===")fmt.Println("1. 双重二分:经典解法,两次独立二分查找,清晰易懂")fmt.Println("2. 优化二分:统一模板,边界处理更加清晰")fmt.Println("3. 标准库法:利用内置函数,代码简洁")fmt.Println("4. 递归二分:递归实现,代码简洁但有栈溢出风险")fmt.Println("5. 线性查找:时间复杂度O(n),不满足题目要求")fmt.Println("\n=== 关键技巧总结 ===")fmt.Println("• 左边界查找:找第一个>=target的位置")fmt.Println("• 右边界查找:找最后一个<=target的位置")fmt.Println("• 边界检查:确保找到的位置值等于target")fmt.Println("• 溢出防护:mid计算使用left+(right-left)/2")fmt.Println("• 模板统一:使用一致的二分查找边界处理")
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/91589.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/91589.shtml
英文地址,请注明出处:http://en.pswp.cn/web/91589.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【网络编程】WebSocket 实现简易Web多人聊天室

一、实现思路 Web端就是使用html JavaScript来实现页面&#xff0c;通过WebSocket长连接和服务器保持通讯&#xff0c;协议的payload使用JSON格式封装 服务端使用C配合第三方库WebSocket和nlonlohmann库来实现 二、Web端 2.1 界面显示 首先&#xff0c;使用html来设计一个…

AI 驱动、设施扩展、验证器强化、上线 EVM 测试网,Injective 近期动态全更新!

作为一个专注于金融应用、且具有高度可互操作性的高性能 Layer-1 区块链&#xff0c;Injective 自诞生以来便为开发者提供有即插即用的技术模块&#xff0c;以便开发者能够更好地搭建新一代 Web3 金融类应用。谈及项目发展的愿景和基本定位&#xff0c;创始团队曾提到希望 Inje…

Qt-----初识

1. 什么是Qt定义&#xff1a;Qt是一个跨平台的应用程序和用户界面框架&#xff0c;主要用于开发具有图形用户界面的应用程序&#xff0c;同时也支持非GUI程序的开发。 编程语言&#xff1a;主要使用C&#xff0c;但也提供了对Python&#xff08;PyQt&#xff09;、JavaScript&a…

理解微信体系中的 AppID、OpenID 和 UnionID

前言: 在开发微信相关的服务(如小程序,公众号,微信开放平台等)时,很多人都会接触到几个看起来相似但实际用途不同的额ID: AppiD, OpenID,UnionID. 搞清楚这三者的区别,是微信生态开发中的基本功,本文将从开发者视角触发,深入浅出地解释它们的关系,区别以及实际应用场景一.什么是…

FFmpeg,如何插入SEI自定义数据

FFmpeg&#xff0c;如何插入SEI自定义数据 一、什么是SEI&#xff1f; SEI&#xff08;Supplemental Enhancement Information&#xff0c;补充增强信息&#xff09;是H.264/H.265视频编码标准中的一种元数据载体&#xff0c;它允许在视频流中嵌入额外的信息&#xff0c;如时…

为什么分类任务偏爱交叉熵?MSE 为何折戟?

在机器学习的世界里&#xff0c;损失函数是模型的“指南针”——它定义了模型“好坏”的标准&#xff0c;直接决定了参数优化的方向。对于分类任务&#xff08;比如判断一张图片是猫还是狗&#xff09;&#xff0c;我们通常会选择交叉熵作为损失函数&#xff1b;而在回归任务&a…

[echarts]横向柱状图

前言 接到一个需求&#xff0c;需要展示一个横向的柱状图&#xff0c;按数量从大到小排序&#xff0c;并定时刷新 使用react配合echarts进行实现。 react引入echarts import React, { useEffect, useRef } from react; import * as echarts from echarts; import DeviceApi fro…

【开源项目】轻量加速利器 HubProxy 自建 Docker、GitHub 下载加速服务

​​引言​​ 如果你经常被 Docker 镜像拉取、GitHub 文件下载的龟速折磨&#xff0c;又不想依赖第三方加速服务&#xff08;担心稳定性或隐私&#xff09;&#xff0c;今天分享的 ​​HubProxy​​ 可能正是你需要的。这个开源工具用一行命令就能部署&#xff0c;以极低资源消…

java web jsp jstl练习

JSP 的学习。 核心功能模块 1. 源代码层 &#xff08; src &#xff09; HelloWorld &#xff1a;主程序入口领域模型 &#xff1a; domain 包含User.java和ceshi.java控制器 &#xff1a; servlet 包含登录验证和验证码相关ServletWeb表现层 &#xff08; web &#xff09; JS…

VSCode 完全指南:释放你的编码潜能

零、简介 在当今的软件开发领域&#xff0c;代码编辑器的选择至关重要&#xff0c;它就像是工匠手中的工具&#xff0c;直接影响着工作效率和成果质量。Visual Studio Code&#xff08;简称 VSCode&#xff09;自问世以来&#xff0c;迅速在全球开发者社区中崭露头角&#xff…

《n8n基础教学》第一节:如何使用编辑器UI界面

在本课中&#xff0c;你将学习如何操作编辑器界面。我们将浏览画布&#xff0c;向您展示每个图标的含义&#xff0c;以及在 n8n 中构建工作流程时在哪里可以找到您需要的东西。本课程基于 n8n 最新版本 。在其他版本中&#xff0c;某些用户界面可能有所不同&#xff0c;但这不会…

gcc g++ makefile CMakeLists.txt cmake make 的关系

gcc&#xff1a;C语言编译器g&#xff1a;C编译器makefile&#xff1a;定义编译规则、依赖关系和构建目标。可以手动编写&#xff0c;也可以由CMakeLists.txt生成cmake&#xff1a;读取CMakeLists.txt文件&#xff0c;生成Makefilemake&#xff1a;构建工具&#xff0c;执行Mak…

SFT 训练器

SFT 训练器 “训练时间到!” 我们现在终于可以创建一个监督微调训练器的实例了: trainer = SFTTrainer( model=model, processing_class=tokenizer, args=sft_config, train_dataset=dataset, )SFTTrainer 已经对数据集进行了预处理,因此我们可以深入查看,了解每个小批次…

Android Material Components 全面解析:打造现代化 Material Design 应用

引言 在当今移动应用开发领域&#xff0c;用户体验(UX)已成为决定应用成功与否的关键因素之一。Google推出的Material Design设计语言为开发者提供了一套完整的视觉、交互和动效规范&#xff0c;而Material Components for Android(MDC-Android)则是将这些设计理念转化为可重用…

Windows使用Powershell自动安装SqlServer2025服务器与SSMS管理工具

安装结果: 安装前准备: 1.下载mssql server 2025安装器 2.下载iso镜像 3.下载好SSMS安装程序,并放到iso同目录下 4.执行脚本开始自动安装

09 RK3568 Debian11 ES8388 模拟音频输出

1、设备树配置 确认自己的i2c,使用sdk带的驱动es8323 /SDK/kernel/sound/soc/codecs/es8323.c es8388_sound: es8388-sound {status = "okay";compatible = "rockchip,multicodecs-card"; rockchip,card-name = "rockchip,es8388-codec"; …

力扣-199.二叉树的右视图

题目链接 199.二叉树的右视图 class Solution {public List<Integer> rightSideView(TreeNode root) {List<Integer> res new ArrayList<>();Queue<TreeNode> queue new LinkedList<>();if (root null)return res;queue.offer(root);while …

Android Bitmap 完全指南:从基础到高级优化

在 Android 开发中&#xff0c;图像处理是一个核心且复杂的领域&#xff0c;而 Bitmap 作为 Android 中表示图像的基本单位&#xff0c;贯穿了从简单图片显示到复杂图像编辑的各个场景。然而&#xff0c;Bitmap 处理不当往往会导致应用性能下降、内存溢出&#xff08;OOM&#…

unity日志过滤器

背景&#xff1a;之前做游戏的时候和同组的同事聊过说日志过滤尽量不要限制大家怎么使用日志打印的接口&#xff0c;不要加额外的参数&#xff0c;比如多加一个标签string,或者使用特定的接口&#xff0c;枚举。最好就是日志大家还是用Debug.Log无感去用&#xff0c;然后通过勾…

OpenGL Camera

一. lookAt函数的参数含义glm::mat4 view glm::lookAt(cameraPos, // 相机在世界坐标系中的位置&#xff08;任意值&#xff09;cameraPos cameraFront, // 相机看向的目标点&#xff08;位置朝向&#xff09;cameraUp // 相机的"上方向"&#xff08;通…