牛客網演算法基礎培訓第三期 第三課
基礎資料結構(一)
1)陣列,連結串列,陣列,佇列,棧
2)雜湊函式、雜湊表,一致性雜湊和布隆過濾器
3)堆(優先順序佇列,赫夫曼編碼相關問題)
知識點總結
佇列 棧連結串列 列印矩形
轉區域性為巨集觀思想
題目一 用陣列結構實現大小固定的佇列和棧
(實現佇列的時候是一個解耦的比其他課程更好的方法,一定要看一下思路)
public class Code_01_Array_To_Stack_Queue {
//用一個index下標記位置只對index下標的值操作,天然實現
public static class ArrayStack {
private Integer[] arr;
private Integer size;
public ArrayStack(int initSize) {
if (initSize < 0) {
throw new IllegalArgumentException("The init size is less than 0");
}
arr = new Integer[initSize];
size = 0;
}
public Integer peek() {
if (size == 0) {
return null;
}
return arr[size - 1];
}
public void push(int obj) {
if (size == arr.length) {
throw new ArrayIndexOutOfBoundsException("The queue is full");
}
arr[size++] = obj;
}
public Integer pop() {
if (size == 0) {
throw new ArrayIndexOutOfBoundsException("The queue is empty");
}
return arr[--size];
}
}
/*
* 這裡用size對end和start做了解耦
* end-->新進的數放的位置
* start-->出隊的數從哪拿
* 優於那種start追end的結構,多一個空餘位置判斷是否滿,更容易實現也容易理解
* size!=0的時候能拿數(pop)
* size!=length的時候能入隊(push)
* 在已經滿的佇列push的時候,end位置從陣列末尾跳到陣列開頭
*/
public static class ArrayQueue {
private Integer[] arr;
private Integer size;
private Integer first;
private Integer last;
public ArrayQueue(int initSize) {
if (initSize < 0) {
throw new IllegalArgumentException("The init size is less than 0");
}
arr = new Integer[initSize];
size = 0;
first = 0;
last = 0;
}
public Integer peek() {
if (size == 0) {
return null;
}
return arr[first];
}
//入隊 size++
public void push(int obj) {
if (size == arr.length) {
throw new ArrayIndexOutOfBoundsException("The queue is full");
}
size++;
arr[last] = obj;
last = last == arr.length - 1 ? 0 : last + 1;
}
//出隊size--
public Integer poll() {
if (size == 0) {
throw new ArrayIndexOutOfBoundsException("The queue is empty");
}
size--;
int tmp = first;
first = first == arr.length - 1 ? 0 : first + 1;
return arr[tmp];
}
}
public static void main(String[] args) {
}
}
題目二 實現一個特殊的棧,在實現棧的基本功能的基礎上,再實現返回棧中最小元素的操作。
【要求】
1.pop、push、getMin操作的時間複雜度都是O(1)。
2.設計的棧型別可以使用現成的棧結構。
import java.util.Stack;
/*
* 修改基本棧
* 加一個最小數棧
* 入棧 3 2 4 1 5
* 最小棧3 2 2 1 1 //同步壓 同步彈 壓入的時候,最小棧每次壓入的是值棧中存在的最小數
*
* 實現方法2 同樣加入一個最小數棧
* 入棧 3 2 4 1 5
* 最小棧3 2 1 //壓入的時候,最小棧每次只把小於等於最小棧棧頂的元素同步壓入 彈出時候比較值棧和最小棧棧頂元素是否相同,相同就同步彈出
*
* peek函式返回棧頂元素
*
*/
public class Code_02_GetMinStack {
public static class MyStack1 {
private Stack<Integer> stackData;
private Stack<Integer> stackMin;
public MyStack1() {
this.stackData = new Stack<Integer>();
this.stackMin = new Stack<Integer>();
}
public void push(int newNum) {
if (this.stackMin.isEmpty()) {
this.stackMin.push(newNum);
} else if (newNum <= this.getmin()) {
this.stackMin.push(newNum);
}
this.stackData.push(newNum);
}
public int pop() {
if (this.stackData.isEmpty()) {
throw new RuntimeException("Your stack is empty.");
}
int value = this.stackData.pop();
if (value == this.getmin()) {
this.stackMin.pop();
}
return value;
}
public int getmin() {
if (this.stackMin.isEmpty()) {
throw new RuntimeException("Your stack is empty.");
}
return this.stackMin.peek();
}
}
public static class MyStack2 {
private Stack<Integer> stackData;
private Stack<Integer> stackMin;
public MyStack2() {
this.stackData = new Stack<Integer>();
this.stackMin = new Stack<Integer>();
}
public void push(int newNum) {
if (this.stackMin.isEmpty()) {
this.stackMin.push(newNum);
} else if (newNum < this.getmin()) {
this.stackMin.push(newNum);
} else {
int newMin = this.stackMin.peek();
this.stackMin.push(newMin);
}
this.stackData.push(newNum);
}
public int pop() {
if (this.stackData.isEmpty()) {
throw new RuntimeException("Your stack is empty.");
}
this.stackMin.pop();
return this.stackData.pop();
}
public int getmin() {
if (this.stackMin.isEmpty()) {
throw new RuntimeException("Your stack is empty.");
}
return this.stackMin.peek();
}
}
public static void main(String[] args) {
MyStack1 stack1 = new MyStack1();
stack1.push(3);
System.out.println(stack1.getmin());
stack1.push(4);
System.out.println(stack1.getmin());
stack1.push(1);
System.out.println(stack1.getmin());
System.out.println(stack1.pop());
System.out.println(stack1.getmin());
System.out.println("=============");
MyStack1 stack2 = new MyStack1();
stack2.push(3);
System.out.println(stack2.getmin());
stack2.push(4);
System.out.println(stack2.getmin());
stack2.push(1);
System.out.println(stack2.getmin());
System.out.println(stack2.pop());
System.out.println(stack2.getmin());
}
}
題目三 如何僅用佇列結構實現棧結構 如何僅用棧結構實現佇列結構
import java.util.LinkedList;
import java.util.Queue;
import java.util.Stack;
public class Code_03_StackAndQueueConvert {
/*
* 用兩個棧實現佇列
* 隊A 隊B
* 隊A 入隊 1 2 3 4 5
* 隊A出隊填入隊B,保留隊A最後一個元素
* 此時 隊A 5
* 隊B 1 2 3 4
* 隊A最後一個數彈出
* 然後隊B填入隊A,隊B保留最後一個元素
* 隊B最後一個數彈出
* 然後隊A填入隊B,隊A保留最後一個元素
* 隊A最後一個數彈出
*/
public static class TwoStacksQueue {
private Stack<Integer> stackPush;
private Stack<Integer> stackPop;
public TwoStacksQueue() {
stackPush = new Stack<Integer>();
stackPop = new Stack<Integer>();
}
public void push(int pushInt) {
stackPush.push(pushInt);
}
public int poll() {
if (stackPop.empty() && stackPush.empty()) {
throw new RuntimeException("Queue is empty!");
} else if (stackPop.empty()) {
while (!stackPush.empty()) {
stackPop.push(stackPush.pop());
}
}
return stackPop.pop();
}
public int peek() {
if (stackPop.empty() && stackPush.empty()) {
throw new RuntimeException("Queue is empty!");
} else if (stackPop.empty()) {
while (!stackPush.empty()) {
stackPop.push(stackPush.pop());
}
}
return stackPop.peek();
}
}
/*
* 用兩個佇列實現棧
* push棧和pop棧
* 壓只進push,出只彈pop
* 原則一 pop棧有東西,push棧不能壓入
* 原則二 push棧往pop棧壓入的時候,必須一次性把所有資料壓完
* 只要滿足兩個原則,可以在任意一個時刻判斷和操作
*/
public static class TwoQueuesStack {
private Queue<Integer> queue;
private Queue<Integer> help;
public TwoQueuesStack() {
queue = new LinkedList<Integer>();
help = new LinkedList<Integer>();
}
public void push(int pushInt) {
queue.add(pushInt);
}
public int peek() {
if (queue.isEmpty()) {
throw new RuntimeException("Stack is empty!");
}
while (queue.size() != 1) {
help.add(queue.poll());
}
int res = queue.poll();
help.add(res);
swap();
return res;
}
public int pop() {
if (queue.isEmpty()) {
throw new RuntimeException("Stack is empty!");
}
while (queue.size() != 1) {
help.add(queue.poll());
}
int res = queue.poll();
swap();
return res;
}
private void swap() {
Queue<Integer> tmp = help;
help = queue;
queue = tmp;
}
}
}
題目四
實現一種狗貓佇列的結構,要求如下:
使用者可以呼叫add方法將cat類或dog類的例項放入佇列中;
使用者可以呼叫pollAll方法,將佇列中所有的例項按照進佇列的先後順序依次彈出;
使用者可以呼叫pollDog方法,將佇列中dog類的例項按照進佇列的先後順序依次彈出;
使用者可以呼叫pollCat方法,將佇列中cat類的例項按照進佇列的先後順序依次彈出;
使用者可以呼叫isEmpty方法,檢查佇列中是否還有dog或cat的例項;
使用者可以呼叫isDogEmpty方法,檢查佇列中是否有dog類的例項;
使用者可以呼叫isCatEmpty方法,檢查佇列中是否有cat類的例項。
public class Pet {
private String type;
public Pet(String type) {
this.type = type;
}
public String getPetType() {
return this.type;
}
}
public class Dog extends Pet {
public Dog() {
super("dog");
}
}
public class Cat extends Pet {
public Cat() {
super("cat");
}
}
import java.util.LinkedList;
import java.util.Queue;
public class Code_04_DogCatQueue {
public static class Pet {
private String type;
public Pet(String type) {
this.type = type;
}
public String getPetType() {
return this.type;
}
}
public static class Dog extends Pet {
public Dog() {
super("dog");
}
}
public static class Cat extends Pet {
public Cat() {
super("cat");
}
}
public static class PetEnterQueue {
private Pet pet;
private long count;
public PetEnterQueue(Pet pet, long count) {
this.pet = pet;
this.count = count;
}
public Pet getPet() {
return this.pet;
}
public long getCount() {
return this.count;
}
public String getEnterPetType() {
return this.pet.getPetType();
}
}
public static class DogCatQueue {
private Queue<PetEnterQueue> dogQ;
private Queue<PetEnterQueue> catQ;
private long count;
public DogCatQueue() {
this.dogQ = new LinkedList<PetEnterQueue>();
this.catQ = new LinkedList<PetEnterQueue>();
this.count = 0;
}
public void add(Pet pet) {
if (pet.getPetType().equals("dog")) {
this.dogQ.add(new PetEnterQueue(pet, this.count++));
} else if (pet.getPetType().equals("cat")) {
this.catQ.add(new PetEnterQueue(pet, this.count++));
} else {
throw new RuntimeException("err, not dog or cat");
}
}
public Pet pollAll() {
if (!this.dogQ.isEmpty() && !this.catQ.isEmpty()) {
if (this.dogQ.peek().getCount() < this.catQ.peek().getCount()) {
return this.dogQ.poll().getPet();
} else {
return this.catQ.poll().getPet();
}
} else if (!this.dogQ.isEmpty()) {
return this.dogQ.poll().getPet();
} else if (!this.catQ.isEmpty()) {
return this.catQ.poll().getPet();
} else {
throw new RuntimeException("err, queue is empty!");
}
}
public Dog pollDog() {
if (!this.isDogQueueEmpty()) {
return (Dog) this.dogQ.poll().getPet();
} else {
throw new RuntimeException("Dog queue is empty!");
}
}
public Cat pollCat() {
if (!this.isCatQueueEmpty()) {
return (Cat) this.catQ.poll().getPet();
} else
throw new RuntimeException("Cat queue is empty!");
}
public boolean isEmpty() {
return this.dogQ.isEmpty() && this.catQ.isEmpty();
}
public boolean isDogQueueEmpty() {
return this.dogQ.isEmpty();
}
public boolean isCatQueueEmpty() {
return this.catQ.isEmpty();
}
}
public static void main(String[] args) {
DogCatQueue test = new DogCatQueue();
Pet dog1 = new Dog();
Pet cat1 = new Cat();
Pet dog2 = new Dog();
Pet cat2 = new Cat();
Pet dog3 = new Dog();
Pet cat3 = new Cat();
test.add(dog1);
test.add(cat1);
test.add(dog2);
test.add(cat2);
test.add(dog3);
test.add(cat3);
test.add(dog1);
test.add(cat1);
test.add(dog2);
test.add(cat2);
test.add(dog3);
test.add(cat3);
test.add(dog1);
test.add(cat1);
test.add(dog2);
test.add(cat2);
test.add(dog3);
test.add(cat3);
while (!test.isDogQueueEmpty()) {
System.out.println(test.pollDog().getPetType());
}
while (!test.isEmpty()) {
System.out.println(test.pollAll().getPetType());
}
}
}
第五題 認識雜湊函式和雜湊表
雜湊表就是散列表
1.多種實現 多種定址
2.經典的雜湊表是陣列+連結串列實現的 在java現在的版本中雜湊表是陣列+紅黑樹(TreeMap實現的)
3.HashMap的CRUD認為都是O(1)的
4.散列表的擴容(每一次原長度*2) 可以離線完成,建好了再算一遍位置,填入 然後舊錶換新表
//hashmap在使用的時候增刪改查都視為時間複雜度O(1)的操作
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map.Entry;
public class Code_05_HashMap {
public static void main(String[] args) {
HashMap<String, String> map = new HashMap<>();
map.put("zuo", "31");
System.out.println(map.containsKey("zuo"));
System.out.println(map.containsKey("chengyun"));
System.out.println("=========================");
System.out.println(map.get("zuo"));
System.out.println(map.get("chengyun"));
System.out.println("=========================");
System.out.println(map.isEmpty());
System.out.println(map.size());
System.out.println("=========================");
System.out.println(map.remove("zuo"));
System.out.println(map.containsKey("zuo"));
System.out.println(map.get("zuo"));
System.out.println(map.isEmpty());
System.out.println(map.size());
System.out.println("=========================");
map.put("zuo", "31");
System.out.println(map.get("zuo"));
map.put("zuo", "32");
System.out.println(map.get("zuo"));
System.out.println("=========================");
map.put("zuo", "31");
map.put("cheng", "32");
map.put("yun", "33");
for (String key : map.keySet()) {
System.out.println(key);
}
System.out.println("=========================");
for (String values : map.values()) {
System.out.println(values);
}
System.out.println("=========================");
map.clear();
map.put("A", "1");
map.put("B", "2");
map.put("C", "3");
map.put("D", "1");
map.put("E", "2");
map.put("F", "3");
map.put("G", "1");
map.put("H", "2");
map.put("I", "3");
for (Entry<String, String> entry : map.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
System.out.println(key + "," + value);
}
System.out.println("=========================");
// you can not remove item in map when you use the iterator of map
// for(Entry<String,String> entry : map.entrySet()){
// if(!entry.getValue().equals("1")){
// map.remove(entry.getKey());
// }
// }
// if you want to remove items, collect them first, then remove them by
// this way.
List<String> removeKeys = new ArrayList<String>();
for (Entry<String, String> entry : map.entrySet()) {
if (!entry.getValue().equals("1")) {
removeKeys.add(entry.getKey());
}
}
for (String removeKey : removeKeys) {
map.remove(removeKey);
}
for (Entry<String, String> entry : map.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
System.out.println(key + "," + value);
}
System.out.println("=========================");
}
}
題目六 設計RandomPool結構
【題目】
設計一種結構,在該結構中有如下三個功能:
insert(key):將某個key加入到該結構,做到不重複加入。
delete(key):將原本在結構中的某個key移除。
getRandom():等概率隨機返回結構中的任何一個key。
【要求】
Insert、delete和getRandom方法的時間複雜度都是O(1)。
import java.util.HashMap;
public class Code_06_RandomPool {
public static class Pool<K> {
private HashMap<K, Integer> keyIndexMap;
private HashMap<Integer, K> indexKeyMap;
private int size;
public Pool() {
this.keyIndexMap = new HashMap<K, Integer>();
this.indexKeyMap = new HashMap<Integer, K>();
this.size = 0;
}
public void insert(K key) {
if (!this.keyIndexMap.containsKey(key)) {
this.keyIndexMap.put(key, this.size);
this.indexKeyMap.put(this.size++, key);
}
}
public void delete(K key) {
if (this.keyIndexMap.containsKey(key)) {
int deleteIndex = this.keyIndexMap.get(key);
int lastIndex = --this.size;
K lastKey = this.indexKeyMap.get(lastIndex);
this.keyIndexMap.put(lastKey, deleteIndex);
this.indexKeyMap.put(deleteIndex, lastKey);
this.keyIndexMap.remove(key);
this.indexKeyMap.remove(lastIndex);
}
}
public K getRandom() {
if (this.size == 0) {
return null;
}
int randomIndex = (int) (Math.random() * this.size);
return this.indexKeyMap.get(randomIndex);
}
}
public static void main(String[] args) {
Pool<String> pool = new Pool<String>();
pool.insert("zuo");
pool.insert("cheng");
pool.insert("yun");
System.out.println(pool.getRandom());
System.out.println(pool.getRandom());
System.out.println(pool.getRandom());
System.out.println(pool.getRandom());
System.out.println(pool.getRandom());
System.out.println(pool.getRandom());
}
}
題目七 轉圈列印矩陣
【題目】
給定一個整型矩陣matrix,請按照轉圈的方式列印它。
例如:
1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 16
列印結果為:1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,
10
【要求】
public class Code_07_PrintMatrixSpiralOrder {
public static void spiralOrderPrint(int[][] matrix) {
int row1 = 0;
int col1 = 0;
int row2 = matrix.length - 1;
int col2 = matrix[0].length - 1;
while (row1 <= row2 && col1 <= col2) {//一次一圈
printEdge(matrix, row1++, col1++, row2--, col2--);
}
}
public static void printEdge(int[][] m, int row1, int col1, int row2, int col2) {
if (row1 == row2) { //一行的情況
for (int i = col1; i <= col2; i++) {
System.out.print(m[row1][i] + " ");
}
} else if (col1 == col2) { // 一列的情況
for (int i = row1; i <= row2; i++) {
System.out.print(m[i][col1] + " ");
}
} else { // 矩陣
int curC = col1;
int curR = row1;
while (curC != col2) {//上下左右四個邊列印完剛好一圈
System.out.print(m[row1][curC] + " ");
curC++;
}
while (curR != row2) {
System.out.print(m[curR][col2] + " ");
curR++;
}
while (curC != col1) {
System.out.print(m[row2][curC] + " ");
curC--;
}
while (curR != row1) {
System.out.print(m[curR][col1] + " ");
curR--;
}
}
}
public static void main(String[] args) {
int[][] matrix = {
{ 1, 2, 3, 4 },
{ 5, 6, 7, 8 },
{ 9, 10, 11, 12 },
{ 13, 14, 15, 16 } };
spiralOrderPrint(matrix);
}
}
第八題 “之”字形列印矩陣
【題目】
給定一個矩陣matrix,按照“之”字形的方式列印這個矩陣,例如:
1 2 3 4
5 6 7 8
9 10 11 12
“之”字形列印的結果為:1,2,5,9,6,3,4,7,10,11,8,12
【要求】
額外空間複雜度為O(1)。
跳出如何變換下標的區域性思想
從巨集觀思考
public class Code_08_ZigZagPrintMatrix {
public static void printMatrixZigZag(int[][] matrix) {
int row1 = 0;
int col1 = 0;
int row2 = 0;
int col2 = 0;
int endR = matrix.length - 1;
int endC = matrix[0].length - 1;
boolean fromUp = false;
while (row1 != endR + 1) {
printLevel(matrix, row1, col1, row2, col2, fromUp);
row1 = col1 == endC ? row1 + 1 : row1;
col1 = col1 == endC ? col1 : col1 + 1;
col2 = row2 == endR ? col2 + 1 : col2;
row2 = row2 == endR ? row2 : row2 + 1;
fromUp = !fromUp;
}
System.out.println();
}
public static void printLevel(int[][] m, int row1, int col1, int row2, int col2,
boolean f) {
if (f) {
while (row1 < row2) {//tR != dR + 1
System.out.print(m[row1++][col1--] + " ");
}
} else {
while (row2 != row1 - 1) {
System.out.print(m[row2--][col2++] + " ");
}
}
}
public static void main(String[] args) {
int[][] matrix = { { 1, 2, 3, 4 }, { 5, 6, 7, 8 }, { 9, 10, 11, 12 } };
printMatrixZigZag(matrix);
}
}
題目九 在行列都排好序的矩陣中找數
【題目】
給定一個有N*M的整型矩陣matrix和一個整數K,matrix的每一行和每一
列都是排好序的。實現一個函式,判斷K是否在matrix中。
例如:
0 1 2 5
2 3 4 7
4 4 4 8
5 7 7 9
如果K為7,返回true;如果K為6,返回false。
【要求】
時間複雜度為O(N+M),額外空間複雜度為O(1)。
/*
從左上或者右下開始找
m[i][j]>x向左不向下 m[i][j]<x向下越界找不到就是false
水題
*/
public class Code_09_FindNumInSortedMatrix {
public static boolean isContains(int[][] matrix, int K) {
int row = 0;
int col = matrix[0].length - 1;
while (row < matrix.length && col > -1) {
if (matrix[row][col] == K) {
return true;
} else if (matrix[row][col] > K) {
col--;
} else {
row++;
}
}
return false;
}
public static void main(String[] args) {
int[][] matrix = new int[][] { { 0, 1, 2, 3, 4, 5, 6 },// 0
{ 10, 12, 13, 15, 16, 17, 18 },// 1
{ 23, 24, 25, 26, 27, 28, 29 },// 2
{ 44, 45, 46, 47, 48, 49, 50 },// 3
{ 65, 66, 67, 68, 69, 70, 71 },// 4
{ 96, 97, 98, 99, 100, 111, 122 },// 5
{ 166, 176, 186, 187, 190, 195, 200 },// 6
{ 233, 243, 321, 341, 356, 370, 380 } // 7
};
int K = 233;
System.out.println(isContains(matrix, K));
}
}
題目十 列印兩個有序連結串列的公共部分
【題目】
給定兩個有序連結串列的頭指標head1和head2,列印兩個連結串列的公共部分。
//水題 看程式碼就行
public class Code_10_PrintCommonPart {
public static class Node {
public int value;
public Node next;
public Node(int data) {
this.value = data;
}
}
public static void printCommonPart(Node head1, Node head2) {
System.out.print("Common Part: ");