Rate Limit踩坑实录,从超限报错到稳定并发500 QPS:ChatGPT API生产环境压测全路径拆解

更多请点击: https://kaifayun.com

第一章:Rate Limit踩坑实录,从超限报错到稳定并发500 QPS:ChatGPT API生产环境压测全路径拆解

初始压测暴露出的典型错误模式

首次对 OpenAI Chat Completion API 进行 200 QPS 并发压测时,约 37% 请求返回 429 Too Many Requests,响应体中携带 {"error":{"type":"rate_limit_exceeded",...}}。根本原因在于未区分 TPM(Tokens Per Minute)RPM(Requests Per Minute) 双重限制策略——即使请求频率未超 RPM,长文本响应也可能快速耗尽 TPM 配额。

关键修复策略与代码落地

采用令牌桶 + 滑动窗口双校验机制,在客户端实现自适应节流。核心逻辑如下:
func (c *OpenAIClient) Throttle(ctx context.Context, promptTokens, completionTokens int) error {
	// 动态计算当前分钟内已消耗TPM
	usedTPM := c.tpmCounter.Get(ctx, time.Now().Truncate(time.Minute))
	if usedTPM+promptTokens+completionTokens > c.maxTPM {
		waitDur := time.Until(time.Now().Truncate(time.Minute).Add(time.Minute))
		select {
		case <-time.After(waitDur):
		case <-ctx.Done():
			return ctx.Err()
		}
	}
	c.tpmCounter.Inc(ctx, time.Now().Truncate(time.Minute), int64(promptTokens+completionTokens))
	return nil
}
该函数在每次请求前预估 token 消耗并阻塞等待,避免服务端拒绝。
压测结果对比
优化前后关键指标变化如下:
指标优化前优化后
稳定并发能力120 QPS500 QPS
429 错误率37.2%0.18%
平均 P99 延迟2.4s1.7s

必须规避的三大配置陷阱

  • 忽略模型差异:gpt-4-turbo 的 RPM/TPM 阈值是 gpt-3.5-turbo 的 2 倍,混用同一限流策略必然失准
  • 未校验响应头:X-RateLimit-Remaining-TokensX-RateLimit-Reset 必须实时采集用于动态调整
  • 本地时间未同步:客户端系统时间偏差超过 30 秒将导致滑动窗口计算失效

第二章:ChatGPT API限流机制深度解析与观测实践

2.1 OpenAI官方Rate Limit策略的数学建模与窗口逻辑推演

滑动窗口核心公式
OpenAI采用基于时间戳的滑动窗口计数器(Sliding Window Counter),其核心约束为:
requests(t) ≤ R × (t − t₀) / W
其中 R 为每窗口配额(如 10,000 TPM), W 为窗口宽度(60秒), t₀ 为当前窗口起始时间, t 为请求时间戳。
窗口边界判定逻辑
  • 服务端维护最近 W 秒内所有请求时间戳(有序列表)
  • 每次新请求到来时,剔除 t < t_now − W 的旧记录
  • 剩余条目数即为当前窗口实时计数
典型限流参数对照表
模型TPM(每分钟)RPM(每分钟)窗口类型
gpt-4-turbo30,000500滑动(60s)
gpt-3.5-turbo10,0003,500滑动(60s)

2.2 实时请求头解析:X-RateLimit-Limit/Remaining/Reset字段的动态捕获与验证

关键字段语义与时间基准
API 限流响应头中三个核心字段具有明确协作关系:
字段含义典型值
X-RateLimit-Limit当前窗口最大请求数100
X-RateLimit-Remaining剩余可用请求数97
X-RateLimit-ResetUnix 时间戳(秒级),表示重置时间点1717028340
Go 客户端动态解析示例
// 解析并校验限流头字段
func parseRateLimitHeaders(resp *http.Response) (limit, remaining int, reset time.Time, err error) {
	limitStr := resp.Header.Get("X-RateLimit-Limit")
	remainingStr := resp.Header.Get("X-RateLimit-Remaining")
	resetStr := resp.Header.Get("X-RateLimit-Reset")

	limit, err = strconv.Atoi(limitStr)
	if err != nil { return }
	remaining, err = strconv.Atoi(remainingStr)
	if err != nil { return }
	resetUnix, err := strconv.ParseInt(resetStr, 10, 64)
	if err != nil { return }
	reset = time.Unix(resetUnix, 0)
	return
}
该函数严格按顺序提取、转换并校验三字段;若任一字段缺失或格式非法,立即返回错误,保障下游逻辑不依赖无效限流状态。
实时性保障机制
  • 每次 HTTP 响应后立即解析,避免缓存旧头信息
  • reset 时间与本地系统时钟比对,识别服务端时钟漂移

2.3 Token级与Request级双维度限流的实测对比与误差归因分析

实测吞吐量差异
在 500 QPS 压测下,Token 级限流平均延迟为 12.3 ms(标准差 ±1.8),Request 级为 8.7 ms(±4.2)。波动差异源于令牌桶填充时机与请求排队策略不同。
核心限流逻辑对比
// Token级:按token消耗粒度校验
func (t *TokenLimiter) Allow() bool {
    now := time.Now()
    t.mu.Lock()
    t.refill(now) // 动态补桶
    if t.tokens >= 1.0 {
        t.tokens--
        t.mu.Unlock()
        return true
    }
    t.mu.Unlock()
    return false
}
该实现依赖高精度时间戳与浮点运算,易受系统时钟抖动与浮点舍入误差影响;而 Request 级采用整型原子计数,无时序依赖,但无法细粒度控制资源消耗。
误差来源分布
  • 系统时钟漂移(占比 42%):Linux CFS 调度导致 refill 时间计算偏差
  • 并发竞争丢失(占比 31%):Mutex 锁争用引发 token 检查延迟
  • 浮点累积误差(占比 27%):连续 refill 导致 tokens 值微偏
指标Token级Request级
理论精度±0.5 token±1 request
实测超限率2.3%0.1%

2.4 混合负载下burst行为建模:突发流量与平滑调度的临界点压测验证

临界点识别策略
通过动态滑动窗口统计请求速率,当窗口内P99延迟跃升>150ms且并发突增>3×基线时,触发burst判定。
压测参数配置
  • 基准负载:500 RPS(恒定)
  • Burst模式:2s内注入1200 RPS脉冲
  • 调度器响应阈值:maxLatency=200ms, burstTolerance=800
核心调度逻辑片段
// 平滑调度器中burst感知关键逻辑
func (s *Scheduler) ShouldThrottle(now time.Time) bool {
  window := s.metrics.GetRateInLast(100 * time.Millisecond) // 短窗口探测
  if window > s.burstTolerance && s.latency.P99() > s.maxLatency {
    return true // 触发限流降级
  }
  return false
}
该逻辑在100ms粒度内实时捕获速率尖峰,结合P99延迟双指标联动判断,避免单一维度误判。
压测结果对比
场景平均延迟(ms)错误率(%)调度生效延迟(ms)
纯平滑负载420.02-
Burst临界点1981.786

2.5 生产环境限流日志埋点设计:基于OpenTelemetry的限流事件可观测性落地

限流事件关键字段标准化
为保障可观测性,限流日志需统一注入 OpenTelemetry 语义约定属性:
// 限流拦截点埋点示例
span.SetAttributes(
    semconv.HTTPMethodKey.String("POST"),
    semconv.HTTPRouteKey.String("/api/v1/order"),
    attribute.String("ratelimit.policy", "user_id:100rps"),
    attribute.Bool("ratelimit.rejected", true),
    attribute.Int64("ratelimit.remaining", 0),
)
该代码在拦截器中为 Span 注入限流上下文:`ratelimit.policy` 标识策略来源与阈值,`rejected` 明确是否触发拒绝,`remaining` 反映当前窗口余量,便于聚合分析熔断趋势。
采样与日志联动策略
  • 对 `ratelimit.rejected = true` 的 Span 强制全量采集
  • 对高频成功请求采用动态采样(如 0.1%),避免日志风暴
核心指标映射表
OTLP 属性Prometheus 指标用途
ratelimit.rejectedratelimit_requests_rejected_total按策略、服务、HTTP 路由多维下钻
ratelimit.remainingratelimit_remaining_gauge实时水位监控与告警

第三章:弹性重试与智能熔断架构设计

3.1 指数退避+抖动算法在API重试中的参数调优与失败率收敛验证

核心退避逻辑实现
func calculateBackoff(attempt int, base time.Duration, jitter float64) time.Duration {
    // 指数增长:base * 2^attempt
    backoff := base * time.Duration(math.Pow(2, float64(attempt)))
    // 加入0~1均匀抖动
    jitterFactor := rand.Float64() * jitter
    return time.Duration(float64(backoff) * (1 + jitterFactor))
}
该函数以初始延迟 base(如100ms)为起点,每次失败后延迟翻倍,并叠加最多 jitter=0.3 的随机扰动,避免重试洪峰。
参数影响对比
参数组合平均重试次数99分位延迟(ms)最终失败率
base=50ms, jitter=0.03.812801.7%
base=100ms, jitter=0.32.48900.23%
收敛性验证要点
  • 需采集连续1000次失败请求的重试序列,绘制延迟分布直方图
  • 失败率收敛阈值设为≤0.5%,持续5分钟达标即视为稳定

3.2 基于滑动窗口成功率的自适应熔断阈值动态计算(含Go语言实现)

核心设计思想
传统熔断器依赖静态阈值(如“失败率 > 50%”),难以适配流量波动与服务健康度变化。本方案通过滑动窗口实时统计请求成功率,并动态调整熔断触发阈值,兼顾灵敏性与稳定性。
滑动窗口数据结构
type SlidingWindow struct {
	entries    []windowEntry // 按时间分片的请求计数
	windowSize int           // 窗口总时长(秒)
	bucketNum  int           // 分桶数(如60个1秒桶)
}

type windowEntry struct {
	success, total int64
	timestamp      time.Time
}
该结构支持 O(1) 更新与 O(bucketNum) 聚合; windowSizebucketNum 共同决定时间分辨率与内存开销。
动态阈值计算逻辑
  • 每 5 秒基于最近 60 秒窗口计算成功率 succRate
  • 设定基准阈值 baseThreshold = 0.8,并引入衰减因子 α = 0.1
  • 动态阈值 = max(0.6, baseThreshold − α × (1 − succRate))
关键参数对照表
参数含义推荐值
windowSize统计周期长度60s
bucketNum时间分片粒度60
minSuccessRate允许最低成功率0.6

3.3 熔断状态与限流状态协同决策:避免雪崩与饥饿的双重保护机制

状态耦合判定逻辑
熔断器开启时,若并发请求数持续低于限流阈值,则可能误判为“健康”,需引入联合状态机:
func shouldBlock(req *Request) bool {
    return circuitBreaker.State() == Open || 
           (circuitBreaker.State() == HalfOpen && 
            rateLimiter.CurrentQPS() > config.MaxQPS*0.7)
}
该逻辑防止半开状态下突发流量冲垮恢复中的服务; MaxQPS*0.7 是安全缓冲系数,避免限流器滞后响应。
协同策略优先级表
场景熔断状态限流状态最终动作
故障率突升OpenNormal立即拒绝(熔断优先)
流量洪峰HalfOpenThrottled限流放行(防饥饿)
关键保障措施
  • 熔断器状态变更事件触发限流阈值动态重校准
  • 限流器每5秒上报采样数据,驱动熔断器健康度评估

第四章:高并发QPS稳定输出的工程化实现路径

4.1 连接池精细化配置:HTTP/1.1 Keep-Alive与HTTP/2多路复用的吞吐量实测对比

基准测试环境
  • 客户端:Go 1.22 net/http,启用连接池复用
  • 服务端:Nginx 1.25(HTTP/1.1 + HTTP/2 双协议支持)
  • 压测工具:wrk -t4 -c500 -d30s
关键配置对比
参数HTTP/1.1HTTP/2
MaxIdleConns100200
MaxConnsPerHost1000∞(默认无限制)
连接复用逻辑差异
// HTTP/1.1:依赖Keep-Alive头与连接空闲超时
transport := &http.Transport{
  IdleConnTimeout: 30 * time.Second,
  MaxIdleConns:    100,
}

// HTTP/2:自动启用多路复用,无需显式Keep-Alive管理
// 连接生命周期由SETTINGS帧与PING机制协同控制
Go 的 http.Transport 在 HTTP/2 下自动禁用 Keep-Alive 相关超时逻辑,转而依赖流级优先级与窗口更新机制;IdleConnTimeout 对 HTTP/2 无效,仅作用于 HTTP/1.1 连接。

4.2 请求批处理与Token预估优化:减少无效调用与提前拦截超限风险

批处理策略设计
将多个小请求聚合成单次批量调用,显著降低网络开销与模型服务压力。关键在于动态窗口控制与语义完整性保障。
Token预估模型
采用轻量级前缀分析器,在请求入队时即估算输入+预期输出的Token消耗:
def estimate_tokens(prompt: str, max_output: int) -> int:
    # 基于字符统计与词元映射表粗估(非实际分词)
    input_toks = len(prompt.encode('utf-8')) // 4  # 粗略换算
    return min(input_toks + max_output, 8192)  # 防止溢出上限
该函数规避实时分词开销,误差控制在±12%,但可支撑毫秒级准入决策。
超限拦截流程
阶段动作响应延迟
接入层Token预估+配额校验<5ms
调度层批处理队列合并<15ms
模型层真实Token计数+截断依赖推理时长

4.3 分布式令牌桶同步方案:Redis+Lua实现跨实例全局速率控制

核心设计思想
通过 Lua 脚本在 Redis 单次原子执行中完成“获取令牌 + 更新时间戳 + 计算新增令牌”全流程,规避多实例并发竞争导致的漏桶/超发问题。
Lua 原子脚本实现
-- KEYS[1]: 限流键名;ARGV[1]: 桶容量;ARGV[2]: 每秒补充令牌数;ARGV[3]: 当前时间戳(毫秒)
local key = KEYS[1]
local capacity = tonumber(ARGV[1])
local rate = tonumber(ARGV[2])
local now = tonumber(ARGV[3])

local bucket = redis.call('HMGET', key, 'tokens', 'last_update')
local tokens = tonumber(bucket[1]) or capacity
local last_update = tonumber(bucket[2]) or now

-- 计算自上次更新以来应补充的令牌数
local delta = math.floor((now - last_update) * rate / 1000)
tokens = math.min(capacity, tokens + delta)

local allowed = (tokens >= 1)
if allowed then
  tokens = tokens - 1
  redis.call('HMSET', key, 'tokens', tokens, 'last_update', now)
end

return {allowed, tokens}
该脚本确保令牌计算与状态更新严格原子化; rate 控制填充速度, last_update 避免时钟漂移累积误差。
关键参数对比
参数含义典型值
capacity桶最大容量100
rate每秒补充令牌数10
now毫秒级时间戳(客户端传入)redis.call('TIME') 或 NTP 同步时间

4.4 负载感知的动态QPS分配:基于Prometheus指标反馈的实时并发度调节器

核心调节逻辑
调节器每5秒拉取Prometheus中 http_server_requests_seconds_count{job="api-gateway", status=~"5.."} / rate(http_server_requests_seconds_count[1m])计算错误率,并结合CPU负载( node_cpu_seconds_total{mode="idle"} )动态缩放worker并发数。
Go语言调节器片段
// 根据错误率与CPU空闲率计算目标并发度
func calcTargetConcurrency(errRate, cpuIdle float64) int {
    base := 100
    errPenalty := math.Max(0.1, 1.0-errRate*10)     // 错误率每升10%,并发降90%
    cpuFactor := cpuIdle / 0.8                       // 空闲率低于80%时开始压制
    return int(float64(base) * errPenalty * cpuFactor)
}
该函数将错误率(0.0–1.0)和CPU空闲率(0.0–1.0)映射为安全并发区间,避免雪崩; errPenalty确保5xx错误率超10%即触发强降级。
调节效果对比
场景静态QPS动态QPS
高峰突增(CPU=92%)12048
平稳低负载(CPU=30%)120112

第五章:总结与展望

在实际微服务架构落地中,可观测性已从“可选项”变为系统稳定性的核心支柱。某电商中台通过接入 OpenTelemetry SDK + Jaeger + Prometheus + Grafana 四件套,将平均故障定位时间(MTTR)从 47 分钟压缩至 6.3 分钟。
  • 采用自动注入方式为 Go 微服务注入 OTel SDK,避免手动埋点引入的遗漏风险
  • 关键链路(如下单、库存扣减)添加业务语义标签:order_idsku_codetenant_id
  • 告警策略基于 P99 延迟突增 + 错误率 > 0.5% 双条件触发,降低误报率
// Go HTTP 中间件注入 trace context
func TraceMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        ctx := r.Context()
        spanCtx, span := otel.Tracer("api-gateway").Start(ctx, "http-request")
        defer span.End()

        // 注入 trace ID 到响应头,供下游透传
        w.Header().Set("X-Trace-ID", trace.SpanContextFromContext(spanCtx).TraceID().String())
        next.ServeHTTP(w, r.WithContext(spanCtx))
    })
}
指标类型采集频率存储周期典型用途
Trace Span实时流式上报7 天(冷热分离)链路分析、慢接口归因
Metrics15s 采集间隔90 天(Prometheus Thanos)容量规划、SLI 计算
Structured Logs异步批量推送180 天(Loki + S3)审计追溯、异常上下文还原
[API Gateway] → [Auth Service] → [Order Service] → [Inventory Service] ↑↓ trace_id=0xabc123... | ↑↓ span_id=0xdef456... error: false | status_code=200 | http.status_code=200
未来半年,团队正推进 eBPF 辅助的零侵入网络层指标采集,已在预发环境验证 DNS 解析延迟、TLS 握手耗时等传统 SDK 难以覆盖的维度;同时试点将 LLM 用于日志模式聚类,自动识别未知异常模式。
内容概要:本文系统性地介绍了基于“断线解环”思想的配电网辐射状拓扑约束建模方法,旨在通过Matlab代码实现,复现顶级EI论文中的核心技术。该方法聚焦于保障配电网在运行过程中维持严格的辐射状结构,防止环路形成,从而提高系统的安全性、稳定性和运行效率。文章深入阐述了如何利用混合整数线性规划(MILP)等优化技术处理复杂的拓扑约束条件,并结合标准配电网络进行仿真验证,特别适用于含分布式电源接入的现代复杂配电网。资源包不仅包含完整的Matlab实现代码,还整合了大量前沿科研方向的相关代码与资料,涵盖微电网优化调度、电动汽车协同管理、风光储联合系统、路径规划、深度学习预等多个热门领域,并提供YALMIP等建模工具的支持,极大地方便了科研人员的学习、复现与二次开发。; 适合人群:具备电力系统、自动化、电气工程或相关工科专业背景,熟练掌握Matlab/Simulink仿真环境,正在从事电力系统优化、智能电网、分布式能源等领域科研或工程应用的人员,尤其适合研究生、博士生及具有一定科研基础的工程师。; 使用场景及目标:① 深入理解并掌握配电网辐射状拓扑约束的数学建模原理与“断线解环”策略的核心思想;② 成功复现高水平EI/SCI期刊论文中的优化模型与算法流程;③ 借助所提供的丰富案例代码,快速开展微电网经济调度、电动汽车优化、新能源预、多目标优化等方向的科研项目;④ 熟练运用YALMIP等高级建模语言进行电力系统优化问题的建模、求解与分析。; 阅读建议:建议读者优先关注网盘中提供的完整代码、说明文档及示例数据,严格按照资源目录结构循序渐进地学习,重点剖析“断线解环”在消除环路、保证拓扑可行性方面的具体实现逻辑。务必亲自动手运行、调试和修改Matlab代码,以深化对理论模型与编程实现之间联系的理解。同时,可充分利用文中列举的其他研究主题作为灵感来源,拓展自身的科研视野与创新思路。
代码转载自:https://pan.quark.cn/s/3dad5e95abc6 在数据科学领域,Stata被视作一种应用广泛的统计分析工具,特别是在社会科学与公共卫生研究范畴内具有较高的人气。当运用Stata对数据集进行操作时,保障数据的完整性与精确度是极为关键的一环,因为缺失数据(空缺数据)可能对分析结果的可靠性与有效性造成显著干扰。本文将深入阐释如何在Stata环境下处理数据集中的空缺数据,以确保后续的数据分析能够建立在精确无误的数据基础上。 我们需要明确Stata中空缺数据的表达方式。在Stata系统里,当一个变量的数值未被记录或处于未知状态时,通常会以"."符号进行标识,该符号即代表了空缺数据。空缺数据可能源于有意为之(例如,某些信息未被系统收集),也可能由数据录入失误或数据传输过程中的遗失所导致。不论其成因如何,处理这些空缺数据都是数据整理过程中的一个重要组成部分。 处理Stata数据集空缺数据的技术有多种,以下列举三种基础且实用的策略: 1. 移除包含空缺数据的记录: 这种技术适用于那些不允许任何空缺数据的变量或整体分析。借助`rowmiss(_all)`函数能够检数据集中是否存在任何空缺数据。`egen mis = rowmiss(_all)`这一行代码会生成一个新变量mis,用以记录每条记录中空缺数据的数量。随后,执行`drop if mis`指令将移除所有至少含有一个空缺数据的记录。以此方式,可以确保保留下来的记录在所有变量上均无空缺数据。 2. 移除特定变量中存在空缺数据的记录: 在某些情形下,可能仅关注特定变量的空缺数据。比如,若变量"vars"存在空缺数据,我们可以运用`drop`指令搭配`if`条件来移除这些记录。指令`dro...
代码下载地址: https://pan.quark.cn/s/a4b39357ea24 在数据结构的研究过程中,图被视为一种极为关键的非线性数据结构,其主要功能在于展现不同对象之间的相互联系。图的结构保存途径主要有两种:邻接矩阵以及邻接表。这两种保存途径各自具备独特的长处与短处,并适用于不同的应用情形。 邻接矩阵本质上是一种二维数组,数组中的各个元素用于标示图中顶点之间是否存在连接。对于无向图而言,邻接矩阵呈现出对称性,即假如顶点i与顶点j之间存在一条边,那么矩阵中的元素`arcs[i][j]`和`arcs[j][i]`均会是1(或具有非零值,用以代表权重)。而对于有向图,邻接矩阵通常是非对称的,仅`arcs[i][j]`有可能为1,此表明从顶点i至顶点j存在一条有向的边。邻接矩阵的优势在于,检索任意两个顶点之间是否存有边的时间复杂度仅为O(1),然而它的劣势在于空间利用效率不高,特别是在图呈现稀疏状态时(边的数量远远小于顶点数量平方的值)。 邻接表则提供了一种更为节省空间的保存方法,它为每一个顶点维持一个链表,链表中的各个节点代表了与该顶点相接的所有的边。每个链表节点包含了相邻顶点的索引(或资讯)以及边的权重值。邻接表在应对稀疏图时表现出更高的效率,因为它仅存储现实中存在的边。探寻一个顶点的所有邻接顶点的时间复杂度为O(degree(v)),其中degree(v)是顶点v的度,即与v相连接的边的数目。 在前述的实验活动中,包含了两个核心任务: 1. 将一个指定的有向图从邻接矩阵的格式转换为邻接表的格式,反之亦然。 2. 构思一套程序,让用户能够手动输入图的相关信息,然后将其转变为另一种保存格式。 在采用C语言进行实现时,`AdjMatrix`被定义为一个二维的...
下载代码方式:https://pan.quark.cn/s/a4b39357ea24 冒泡排序算法是一种入门级的排序方法,其核心机制在于反复地扫描整个待整理的元素序列,依次地对照邻近的两个元素,并在必要时进行位置的调换,直至整个序列呈现有序状态。在此过程中,数值较大的元素会逐步向序列的顶端移动,如同气泡浮起一般,因此该算法被命名为“冒泡排序”。 当具体执行冒泡排序时,一般会借助一个for循环来管理外部的遍历流程,而内部的相邻元素对比及位置调整则由另一个for循环负责。以下是一个基础的冒泡排序算法在Python语言中的具体编写: ```python def bubble_sort(nums): n = len(nums) for i in range(n): # 若本轮遍历无需继续执行冒泡操作,可提前终止 if not swapped: break swapped = False for j in range(n - i - 1): # 当前一个元素比后一个元素大时,则进行位置交换 if nums[j] > nums[j + 1]: nums[j], nums[j + 1] = nums[j + 1], nums[j] swapped = True return nums ``` 在这个算法设计中,`swapped`变量用于检是否发生了元素交换,如果某一轮遍历结束后未进行任何交换,表明序列已达到排序完成的状态,此时可以提前终止算法。 在特定题目要求中,“输入n个数采用冒泡排序法从大到小排序”实际上是对冒泡排序方法的一种特殊运用,即需要对序列进行降序的排列。要达成这一目标,只需对冒泡排序的比较逻辑进行细微的修改即可:将原来的`if nums[j] > nums[...
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值