0%

灵神题单

自底向上dfs: 后序遍历

对于当前节点root做什么,需要它的子树的信息。

  1. LC 1110, 删点成林
  2. LC 1123 公共祖先

先序遍历

  1. LC 1026, 节点与其祖先之间的最大差值. (也可以postorder)

构造二叉树

105:前序和中序构造二叉树。直接 recursion 调用原函数,传入 inorder 和 pre-order 的切片即可。

子集型回溯

在一次 dfs函数里面,如果 path 是 mutable 类型, 一定保证path进入函数和出函数时 状态一定不变。不管有多少个分支,每个分支都要考虑这个问题。

如果 path是immutable,如string,它作为参数传递给dfs,这种情况下根本没有在修改它的状态,而是每次新建一个string,传给子节点。因而不需要恢复状态。LC 257.

灵神题单

反转链表

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
def reverseBetween(self, head: Optional[ListNode], left: int, right: int) -> Optional[ListNode]:
    #因为答案的头节点可能会改,所以搞一个dummy
    dummy=ListNode(next=head)
    pre=dummy
    #移动pre, cur到1和2
    for _ in range(left-1):
        pre=pre.next
    cur=pre.next
    #记录一下此时pre
    p0=pre
    #开始反转2到4这一段, 其实就是分别把2到4的next改了
    for _ in range(right-left+1):
        nxt=cur.next
        cur.next=pre
        pre=cur
        cur=nxt
    #新的pre,cur分别是4,5
    #连接 2 和 5
    p0.next.next=cur
    #连接1和4
    p0.next=pre
    return dummy.next

反转链表关注 pre和cur在开始前和结束后的位置,在这一题中,旧pre的next为新的pre,旧cur的 next为新的cur. Alt text

BeanFactory Interface

1
2
3
4
5
6
@SpringBootApplication
public class DemoJavaSpringApplication {
    public static void main(String[] args) {
       ConfigurableApplicationContext context= SpringApplication.run(DemoJavaSpringApplication.class, args);
    }
}

可以发现ConfigurableApplicationContext接口继承了BeanFactory接口 Alt text 这个context里面包含了一个beanFactory变量。

Functional Interface

函数式接口的定义是,必须只含有一个abstract method,它可以拥有许多static或default method(包含实现), 但是 abstract method必须只有一个。

点赞设计

用Redis的 sorted set, key是当前 post的ID,set里面存点赞用户的 ID,score 为点赞的时间戳。

前端在看帖子详情的时候,后端不仅需要根据帖子id去数据库里拿内容,还要通过redis判断帖子是否被当前用户点过赞。在 Blog里面加一个liked字段,作为结果一并返回给前端。

Interface

接口是对行为的抽象。

  1. abstract method, Java 8以前接口只能有抽象方法
  2. default method,提供方法体,Java 8 以后有
  3. static method, 提供方法体,Java 8 以后有,可以通过Interface.method()调用
 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
public interface MyInterface {
    // 抽象方法
    void abstractMethod();

    // 默认方法
    default void defaultMethod() {
        System.out.println("这是接口的默认方法");
        privateHelperMethod();  // 调用私有辅助方法(注意:私有方法是Java 9的特性,这里仅为示例)
    }

    // 静态方法
    static void staticMethod() {
        System.out.println("这是接口的静态方法");
    }

    // 私有辅助方法(Java 9 特性,但为了完整性展示,包含在此)
    private void privateHelperMethod() {
        System.out.println("这是接口的私有辅助方法");
    }
}

public class MyClass implements MyInterface {
    @Override
    public void abstractMethod() {
        System.out.println("实现接口的抽象方法");
    }

    // 可以选择覆盖默认方法
    @Override
    public void defaultMethod() {
        System.out.println("覆盖接口的默认方法");
        MyInterface.super.defaultMethod();  // 调用接口的默认方法
    }
}

public class Main {
    public static void main(String[] args) {
        MyClass myClass = new MyClass();

        // 调用实现的抽象方法
        myClass.abstractMethod();

        // 调用默认方法
        myClass.defaultMethod();  // 注意这里调用的是覆盖后的版本

        // 调用接口的静态方法
        MyInterface.staticMethod();
    }
}

Abstract Class

抽象类通常作为基类,比如 Person,Animal。因为不同子类的某种方法的 body可能不一样,所以把这些方法作为abstract method放在抽象类中。

一致性

缓存更新策略

  1. 内存淘汰:Redis 内存不足时自动淘汰部分数据,下次请求时更新缓存
  2. 超时剔除:给缓存数据添加 TTL 时间,到期后自动删除缓存。下次请求时更新缓存。
  3. 主动更新:在修改数据库的同时更新缓存。

缓存数据库一致性

有请求并发到来时,不管是先更新缓存后更新数据库,还是先更新数据库后更新缓存,都可能出现缓存不一致问题。

全局ID生成器: Redis自增

使用数据库自增ID的缺点:

  1. 可能会暴露给用户一些信息:用户可能会根据此推断,一天之内销售出了多少张优惠券。
  2. 当优惠券太多时,会导致数据库ID过大。但如果为优惠券分表,又会出现许多优惠券共用同一个ID的情况。 Alt text
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
//符号位0+时间戳31bit+序列号32bit
public long nextId(String keyPrefix){
    //生成时间戳
    LocalDateTime now=LocalDateTime.now();
    long nowSecond=now.toEpochSecond(ZoneOffset.UTC);
    long timestamp=nowSecond-BEGIN_TIMESTAMP;
    //生成序列号. 每一天下的单使用同一个Key
    String date=now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));
    long count=  stringRedisTemplate.opsForValue().increment("icr:"+keyPrefix+":"+date);
    //拼接并返回
    return timestamp<<COUNT_BITS|count;
}

这里的 keyPrefix表示一种object,比如优惠券订单