Java&OOP

Java OOP

OOP

类型

接口:定义需要具有哪些属性

类:比较复杂的结构、存储同一个类型的多个变量

  • 封装起来不同的属性,都扔到一个桶里面
  • 规定:记录下来的属性有啥&能干啥
  • 实例化:变成一个东西
    • 构造函数里面可以进行初始化
    • 没有初始化的:基本类型设置为默认值,类的设置为null
    • 从模具到东西
  • object:对象
    • 内存一片区域
    • 分成几个部分,里面分别装着属性值(instance variables,实例变量,每个object都有自己的)
      • 加上static之后就是类变量
    • 通过.来得到他的实例变量引用,可以直接进行操作
      • 解引用,de-reference
  • 方法:
    • 构造函数(上面实例化)
    • accessor method:访问方法,只访问不修改 getter
    • update method:更新方法,进行修改setter

多态性

- 重载:overloading,参数不同,方法名相同,一个类的多态性
  - ![image-20200227093906231](Java OOP&数据结构.assets/image-20200227093906231.png)
- 重写:overriding,方法名参数都相同,父类子类
- ![img](Java OOP&数据结构.assets/overloading-vs-overriding.png)

引用类型

  • java通过引用操作自己的对象

  • new函数创建一个对象,把引用给了变量(引用变量),class是引用类型

    • 在heap堆上开一片地方放着

    • Heap内存用于存放由new创建的对象和数组。在中分配的内存,由java虚拟机自动垃圾回收器来管理。

  • Java默认new 对象则为强引用,如

    1
    >StringBuffer buffer = new StringBuffer();

    上面创建了一个StringBuffer对象,并将这个对象的强引用存到变量buffer中。如果一个对象通过一串强引用链接可到达,即使内存不足,也不会回收该对象。

    作者:挨踢小能手
    链接:https://www.jianshu.com/p/c54d1f00ca5f
    来源:简书
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

NULL

  • 不赋值默认为null,不同类型的null相等

  • 不能转换为null,不能设置null类型

  • 不能解引用

1定义

null 是所有引用类型的默认值。

2. 转换

null既不是对象也不是一种类型,它仅是一种特殊的值,你可以将其赋予任何引用类型,它还仅仅是一个特殊值,并不属于任何类型,用instanceof永远返回false。
不能将null赋给基本类型变量,例如int、double、float、boolean。如果将null赋值给包装类object,然后将object赋给各自的基本类型,编译器不会报,但是你将会在运行时期遇到空指针异常。
null可以被转化为任何引用类型,可以调用引用类型中的静态方法,但是不可以调用非静态方法,运行时会报错。

1
2
3
4
5
6
7
8
9
10
11
public class NullTest {
public static void main(String[] args) {
Object o = (Object) null;
//int i = null;
Integer i = (Integer) null;
String s = (String) null;

System.out.println("o: " + o + "i: " + i + "s: " + s); //o: nulli: nulls: null
System.out.println(o instanceof Object); //false
}
}

3. 运算

null==null返回true,被转换为同种类型的null,都返回true,不同类型直接编译报错.
用String转换后的null可以进行字符串运算,这是因为字符串进行连接的时候,编译器对null进行了特别的优化,其实就是例化StringBuilder,在调用append()方法时对null的一个特别处理,当为null时,转化为“null”,最后调用toString()返回一个String对象.
用八大基本类型转换后的null,不可以进行基本类型的运算,否则会出现编译或者运行错误.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class NullTest {
public static void main(String[] args) {
Object o = (Object) null;
Integer i = (Integer) null;
Integer j = (Integer) null;
String s = (String) null;
// System.out.println(Objects.equals(i, j));
// System.out.println(i.equals(s));
// System.out.println(null == null);
// i = i + 1; //运行时空指针

// System.out.println(2 == null);

System.out.println("o: " + o + "i: " + i + "s: " + s);
System.out.println(o instanceof Object);

}
}

4. 集合中的key

集合类 key value super 说明
HashTable 不能为null 不能为null Dictionary 线程安全
ConcurrentHashMap 不能为null 不能为null AbstractMap 线程局部安全
TreeMap 不能为null 可以为null AbstractMap 线程不安全
HashMap 可以为null 可以为null AbstractMap 线程不安全

hash表需要进行hash值运算,key不能为null好理解,如果map中value为null也好理解。
表中不好理解的是HashMap中key可以为null,看下面代码中对null有个特殊处理,索引位置为0。

img

image.png

表中第二个不好理解的点是ConcurrentHashMap中value不能为null的问题。

img

image.png

作者:扎Zn了老Fe
链接:https://www.jianshu.com/p/83fc81baf115
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

OOP:Object Oriented Programming

  • 基于object进行编程

  • 数据&行为

四大特征

  • 抽象,封装(Encapsulation),继承(inheritance),多态(polymorphism)

    • 三大特性就是封装继承多态
抽象
  • abstraction:抽象

    • 不用管内部怎么实现的,内部细节对于用户隐藏的

    • 只暴露必要的/相关的操作&数据

    • 不同层次:函数层次和类层次

    • ![image-20200227091723756](Java OOP&数据结构.assets/image-20200227091723756.png)

封装
  • 不要让别人单独看到,用private

    • 用setxxx和getxxx

    • 优点

      1.类内部可以自由修改
      2.可以对成员变量更准确的控制
      3.隐藏信息,保护数据
      4.降低耦合度

      耦合度:耦合性是编程中的一个判断代码模块构成质量的属性,不影响已有功能,但影响未来拓展,与之对应的是内聚性

      例如:卧室的窗户与墙壁,如果窗子是扣死在墙里的 ,那么修改窗子时,就必须修改墙,这就比较紧密了,但是如果你窗子是按照某种规格的 可以自由拆装的 那么修改的代价就小,耦合度也就低了
      在程序中我们要求 高内聚,低耦合!
      ————————————————
      版权声明:本文为CSDN博主「66Kevin」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
      原文链接:https://blog.csdn.net/weixin_44551646/article/details/93721343

多态
  • 重载:overloading,参数不同,方法名相同,一个类的多态性
  • 重写:overriding,方法名参数都相同,父类子类
    • 不想具有虚函数特性,就加上final
继承
  • 继承:
- >  继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。
  >
  >  ### 生活中的继承:
  >
  >  ![img](Java OOP&数据结构.assets/14B0951E-FC75-47A3-B611-4E1883887339.jpg)
  >
  >  兔子和羊属于食草动物类,狮子和豹属于食肉动物类。
  >
  >  食草动物和食肉动物又是属于动物类。
  >
  >  所以继承需要符合的关系是:is-a,父类更通用,子类更具体。
  >
  >  虽然食草动物和食肉动物都是属于动物,但是两者的属性和行为上有差别,所以子类会具有父类的一般特性也会具有自身的特性。
  >
  >  ### 类的继承格式
  >
  >  在 Java 中通过 extends 关键字可以申明一个类是从另外一个类继承而来的,一般形式如下:
  >
  >  ## 类的继承格式
  >
  >  
1
2
3
4
5
class 父类 {
}

class 子类 extends 父类 {
}
> > ### 为什么需要继承 > > 接下来我们通过实例来说明这个需求。 > > 开发动物类,其中动物分别为企鹅以及老鼠,要求如下: > > - 企鹅:属性(姓名,id),方法(吃,睡,自我介绍) > - 老鼠:属性(姓名,id),方法(吃,睡,自我介绍) - 不支持多继承,但能支持多重继承: - ![img](Java OOP&数据结构.assets/types_of_inheritance-1.png) - > 子类拥有父类非 private 的属性、方法。 > > 子类可以拥有自己的属性和方法,即子类可以对父类进行扩展。 > > 子类可以用自己的方式实现父类的方法。
Super、this

super 与 this 关键字

super关键字:我们可以通过super关键字来实现对父类成员的访问,用来引用当前对象的父类。

this关键字:指向自己的引用。

Final

Final

final 关键字声明类可以把类定义为不能继承的,即最终类;或者用于修饰方法,该方法不能被子类重写:

  • 声明类:

    1
    final class 类名 {//类体}
  • 声明方法:

    1
    修饰符(public/private/default/protected) final 返回值类型 方法名(){//方法体}
  • 子类也是父类,可以转换

    构造器

    子类是不继承父类的构造器(构造方法或者构造函数)的,它只是调用(隐式或显式)。如果父类的构造器带有参数,则必须在子类的构造器中显式地通过 super 关键字调用父类的构造器并配以适当的参数列表。

    如果父类构造器没有参数,则在子类的构造器中不需要使用 super 关键字调用父类构造器,系统会自动调用父类的无参构造器。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // SubClass 类继承
    class SubClass extends SuperClass{
    private int n;

    SubClass(){ // 自动调用父类的无参数构造器
    System.out.println("SubClass");
    }

    public SubClass(int n){
    super(300); // 调用父类中带有参数的构造器
    System.out.println("SubClass(int n):"+n);
    this.n = n;
    }
    }
接口
  • Interface关键字用来声明一个接口。下面是接口声明的一个简单例子。

    NameOfInterface.java 文件代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    /* 文件名 : NameOfInterface.java */
    import java.lang.*;
    //引入包

    public interface NameOfInterface
    {
    //任何类型 final, static 字段
    //抽象方法
    }

    接口有以下特性:

    • 接口是隐式抽象的,当声明一个接口的时候,不必使用abstract关键字。
    • 接口中每一个方法也是隐式抽象的,声明时同样不需要abstract关键字。
    • 接口中的方法都是公有的。
    • 可以用public class MammalInt implements Animal{来说明他要有这几个函数

    • 接口的继承

      一个接口能继承另一个接口,和类之间的继承方式比较相似。接口的继承使用extends关键字,子接口继承父接口的方法。

      下面的Sports接口被Hockey和Football接口继承:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      // 文件名: Sports.java
      public interface Sports
      {
      public void setHomeTeam(String name);
      public void setVisitingTeam(String name);
      }

      // 文件名: Football.java
      public interface Football extends Sports
      {
      public void homeTeamScored(int points);
      public void visitingTeamScored(int points);
      public void endOfQuarter(int quarter);
      }

      // 文件名: Hockey.java
      public interface Hockey extends Sports
      {
      public void homeGoalScored();
      public void visitingGoalScored();
      public void endOfPeriod(int period);
      public void overtimePeriod(int ot);
      }

      接口的多继承

      在Java中,类的多继承是不合法,但接口允许多继承。

      在接口的多继承中extends关键字只需要使用一次,在其后跟着继承接口。 如下所示:

      public interface Hockey extends Sports, Event

      以上的程序片段是合法定义的子接口,与类不同的是,接口允许多继承,而 Sports及 Event 可能定义或是继承相同的方法

抽象类
  • 使用abstract class来定义抽象类
    • 不能被实例化,只能继承
      • 抽象方法

        如果你想设计这样一个类,该类包含一个特别的成员方法,该方法的具体实现由它的子类确定,那么你可以在父类中声明该方法为抽象方法。

        Abstract 关键字同样可以用来声明抽象方法,抽象方法只包含一个方法名,而没有方法体。

        抽象方法没有定义,方法名后面直接跟一个分号,而不是花括号。

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        public abstract class Employee
        {
        private String name;
        private String address;
        private int number;

        public abstract double computePay();

        //其余代码
        }

        声明抽象方法会造成以下两个结果:

        • 如果一个类包含抽象方法,那么该类必须是抽象类。
        • 任何子类必须重写父类的抽象方法,或者声明自身为抽象类。

函数

  • 值传递:

    • 对于基本类型 num ,赋值运算符会直接改变变量的值,原来的值被覆盖掉。
      对于引用类型 str,赋值运算符会改变引用中所保存的地址,原来的地址被覆盖掉。但是原来的对象不会被改变(重要)。

    • 因为传递的是引用

    • 作者:Intopass
      链接:https://www.zhihu.com/question/31203609/answer/50992895
      来源:知乎
      著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • 三:调用方法时发生了什么?参数传递基本上就是赋值操作

    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
    第一个例子:基本类型
    void foo(int value) {
    value = 100;
    }
    foo(num); // num 没有被改变

    第二个例子:没有提供改变自身方法的引用类型
    void foo(String text) {
    text = "windows";
    }
    foo(str); // str 也没有被改变

    第三个例子:提供了改变自身方法的引用类型
    StringBuilder sb = new StringBuilder("iphone");
    void foo(StringBuilder builder) {
    builder.append("4");
    }
    foo(sb); // sb 被改变了,变成了"iphone4"。

    第四个例子:提供了改变自身方法的引用类型,但是不使用,而是使用赋值运算符。
    StringBuilder sb = new StringBuilder("iphone");
    void foo(StringBuilder builder) {
    builder = new StringBuilder("ipad");
    }
    foo(sb); // sb 没有被改变,还是 "iphone"。

final:

  • final关键字用法

    修饰类当用final去修饰一个类的时候,表示这个类不能被继承。注意:a. 被final修饰的类,final类中的成员变量可以根据自己的实际需要设计为fianl。b. final类中的成员方法都会被隐式的指定为final方法。说明:在自己设计一个类的时候,要想好这个类将来是否会被继承,如果可以被继承,则该类不能使用fianl修饰,在这里呢,一般来说工具类我们往往都会设计成为一个fianl类。在JDK中,被设计为final类的有String、System等。代码:

    ![img](Java OOP&数据结构.assets/u=2411423419,2520809892&fm=173&app=25&f=JPEG.jpeg)

    \2. 修饰方法

    被final修饰的方法不能被重写。

    注意:

    a. 一个类的private方法会隐式的被指定为final方法。

    b. 如果父类中有final修饰的方法,那么子类不能去重写。

    代码:

    ![img](Java OOP&数据结构.assets/u=876634331,3528497804&fm=173&app=25&f=JPEG.jpeg)

    3. 修饰成员变量

    注意:

    a. 必须要赋初始值,而且是只能初始化一次。

    代码:

    ![img](Java OOP&数据结构.assets/u=433607551,3063868730&fm=173&app=25&f=JPEG.jpeg)

    \4. 修饰成员变量

    注意:

    a. 必须初始化值。

    b. 被fianl修饰的成员变量赋值,有两种方式:1、直接赋值 2、全部在构造方法中赋初值。

    c. 如果修饰的成员变量是基本类型,则表示这个变量的值不能改变。

    d. 如果修饰的成员变量是一个引用类型,则是说这个引用的地址的值不能修改,但是这个引用所指向的对象里面的内容还是可以改变的。

    代码:

    ![img](Java OOP&数据结构.assets/u=682519727,1257936163&fm=173&app=25&f=JPEG.jpeg)

Static:

  • 我们可以一句话来概括:方便在没有创建对象的情况下来进行调用。

  • 2、static关键字修饰类

    java里面static一般用来修饰成员变量或函数。但有一种特殊用法是用static修饰内部类,普通类是不允许声明为静态的,只有内部类才可以。下面看看如何使用。

    ![img](Java OOP&数据结构.assets/8644ebf81a4c510f633a3493cb792028d52aa567.jpeg)

    如果没有用static修饰InterClass,则只能new 一个外部类实例。再通过外部实例创建内部类。

    3、static关键字修饰方法

    修饰方法的时候,其实跟类一样,可以直接通过类名来进行调用:

    ![img](Java OOP&数据结构.assets/4afbfbedab64034f7b86b4dc05e37c340b551d41.jpeg)

    4、static关键字修饰变量

    被static修饰的成员变量叫做静态变量,也叫做类变量,说明这个变量是属于这个类的,而不是属于是对象,没有被static修饰的成员变量叫做实例变量,说明这个变量是属于某个具体的对象的。

    我们同样可以使用上面的方式进行调用变量:

    ![img](Java OOP&数据结构.assets/d8f9d72a6059252d0a2aeb289fbb063e5bb5b987.jpeg)

    5、static关键字修饰代码块

    静态代码块在类第一次被载入时执行,在这里主要是想验证一下,类初始化的顺序。

    父类静态变量

    父类静态代码块

    子类静态变量

    子类静态代码块

    父类普通变量

    父类普通代码块

    父类构造函数

    子类普通变量

    子类普通代码块

    子类构造函数

    代码验证一下:

    首先我们定义一个父类

    ![img](Java OOP&数据结构.assets/faf2b2119313b07ed299fae9a6f7942696dd8cd4.jpeg)

    然后定义一个子类

    ![img](Java OOP&数据结构.assets/37d3d539b6003af36f87b1b99f0ac3591138b6cb.jpeg)

    看个结果

    ![img](Java OOP&数据结构.assets/d009b3de9c82d158c5bd9c3e2a2a1cddbc3e4223.jpeg)

    二、深入分析static关键字

    上面我们只是描述了一下static关键字的基本使用场景,下面主要解析一下static关键字的深层原理。要理解static为什么会有上面的特性,首先我们还需要从jvm内存说起。我们先给出一张java的内存结构图,然后通过案例描述一下static修饰的变量存放在哪?

    ![img](Java OOP&数据结构.assets/024f78f0f736afc33409f1471839eec1b74512b4.jpeg)

    从上图我们可以发现,静态变量存放在方法区中,并且是被所有线程所共享的。这里要说一下java堆,java堆存放的就是我们创建的一个个实例变量。

    堆区:

    1、存储的全部是对象,每个对象都包含一个与之对应的class的信息。(class的目的是得到操作指令)

    2、jvm只有一个堆区(heap)被所有线程共享,堆中不存放基本类型和对象引用,只存放对象本身

    栈区:

    1.每个线程包含一个栈区,栈中只保存基础数据类型的对象和自定义对象的引用(不是对象),对象都存放在堆区中

    2、每个栈中的数据(原始类型和对象引用)都是私有的,其他栈不能访问。

    3、栈分为3个部分:基本类型变量区、执行环境上下文、操作指令区(存放操作指令)。、

    方法区:

    1、又叫静态区,跟堆一样,被所有的线程共享。方法区包含所有的class和static变量。

    2、方法区中包含的都是在整个程序中永远唯一的元素,如class,static变量。

    下面通过一个案例说明一下,从内存的角度来看,static关键字为什么会有这样的特性。

    首先我们定义一个类

    ![img](Java OOP&数据结构.assets/359b033b5bb5c9ea023ba6fe7e19b3053bf3b38c.jpeg)

    接下来我们从内存的角度出发,看看

    ![img](Java OOP&数据结构.assets/f3d3572c11dfa9ec028d9199c8f0f206908fc147.jpeg)

    从上面可以看到,我们的方法在调用的时候,是从方法区调用的,但是堆内存不一样,堆内存中的成员变量lastname是随着对象的产生而产生。随着对象的消失而消失。静态变量是所有线程共享的,所以不会消失。这也就能解释上面static关键字的真正原因。

    下面对static关键字进行一个小结:

    (1)特点:

    1、static是一个修饰符,用于修饰成员。(成员变量,成员函数)static修饰的成员变量 称之为静态变量或类变量。

    2、static修饰的成员被所有的对象共享。

    3、static优先于对象存在,因为static的成员随着类的加载就已经存在。

    4、static修饰的成员多了一种调用方式,可以直接被类名所调用,(类名.静态成员)。

    5、static修饰的数据是共享数据,对象中的存储的是特有的数据。

    (2)成员变量和静态变量的区别:

    1、生命周期的不同:

    成员变量随着对象的创建而存在随着对象的回收而释放。

    静态变量随着类的加载而存在随着类的消失而消失。

    2、调用方式不同:

    成员变量只能被对象调用。

    静态变量可以被对象调用,也可以用类名调用。(推荐用类名调用)

    3、别名不同:

    成员变量也称为实例变量。

    静态变量称为类变量。

    4、数据存储位置不同:

    成员变量数据存储在堆内存的对象中,所以也叫对象的特有数据。

    静态变量数据存储在方法区(共享数据区)的静态区,所以也叫对象的共享数据。

    (3)静态使用时需要注意的事项:

    1、静态方法只能访问静态成员。(非静态既可以访问静态,又可以访问非静态)

    2、静态方法中不可以使用this或者super关键字。

    3、主函数是静态的

    好了,static关键字就介绍道这里,谢谢您的支持,如有问题,还请批评指正

  • static代码块也叫静态代码块,是在类中独立于类成员的static语句块,可以有多个,位置可以随便放,它不在任何的方法体内,JVM加载类时会执行这些静态的代码块,如果static代码块有多个,JVM将按照它们在类中出现的先后顺序依次执行它们,每个代码块只会被执行一次。例如:

    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
    public class Test5 {
    private static int a;
    private int b;

    static{
    Test5.a=3;
    System.out.println(a);
    Test5 t=new Test5();
    t.f();
    t.b=1000;
    System.out.println(t.b);
    }
    static{
    Test5.a=4;
    System.out.println(a);
    }
    public static void main(String[] args) {
    // TODO 自动生成方法存根
    }
    static{
    Test5.a=5;
    System.out.println(a);
    }
    public void f(){
    System.out.println("hhahhahah");
    }
    }

      运行结果:

    1
    2
    3
    4
    5
    3
    hhahhahah
    1000
    4
    5

      利用静态代码块可以对一些static变量进行赋值,最后再看一眼这些例子,都一个static的main方法,这样JVM在运行main方法的时候可以直接调用而不用创建实例。 

      4、static和final一块用表示什么
      static final用来修饰成员变量和成员方法,可简单理解为“全局常量”!
      对于变量,表示一旦给值就不可修改,并且通过类名可以访问。
      对于方法,表示不可覆盖,并且可以通过类名直接访问。

this:

  • 指代自己变量的,防止被覆盖
  • 可以调用对应的构造函数

每日leetcode-145

二叉树的后序遍历。

两个栈方式的

直接看http://www.bilibili.com/video/av23885544/

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
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
vector<int> ans;
if (!root) {
return ans;
}
stack<TreeNode*> nodes;
stack<bool> tag;
nodes.push(root);
tag.push(false); // 标记应不应该访问nodes的第一个元素
TreeNode* p = root->left;
while (p || !nodes.empty()) {
if (p) {
nodes.push(p);
tag.push(false);
p = p->left;
}
else {
if (tag.top()) {
// 这个时候已经访问了nodes这个stack里面第一个节点的左右子树了,所以需要访问nodes这个节点,之后就pop出来,继续访问他的上面没有访问的节点或者右子树
tag.pop();
auto a = nodes.top();
ans.push_back(a->val);
nodes.pop();
}
else {
tag.pop();
tag.push(true);
p = nodes.top()->right; // 这时候要访问右子树了!
}
}
}
return ans;
}
};

前序遍历改一下

前序遍历的时候,把左右子树进栈的顺序反一下,得到的结果就是后序遍历的反向。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
vector<int> ans;
if (!root) {
return ans;
}
stack<TreeNode*> nodes;
nodes.push(root);
while (!nodes.empty()) {
auto a = nodes.top();
nodes.pop();
if (a) {
ans.insert(ans.begin(), a->val);
nodes.push(a->left);
nodes.push(a->right);
}
}
return ans;
}
};

每日leetcode-140

  1. 单词拆分 II

给定一个非空字符串 s 和一个包含非空单词列表的字典 wordDict,在字符串中增加空格来构建一个句子,使得句子中所有的单词都在词典中。返回所有这些可能的句子。

说明:

  • 分隔时可以重复使用字典中的单词。
  • 你可以假设字典中没有重复的单词。

示例 1:

输入: s = “catsanddog
wordDict = ["cat", "cats", "and", "sand", "dog"]
输出: [   "cats and dog",   "cat sand dog" ]

示例 2:

输入: s = “pineapplepenapple”
wordDict = [“apple”, “pen”, “applepen”, “pine”, “pineapple”]
输出: [
  “pine apple pen apple”,
  “pineapple pen apple”,
  “pine applepen apple”
]
解释: 注意你可以重复使用字典中的单词。

示例 3:

输入: s = “catsandog”
wordDict = [“cats”, “dog”, “sand”, “and”, “cat”]
输出: []

简单递归写法

会超时:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
public:
vector<string> wordBreak(string s, vector<string>& wordDict) {
vector<string> ans;
for (auto i = wordDict.begin(); i != wordDict.end(); i++) {
if (s.rfind(*i, 0) == 0) {
// 找到了
if (s.size() == (*i).size()) {
ans.push_back(*i);
}
else {
auto m = wordBreak(s.substr((*i).size()), wordDict);
for (auto t = m.begin(); t != m.end(); t++) {
ans.push_back(*i + " " + *t);
}
}
}
}
return ans;
}
};

加上记录,不多算

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
class Solution {
public:
map<int, vector<string>> memo;

vector<string> wordBreakIndex(string& s, vector<string>& wordDict, int index) {
if (memo.find(index) != memo.end()) {
return memo[index];
}
vector<string> ans;
for (auto i = wordDict.begin(); i != wordDict.end(); i++) {
if (s.rfind(*i, index) == index) {
// 找到了
if (s.size() == index + (*i).size()) {
ans.push_back(*i);
}
else {
auto m = wordBreakIndex(s, wordDict, index + (*i).size());
for (auto t = m.begin(); t != m.end(); t++) {
ans.push_back(*i + " " + *t);
}
}
}
}
memo[index] = ans;
return ans;
}

vector<string> wordBreak(string s, vector<string>& wordDict) {
memo.clear();
return wordBreakIndex(s, wordDict, 0);
}
};

结果是:

1
2
执行用时 : 112 ms, 在所有 C++ 提交中击败了5.27%的用户
内存消耗 : 11.2 MB, 在所有 C++ 提交中击败了77.66%的用户

每日leetcode-337

  1. 打家劫舍 III

在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为“根”。 除了“根”之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。

计算在不触动警报的情况下,小偷一晚能够盗取的最高金额。

示例 1:

输入: [3,2,3,null,3,null,1]

1
2
3
4
5
  3
/ \
2 3
\ \
3 1

输出: 7
解释: 小偷一晚能够盗取的最高金额 = 3 + 3 + 1 = 7.

示例 2:

输入: [3,4,5,1,3,null,1]

1
2
3
4
5
     3
/ \
4 5
/ \ \
1 3 1

输出: 9
解释: 小偷一晚能够盗取的最高金额 = 4 + 5 = 9.

解答

使用后序遍历非递归,分别记录每一个节点的抢劫能抢到的钱,以及不抢它能抢到的钱,总之还是动态规划。

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
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
int rob(TreeNode* root) {
if (!root) {
return 0;
}
unordered_map<TreeNode*, int> data; // 抢了这个节点的话,可以有多少钱
unordered_map<TreeNode*, int> pre; // 不抢这个节点的话,可以有多少钱
// 后序遍历,先把孩子整完了,在整爹,具体看http://www.bilibili.com/video/av23885544/
// 每一次后序遍历的时候,先把指针p指向左子树,再对根节点做个标记后指向右子树
TreeNode* p = root;
stack<TreeNode*> tree;
stack<bool> check;
while (p || !tree.empty()) {
if (p) {
tree.push(p);
check.push(false);
p = p->left;
}
else {
bool need_pop = check.top();
if (need_pop) {
TreeNode* check_node = tree.top();
check.pop();
tree.pop();
int check_val = check_node->val;;
int pre_val = 0;
if (check_node->left) {
check_val += pre[check_node->left];
pre_val += data[check_node->left];
}
if (check_node->right) {
check_val += pre[check_node->right];
pre_val += data[check_node->right];
}
data[check_node] = max(check_val, pre_val);
pre[check_node] = pre_val;
}
else {
check.pop();
check.push(true);
p = tree.top()->right;
}
}
}
return data[root];
}
};

http://www.bilibili.com/video/av23885544/ 这里的后序遍历非递归的说明挺清楚的

后序遍历递归版本

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
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
int rob(TreeNode* root) {
if (!root) {
return 0;
}
unordered_map<TreeNode*, int> data; // 抢了这个节点的话,可以有多少钱
unordered_map<TreeNode*, int> pre; // 不抢这个节点的话,可以有多少钱
// 这次直接用递归方式的后序遍历
search(root, data, pre);
return data[root];
}

void search(TreeNode* check_node, unordered_map<TreeNode*, int>& data, unordered_map<TreeNode*, int>& pre) {
int check_val = check_node->val;;
int pre_val = 0;
if (check_node->left) {
search(check_node->left, data, pre);
check_val += pre[check_node->left];
pre_val += data[check_node->left];
}
if (check_node->right) {
search(check_node->right, data, pre);
check_val += pre[check_node->right];
pre_val += data[check_node->right];
}
data[check_node] = max(check_val, pre_val);
pre[check_node] = pre_val;
}
};

其实比上面的要慢,主要是因为unordered_map的使用,其实并不需要用这个map,每一次返回两个数就行

优化

换成返回两个数

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
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
int rob(TreeNode* root) {
if (!root) {
return 0;
}
// 这次直接用递归方式的后序遍历
auto ans = search(root);
return ans.first;
}

pair<int, int> search(TreeNode* check_node) {
if (!check_node) {
return {0, 0};
}
auto left = search(check_node->left);
auto right = search(check_node->right);
int check_val = check_node->val + left.second + right.second;
int pre_val = left.first + right.first;
return {max(check_val, pre_val), pre_val};
}
};

Notability

打开就开始从网上下载一直转圈:iPad存储内存不够了(

mac上面找到自己的文件:直接搜索笔记名,加上.nbn后缀,搜到后打开上层文件夹,这个是在icloud里面的

元数据错误:直接找历史版本,如果找不到的话多试几次就有了……不懂原理