leetcode

更多内容请关注:

Java快速开发学习

锁清秋

LRU

运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制 。
实现 LRUCache 类:

LRUCache(int capacity) 以正整数作为容量 capacity 初始化 LRU 缓存
int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。
void put(int key, int value) 如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字-值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。

进阶:你是否可以在 O(1) 时间复杂度内完成这两种操作?

class LRUCache {
    private Map<Integer, DoubleNode> map;
    private Integer capacity;

    public LRUCache(int capacity) {
      this.capacity = capacity;
      this.map = new HashMap<>(capacity);
      this.head = new DoubleNode(-1, -1);
      this.tail = new DoubleNode(-1, -1);
      head.next = tail;
      tail.pre = head;
    }

    public int get(int key) {
      if (!map.containsKey(key)) {
        return -1;
      }
      DoubleNode doubleNode = map.get(key);
      updateNode(doubleNode);
      return doubleNode.val;
    }

    public void put(int key, int value) {
      if (map.containsKey(key)) {
        DoubleNode doubleNode = map.remove(key);
        doubleNode.val = value;
        updateNode(doubleNode);
        map.put(key, doubleNode);
        return;
      }
      DoubleNode node = new DoubleNode(key, value);
      if (capacity <= map.size()) {
        map.remove(tail.pre.key);
        removeNode(tail.pre);
      }
      map.put(key, node);
      addNode(node);
    }

    private class DoubleNode {
      private DoubleNode pre, next;
      private Integer key;
      private Integer val;

      public DoubleNode(Integer key, Integer val) {
        this.key = key;
        this.val = val;
      }
    }

    private DoubleNode head, tail;

    private void addNode(DoubleNode node) {
      node.next = head.next;
      head.next.pre = node;
      node.pre = head;
      head.next = node;
    }

    private void removeNode(DoubleNode node) {
      node.pre.next = node.next;
      node.next.pre = node.pre;
    }

    private void updateNode(DoubleNode node) {
      removeNode(node);
      addNode(node);
    }
  }

和为K的子数组

前缀和思想 + map(添加时查看是否包含,将双重循环变为一重)

给定一个整数数组和一个整数 k,你需要找到该数组中和为 k 的连续的子数组的个数。

输入:nums = [1,1,1], k = 2,输出: 2 , [1,1] 与 [1,1] 为两种不同的情况。

class Solution {
  public int subarraySum(int[] nums, int k) {
    int count = 0;
    int sum = 0;
    // 前缀和 : 出现次数
    final HashMap<Integer, Integer> map = new HashMap<>();
    map.put(0, 1);
    for (int i = 0; i < nums.length; i++) {
      sum += nums[i];
      if (map.containsKey(sum - k)) count += map.get(sum - k);

      if (map.containsKey(sum)) map.put(sum, map.get(sum) + 1);
      else map.put(sum, 1);
    }
    return count;
  }
}

并查集

/** 并查集 */
private class UnionFind {
  private int[] parent;

  public UnionFind(int size) {
    parent = new int[size];
    for (int i = 0; i < parent.length; i++) parent[i] = i;
  }

  /**
   * 连接两个元素
   *
   * @param p index
   * @param q index
   */
  void union(int p, int q) {
    if (parent[p] == parent[q]) return;
    for (int i = 0; i < parent.length; i++)
      // 可先不改变p,在遍历完后再改变
      if (i != p && parent[i] == parent[p]) parent[i] = parent[q];
    parent[p] = parent[q];
  }

  /**
   * 判断 是否相连
   *
   * @param p index
   * @param q index
   * @return
   */
  Boolean isUnion(int p, int q) {
    return parent[p] == parent[q];
  }
}

回溯算法

优质博客:一文学会回溯算法解题技巧

回溯法可以理解为通过选择不同的岔路口寻找目的地,一个岔路口一个岔路口的去尝试找到目的地。如果走错了路,继续返回来找到岔路口的另一条路,直到找到目的地。

模板:

回溯算法

void dfs(已选解集合,每个阶段可选解) {
    if (已选解集合满足条件) {
        结果集.add(已选解集合);
        return;
    }

    // 遍历每个阶段的可选解集合
    for (可选解 in 每个阶段的可选解) {

        // 选择此阶段其中一个解,将其加入到已选解集合中
        已选解集合.add(可选解)

        // 进入下一个阶段
        dfs(已选解集合,下个阶段可选的空间解)

        // 「回溯」换个解再遍历
        已选解集合.remove(可选解)

    }
}

动态规划(DP)

动态规划题目类型

image-20210304142140940

一、 最值型动态规划:

动态规划组成部分:

1.确定状态

  • 最后一步(最优策略中使用的最后一 枚硬币ak )
  • 化成子问题(最少的硬币拼出更小的面值27-aK )

2.转移方程

  • f[X] = min{f[X-2]+1, f[X-5]+1, f[X-7]+1}

3.初始条件和边界情况

  • f[O] = 0,如果不能拼出Y , f[Y]=正无穷

4.计算顺序(倒着来)

  • f[0], f[1], f[2]

    例1

// Java:零钱兑换 : 322
public int coinChange(int[] coins, int amount) {
  if (amount == 0) return 0;
  /** 
   * f(n): 数量;n:金额 ;
   * f(n) = [f(n-X0),...,f(n-Xn)]min
   * */
  final int[] f = new int[amount + 1];
  f[0] = 0;
  for (int i = 1; i < f.length; i++) {
    f[i] = Integer.MAX_VALUE;
    for (int j = 0; j < coins.length; j++)
      /** 处理 金额比面值小得情况 和 f[i - coins[j]] == Integer.MAX_VALUE 时,+1 会溢出的问题 */
      if (i >= coins[j] && f[i - coins[j]] != Integer.MAX_VALUE)
        f[i] = Math.min(f[i], f[i - coins[j]] + 1);
  }
  return f[amount] == Integer.MAX_VALUE ? -1 : f[amount];
}

例2

class Solution {
  //    跳跃游戏 : 55
  public boolean canJump(int[] nums) {
    if (nums == null || nums.length == 0) {
      return true;
    }
    int l = nums.length;
    boolean[] f = new boolean[l]; // 是否能跳到 i
    f[0] = true;
    /*判断是否能从 j 跳到 i */
    for (int i = 1; i < nums.length; i++) {
      for (int j = 0; j < i; j++) {
        if (f[j] && j + nums[j] >= i) {
          f[i] = true;
          break;
        }
      }
    }
    return f[l - 1];
  }
}

总结:

image-20210304172333730

常见排序算法

image-20210306032223402


文章作者: 张文军
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 张文军 !
评论