算法练习-成功之后不许掉队

题目由实际的业务抽象而来。

题目描述

有一排任务,每个任务有一个状态:

0 待处理 1 取消 2 执行中3 执行成功 4 执行失败

业务规定

状态为 1(取消)的任务既不会触发重跑,也不会被重跑,就当它不存在。只要前面出现过任何一次非成功(即 0、2、4),那么后面再出现的所有"成功"(3)都必须重新执行。

现在请你返回本次需要重新执行的第一个任务下标;如果无需执行,返回 -1。

输入格式

第一行一个整数 n (0 ≤ n ≤ 10⁶)。

第二行 n 个整数,第 i 个数表示第 i 个任务的状态。

输出格式

一个整数:第一个需要重跑的任务下标;没有就输出 -1。

示例

输入 1

4

4 3 3 3

输出 1

解释

下标 0 是失败(非成功),后面所有成功都要重跑,因此第一个要重跑的是下标 1。

输入 2

5

3 3 4 3 3

输出 2

解释

下标 2 第一次出现非成功,其后所有成功都要重跑,所以答案 2。

输入 3

3

1 1 3

输出 -1

解释

只有取消和成功,没有非成功出现,无需执行。

数据范围

n ≤ 10⁶,保证 O(n) 一次扫描即可。

提示

用"红绿灯"思想:

绿灯:还没遇到非成功。

红灯:已遇到非成功,此后一旦出现成功就要重跑。

全程只需两个变量,额外空间 O(1)。

代码实现如下:

java 复制代码
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

public class TaskExecutor {

    /* ===================== 模型(同上,省略) ===================== */
    public enum Status {待处理, 取消, 执行中, 执行成功, 执行失败}

    public static class Task {
        private Integer id;
        private Status status;

        public Task(Integer id, Status status) {
            this.id = id;
            this.status = status;
        }

        public Integer getId() {
            return id;
        }

        public Status getStatus() {
            return status;
        }

        public void setStatus(Status status) {
            this.status = status;
        }

        @Override
        public String toString() {
            return "Task#" + id + "(" + status + ")";
        }
    }

    /* ===================== 执行器 ===================== */
    public static class SimpleTaskExecutor {

        public void process(List<Task> taskList) {
            if (taskList == null || taskList.isEmpty()) return;

            // 1. 按id升序
            taskList.sort(Comparator.comparing(Task::getId));

            // 2. 找到"第一个需要被重新执行"的索引
            int start = findFirstNeedReExec(taskList);

            // 3. 从该索引开始顺序执行
            for (int i = start; i < taskList.size(); i++) {
                Task t = taskList.get(i);
                if (t.getStatus() == Status.取消) continue;   // 明确取消就跳过

                t.setStatus(Status.执行中);
                System.out.println("开始执行 " + t);

                boolean success = doBiz(t);

                t.setStatus(success ? Status.执行成功 : Status.执行失败);
                System.out.println("执行完毕 " + t);
            }
            System.out.println("全部处理完成");
        }

        /**
         * 核心:找"第一个需要被重新执行"的任务
         * 规则:只要前面出现过非成功,后面再出现成功,则这条成功也要重跑,
         * 返回这段区间里的第一个任务索引。
         */
         private static int findFirstNeedReExec(List<Task> list) {
        int startIndex = list.size(); // 默认:不跑
        for (int i = 0; i < list.size(); i++) {
            Status s = list.get(i).getStatus();
            if (s == Status.取消) continue;
            if (s != Status.执行成功 && startIndex == list.size()) {
                startIndex = i;   // 只记录第一次
            }
        }
        return startIndex;
    }
        private boolean doBiz(Task t) {
            try {
                Thread.sleep(500);
            } catch (InterruptedException ignore) {
            }
            return Math.random() > 0.3; // 70% 成功率
        }
    }

    /* ===================== 测试 ===================== */
    public static void main(String[] args) {
        List<Task> list = new ArrayList<>();
        // 构造场景:成功前面有非成功
        list.add(new Task(1, Status.执行失败));
        list.add(new Task(2, Status.执行成功)); 
        list.add(new Task(3, Status.执行成功));  
        list.add(new Task(4, Status.执行成功));

        new SimpleTaskExecutor().process(list);
    }
}

实现思路一句话:
"扫一遍,遇见非成功就亮红灯,红灯后再遇到第一个成功就是要重跑的开头;如果一直没亮红灯,就返回第一个非成功即可。"


变量解释(两个布尔/整数就够)

变量 含义
metFail 布尔,是否已亮红灯(即前面出现过 0/2/4 且未被取消)。
first 整数,答案下标,初始 -1。

遍历逻辑(伪代码,与题目状态编码一致)

复制代码
metFail = False
first   = -1
for i in 0..n-1:
    s = status[i]
    if s == 1:            # 取消当空气,直接跳过
        continue
    if s != 3:            # 0、2、4 都是非成功
        metFail = True
        if first == -1:   # 记录第一个非成功位置
            first = i
    else:                 # s == 3 成功
        if metFail:       # 红灯已亮,这条成功必须重跑
            if first == -1:
                first = i  # 理论上不会进,保险
            break          # 找到"第一段"就可以提前退出

循环结束后:

  • 如果 first != -1 → 返回 first
  • 否则全程无非成功 → 返回 -1

正确性证明

  1. 没亮红灯(metFail == False
    说明前面只有取消或成功,按规则无需重跑 ;此时 first 保持 -1,符合要求。
  2. 亮红灯后(metFail == True
    • 若当前是非成功 ,我们把它记为 first,继续往后扫。
    • 一旦遇到成功 ,它就是"红灯后的第一个成功",按规则必须重跑,且它前面的非成功已经被记录在 first,所以直接 break 返回即可。

因此 first 总是第一个需要被重新执行的任务下标。


复杂度

时间:O(n) ------ 每个元素常数时间,最多扫一遍。

空间:O(1) ------ 只用到两个变量。

相关推荐
草履虫建模38 分钟前
力扣算法 1768. 交替合并字符串
java·开发语言·算法·leetcode·职场和发展·idea·基础
naruto_lnq3 小时前
分布式系统安全通信
开发语言·c++·算法
Jasmine_llq3 小时前
《P3157 [CQOI2011] 动态逆序对》
算法·cdq 分治·动态问题静态化+双向偏序统计·树状数组(高效统计元素大小关系·排序算法(预处理偏序和时间戳)·前缀和(合并单个贡献为总逆序对·动态问题静态化
爱吃rabbit的mq4 小时前
第09章:随机森林:集成学习的威力
算法·随机森林·集成学习
(❁´◡`❁)Jimmy(❁´◡`❁)4 小时前
Exgcd 学习笔记
笔记·学习·算法
YYuCChi5 小时前
代码随想录算法训练营第三十七天 | 52.携带研究材料(卡码网)、518.零钱兑换||、377.组合总和IV、57.爬楼梯(卡码网)
算法·动态规划
不能隔夜的咖喱5 小时前
牛客网刷题(2)
java·开发语言·算法
VT.馒头5 小时前
【力扣】2721. 并行执行异步函数
前端·javascript·算法·leetcode·typescript
进击的小头5 小时前
实战案例:51单片机低功耗场景下的简易滤波实现
c语言·单片机·算法·51单片机
咖丨喱7 小时前
IP校验和算法解析与实现
网络·tcp/ip·算法