diff --git a/second/week_06/83/leecode.md b/second/week_06/83/leecode.md new file mode 100644 index 0000000000000000000000000000000000000000..54148e3f14798d10e9768d8320ad31e9164b1648 --- /dev/null +++ b/second/week_06/83/leecode.md @@ -0,0 +1,544 @@ +# 706 设计哈希映射 +不使用任何内建的哈希表库设计一个哈希映射 +具体地说,你的设计应该包含以下的功能 +put(key, value):向哈希映射中插入(键,值)的数值对。如果键对应的值已经存在,更新这个值。 +get(key):返回给定的键所对应的值,如果映射中不包含这个键,返回-1。 +remove(key):如果映射中存在这个键,删除这个数值对。 + +示例: +MyHashMap hashMap = new MyHashMap(); +hashMap.put(1, 1);           +hashMap.put(2, 2);         +hashMap.get(1);            // 返回 1 +hashMap.get(3);            // 返回 -1 (未找到) +hashMap.put(2, 1);         // 更新已有的值 +hashMap.get(2);            // 返回 1 +hashMap.remove(2);         // 删除键为2的数据 +hashMap.get(2);            // 返回 -1 (未找到) + +注意: +所有的值都在 [0, 1000000]的范围内。 +操作的总数目在[1, 10000]范围内。 +不要使用内建的哈希库。 + +```java +class MyHashMap { + private final int N = 1000;//静态数组长度1000 + private Node[] data; + /** Initialize your data structure here. */ + public MyHashMap() { + data = new Node[N]; + } + + /** value will always be non-negative. */ + public void put(int key, int value) { + int hash = hash(key); + if (data[hash] == null) {//该hash地址没有链表节点 + data[hash] = new Node(-1, -1);//先存虚拟头 + data[hash].next = new Node(key, value);//再存实际头节点 + } else { + Node prev = data[hash];//从虚拟头开始遍历 + while (prev.next != null) { + if (prev.next.key == key) { + prev.next.value = value;//有键,更新值 + return; + } + prev = prev.next; + } + prev.next = new Node(key, value);//没有键,添加节点 + } + + } + + /** Returns the value to which the specified key is mapped, or -1 if this map contains no mapping for the key */ + public int get(int key) { + int hash = hash(key); + if (data[hash] != null) { + Node cur = data[hash].next;//从实际头节点开始寻找 + while (cur != null) { + if (cur.key == key) { + return cur.value;//找到 + } + cur = cur.next; + } + } + return -1;//没有找到 + } + + /** Removes the mapping of the specified value key if this map contains a mapping for the key */ + public void remove(int key) { + int hash = hash(key); + if (data[hash] != null) { + Node prev = data[hash]; + while (prev.next != null) { + //删除节点 + if (prev.next.key == key) { + Node delNode = prev.next; + prev.next = delNode.next; + delNode.next = null; + return; + } + prev = prev.next; + } + } + + } + private int hash(int key) { + return key % N; + } + private class Node { + int key;//key唯一 + int value; + Node next; + + Node(int key, int value) { + this.key = key; + this.value = value; + } + } + +} +``` + +## 705 hashset +不使用任何内建的哈希表库设计一个哈希集合 +具体地说,你的设计应该包含以下的功能 +add(value):向哈希集合中插入一个值。 +contains(value) :返回哈希集合中是否存在这个值。 +remove(value):将给定值从哈希集合中删除。如果哈希集合中没有这个值,什么也不做。 + +```java +class MyHashSet { + private static final int capacity = 4;//数组默认大小 + private int m;//数组大小,即散列表大小 + private int n;//散列表中key的数目 + private Integer[] keys;//数组 + + /** Initialize your data structure here. */ + public MyHashSet() { + this(capacity); + } + + //指定散列表大小的构造函数 + public MyHashSet(int cap) { + m = cap; + n = 0; + keys = new Integer[m]; + } + + public void add(int key) { + MyHashSet temp = new MyHashSet(cap); + for(int i = 0; i < m; i++) + if(keys[i] != null) + temp.add(keys[i]); + keys = temp.keys; + m = temp.m; + + } + + public void remove(int key) { + if(!contains(key)) return; + int i = hash(key); + while(keys[i] != key) + { + i = (i+1) % m; + } + keys[i] = null; + n--; + i = (i+1) % m; + while(keys[i] != null) + { + int temp = keys[i]; + keys[i] = null; + n--; + add(temp); + i = (i+1) % m; + } + if(n > 0 && n <= m/8) resize(m / 2); + + } + + /** Returns true if this set contains the specified element */ + public boolean contains(int key) { + for(int i = hash(key); keys[i] != null; i = (i+1) % m) + { + if(keys[i] == key) return true; + } + return false; + + } +} +``` +### 设计你的循环队列实现。 +循环队列是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环。它也被称为“环形缓冲器”。 + +循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里,一旦一个队列满了,我们就不能插入下一个元素,即使在队列前面仍有空间。但是使用循环队列,我们能使用这些空间去存储新的值。 + +你的实现应该支持如下操作: + +MyCircularQueue(k): 构造器,设置队列长度为 k 。 +Front: 从队首获取元素。如果队列为空,返回 -1 。 +Rear: 获取队尾元素。如果队列为空,返回 -1 。 +enQueue(value): 向循环队列插入一个元素。如果成功插入则返回真。 +deQueue(): 从循环队列中删除一个元素。如果成功删除则返回真。 +isEmpty(): 检查循环队列是否为空。 +isFull(): 检查循环队列是否已满。 +```java +class MyCircularQueue { + private int[] queue; + private int headIndex; + private int count; + private int capacity; + + /** Initialize your data structure here. Set the size of the queue to be k. */ + public MyCircularQueue(int k) { + this.capacity = k; + this.queue = new int[k]; + this.headIndex = 0; + this.count = 0; + } + + /** Insert an element into the circular queue. Return true if the operation is successful. */ + public boolean enQueue(int value) { + if (this.count == this.capacity) + return false; + this.queue[(this.headIndex + this.count) % this.capacity] = value; + this.count += 1; + return true; + } + + /** Delete an element from the circular queue. Return true if the operation is successful. */ + public boolean deQueue() { + if (this.count == 0) + return false; + this.headIndex = (this.headIndex + 1) % this.capacity; + this.count -= 1; + return true; + } + + /** Get the front item from the queue. */ + public int Front() { + if (this.count == 0) + return -1; + return this.queue[this.headIndex]; + } + + /** Get the last item from the queue. */ + public int Rear() { + if (this.count == 0) + return -1; + int tailIndex = (this.headIndex + this.count - 1) % this.capacity; + return this.queue[tailIndex]; + } + + /** Checks whether the circular queue is empty or not. */ + public boolean isEmpty() { + return (this.count == 0); + } + + /** Checks whether the circular queue is full or not. */ + public boolean isFull() { + return (this.count == this.capacity); + } + +} +``` +#### 641.设计实现双端队列。 +你的实现需要支持以下操作: + +MyCircularDeque(k):构造函数,双端队列的大小为k。 +insertFront():将一个元素添加到双端队列头部。 如果操作成功返回 true。 +insertLast():将一个元素添加到双端队列尾部。如果操作成功返回 true。 +deleteFront():从双端队列头部删除一个元素。 如果操作成功返回 true。 +deleteLast():从双端队列尾部删除一个元素。如果操作成功返回 true。 +getFront():从双端队列头部获得一个元素。如果双端队列为空,返回 -1。 +getRear():获得双端队列的最后一个元素。 如果双端队列为空,返回 -1。 +isEmpty():检查双端队列是否为空。 +isFull():检查双端队列是否满了。 +```java +public class MyCircularDeque { + + // 1、不用设计成动态数组,使用静态数组即可 + // 2、设计 head 和 tail 指针变量 + // 3、head == tail 成立的时候表示队列为空 + // 4、tail + 1 == head + + private int capacity; + private int[] arr; + private int front; + private int rear; + + /** + * Initialize your data structure here. Set the size of the deque to be k. + */ + public MyCircularDeque(int k) { + capacity = k + 1; + arr = new int[capacity]; + + // 头部指向第 1 个存放元素的位置 + // 插入时,先减,再赋值 + // 删除时,索引 +1(注意取模) + front = 0; + // 尾部指向下一个插入元素的位置 + // 插入时,先赋值,再加 + // 删除时,索引 -1(注意取模) + rear = 0; + } + + /** + * Adds an item at the front of Deque. Return true if the operation is successful. + */ + public boolean insertFront(int value) { + if (isFull()) { + return false; + } + front = (front - 1 + capacity) % capacity; + arr[front] = value; + return true; + } + + /** + * Adds an item at the rear of Deque. Return true if the operation is successful. + */ + public boolean insertLast(int value) { + if (isFull()) { + return false; + } + arr[rear] = value; + rear = (rear + 1) % capacity; + return true; + } + + /** + * Deletes an item from the front of Deque. Return true if the operation is successful. + */ + public boolean deleteFront() { + if (isEmpty()) { + return false; + } + // front 被设计在数组的开头,所以是 +1 + front = (front + 1) % capacity; + return true; + } + + /** + * Deletes an item from the rear of Deque. Return true if the operation is successful. + */ + public boolean deleteLast() { + if (isEmpty()) { + return false; + } + // rear 被设计在数组的末尾,所以是 -1 + rear = (rear - 1 + capacity) % capacity; + return true; + } + + /** + * Get the front item from the deque. + */ + public int getFront() { + if (isEmpty()) { + return -1; + } + return arr[front]; + } + + /** + * Get the last item from the deque. + */ + public int getRear() { + if (isEmpty()) { + return -1; + } + // 当 rear 为 0 时防止数组越界 + return arr[(rear - 1 + capacity) % capacity]; + } + + /** + * Checks whether the circular deque is empty or not. + */ + public boolean isEmpty() { + return front == rear; + } + + /** + * Checks whether the circular deque is full or not. + */ + public boolean isFull() { + // 注意:这个设计是非常经典的做法 + return (rear + 1) % capacity == front; + } +} +``` +#### 707.设计链表 +设计链表的实现。您可以选择使用单链表或双链表。单链表中的节点应该具有两个属性:val 和 next。val 是当前节点的值,next 是指向下一个节点的指针/引用。如果要使用双向链表,则还需要一个属性 prev 以指示链表中的上一个节点。假设链表中的所有节点都是 0-index 的。 + +在链表类中实现这些功能: + +get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。 +addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。 +addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。 +addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val  的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。 +deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点。 +```java +public class ListNode { + int val; + ListNode next; + ListNode(int x) { val = x; } +} + +class MyLinkedList { + int size; + ListNode head; // sentinel node as pseudo-head + public MyLinkedList() { + size = 0; + head = new ListNode(0); + } + + /** Get the value of the index-th node in the linked list. If the index is invalid, return -1. */ + public int get(int index) { + // if index is invalid + if (index < 0 || index >= size) return -1; + + ListNode curr = head; + // index steps needed + // to move from sentinel node to wanted index + for(int i = 0; i < index + 1; ++i) curr = curr.next; + return curr.val; + } + + /** Add a node of value val before the first element of the linked list. After the insertion, the new node will be the first node of the linked list. */ + public void addAtHead(int val) { + addAtIndex(0, val); + } + + /** Append a node of value val to the last element of the linked list. */ + public void addAtTail(int val) { + addAtIndex(size, val); + } + + /** Add a node of value val before the index-th node in the linked list. If index equals to the length of linked list, the node will be appended to the end of linked list. If index is greater than the length, the node will not be inserted. */ + public void addAtIndex(int index, int val) { + // If index is greater than the length, + // the node will not be inserted. + if (index > size) return; + + // [so weird] If index is negative, + // the node will be inserted at the head of the list. + if (index < 0) index = 0; + + ++size; + // find predecessor of the node to be added + ListNode pred = head; + for(int i = 0; i < index; ++i) pred = pred.next; + + // node to be added + ListNode toAdd = new ListNode(val); + // insertion itself + toAdd.next = pred.next; + pred.next = toAdd; + } + + /** Delete the index-th node in the linked list, if the index is valid. */ + public void deleteAtIndex(int index) { + // if the index is invalid, do nothing + if (index < 0 || index >= size) return; + + size--; + // find predecessor of the node to be deleted + ListNode pred = head; + for(int i = 0; i < index; ++i) pred = pred.next; + + // delete pred.next + pred.next = pred.next.next; + } +} +``` +### 707.设计链表 +设计链表的实现。您可以选择使用单链表或双链表。单链表中的节点应该具有两个属性:val 和 next。val 是当前节点的值,next 是指向下一个节点的指针/引用。如果要使用双向链表,则还需要一个属性 prev 以指示链表中的上一个节点。假设链表中的所有节点都是 0-index 的。 + +在链表类中实现这些功能: + +get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。 +addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。 +addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。 +addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val  的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。 +deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点。 +```java +public class ListNode { + int val; + ListNode next; + ListNode(int x) { val = x; } +} + +class MyLinkedList { + int size; + ListNode head; // sentinel node as pseudo-head + public MyLinkedList() { + size = 0; + head = new ListNode(0); + } + + /** Get the value of the index-th node in the linked list. If the index is invalid, return -1. */ + public int get(int index) { + // if index is invalid + if (index < 0 || index >= size) return -1; + + ListNode curr = head; + // index steps needed + // to move from sentinel node to wanted index + for(int i = 0; i < index + 1; ++i) curr = curr.next; + return curr.val; + } + + /** Add a node of value val before the first element of the linked list. After the insertion, the new node will be the first node of the linked list. */ + public void addAtHead(int val) { + addAtIndex(0, val); + } + + /** Append a node of value val to the last element of the linked list. */ + public void addAtTail(int val) { + addAtIndex(size, val); + } + + /** Add a node of value val before the index-th node in the linked list. If index equals to the length of linked list, the node will be appended to the end of linked list. If index is greater than the length, the node will not be inserted. */ + public void addAtIndex(int index, int val) { + // If index is greater than the length, + // the node will not be inserted. + if (index > size) return; + + // [so weird] If index is negative, + // the node will be inserted at the head of the list. + if (index < 0) index = 0; + + ++size; + // find predecessor of the node to be added + ListNode pred = head; + for(int i = 0; i < index; ++i) pred = pred.next; + + // node to be added + ListNode toAdd = new ListNode(val); + // insertion itself + toAdd.next = pred.next; + pred.next = toAdd; + } + + /** Delete the index-th node in the linked list, if the index is valid. */ + public void deleteAtIndex(int index) { + // if the index is invalid, do nothing + if (index < 0 || index >= size) return; + + size--; + // find predecessor of the node to be deleted + ListNode pred = head; + for(int i = 0; i < index; ++i) pred = pred.next; + + // delete pred.next + pred.next = pred.next.next; + } +} + +``` + + +