Fork me on GitHub
0%

oh-my-claudecode - Claude Code 多智能体编排框架

概述

oh-my-claudecode (OMC) 是一个为 Claude Code 设计的多智能体编排系统,让用户无需学习复杂的 Claude Code 用法,直接通过自然语言指令完成复杂任务。

项目地址: https://github.com/Yeachan-Heo/oh-my-claudecode

官方网站: https://yeachan-heo.github.io/oh-my-claudecode-website

npm 包: oh-my-claude-sisyphus

核心特性

🤖 Autopilot - 自动执行

只需描述你想要做什么,OMC 自动完成规划和执行:

1
/autopilot "build a REST API for managing tasks"

👥 Team 模式 - 多智能体协作

支持同时启动多个 Claude/Codex/Gemini 实例协同工作:

1
/team 3:executor "fix all TypeScript errors"

Team 执行流程:

  • team-plan → 规划
  • team-prd → 需求分析
  • team-exec → 执行
  • team-verify → 验证
  • team-fix → 修复(循环)

💬 Deep Interview - 需求分析

使用苏格拉底式提问帮你理清需求:

1
/deep-interview "I want to build a task management app"

🔄 双入口设计

方式 命令 说明
终端 CLI omc setup 安装 npm 包后使用
会话技能 /setup 在 Claude Code 会话中直接使用

安装使用

方式一:Marketplace 安装(推荐)

1
2
/plugin marketplace add https://github.com/Yeachan-Heo/oh-my-claudecode
/plugin install oh-my-claudecode

方式二:npm 全局安装

1
2
npm i -g oh-my-claude-sisyphus@latest
omc setup

常用命令

命令 功能
/setup 初始化配置
/autopilot <任务> 自动执行任务
/team N:executor <任务> 启动 N 个执行者
/deep-interview <需求> 深度需求分析
/ccg <任务> Codex + Gemini 混合编排

项目结构

1
2
3
4
5
6
7
8
oh-my-claudecode/
├── agents/ # 智能体定义
├── bridge/ # CLI 桥接层
├── commands/ # 命令实现
├── skills/ # 会话技能
├── templates/ # 项目模板
├── docs/ # 文档
└── .claude-plugin/ # 插件配置

适用场景

  • 快速开发: 用自然语言描述需求,自动完成代码编写
  • 代码审查: 多实例并行审查不同模块
  • 复杂任务: 自动分解任务并分配给专业智能体
  • 需求梳理: 通过深度访谈理清模糊想法

与 Superpowers 的横向对比

如果把 Claude Code 生态里的工具分成两类,oh-my-claudecode 更偏“执行编排”,而 Superpowers 更偏“工程方法论”。前者关心如何把一个复杂任务拆分给多个 agent 并行推进,后者关心如何让 Claude Code 按更稳定、更可验证的开发流程工作。

两者并不是简单的替代关系,更像是两个不同层面的增强:OMC 提高吞吐量,Superpowers 提高过程质量。

维度 oh-my-claudecode Superpowers
核心定位 多智能体编排、自动化执行 结构化开发流程、TDD、调试、评审
主要目标 让多个 Claude/Codex/Gemini 实例协同完成任务 让 Claude Code 按严谨工程流程推进任务
典型能力 /autopilot/team/ccg、多模型协作 brainstorming、TDD、debugging、execute-plan、code review
优势 并行能力强,适合大任务拆分 稳定性更强,质量控制更好
代价 token 消耗更高,结果合并成本更高 流程更重,单次任务启动更慢
适合场景 多模块开发、批量修复、大规模重构、并行评审 需求澄清、复杂 bug、测试驱动开发、严肃工程交付
性能特征 可能缩短等待时间,但增加总调用成本 可能增加前期时间,但减少返工成本

效果差异

OMC 的效果主要来自“并行”。当任务可以被自然拆成多个边界清晰的子任务时,多个 agent 可以同时推进,整体等待时间会明显下降。例如多模块重构、批量修复 lint/test 问题、并行阅读不同代码区域,都是适合 OMC 的场景。

但并行不等于一定更好。多 agent 同时工作时,容易出现三个问题:

  1. 重复劳动:不同 agent 可能分析同一段上下文,造成 token 浪费。
  2. 结果冲突:多个 agent 修改相邻文件或同一抽象层时,合并成本会上升。
  3. 上下文漂移:每个 agent 看到的信息不完全一致,最后产出的方案可能风格不同。

Superpowers 的效果则主要来自“约束”。它通过 brainstorming、计划、TDD、debug、review 等步骤限制模型直接跳到实现阶段。这样做的短期速度不一定最快,但更容易得到可解释、可验证、可维护的结果。

换句话说,OMC 更像“提高产能的并行执行器”,Superpowers 更像“降低跑偏概率的工程护栏”。

性能与成本分析

从性能角度看,需要区分两个指标:

  • Wall-clock time:用户等待任务完成的真实时间。
  • Total compute/token cost:所有 agent 合计消耗的模型调用成本。

OMC 可能显著降低 wall-clock time。例如一个任务被拆成 4 个独立模块,单 agent 串行需要 40 分钟,多 agent 并行可能 15-20 分钟完成。但是总 token 消耗通常会上升,因为每个 agent 都需要读取上下文、制定计划、执行和验证。

Superpowers 往往不会追求最短 wall-clock time。它会把时间花在需求澄清、测试设计、边界条件讨论和复盘上。短期看更慢,但如果任务复杂度高,后期返工更少,整体交付成本反而更低。

因此,两者的性能结论可以概括为:

目标 更适合的工具 原因
快速探索方案 OMC 可以并行尝试多个方向
快速批量改代码 OMC 多 agent 可分文件/分模块执行
复杂需求澄清 Superpowers 更强调问题定义和验收标准
高质量重构 Superpowers + OMC 先定计划和测试,再并行执行
修疑难 bug Superpowers 系统化 debug 比盲目并行更重要
大规模代码审查 OMC + Superpowers 并行阅读,统一 review 标准

组合使用策略

最理想的方式不是二选一,而是组合使用:

  1. 先用 Superpowers 做需求澄清:明确目标、边界、验收标准和测试策略。
  2. 再用 OMC 做任务拆分和并行执行:把模块化、低耦合的部分交给多个 agent。
  3. 最后回到 Superpowers 做 review 和验证:统一检查代码质量、测试覆盖和设计一致性。

一个更稳的工作流可以是:

1
2
3
4
5
6
7
8
9
brainstorm / plan

拆分任务边界

/team 并行执行

统一合并与测试

review / debug / polish

这个组合的核心思想是:用 Superpowers 保证方向正确,用 OMC 提高执行吞吐量

使用建议

OMC 最适合处理“可以拆分、可以并行、结果容易合并”的任务。如果任务本身高度耦合,例如核心架构设计、复杂状态机重构、疑难线上 bug,盲目开多个 agent 反而可能制造噪音。

更推荐的实践是:

  • 小任务:直接用 Claude Code 或 OMC 的 /autopilot
  • 中等任务:先手动描述清楚目标,再用 OMC 拆分执行。
  • 大任务:先用结构化流程产出计划,再用 /team 并行执行。
  • 高风险任务:必须保留人工 review、测试验证和回滚策略。

对个人开发者来说,OMC 的价值在于把“一个人盯着一个 AI 慢慢做事”变成“一个人管理多个 AI 同时推进”。但这也意味着用户角色发生变化:你不再只是提 prompt 的人,而更像一个任务调度者和结果审核者。

总结

oh-my-claudecode 降低了 Claude Code 的使用门槛,让开发者专注于“做什么”而非“怎么做”。它的核心价值不是替代 Claude Code,而是在 Claude Code 之上增加任务编排、多 agent 协作和多模型协同能力。

和 Superpowers 相比,OMC 更重执行效率,Superpowers 更重工程可靠性。前者适合提高吞吐量,后者适合降低返工率。真正高效的 AI coding 工作流,往往不是选择某一个工具,而是把“结构化思考”和“并行执行”结合起来:先把问题定义清楚,再让多个 agent 高效完成可拆分的部分,最后通过测试和 review 收敛质量。


本文由 AI 自动生成

spark生态

(TODO: 放个高清图)

  • 支持多种编程语言, 资源调度层支持local, standalone, yarn和k8s模式
  • 提供丰富组件对应应用场景, spark core + Streaming, SQL, MLLib, R, GraphX等
  • 多种存储介质, hdfs, hhive, aws, hbase, es, mysql, pg读写, 实时计算flume, kafka读写
  • 数据格式丰富 txt, json, csv等格式, 数据压缩和海量查询有优势

spark相对于hadoop的优势

  1. 高性能: spark具有hadoop MR的所有优点, 且中间结果不需要写存储到HDFS, 直接保存到内存, 在内存做处理
  2. 高容错:
    1. 血统(Lineage)数据恢复: RDD(Resilient Distributed Dataset): 分布在一组节点中只读数据集合, 集合弹性且相互依赖. RDD中内容丢失可以根据血统关系重建.
    2. CheckPoint容错: 两种检测方式 – 冗余数据和日志记录更新操作.
  3. 通用性: 提供了除MR更丰富的操作, action(collect, reduce, save…)和transformations (map, union, join, filter…), shuffle, partition, 控制中间结果存储

spark原理和特点

(TODO: 放张高清思维导图)

spark core: 包括基础配置, 存储系统, 计算层, 调度原理

spark基础配置

  • spark context是spark应用程序入口, 隐藏了网络通信, 分布式部署, 消息通信… 开放人员根据sparkContext等api进行开放即可
  • sparkRpc基于netty实现, 分为同步和异步. 事件总线用于sparkContext组建间交换, 属于监听者模式化, 采用异步调用. 度量系统用于系统监控

spark存储系统

  • 优先考虑各节点内存, 内存存不下写磁盘, 可以控制远程调用到存储

spark调度系统

包括DAGScheduler和TaskScheduler组成

  • DAGScheduler把一个Job根据RDD间依赖, 划分成多个Stage, 对划分后的每个Stage抽象为一个或者多个Task组成的任务集, 随后交给TaskScheduler做Task的任务调度
  • 调度算法: FIFO,FAIR

spark sql

(TODO: 没大看懂, 回来再看看)
spark参与计算的数据类型区别

基于sql数据处理, 简化分布式数据集处理, 提供两种数据抽象, DataFrame和DataSet

  • RDD, 相当于java里的进程能看到的对象, 但并不能看到对象内字段具体的内容
  • DataFrame是分布式的Row对象的集合。DataFrame相当于结构化数据, 哪些列,每列的名称和类型各是什么。DataFrame多了数据的结构信息.
  • Dataset可以认为是DataFrame的一个特例,主要区别是Dataset每一个record存储的是一个强类型值而不是一个Row。

spark streaming

支持流数据的可伸缩和容错

spark运行模式及集群角色

spark运行模式

spark集群角色

(TODO: 集群角色图)

集群管理节点cluster manager, 工作节点worker, 执行器executor, 驱动器driver, 应用程序application.

  1. Cluster Manager
    集群管理器, 存在Master进程中, 区分部署模式, 用于应用程序申请的资源管理
  2. worker
    worker是spark工作节点, 用于提交任务
  • 通过注册机向cluster manager汇报cpu, 内存等

方法

private 方法

定义private方法的理由是内部方法是可以调用private方法

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
public class Main {
public static void main(String[] args) {
Person ming = new Person();
ming.setBirth(2008);
System.out.println(ming.getAge());
}
}

class Person {
private String name;
private int birth;
}

public void setBirth(int birth) {
this.birth = birth;
}

public int getAge() {
return calcAge(2019); // 调用private方法
}

// private方法:
private int calcAge(int currentYear) {
return currentYear - this.birth;
}

calcAge()是一个private方法,外部代码无法调用,但是,内部方法getAge()可以调用它。

Person类只定义了birth字段,没有定义age字段,获取age时,通过方法getAge()返回的是一个实时计算的值,并非存储在某个字段的值。这说明方法可以封装一个类的对外接口,调用方不需要知道也不关心Person实例在内部到底有没有age字段。

this 变量

在方法内部,可以使用一个隐含的变量this,它始终指向当前实例。因此,通过this.field就可以访问当前实例的字段。如果没有命名冲突,可以省略this。如果有局部变量和字段重名,那么局部变量优先级更高,就必须加上this

1
2
3
4
5
6
class Person {
private String name;
public void setName(String name) {
this.name = name;
}
}

可变参数

可变参数用类型...定义,可变参数相当于数组类型

1
2
3
4
5
6
7
class Group {
private String[] names;

public void setNames(String... names) {
this.names = names;
}
}

可以把可变参数改写为String[]类型

1
2
3
public void setNames(String[] names) {
this.names = names;
}

调用方需要自己先构造String[],调用方可以传入null

1
2
Group g = new Group();
g.setNames(new String[] {"Xiao Ming", "Xiao Hong", "Xiao Jun"});

可变参数可以保证无法传入null,因为传入0个参数时,接收到的实际值是一个空数组而不是null

参数绑定

  • 基本类型参数传递,是调用方值的复制。双方各自的后续修改,互不影响。
  • 引用类型参数的传递,调用方的变量,接收方的参数变量,指向的是同一个对象。双方任意一方对这个对象的修改,都会影响对方

构造函数

  • 调用构造方法,必须用new操作符。

默认构造方法

  • 如果一个类没有定义构造方法,编译器会自动为我们生成一个默认构造方法,它没有参数,也没有执行语句
  • 自定义了一个构造方法,那么,编译器就不再自动创建默认构造方法
  • 如果既要能使用带参数的构造方法,又想保留不带参数的构造方法,那么只能把两个构造方法都定义出来
  • 也可以对字段直接进行初始化,最终根据调用的构造方法确定
  • 可以定义多个构造方法,在通过new操作符调用的时候,编译器通过构造方法的参数数量、位置和类型自动区分
  • 一个构造方法可以调用其他构造方法,这样做的目的是便于代码复用。调用其他构造方法的语法是this(…)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Person {
private String name;
private int age;

public Person(String name, int age) {
this.name = name;
this.age = age;
}

public Person(String name) {
this(name, 18); // 调用另一个构造方法Person(String, int)
}

public Person() {
this("Unnamed"); // 调用另一个构造方法Person(String)
}
}

方法重载

  • 定义:如果有一系列方法,它们的功能都是类似的,只有参数有所不同,那么,可以把这一组方法名做成同名方法。这种方法名相同,但各自的参数不同,称为方法重载(Overload)。
  • 方法重载的返回值类型通常都是相同的。
  • 方法重载的目的是,功能类似的方法使用同一名字,调用起来更简单

继承

protected

  • 继承子类无法访问父类的private字段或者private方法

  • protected关键字可以把字段和方法的访问权限控制在继承树内部,一个protected字段和方法可以被其子类,以及子类的子类所访问

    super

    • super关键字表示父类(超类)。子类引用父类的字段时,可以用super.fieldName

      1
      2
      3
      4
      5
      class Student extends Person {
      public String hello() {
      return "Hello, " + super.name;
      }
      }
    • 这里使用super.name,或者this.name,或者name,效果都是一样的。编译器会自动定位到父类的name字段。

    • 任何class的构造方法,第一行语句必须是调用父类的构造方法。如果没有明确地调用父类的构造方法,编译器会帮我们自动加一句super();

      1
      2
      3
      4
      5
      6
      7
      8
      class Student extends Person {
      protected int score;

      public Student(String name, int age, int score) {
      super(); // 自动调用父类的构造方法
      this.score = score;
      }
      }

      此时Person类并没有无参数的构造方法,因此,编译失败。改super父类调用方法

      1
      2
      3
      4
      public Student(String name, int age, int score) {
      super(name, age); // 调用父类的构造方法Person(String, int)
      this.score = score;
      }
    • 如果父类没有默认的构造方法,子类就必须显式调用super()并给出参数以便让编译器定位到父类的一个合适的构造方法。

    • 子类不会继承任何父类的构造方法。子类默认的构造方法是编译器自动生成的,不是继承的。

阻止继承

Java 15开始,允许使用sealed修饰class,并通过permits明确写出能够从该class继承的子类名称。

定义一个Shape

1
2
3
public sealed class Shape permits Rect, Circle, Triangle {
...
}
  • 上述Shape类就是一个sealed类,它只允许指定的3个类继承它
  • Ellipse并未出现在Shapepermits列表中。这种sealed类主要用于一些框架,防止继承被滥用

向上、向下转型

  • Person类型的变量,如果指向Student类型的实例,对它进行操作。把一个子类类型安全地变为父类类型的赋值,被称为向上转型(upcasting)

  • 把一个父类类型强制转型为子类类型,就是向下转型(downcasting)

    1
    Student s2 = (Student) p2; // runtime error! ClassCastException!
    • 不能把父类变为子类,因为子类功能比父类多,多的功能无法凭空变出来。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      Person p = new Student();
      if (p instanceof Student) {
      // 只有判断成功才会向下转型:
      Student s = (Student) p; // 一定会成功
      }


      //Java 14开始,判断instanceof后,可以直接转型为指定变量,避免再次强制转型。
      public class Main {
      public static void main(String[] args) {
      Object obj = "hello";
      if (obj instanceof String s) {
      // 可以直接使用变量s:
      System.out.println(s.toUpperCase());
      }
      }
      }

组合与继承

  • 继承是is关系,组合是has关系

  • 具有has关系不应该使用继承,而是使用组合,即Student可以持有一个Book实例

  • class Student extends Person {
        protected Book book;
        protected int score;
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24

    # 多态

    - 重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。**即外壳不变,核心重写!**

    重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。

    - 重载(overloading) 是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。

    每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。

    - 如果方法签名不同,就是Overload,Overload方法是一个新方法;如果方法签名相同,并且返回值也相同,就是`Override`。

    ```java
    class Person {
    public void run() { … }
    }

    class Student extends Person {
    // 不是Override,因为参数不同:
    public void run(String s) { … }
    // 不是Override,因为返回值不同:
    public int run() { … }
    }

加上@Override可以让编译器帮助检查是否进行了正确的覆写。

  • 引用类型为Person的变量,调用其run()方法,调用的是Studentrun()方法
  • Java的实例方法调用是基于运行时的实际类型的动态调用,而非变量的声明类型。
  • 多态的特性就是,运行期才能动态决定调用的子类方法。
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
public class Main {
public static void main(String[] args) {
// 给一个有普通收入、工资收入和享受国务院特殊津贴的小伙伴算税:
Income[] incomes = new Income[] {
new Income(3000),
new Salary(7500),
new StateCouncilSpecialAllowance(15000)
};
System.out.println(totalTax(incomes));
}
}
public static double totalTax(Income... incomes) {
double total = 0;
for (Income income: incomes) {
total = total + income.getTax();
}
return total;
}

class Income {
protected double income;

public Income(double income) {
this.income = income;
}

public double getTax() {
return income * 0.1; // 税率10%
}
}

class Salary extends Income {
public Salary(double income) {
super(income);
}

@Override
public double getTax() {
if (income <= 5000) {
return 0;
}
return (income - 5000) * 0.2;
}

}

class StateCouncilSpecialAllowance extends Income {
public StateCouncilSpecialAllowance(double income) {
super(income);
}

@Override
public double getTax() {
return 0;
}

}
  • 利用多态,totalTax()方法只需要和Income打交道,它完全不需要知道SalaryStateCouncilSpecialAllowance的存在,就可以正确计算出总的税。如果我们要新增一种稿费收入,只需要从Income派生,然后正确覆写getTax()方法就可以。把新的类型传入totalTax(),不需要修改任何代码。

  • 在编译阶段,只是检查参数的引用类型。

    然而在运行时,Java 虚拟机(JVM)指定对象的类型并且运行该对象的方法

    因此在上面的例子中,之所以能编译成功,是因为 Animal 类中存在 move 方法,然而运行时,运行的是特定对象的方法。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public class TestDog{
    public static void main(String args[]){
    Animal a = new Animal(); // Animal 对象
    Animal b = new Dog(); // Dog 对象

    a.move();// 执行 Animal 类的方法
    b.move();//执行 Dog 类的方法
    b.bark();//报错,因为b的引用类型Animal没有bark方法。
    }
    }

覆写Object方法

因为所有的class最终都继承自Object,而Object定义了几个重要的方法:

  • toString():把instance输出为String
  • equals():判断两个instance是否逻辑相等;
  • hashCode():计算一个instance的哈希值。

在必要的情况下,我们可以覆写Object的这几个方法。

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
class Person {
...
// 显示更有意义的字符串:
@Override
public String toString() {
return "Person:name=" + name;
}

// 比较是否相等:
@Override
public boolean equals(Object o) {
// 当且仅当o为Person类型:
if (o instanceof Person) {
Person p = (Person) o;
// 并且name字段相同时,返回true:
return this.name.equals(p.name);
}
return false;
}

// 计算hash:
@Override
public int hashCode() {
return this.name.hashCode();
}
}

调用super

在子类的覆写方法中,如果要调用父类的被覆写的方法,可以通过super来调用。

final

  • final修饰符有多种作用:
    • final修饰的方法可以阻止被覆写;
    • final修饰的class可以阻止被继承;
    • final修饰的field必须在创建对象时初始化,随后不可修改。

抽象类

  • 一个class定义了方法,但没有具体执行代码,这个方法是抽象方法,抽象方法用abstract修饰。把一个方法声明为abstract,表示它是一个抽象方法,本身没有实现任何方法语句。因为这个抽象方法本身是无法执行的,所以,Person类也无法被实例化。把Person类本身也声明为abstract,才能正确编译它。

  • abstract class Person {
        public abstract void run();
    }
    
    1
    2
    3
    4
    5
    6
    7

    - 因为无法执行抽象方法,因此这个类也必须申明为抽象类(abstract class)。

    - ```java
    abstract class Person {
    public abstract void run();
    }
  • Person类定义了抽象方法run(),那么,在实现子类Student的时候,就必须覆写run()方法。如果不实现抽象方法,则该子类仍是一个抽象类

面向抽象编程

面向抽象编程的本质就是:

  • 上层代码只定义规范(例如:abstract class Person);

  • 不需要子类就可以实现业务逻辑(正常编译);

  • 具体的业务逻辑由不同的子类实现,调用者并不关心。

  • 抽象类本身被设计成只能用于被继承,因此,抽象类可以强迫子类实现其定义的抽象方法,否则编译会报错。因此,抽象方法实际上相当于定义了“规范”。

    1
    2
    3
    4
    5
    6
    Person s = new Student();
    Person t = new Teacher();

    // 不关心Person变量的具体子类型:
    s.run();
    t.run();

对其进行方法调用,并不关心Person类型变量的具体子类型

接口

  • interface,就是比抽象类还要抽象的纯抽象接口,因连字段都不能有。表示一个接口类型和一组方法签名,而编程接口泛指接口规范,如方法签名,数据格式,网络协议等。
  • 抽象方法本质上是定义接口规范:即规定高层类的接口,从而保证所有子类都有相同的接口实现
  • 一个抽象类没有字段,所有方法全部都是抽象方法,可以把该抽象类改写为接口:interface
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
interface Person {
void run();
String getName();
}
class Student implements Person {
private String name;

public Student(String name) {
this.name = name;
}

@Override
public void run() {
System.out.println(this.name + " run");
}

@Override
public String getName() {
return this.name;
}
}
  • Java中,一个类只能继承自另一个类,不能从多个类继承。一个类可以实现多个interface

  • class Student implements Person, Hello { // 实现了两个interface
        ...
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23

    - 方法签名由方法名称和一个参数列表(方法的参数的顺序和类型)组成

    | | abstract class | interface |
    | :--------- | :------------------- | :-------------------------- |
    | 继承 | 只能extends一个class | 可以implements多个interface |
    | 字段 | 可以定义实例字段 | 不能定义实例字段 |
    | 抽象方法 | 可以定义抽象方法 | 可以定义抽象方法 |
    | 非抽象方法 | 可以定义非抽象方法 | 可以定义default方法 |

    ## 接口继承

    - `interface`继承自`interface`使用`extends`,它相当于扩展了接口的方法。

    ```java
    interface Hello {
    void hello();
    }

    interface Person extends Hello {
    void run();
    String getName();
    }

继承关系

  • 公共逻辑适合放在abstract class中,具体逻辑放到各个子类,而接口层次代表抽象程度。

  • 使用的时候,实例化的对象永远只能是某个具体的子类,但总是通过接口去引用它,因为接口比抽象类更抽象

1
2
3
List list = new ArrayList(); // 用List接口引用具体子类的实例
Collection coll = list; // 向上转型为Collection接口
Iterable it = coll; // 向上转型为Iterable接口

default

实现类可以不必覆写default方法。default方法的目的是,当我们需要给接口新增一个方法时,会涉及到修改全部子类。如果新增的是default方法,那么子类就不必全部修改,只需要在需要覆写的地方去覆写新增方法。

静态字段和静态方法

静态字段

  • static修饰的字段,称为静态字段:static field
  • 实例字段在每个实例中都有自己的一个独立“空间”,但是静态字段只有一个共享“空间”,所有实例都会共享该字段。
  • 用类名来访问静态字段。可以把静态字段理解为描述class本身的字段(非实例字段)

静态方法

  • 调用实例方法必须通过一个实例变量,而调用静态方法则不需要实例变量,通过类名就可以调用。

  • 静态方法属于class而不属于实例,因此,静态方法内部,无法访问this变量,也无法访问实例字段,它只能访问静态字段。

  • 静态方法经常用于工具类。例如:

    • Arrays.sort()
    • Math.random()
  • 静态方法也经常用于辅助方法。Java程序的入口main()也是静态方法。

接口的静态字段

  • interface是一个纯抽象类,所以它不能定义实例字段。但是,interface是可以有静态字段的,并且静态字段必须为final类型。
  • 因为interface的字段只能是public static final类型,编译器会自动把该字段变为public static final类型。

  • 没有定义包名的class,它使用的是默认包,非常容易引起名字冲突。使用package来解决名字冲突。

    小明的Person类存放在包ming下面,因此,完整类名是ming.Person

    小红的Person类存放在包hong下面,因此,完整类名是hong.Person

    小军的Arrays类存放在包mr.jun下面,因此,完整类名是mr.jun.Arrays

1
2
3
4
5
6
7
8
9
package_sample
└─ src
├─ hong
│ └─ Person.java
│ ming
│ └─ Person.java
└─ mr
└─ jun
└─ Arrays.java

小军的Arrays.java文件:

1
2
3
4
package mr.jun; // 申明包名mr.jun

public class Arrays {
}
  • **包没有父子关系。java.util和java.util.zip是不同的包,两者没有任何继承关系。

    src目录下执行javac命令

1
javac -d ../bin ming/Person.java hong/Person.java mr/jun/Arrays.java
1
2
3
4
5
6
7
8
9
package_sample
└─ bin
├─ hong
│ └─ Person.class
│ ming
│ └─ Person.class
└─ mr
└─ jun
└─ Arrays.class

import

  • 小明的ming.Person类,如果要引用小军的mr.jun.Arrays类。
1
2
3
4
5
6
7
8
9
10
11
// Person.java
package ming;

// 导入完整类名:
import mr.jun.Arrays;

public class Person {
public void run() {
Arrays arrays = new Arrays();
}
}

Java编译器最终编译出的.class文件只使用完整类名,因此,在代码中,当编译器遇到一个class名称时:

  • 如果是完整类名,就直接根据完整类名查找这个class
  • 如果是简单类名,按下面的顺序依次查找:
    • 查找当前package是否存在这个class
    • 查找import的包是否包含这个class
    • 查找java.lang包是否包含这个class
    • 无法确定类名,编译报错

编写class的时候,编译器会自动帮我们做两个import动作:

  • 默认自动import当前package的其他class
  • 默认自动import java.lang.*。但类似java.lang.reflect这些包仍需要手动导入。
  • 如果有两个class名称相同,例如,mr.jun.Arraysjava.util.Arrays,那么只能import其中一个,另一个必须写完整类名

工程实践包管理

为了避免名字冲突,我们需要确定唯一的包名。推荐的做法是使用倒置的域名来确保唯一性。例如:

  • org.apache
  • org.apache.commons.log
  • com.liaoxuefeng.sample

子包就可以根据功能自行命名。

要注意不要和java.lang包的类重名,即自己的类不要使用这些名字:

  • String
  • System
  • Runtime

要注意也不要和JDK常用类重名:

  • java.util.List
  • java.text.Format
  • java.math.BigInteger

作用域

  • publicprotectedprivate修饰符。这些修饰符可以用来限定访问作用域。

public

  • 定义为publicclassinterface可以被其他任何类访问,不在一个包的类也可以。

  • 定义为publicfieldmethod可以被其他类访问,前提是首先有访问class的权限

private

  • 定义为privatefieldmethod无法被其他类访问
  • private访问权限被限定在class的内部,而且与方法声明顺序无关。推荐把private方法放到后面,因为public方法定义了类对外提供的功能,阅读代码的时候,应该先关注public方法
  • 定义在一个class内部的class称为嵌套类(nested class),嵌套类拥有访问private的权限。

protected

  • protected作用于继承关系。定义为protected的字段和方法可以被子类访问,以及子类的子类

package

  • 一个类允许访问同一个package的没有publicprivate修饰的class,以及没有publicprotectedprivate修饰的字段和方法。

实践

  • 如果不确定是否需要public,就不声明为public,即尽可能少地暴露对外的字段和方法。

  • 把方法定义为package权限有助于测试,因为测试类和被测试类只要位于同一个package,测试代码就可以访问被测试类的package权限方法。

  • 一个.java文件只能包含一个public类,但可以包含多个非public类。如果有public类,文件名必须和public类的名字相同。

内部类

  • 通常情况下,我们把不同的类组织在不同的包下面,对于一个包下面的类来说,它们是在同一层次,没有父子关系
  • 还有一种类,它被定义在另一个类的内部,所以称为内部类(Nested Class)

Inner Class

1
2
3
4
5
class Outer {
class Inner {
// 定义了一个Inner Class
}
}
  • Outer是一个普通类,而Inner是一个Inner Class,它与普通类有个最大的不同,就是Inner Class的实例不能单独存在,必须依附于一个Outer Class的实例。
1
Outer.Inner inner = outer.new Inner();
  • 因为Inner Class的作用域在Outer Class内部,所以能访问Outer Class的private字段和方法。

  • 编译后的.class文件可以发现,Outer类被编译为Outer.class,而Inner类被编译为Outer$Inner.class

Anonymous Class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Outer {
private String name;
Outer(String name) {
this.name = name;
}

void asyncHello() {
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("Hello, " + Outer.this.name);
}
};
new Thread(r).start();
}
}
  • Runnable本身是接口,接口是不能实例化的,所以这里实际上是定义了一个实现了Runnable接口的匿名类,并且通过new实例化该匿名类,然后转型为Runnable。在定义匿名类的时候就必须实例化它
  • 匿名类和Inner Class一样,可以访问Outer Class的private字段和方法。之所以我们要定义匿名类,是因为在这里我们通常不关心类名,比直接定义Inner Class可以少写很多代码。
  • 编译后的.class文件可以发现,Outer类被编译为Outer.class,而匿名类被编译为Outer$1.class。如果有多个匿名类,Java编译器会将每个匿名类依次命名为Outer$1Outer$2Outer$3
  • 除了接口外,匿名类也完全可以继承自普通类

Static Nested Class

  • Static Nested Class是独立类,但拥有Outer Class的private访问权限

学习路线

  1. 首先要学习Java SE,掌握Java语言本身、Java核心开发技术以及Java标准库的使用;
  2. 如果继续学习Java EE,那么Spring框架、数据库开发、分布式架构就是需要学习的;
  3. 如果要学习大数据开发,那么Hadoop、Spark、Flink这些大数据平台就是需要学习的,他们都基于Java或Scala开发;
  4. 如果想要学习移动开发,那么就深入Android平台,掌握Android App开发。

Java 简介

JDK

  • java:这个可执行程序其实就是JVM,运行Java程序,就是启动JVM,然后让JVM执行指定的编译后的代码;
  • javac:这是Java的编译器,它用于把Java源码文件(以.java后缀结尾)编译为Java字节码文件(以.class后缀结尾);
  • jar:用于把一组.class文件打包成一个.jar文件,便于发布;
  • javadoc:用于从Java源码中自动提取注释并生成文档;
  • jdb:Java调试器,用于开发阶段的运行调试。

Java是将代码编译成一种“字节码”,它类似于抽象的CPU指令,然后,针对不同平台编写虚拟机,不同平台的虚拟机负责加载字节码并执行,这样就实现了“一次编写,到处运行”的效果。

  • Java SE:Standard Edition

  • Java EE:Enterprise Edition(Java SE+大量的API和库)

  • Java ME:Micro Edition(嵌入式设备的“瘦身版”)

  • JDK:Java Development Kit(Java源码编译成Java字节码,提供编译器、调试器)

  • JRE:Java Runtime Environment(运行Java字节码的虚拟机)

  • JSR规范:Java Specification Request(标准化JVM的内存模型到Web程序接口)

  • JCP组织:Java Community Process(负责审核JSR组织)

Java 程序基础

java程序基本结构

  • 类名要求

    • 类名必须以英文字母开头,后接字母,数字和下划线的组合
    • 习惯以大写字母开头
  • 方法名要求

    • 命名和class一样,但是首字母小写

      类命名

      • Hello
      • NoteBook
      • VRPlayer
  • 注释

    • 特殊的多行注释需要写在类和方法的定义处,可以用于自动创建文档。
    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
      /**
    * 可以用来自动创建文档的注释
    *
    */

    ## 变量和数据类型

    - 引用:String 字符串
    - 内部存储一个“地址”,指向某个对象在内存中的位置
    - 常量:定义变量前加final修饰
    - var关键字:相当于C++ auto

    ## 浮点数

    整数运算除数0时报错,而浮点数运算在除数为`0`时,不会报错,但会返回几个特殊值:

    - `NaN`表示Not a Number
    - `Infinity`表示无穷大
    - `-Infinity`表示负无穷大

    ## 布尔运算

    - 短路运算
    - 与运算在确定第一个值为`false`后,不再继续计算,而是直接返回`false`。
    - 对于`||`运算,只要能确定第一个值为`true`,后续计算也不再进行,而是直接返回`true`。

    ## 字符串

    - 字符串可以用`"""..."""`表示多行字符串(Text Blocks)

    - 字符串不可变特性

    - 字符串的不可变是指字符串内容不可变。
    - 变的不是字符串,而是变量`s`的“指向”。
    1. 执行`String s = "hello";`时,JVM虚拟机先创建字符串`"hello"`,然后,把字符串变量`s`指向它
    2. 执行`s = "world";`时,JVM虚拟机先创建字符串`"world"`,然后,把字符串变量`s`指向它
    3. 原来的字符串`"hello"`还在,只是我们无法通过变量`s`访问它而已

    - 区分空值`null`和空字符串`""`,空字符串是一个有效的字符串对象,它不等于`null`。

    - ```java
    String s2; // 没有赋初值值,s2也是null

数组

  • 数组是引用类型,并且数组大小不可变

流程控制

输入输出

1
2
3
4
5
6
7
8
9
10
11
12
import java.util.Scanner;

public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in); // 创建Scanner对象
System.out.print("Input your name: "); // 打印提示
String name = scanner.nextLine(); // 读取一行输入并获取字符串
System.out.print("Input your age: "); // 打印提示
int age = scanner.nextInt(); // 读取一行输入并获取整数
System.out.printf("Hi, %s, you are %d\n", name, age); // 格式化输出
}
}
  • 创建Scanner对象并传入System.inSystem.out代表标准输出流,而System.in代表标准输入流。
  • 使用scanner.nextLine(),要读取用户输入的整数,使用scanner.nextInt()Scanner会自动转换数据类型,因此不必手动转换。

判断

if 判断

  • 判断值类型的变量是否相等,可以使用==运算符。

  • 判断引用类型的变量是否相等,==表示“引用是否相等”,即是否指向同一个对象。

  • 要判断引用类型的变量内容是否相等,必须使用equals()方法

1
2
3
4
5
6
7
8
public class Main {
public static void main(String[] args) {
String s1 = null;
if (s1.equals("hello")) {
System.out.println("hello");
}
}
}
  • 变量s1null,会报NullPointerException
    • 短路运算
    • if (“hello”.equals(s)) { … }

switch 判断

  • switch语句升级为更简洁的表达式语法,使用类似模式匹配(Pattern Matching)的方法,保证只有一种路径会被执行,并且不需要break语句
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Main {
public static void main(String[] args) {
String fruit = "apple";
switch (fruit) {
case "apple" -> System.out.println("Selected apple");
case "pear" -> System.out.println("Selected pear");
case "mango" -> {
System.out.println("Selected mango");
System.out.println("Good choice!");
}
default -> System.out.println("No fruit selected");
}
}
}
  • 数组操作

数组遍历

1
2
3
4
5
6
7
8
public class Main {
public static void main(String[] args) {
int[] ns = { 1, 4, 9, 16, 25 };
for (int n : ns) {
System.out.println(n);
}
}
}
  • 缺点:无法拿到数组索引

  • 使用Arrays.toString()可以快速获取数组内容。

数组排序

  • 使用Java标准库提供的Arrays.sort()进行排序

命令行参数

Java程序的入口是main方法,而main方法可以接受一个命令行参数,它是一个String[]数组。

1
2
3
4
5
6
7
8
9
10
public class Main {
public static void main(String[] args) {
for (String arg : args) {
if ("-version".equals(arg)) {
System.out.println("v 1.0");
break;
}
}
}
}

程序必须在命令行执行,先编译

1
$ javac Main.java

执行

1
2
$ java Main -version
v 1.0

编码算法

  • ASCII编码 2字节编码
  • UNICODE编码,需要3个字节的编码
  • UTF-8由UNICODE转换过来,为了节约空间,不定长编码
  • URL编码
    • URL编码是浏览器发送数据给服务器时使用的编码,它通常附加在URL的参数部分。https://www.baidu.com/s?wd=%E4%B8%AD%E6%96%87
    • 编码规则(很多服务器只识别ASCII)
      • 如果字符是AZ,az,0~9以及-、_、.、*,则保持不变;
      • 如果是其他字符,先转换为UTF-8编码,然后对每个字节以%XX表示。
  • Base64编码
    • 把任意长度的二进制数据变为纯文本,编码后数据量会增加1/3

哈希算法

哈希算法(Hash)又称摘要算法(Digest),它的作用是:对任意一组输入数据进行计算,得到一个固定长度的输出摘要。

  • 相同的输入一定得到相同的输出;
  • 不同的输入大概率得到不同的输出。
  • 作用:验证原始数据是否被篡改(下载软件,存储用户口令)
  • 好的哈希算法碰撞概率低,无法猜测输出
  • 常见 MD5, SHA-1, SHA-256, SHA-512

数据库不存储用户口令明文,经过SHA加密。用户输入口令后,经过SHA与存储的SHA对比。尽量不要输入过于简单的密码,或者给用户口令加盐后哈希,digest = hash(salt + input)避免彩虹表反查。

Hmac算法

salt可以看作是一个额外的“认证码”,同样的输入,不同的认证码,会产生不同的输出。因此,要验证输出的哈希,必须同时提供“认证码”
Hmac算法总是和某种哈希算法配合起来用的。例如,我们使用MD5算法,对应的就是HmacMD5算法,它相当于“加盐”的MD5
java HmacMD5 ≈ md5(secure_random_key, input)

HmacMD5随机生成key长度是64字节,更安全;
key混入哈希的算法。验证此哈希时,除了原始的输入数据,还要提供key。

对称加密算法

对称加密算法用一个密码进行加密和解密,又叫私钥加密算法。
java secret = encrypt(key, message);
而解密则相反,它接收密码和密文,然后输出明文。:
java plain = decrypt(key, secret);
key的长度是固定的。常见生成key并加密算法AES长度有128/192/256
加密 123 =》AES( 123 + key ) =》@#$
解密 @#$ =》AES( key + @#$ ) =》123

口令加密算法

WinZIP和WinRAR对压缩包的加密和解密,就是使用对称加密算法但是我们解压时发现输入的密码不是固定的,源文件相当于密文,我们输入的密码相当于key,

根据上一节对称算法 解密 @#$ =》AES(key + @#$) =》123 但是我们此时发现输入的key长度并不是固定的。

实际上key是通过用户密码加一个安全随机的口令生成的,也就是PBE算法Password Based Encryption;

key=PBE(用户输入+随机口令)
加密过程就变为 原始数据 123(例如被压缩文件) =》AES( 123 + PBE(用户输入+随机口令) ) =》@#$

解密过程就变为 密文 @#$(例如待解压文件) =》AES( PBE(用户输入+随机口令) + @#$ ) =》123

但是我们发现解密时我们只输入了用户密码,这里可以理解为压缩文件本身自带随机口令,银行u盾

用户输入密码+U盾(随机口令)==key。

密钥交换算法

在不安全的信道上传递加密文件是没有问题的,因为黑客拿到加密文件没有用。但是,传递密钥成为了问题,对方只有拿到了密钥,才能解开加密文件

DH(Diffie-Hellman)算法解决了密钥在双方不直接传递密钥的情况下完成密钥交换,这个神奇的交换原理完全由数学理论支持。

  1. 甲首选选择一个素数p,例如97,底数g是p的一个原根,例如5,随机数a,例如123,然后计算A=g^a mod p,结果是34,然后,甲发送p=97,g=5,A=34给乙;
  2. 乙方收到后,也选择一个随机数b,例如,456,然后计算B = g^b mod p,结果是75,乙再同时计算s = A^b mod p,结果是22;
  3. 乙把计算的B=75发给甲,甲计算s = B^a mod p,计算结果与乙算出的结果一样,都是22。

所以最终双方协商出的密钥s是22。注意到这个密钥s并没有在网络上传输。而通过网络传输的p,g,A和B是无法推算出s的,由于实际算法选择的素数是非常大,且通过模除计算,会有非常多种可能。

a看成甲的私钥,A看成甲的公钥,b看成乙的私钥,B看成乙的公钥,DH算法的本质就是双方各自生成自己的私钥和公钥,私钥仅对自己可见,然后交换公钥,并根据自己的私钥和对方的公钥,生成最终的密钥secretKey,DH算法通过数学定律保证了双方各自计算出的secretKey是相同的。

DH算法并未解决中间人攻击,消除中间人攻击需要其他方法。
假设甲乙在交换公钥时被丙截获,丙把自己的公钥分别发送给甲乙两人,就变成了,甲丙通信,丙乙通信。甲发消息的话相当于甲生成A, p, g给丙,丙直接充当乙,自己生成一套b,B,若收到乙的直接丢弃。同样乙发消息。

非对称加密算法

  • 公钥-私钥对解密

    非对称加密的典型算法就是RSA算法
    非对称加密就是加密和解密使用的不是相同的密钥:只有同一个公钥-私钥对才能正常加解密。
    对称加密需要协商密钥,而非对称加密可以安全地公开各自的公钥,在N个人之间通信的时候:使用非对称加密只需要N个密钥对,每个人只管理自己的密钥对。而使用对称加密需要则需要N*(N-1)/2个密钥,因此每个人需要管理N-1个密钥,密钥管理难度大,而且非常容易泄漏。非对称加密的缺点是运算速度非常慢,比对称加密要慢很多。

非对称加密总是和对称加密一起使用。假设小明需要给小红需要传输加密文件,他俩首先交换了各自的公钥,然后:

  1. 小明生成一个随机的AES口令,然后用小红的公钥通过RSA加密这个口令,并发给小红;
  2. 小红用自己的RSA私钥解密得到AES口令;
  3. 双方使用这个共享的AES口令,用AES加密通信。

非对称加密在第一步,加密“AES口令”。这也是我们在浏览器中常用的HTTPS协议的做法,即浏览器和服务器先通过RSA交换AES口令,接下来双方通信实际上采用的是速度较快的AES对称加密,而不是缓慢的RSA非对称加密。

既然是加密,那肯定是不希望别人知道我的消息,所以只有我才能解密,所以可得出公钥负责加密,私钥负责解密;同理,既然是签名,那肯定是不希望有人冒充我发消息,只有我才能发布这个签名,所以可得出私钥负责签名,公钥负责验证。

数字签名

私钥加密得到的密文实际上就是数字签名,要验证这个签名是否正确,只能用私钥持有者的公钥进行解密验证。使用数字签名的目的是为了确认某个信息确实是由某个发送方发送的,任何人都不可能伪造消息,并且,发送方也不能抵赖。

  • 签名针对原始消息的哈希进行签名(私钥并没有泄漏)
  • 用户总是使用自己的私钥进行签名,所以,私钥就相当于用户身份。而公钥用来给外部验证用户身份。
    常用数字签名算法有:
    MD5withRSA
    SHA1withRSA
    SHA256withRSA

数字证书

  • 摘要算法用来确保数据没有被篡改,非对称加密算法可以对数据进行加解密,签名算法可以确保数据完整性和抗否认性,把这些算法集合到一起,并搞一套完善的标准,这就是数字证书。
  • 数字证书就是集合了多种密码学算法,用于实现数据加解密、身份认证、签名等多种功能的一种安全标准。
  • 数字证书可以防止中间人攻击,因为它采用链式签名认证,即通过根证书(Root CA)去签名下一级证书,这样层层签名,直到最终的用户证书。

题目

给你一个整数 x ,如果 x 是一个回文整数,返回 true ;否则,返回 false 。
回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。

样例

  • -2^31 <= x <= 2^31 - 1
    输入:x = 121
    输出:true
    输入:x = -121
    输出:false
    解释:从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。
    输入:x = 10
    输出:false
    解释:从右向左读, 为 01 。因此它不是一个回文数。

思路

无需将字符串转化为数组后进行比较,注意尾部为0的情况

  • 直接将数字后半部分进行反转,若为回文则与前半部分相等
  • 或者将整个数字反转,反转后溢出则false,不溢出且相等则true

代码

1
2
3
4
5
6
7
8
9
10
11
12
func isPalindrome(x int) bool {
if x<0 || (x%10==0 && x !=0 ){ // 要对尾部为0数做处理
return false
}
//反转一半
reverseX := 0
for x > reverseX{
reverseX = reverseX * 10 + x%10
x /= 10
}
return x==reverseX||x==reverseX/10 //分奇偶
}

题目

数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。

阅读全文 »

题目

给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。
candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。 
对于给定的输入,保证和为 target 的不同组合数少于 150 个。

阅读全文 »

整数数组的一个 排列  就是将其所有成员以序列或线性顺序排列。

例如,arr = [1,2,3] ,以下这些都可以视作 arr 的排列:[1,2,3]、[1,3,2]、[3,1,2]、[2,3,1] 。
整数数组的 下一个排列 是指其整数的下一个字典序更大的排列。更正式地,如果数组的所有排列根据其字典顺序从小到大排列在一个容器中,那么数组的 下一个排列 就是在这个有序容器中排在它后面的那个排列。如果不存在下一个更大的排列,那么这个数组必须重排为字典序最小的排列(即,其元素按升序排列)。

阅读全文 »

题目

整数数组 nums 按升序排列,数组中的值 互不相同 。
在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行了 旋转,使数组变为 [nums[k], nums[k+1], …, nums[n-1], nums[0], nums[1], …, nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2,4,5,6,7] 在下标 3 处经旋转后可能变为 [4,5,6,7,0,1,2] 。
给你 旋转后 的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的下标,否则返回 -1 。

阅读全文 »

题目

给定一个 n × n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。
你必须在 原地 旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。

阅读全文 »

题目

给你一个链表数组,每个链表都已经按升序排列。
请你将所有链表合并到一个升序链表中,返回合并后的链表。

阅读全文 »