LeetCode 180, 68, 146

目录

180. 连续出现的数字

题目链接

180. 连续出现的数字

  • Logs的字段为idnum

要求

  • 找出所有 至少连续出现三次 的数字。
  • 返回的结果表中的数据可以按 任意顺序 排列。

知识点

  1. distinct:对某个字段进行去重。

思路

可以想到使用三个表来做多表查询,第一个表first查数字在三次出现中的第一次出现的id,第二个表second查第二次出现的id,第三个表third查数字在三次出现中的第三次出现的id,只要满足first.id = second.id - 1 and second.id = third.id - 1即可。

另外,因为是多表查询,所以要使用条件first.num = second.num and second.num = third.sum限制,防止产生无效数据。

最后,也是最关键的一点:如果一个num连续出现三次以上,则按照上面这种判断将会把这个num输出多次,故需要对num进行去重

代码

sql 复制代码
select
    distinct first.num ConsecutiveNums
from
    Logs first,
    Logs second,
    Logs third
where
    first.id = second.id - 1
and
    second.id = third.id - 1
and
    first.num = second.num
and
    second.num = third.num

68. 文本左右对齐

题目链接

68. 文本左右对齐

标签

数组 字符串 模拟

思路

有时候"困难"并不困难。就好比本题,只是解题的步骤多了点,而不是很难想。

题目中明确地说了要使用 贪心 的思想,即 通过局部最优解推出全局最优解 的思想,所以处理一行单词时不需要考虑上一行或下一行,只需要考虑如何让本行的单词数最多。这很简单,假设本行的单词之间只间隔一个空格,然后从文本中获取单词拼接即可。

不过不能直接按一个空格的间隔拼接这些单词,因为间隔一个空格只是假设,实际情况可能不满足(即可能出现 结果中这行的每个单词需要间隔一个以上空格 的情况),所以得先获取这行拼接的所有单词的长度总和,然后再计算每个单词之间的平均间隔,如果间隔不平均,则需要将这些不平均的空格分散到左边的每个单词后。

对于最后一行,需要特殊处理:使用一个空格将最后一行的每个单词拼接起来,然后用空格填满最后一行的剩余部分,最后将结果返回。

对于每行只有一个单词的情况,也需要特殊处理:将这个单词左对齐,即用空格填充这行的右部分。

废话不多说,请看代码中的注释。

代码

java 复制代码
class Solution {
    public List<String> fullJustify(String[] words, int maxWidth) {
        int n = words.length;
        List<String> res = new ArrayList<>();
        int right = 0; // right是每行已经拼接字符串的结尾单词的下标+1
        while (true) {
            int left = right; // left是每行起始单词的下标
            int sumLen = 0; // sumLen是这行的长度,不包括空格

            // 先计算出这行最多能放多少个单词
            // right - left 恰好是单词之间至少间隔的空格数,包含 已拼接的最后一个单词 与 待拼接单词 的空格
            // sumLen + words[right].length() + right - left 是这行已经填充的字符数
            // 如果right == n,说明单词已经放完了,就不需要再计算了
            while (right < n && sumLen + words[right].length() + right - left <= maxWidth) {
                sumLen += words[right++].length(); // 让sumLen加上这个单词的长度
            }

            // 如果right == n,拼接最后一行的字符串,将结果返回即可
            if (right == n) {
                StringBuilder builder = join(words, left, n, " "); // 用单个空格将这行单词拼接起来
                builder.append(blank(maxWidth - builder.length())); // 用空格填充最后一行的剩余部分
                res.add(builder.toString()); // 将最后一行字符串加入结果
                return res; // 返回结果
            }

            // 如果到这里了,说明不是最后一行
            int numWords = right - left; // right - left 可以表示这行拼接的单词数
            int numSpaces = maxWidth - sumLen; // numSpaces 是这行的空格数
            
            // 如果这行只有一个单词,那么直接将这个单词左对齐,看示例2的acknowledgment,它就是左对齐的
            if (numWords == 1) {
                StringBuilder builder = new StringBuilder(words[left]);
                builder.append(blank(numSpaces)); // 用空格填充这行的剩余部分
                res.add(builder.toString());
                continue;
            }

            // 如果到这里了,说明这行有不止一个单词
            // numWords - 1 是这行单词之间的间隔数
            int avgSpaces = numSpaces / (numWords - 1); // avgSpaces 是这行平均的空格数
            int extraSpaces = numSpaces % (numWords - 1); // extraSpaces 是无法平均的空格数

            // 题目中说了 左侧放置的空格数要多于右侧的空格数,所以把 不平均的空格 分散到 左边的每个单词后。即使不平均的空格数为0,也可以按照如下逻辑处理
            StringBuilder builder = new StringBuilder();
            builder.append(join(words, left, left + extraSpaces + 1, blank(avgSpaces + 1))); // 把不平均的空格分散到左边的每个单词后
            builder.append(blank(avgSpaces)); // 从这里开始空格就平均了,放 avgSpaces 个
            builder.append(join(words, left + extraSpaces + 1, right, blank(avgSpaces))); // 把平均的空格分散到右边的每个单词后
            res.add(builder.toString());
        }
    }
    // 构造n个空格的字符串
    private String blank(int n) {
        StringBuilder builder = new StringBuilder();
        while (n-- > 0) {
            builder.append(' ');
        }
        return builder.toString();
    }
    // 将 words中 索引在[left, right)这个左闭右开区间内的字符串 使用 interval 作为间隔 拼接起来
    private StringBuilder join(String[] words, int left, int right, String interval) {
        StringBuilder builder = new StringBuilder(words[left++]); // 先拼接一个字符串,再拼接间隔
        while (left < right) {
            builder.append(interval);
            builder.append(words[left++]);
        }
        return builder;
    }
}

146. LRU 缓存

题目链接

146. LRU 缓存

标签

设计 哈希表 链表 双向链表

思路

本题是一个设计题,要求设计一个数据结构,它具有这样的性质:

  • 如果插入操作导致关键字数量超过 capacity ,则应该 逐出 最久未使用的关键字
  • 函数 getput 必须以 O ( 1 ) O(1) O(1) 的平均时间复杂度运行。

保证逐出最久的关键字很简单,使用队列即可。

但还要保证这个关键字是最久未使用的就比较麻烦了,其实仍然可以使用队列,不过在每次使用关键字时得把它放到队列的尾部。

但是如何把它放到队列尾部呢?实际上,把一个队列中的一个节点放到尾部可以先删除这个节点,再将其加入队列。

问题来了,如何以 O ( 1 ) O(1) O(1) 的平均时间复杂度 查询 和 删除 队列中的某一个节点呢?可以使用Map来映射,key为关键字,value为这个关键字在队列中的节点,在创建节点时就建立关键字与节点的映射,然后在查询和删除时使用关键字查出节点,接着就可以进行操作了。

要想以 O ( 1 ) O(1) O(1) 的平均时间复杂度删除队列中的某一个节点,需要将队列中的节点设置为双向节点,即这个节点即有指向后一个节点的指针,也有指向前一个节点的指针。

另外,为了避免对删除头节点和尾节点的判断,可以使用带哨兵节点的双向循环链表,即链表的头部和尾部都指向一个哨兵节点,这样在删除的时候直接 让前一个节点的后指针指向后一个节点、让后一个节点的前指针指向前一个节点 即可,如果没有带哨兵节点,则在 删除节点是头节点或尾节点 时,还需要让链表的头节点或尾节点重新指向另一个节点。

代码更好理解一点,请看代码。

代码

java 复制代码
class LRUCache {

    private static class Node {
        int key;
        int value;
        Node prev;
        Node next;
        public Node() {}
        public Node(int key, int value) {
            this.key = key;
            this.value = value;
        }
    }

    private static class DoublyLinkedList {
        Node head;
        Node tail;
        public DoublyLinkedList() {
            head = tail = new Node(); // 哨兵节点
            head.next = tail; // 头哨兵节点的后指针指向尾哨兵节点
            tail.prev = head; // 尾哨兵节点的前指针指向头哨兵节点
        }
        // 将新节点添加到队列尾部
        public void addLast(Node newLast) {
            Node oldLast = tail.prev; // 获取原来的尾节点
            newLast.prev = oldLast; // 让新节点的前指针指向原来的尾节点
            newLast.next = tail; // 让新节点的后指针指向尾哨兵节点
            tail.prev = newLast; // 让尾哨兵节点的前指针指向新节点
            oldLast.next = newLast; // 让原来的尾节点的后指针指向新节点
        }
        // 将指定节点从队列中移除
        public void remove(Node node) {
            Node prev = node.prev; // 获取前一个节点
            Node next = node.next; // 获取后一个节点
            prev.next = next; // 让前一个节点的后指针指向后一个节点
            next.prev = prev; // 让后一个节点的前指针指向前一个节点
        }
        // 移除队列中的第一个节点
        public Node removeFirst() {
            Node first = head.next; // 获取头节点
            remove(first); // 将头节点从队列中移除
            return first; // 返回头节点
        }
    }

    private final HashMap<Integer, Node> map = new HashMap<>();
    private final DoublyLinkedList list = new DoublyLinkedList();
    private final int capacity; // 容量

    public LRUCache(int capacity) {
        this.capacity = capacity;
    }
    
    public int get(int key) {
        // 如果没有这个元素,则返回-1
        if (!map.containsKey(key)) {
            return -1;
        }

        Node node = update(key); // 将节点更新到队列尾部
        return node.value; // 然后返回节点的值
    }
    
    public void put(int key, int value) {
        if (map.containsKey(key)) { // 如果有这个关键字,做更新操作
            Node node = update(key); // 将节点更新到队列尾部
            node.value = value; // 然后更新节点的值
        } else { // 否则没有这个关键字,做新增操作
            Node node = new Node(key, value); // 构造新节点
            map.put(key, node); // 建立映射
            list.addLast(node); // 将新节点加入队列尾部
            if (map.size() > capacity) { // 如果数量超出限制,则移除队列中的头节点
                Node removed = list.removeFirst();
                map.remove(removed.key); // 删除这个关键字与节点的映射
            }
        }
    }
    
    // 先删除再添加到尾部,实现将节点更新到尾部的效果
    private Node update(int key) {
        Node node = map.get(key);
        list.remove(node);
        list.addLast(node);
        return node;
    }
}
相关推荐
Dola_Pan3 分钟前
C语言:数组转换指针的时机
c语言·开发语言·算法
ExiFengs3 分钟前
实际项目Java1.8流处理, Optional常见用法
java·开发语言·spring
paj1234567895 分钟前
JDK1.8新增特性
java·开发语言
繁依Fanyi16 分钟前
简易安卓句分器实现
java·服务器·开发语言·算法·eclipse
慧都小妮子27 分钟前
Spire.PDF for .NET【页面设置】演示:打开 PDF 时自动显示书签或缩略图
java·pdf·.net
m512731 分钟前
LinuxC语言
java·服务器·前端
烦躁的大鼻嘎32 分钟前
模拟算法实例讲解:从理论到实践的编程之旅
数据结构·c++·算法·leetcode
IU宝35 分钟前
C/C++内存管理
java·c语言·c++
瓜牛_gn36 分钟前
依赖注入注解
java·后端·spring
hakesashou37 分钟前
Python中常用的函数介绍
java·网络·python