From 52a2d7dedcb95c79f99b316b7774138742b40d25 Mon Sep 17 00:00:00 2001 From: nange Date: Tue, 4 Jul 2023 17:30:30 +0800 Subject: [PATCH 01/50] refactor the project --- .gitignore | 6 ++ README.md | 63 ++++-------- c/arraylist/arraylist.c | 89 ---------------- c/arraylist/arraylist.h | 30 ------ c/arraylist/arraylist_test.c | 70 ------------- c/queue/queue.c | 75 -------------- c/queue/queue.h | 30 ------ c/queue/queue_test.c | 32 ------ c/stack/stack.c | 71 ------------- c/stack/stack.h | 28 ------ c/stack/stack_test.c | 32 ------ java/arraylist/MyArrayList.java | 110 -------------------- java/arraylist/MyArrayListTest.java | 92 ----------------- java/queue/Queue.java | 68 ------------- java/queue/QueueTest.java | 81 --------------- java/stack/Stack.java | 55 ---------- java/stack/StackTest.java | 33 ------ javascript/arraylist/ArrayList.js | 81 --------------- javascript/arraylist/ArrayList_test.js | 123 ----------------------- javascript/linkedlist/LinkedList.js | 84 ---------------- javascript/linkedlist/LinkedList_test.js | 25 ----- javascript/queue/Queue.js | 38 ------- javascript/queue/Queue_test.js | 27 ----- javascript/stack/Stack.js | 30 ------ javascript/stack/Stack_test.js | 39 ------- rust/Cargo.toml | 9 ++ rust/src/lib.rs | 0 27 files changed, 37 insertions(+), 1384 deletions(-) delete mode 100644 c/arraylist/arraylist.c delete mode 100644 c/arraylist/arraylist.h delete mode 100644 c/arraylist/arraylist_test.c delete mode 100644 c/queue/queue.c delete mode 100644 c/queue/queue.h delete mode 100644 c/queue/queue_test.c delete mode 100644 c/stack/stack.c delete mode 100644 c/stack/stack.h delete mode 100644 c/stack/stack_test.c delete mode 100644 java/arraylist/MyArrayList.java delete mode 100644 java/arraylist/MyArrayListTest.java delete mode 100644 java/queue/Queue.java delete mode 100644 java/queue/QueueTest.java delete mode 100644 java/stack/Stack.java delete mode 100644 java/stack/StackTest.java delete mode 100644 javascript/arraylist/ArrayList.js delete mode 100644 javascript/arraylist/ArrayList_test.js delete mode 100644 javascript/linkedlist/LinkedList.js delete mode 100644 javascript/linkedlist/LinkedList_test.js delete mode 100644 javascript/queue/Queue.js delete mode 100644 javascript/queue/Queue_test.js delete mode 100644 javascript/stack/Stack.js delete mode 100644 javascript/stack/Stack_test.js create mode 100644 rust/Cargo.toml create mode 100644 rust/src/lib.rs diff --git a/.gitignore b/.gitignore index 3ae055a..53149f7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,9 @@ pkg .idea java/.project + +/rust/target +**/*.rs.bk +Cargo.lock +/.idea/ +.vscode diff --git a/README.md b/README.md index 194f91f..d47be31 100644 --- a/README.md +++ b/README.md @@ -1,76 +1,57 @@ -# DataStructures-And-Algorithms-With-Four-Language +# The Algorithms in go and rust language -四种语言(Go,Javascript,Java,C)实现常用数据结构和算法。 +用Go、Rust语言实现常用数据结构和算法。用于学习目的,算法效率不一定最优。 所有代码都经过具体测试运行,具体环境为: -- 操作系统:ubuntu16.04 64位 -- Go语言版本: Go 1.8.3 -- Javascript执行环境: Nodejs 6.11.3 -- Java版本: Java 1.8.0_25 -- C语言编译器: gcc 4.8.2 +- 操作系统:Ubuntu-latest 64位 +- Go语言版本: Go 1.20 +- Rust语言版本: Rust 1.70 ## 目录 -1. [列表](#user-content-列表) (完成) -2. [栈](#user-content-栈) (完成) -3. [队列](#user-content-队列) (完成) -4. [链表](#user-content-链表) (golang,javascript 完成...) -5. [字典](#user-content-字典) (golang 完成) -6. [二叉树和二叉查找树](#user-content-二叉树和二叉查找树) (golang 完成) -7. [图和图算法](#user-content-图和图算法) (待处理...) -8. [排序算法](#user-content-排序算法) (待处理...) -9. [检索算法](#user-content-检索算法) (待处理...) +1. [列表](#user-content-列表) +2. [栈](#user-content-栈) +3. [队列](#user-content-队列) +4. [链表](#user-content-链表) +5. [字典](#user-content-字典) +6. [二叉树和二叉查找树](#user-content-二叉树和二叉查找树) +7. [图和图算法](#user-content-图和图算法) +8. [排序算法](#user-content-排序算法) +9. [检索算法](#user-content-检索算法) ### 列表 + 列表是非常常见的一种数据结构,比如日常所见的购物清单、待办事项等等。 它提供了对列表数据的一系列操作,比如:添加、删除、修改、遍历等功能。 当我们把这样的具体问题抽象成用列表去解决的时候,往往可以简化问题。 -具体实现: [Go](go/arraylist) 、 [Javascript](javascript/arraylist) 、 -[Java](java/arraylist) 、 [C](c/arraylist) - - ### 栈 + 栈也是一种非常常见的数据结构,在计算机的世界里,在计算机的世界里, 栈是一种很高效的数据结构,因为数据只能在栈顶添加或者删除. 因此栈也被称为一种后入先出的数据结构.栈的使用遍布程序语言实现的方方面面, 从表达式求值到函数调用. -具体实现: [Go](go/stack) 、 [Javascript](javascript/stack) 、 -[Java](java/stack) 、 [C](c/stack) - - ### 队列 + 队列是一种前进先出的数据结构. 在日常生活中非常常见:比如去银行排队办理业务. 在计算机中也极其常见, 很多情况下,当有大量任务需要完成时, 就会把任务暂时加入到 任务队列中, 执行一个删除一个,继续执行下一个任务. -具体实现: [Go](go/queue) 、[Javascript](javascript/queue) 、 -[Java](java/queue) 、 [C](c/queue) - - ### 链表 + 有时候数组不一定是最佳的组织数据的数据结构,因为数组通常都是固定大小的,当数据填满时, 再加入新元素就变得很困难。在数组中,添加和删除元素也很麻烦,因为要移动数组中的其他元素。 因此如果需要频繁的添加或者删除元素,可以考虑使用链表组织数据。 -具体实现: [Go](go/linkedlist) 、[Javascript](javascript/linkedlist) 、 -[Java](java/linkedlist) 、 [C](c/linkedlist) - - ### 字典 + 字典类型(map),是一种KV结构,通过key能够找到对应的value,map类型的特点是查询非常快(O(1)), 原理是通过一个hash函数能快速的找到对应的key的位置,在实际编程中,map类型几乎无处不在。 -具体实现: [Go](go/hashmap) 、[Javascript](javascript/hashmap) 、 -[Java](java/hashmap) 、 [C](c/hashmap) - - ### 二叉树和二叉查找(搜索)树 -二叉树可以看作是链表的二维扩展,链表的一个节点只有一个next指针,但是二叉树的一个节点可以有两个 -next指针(通常被称作left,right),二维扩展后可以解决一维链表的一些问题,比如查找慢等。 -二叉查找树可以把查找,添加,删除元素的时间复杂度降到O(logN)。 -具体实现: [Go](go/binarytree) 、[Javascript](javascript/binarytree) 、 -[Java](java/binarytree) 、 [C](c/binarytree) +二叉树可以看作是链表的二维扩展,链表的一个节点只有一个next指针,但是二叉树的一个节点可以有两个 +next指针(通常被称作left,right),二维扩展后可以解决一维链表的一些问题,比如查找慢等。 +二叉查找树可以把查找,添加,删除元素的时间复杂度降到O(logN)。 diff --git a/c/arraylist/arraylist.c b/c/arraylist/arraylist.c deleted file mode 100644 index 990888b..0000000 --- a/c/arraylist/arraylist.c +++ /dev/null @@ -1,89 +0,0 @@ -#include -#include -#include -#include "arraylist.h" - -#define ARRAYLIST_DEAUFALT_SIZE 4 - -/** - * create a new empty arraylist - */ -ArrayList *listCreate() { - ArrayList *list = malloc(sizeof(ArrayList)); - - list->dataStore = malloc(sizeof(void *) * ARRAYLIST_DEAUFALT_SIZE); - assert(list->dataStore); - list->theSize = 0; - list->capacity = ARRAYLIST_DEAUFALT_SIZE; - - list->size = listSize; - list->get = listGet; - list->set = listSet; - list->append = listAppend; - list->insert = listInsert; - list->remove = listRemove; - list->clear = listClear; - list->destory = listDestory; - - return list; -} - -void listIsRealloc(ArrayList *list) { - if (list->theSize == list->capacity) { - unsigned int newCapa = list->capacity * 2; - list->capacity = newCapa; - list->dataStore = realloc(list->dataStore, sizeof(void *) * newCapa); - assert(list->dataStore); - } -} - -int listSize(ArrayList *list) { - return list->theSize; -} - -void *listGet(ArrayList *list, unsigned int index) { - assert(index < list->theSize); - return list->dataStore[index]; -} - -void listSet(ArrayList *list, unsigned int index, void *value) { - assert(index < list->theSize); - list->dataStore[index] = value; -} - -void listAppend(ArrayList *list, void *value) { - listIsRealloc(list); - list->dataStore[list->theSize++] = value; -} - -void listInsert(ArrayList *list, unsigned int index, void *value) { - assert(index < list->theSize); - listIsRealloc(list); - - memmove(list->dataStore+index+1, list->dataStore+index, - sizeof(void *) * (list->theSize-index)); - - list->dataStore[index] = value; - list->theSize++; -} - -void listRemove(ArrayList *list, unsigned int index) { - assert(index < list->theSize); - memmove(list->dataStore+index, list->dataStore+index+1, - sizeof(void *) * (list->theSize-index-1)); - - list->theSize--; -} - -void listClear(ArrayList *list) { - free(list->dataStore); - list->dataStore = malloc(sizeof(void *) * ARRAYLIST_DEAUFALT_SIZE); - assert(list->dataStore); - list->theSize = 0; - list->capacity = ARRAYLIST_DEAUFALT_SIZE; -} - -void listDestory(ArrayList *list) { - free(list->dataStore); - free(list); -} diff --git a/c/arraylist/arraylist.h b/c/arraylist/arraylist.h deleted file mode 100644 index b135812..0000000 --- a/c/arraylist/arraylist.h +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef __ARRAYLIST_H__ -#define __ARRAYLIST_H__ - -typedef struct ArrayList { - int theSize; - int capacity; - void **dataStore; - - int (*size)(struct ArrayList *); - void *(*get)(struct ArrayList *, unsigned int); - void (*set)(struct ArrayList *, unsigned int, void *); - void (*append)(struct ArrayList *, void *); - void (*insert)(struct ArrayList *, unsigned int, void *); - void (*remove)(struct ArrayList *, unsigned int); - void (*clear)(struct ArrayList *); - void (*destory)(struct ArrayList *); -} ArrayList; - -/* Prototypes */ -ArrayList *listCreate(); -int listSize(ArrayList *list); -void *listGet(ArrayList *list, unsigned int index); -void listSet(ArrayList *list, unsigned int index, void *value); -void listAppend(ArrayList *list, void *value); -void listInsert(ArrayList *list, unsigned int index, void *value); -void listRemove(ArrayList *list, unsigned int index); -void listClear(ArrayList *list); -void listDestory(ArrayList *list); - -#endif /* __ARRAYLIST_H__ */ diff --git a/c/arraylist/arraylist_test.c b/c/arraylist/arraylist_test.c deleted file mode 100644 index c0592b6..0000000 --- a/c/arraylist/arraylist_test.c +++ /dev/null @@ -1,70 +0,0 @@ -#include -#include -#include "arraylist.c" - -int main() { - printf("\nRunning arraylist.c tests.\n\n"); - - ArrayList *list = listCreate(); - assert(list->size(list) == 0); - - int a = 1; - list->append(list, &a); - assert(list->size(list) == 1); - - int item1 = *(int *)list->get(list, 0); - assert(item1 == 1); - - char c = 'c'; - list->append(list, &c); - assert(list->size(list) == 2); - - char item2 = *(char *)list->get(list, 1); - assert(item2 == 'c'); - - int b = 2; - list->set(list, 1, &b); - assert(list->size(list) == 2); - - int item2_new = *(int *)list->get(list, 1); - assert(item2_new == 2); - - list->destory(list); - list = NULL; - assert(!list); - - int a1 = 1; - int a2 = 2; - int a3 = 3; - int a4 = 4; - ArrayList *list2 = listCreate(); - list2->append(list2, &a1); - list2->append(list2, &a2); - list2->append(list2, &a3); - list2->insert(list2, 2, &a4); - assert(list2->size(list2) == 4); - - int r1 = *(int *)list2->get(list2, 0); - int r2 = *(int *)list2->get(list2, 1); - int r3 = *(int *)list2->get(list2, 2); - int r4 = *(int *)list2->get(list2, 3); - assert(r1 == 1); - assert(r2 == 2); - assert(r3 == 4); - assert(r4 == 3); - - list2->remove(list2, 0); - r1 = *(int *)list2->get(list2, 0); - assert(r1 == 2); - assert(list2->size(list2) == 3); - - list2->clear(list2); - assert(list2->size(list2) == 0); - - list2->destory(list2); - list2 = NULL; - assert(!list2); - - printf("All arraylist.c tests completed.\n\n"); - return 0; -} diff --git a/c/queue/queue.c b/c/queue/queue.c deleted file mode 100644 index a264e5a..0000000 --- a/c/queue/queue.c +++ /dev/null @@ -1,75 +0,0 @@ -#include -#include -#include -#include -#include "queue.h" - -#define DEFAULT_QUEUE_SIZE 4 - -Queue *queueCreate() { - Queue *q = malloc(sizeof(Queue)); - assert(q); - q->theSize = 0; - q->capacity = DEFAULT_QUEUE_SIZE; - q->dataStore = malloc(sizeof(void *) * DEFAULT_QUEUE_SIZE); - - q->enqueue = queueEnqueue; - q->dequeue = queueDequeue; - q->size = queueSize; - q->isEmpty = queueIsEmpty; - q->font = queueFont; - q->end = queueEnd; - q->destory = queueDestory; -} - -void queueShouldRealloc(Queue *q) { - if (q->theSize == q->capacity) { - q->capacity = q->capacity * 2; - q->dataStore = realloc(q->dataStore, sizeof(void *) * q->capacity); - } -} - -void queueEnqueue(Queue *q, void *element) { - queueShouldRealloc(q); - q->dataStore[q->theSize++] = element; -} - -void* queueDequeue(Queue *q) { - if (q->theSize == 0) return NULL; - void *d = q->dataStore[0]; - if (q->theSize > 1) { - int i; - for (i = 0; i < q->theSize-1; i++) { - q->dataStore[i] = q->dataStore[i+1]; - } - } - q->theSize--; - return d; -} - -int queueSize(Queue *q) { - return q->theSize; -} - -bool queueIsEmpty(Queue *q) { - return q->theSize == 0; -} - -void *queueFont(Queue *q) { - if (q->theSize == 0) return NULL; - return q->dataStore[0]; -} - -void *queueEnd(Queue *q) { - if (q->theSize == 0) return NULL; - return q->dataStore[q->theSize-1]; -} - -void queueClear(Queue *q) { - q->theSize = 0; -} - -void queueDestory(Queue *q) { - free(q->dataStore); - free(q); -} diff --git a/c/queue/queue.h b/c/queue/queue.h deleted file mode 100644 index 75b3322..0000000 --- a/c/queue/queue.h +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef __QUEUE_H__ -#define __QUEUE_H__ - -typedef struct Queue { - int theSize; - int capacity; - void **dataStore; - - void (*enqueue)(); - void *(*dequeue)(); - int (*size)(); - bool (*isEmpty)(); - void *(*font)(); - void *(*end)(); - void (*clear)(); - void (*destory)(); -} Queue; - -/* Prototypes */ -Queue *queueCreate(); -void queueEnqueue(Queue *, void *); -void *queueDequeue(Queue *); -int queueSize(Queue *); -bool queueIsEmpty(Queue *); -void *queueFont(Queue *); -void *queueEnd(Queue *); -void queueClear(Queue *); -void queueDestory(Queue *); - -#endif /*__QUEUE_H__*/ diff --git a/c/queue/queue_test.c b/c/queue/queue_test.c deleted file mode 100644 index c2fb090..0000000 --- a/c/queue/queue_test.c +++ /dev/null @@ -1,32 +0,0 @@ -#include -#include -#include "queue.c" - -int main() { - printf("\nRunning queue.c tests.\n\n"); - - Queue *q = queueCreate(); - assert(q->isEmpty(q)); - assert(q->size(q) == 0); - - int n1 = 1, n2 = 2, n3 = 3; - int *p1 = &n1, *p2 = &n2, *p3 = &n3; - q->enqueue(q, p1); - q->enqueue(q, p2); - q->enqueue(q, p3); - assert(q->size(q) == 3); - - int d = *(int*)(q->dequeue(q)); - assert(q->size(q) == 2); - assert(d == 1); - - int d2 = *(int*)q->font(q); - int d3 = *(int*)q->end(q); - assert(d2 == 2); - assert(d3 == 3); - - q->destory(q); - - printf("All queue.c tests completed.\n\n"); - return 0; -} diff --git a/c/stack/stack.c b/c/stack/stack.c deleted file mode 100644 index 81fd9f3..0000000 --- a/c/stack/stack.c +++ /dev/null @@ -1,71 +0,0 @@ -#include -#include -#include -#include -#include "stack.h" - -#define DEFAULT_STACK_SIZE 4 - -/** - * create a new empty stack - */ -Stack *stackCreate() { - Stack *stack = malloc(sizeof(Stack)); - assert(stack); - stack->top = 0; - stack->capacity = DEFAULT_STACK_SIZE; - stack->dataStore = malloc(sizeof(void *)*DEFAULT_STACK_SIZE); - - stack->size = stackSize; - stack->isEmpty = stackIsEmpty; - stack->push = stackPush; - stack->pop = stackPop; - stack->peek = stackPeek; - stack->clear = stackClear; - stack->destory = stackDestory; -} - -void stackShouldRealloc(Stack *s) { - if (s->top == s->capacity) { - s->capacity = s->capacity*2; - s->dataStore = realloc(s->dataStore, sizeof(void *) * s->capacity); - } -} - -int stackSize(Stack *s) { - return s->top; -} - -bool stackIsEmpty(Stack *s) { - return s->top == 0; -} - -void stackPush(Stack *s, void *element) { - stackShouldRealloc(s); - s->dataStore[s->top++] = element; -} - -void *stackPop(Stack *s) { - if (s->isEmpty(s)) { - return NULL; - } - - return s->dataStore[--s->top]; -} - -void *stackPeek(Stack *s) { - if (s->isEmpty(s)) { - return NULL; - } - - return s->dataStore[s->top-1]; -} - -void stackClear(Stack *s) { - s->top = 0; -} - -void stackDestory(Stack *s) { - free(s->dataStore); - free(s); -} diff --git a/c/stack/stack.h b/c/stack/stack.h deleted file mode 100644 index 182d5e3..0000000 --- a/c/stack/stack.h +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef __STACK_H__ -#define __STACK_H__ - -typedef struct Stack { - int top; - int capacity; - void **dataStore; - - int (*size)(); - bool (*isEmpty)(); - void (*push)(); - void *(*pop)(); - void *(*peek)(); - void (*clear)(); - void (*destory)(); -} Stack; - -/* Prototypes */ -Stack *stackCreate(); -int stackSize(Stack *s); -bool stackIsEmpty(Stack *s); -void stackPush(Stack *s, void *); -void *stackPop(Stack *s); -void *stackPeek(Stack *s); -void stackClear(Stack *s); -void stackDestory(Stack *s); - -#endif /* __STACK_H__ */ diff --git a/c/stack/stack_test.c b/c/stack/stack_test.c deleted file mode 100644 index 1096e9b..0000000 --- a/c/stack/stack_test.c +++ /dev/null @@ -1,32 +0,0 @@ -#include -#include -#include "stack.c" - -int main() { - printf("\nRunning stack.c tests.\n\n"); - - Stack *s = stackCreate(); - assert(s->isEmpty(s)); - assert(s->size(s) == 0); - - char *c1 = "12"; - char *c2 = "23"; - char *c3 = "34"; - s->push(s, c1); - s->push(s, c2); - s->push(s, c3); - assert(s->size(s) == 3); - - char *c4 = "34"; - assert(strcmp(c4, (char *)s->pop(s)) == 0); - char *c5 = "23"; - assert(strcmp(c5, (char *)s->peek(s)) == 0); - - s->clear(s); - assert(s->isEmpty(s)); - - s->destory(s); - - printf("All stack.c tests completed.\n\n"); - return 0; -} diff --git a/java/arraylist/MyArrayList.java b/java/arraylist/MyArrayList.java deleted file mode 100644 index f0247f2..0000000 --- a/java/arraylist/MyArrayList.java +++ /dev/null @@ -1,110 +0,0 @@ -package arraylist; - -import java.lang.ArrayIndexOutOfBoundsException; -import java.util.Iterator; -import java.util.NoSuchElementException; - -public class MyArrayList implements Iterable{ - private static final int DEFAULT_CAPACITY = 10; - - private int size; - private AnyType[] items; - - public MyArrayList() { - clear(); - } - - public void clear() { - this.size = 0; - this.ensureCapacity(DEFAULT_CAPACITY); - } - - public int size() { - return this.size; - } - - public AnyType get(int index) { - if (index < 0 || index >= this.size()) { - throw new ArrayIndexOutOfBoundsException(); - } - return this.items[index]; - } - - public void set(int index, AnyType newVal) { - if (index < 0 || index >= this.size()) { - throw new ArrayIndexOutOfBoundsException(); - } - this.items[index] = newVal; - } - - public void add(int index, AnyType item) { - if (this.size() == this.items.length) { - ensureCapacity(2 * this.size()); - } - - for (int i = this.size; i > index; i--) { - this.items[i] = this.items[i - 1]; - } - this.items[index] = item; - - this.size++; - } - - public void add(AnyType item) { - this.add(this.size(), item); - } - - public AnyType remove(int index) { - if (index < 0 || index >= this.size()) { - throw new ArrayIndexOutOfBoundsException(); - } - AnyType removedItem = this.items[index]; - for (int i = index; i < this.size(); i++) { - this.items[i] = this.items[i + 1]; - } - - this.size--; - return removedItem; - } - - @SuppressWarnings("unchecked") - private void ensureCapacity(int newCapacity) { - if (newCapacity < this.size) - return; - - AnyType[] old = this.items; - items = (AnyType[]) new Object[newCapacity]; - for (int i = 0; i < this.size(); i++) { - items[i] = old[i]; - } - } - - @Override - public Iterator iterator() { - return new ArrayListIterator(); - } - - private class ArrayListIterator implements Iterator { - private int current = 0; - - @Override - public boolean hasNext() { - return current < size(); - } - - @Override - public AnyType next() { - if (!hasNext()) { - throw new NoSuchElementException(); - } - return items[current++]; - } - - @Override - public void remove() { - MyArrayList.this.remove(--current); - } - - } - -} diff --git a/java/arraylist/MyArrayListTest.java b/java/arraylist/MyArrayListTest.java deleted file mode 100644 index 50adc4e..0000000 --- a/java/arraylist/MyArrayListTest.java +++ /dev/null @@ -1,92 +0,0 @@ -package arraylist; - -import static org.junit.Assert.*; - -import java.util.Iterator; - -import org.junit.Before; -import org.junit.Test; - -public class MyArrayListTest { - - private static MyArrayList list = new MyArrayList(); - - @Before - public void setUp() throws Exception { - list.clear(); - } - - @Test - public void testClear() { - list.clear(); - assertEquals(0, list.size()); - } - - @Test - public void testSize() { - assertEquals(0, list.size()); - list.add("1"); - assertEquals(1, list.size()); - } - - @Test - public void testGet() { - list.add("1"); - list.add("2"); - list.add("3"); - String item = list.get(0); - assertEquals("1", item); - } - - @Test - public void testSet() { - list.add("1"); - list.add("2"); - list.add("3"); - list.set(2, "4"); - assertEquals("4", list.get(2)); - } - - @Test - public void testAddIntAnyType() { - list.add("1"); - list.add("2"); - list.add("3"); - list.add(0, "4"); - assertEquals("4", list.get(0)); - } - - @Test - public void testAddAnyType() { - list.add("1"); - list.add("2"); - list.add("3"); - assertEquals(3, list.size()); - assertEquals("3", list.get(2)); - } - - @Test - public void testRemove() { - list.add("1"); - list.add("2"); - list.add("3"); - list.remove(2); - assertEquals(2, list.size()); - assertEquals("2", list.get(1)); - } - - public void testIterator() { - list.add("1"); - list.add("2"); - list.add("3"); - - for (Iterator it = list.iterator(); it.hasNext();) { - String item = it.next(); - if (item.equals("2")) { - it.remove(); - } - } - assertEquals("3", list.get(1)); - } - -} diff --git a/java/queue/Queue.java b/java/queue/Queue.java deleted file mode 100644 index 02d1d2d..0000000 --- a/java/queue/Queue.java +++ /dev/null @@ -1,68 +0,0 @@ -package queue; - -public class Queue { - private static final int DEFAULT_CAPACITY = 10; - - private T[] dataStore; - private int theSize; - - public Queue() { - this.theSize = 0; - this.ensureCapacity(DEFAULT_CAPACITY); - } - - public void enqueue(T element) { - if (this.theSize == this.dataStore.length) { - this.ensureCapacity(this.theSize *2); - } - this.dataStore[this.theSize++] = element; - } - - public T dequeue() { - if (this.size() == 0) return null; - - T d = this.dataStore[0]; - if (this.size() > 1) { - for (int i = 0; i < this.theSize-1; i++) { - this.dataStore[i] = this.dataStore[i+1]; - } - } - this.theSize--; - - return d; - } - - public int size() { - return this.theSize; - } - - public boolean isEmpty() { - return this.theSize == 0; - } - - public T font() { - if (this.size() == 0) return null; - return this.dataStore[0]; - } - - public T end() { - if (this.size() == 0) return null; - return this.dataStore[this.theSize-1]; - } - - public void clear() { - this.theSize = 0; - } - - @SuppressWarnings("unchecked") - private void ensureCapacity(int newCapa) { - if (this.theSize > newCapa) return; - - T[] old = this.dataStore; - this.dataStore = (T[])new Object[newCapa]; - for (int i = 0; i < this.theSize; i++) { - this.dataStore[i] = old[i]; - } - } - -} diff --git a/java/queue/QueueTest.java b/java/queue/QueueTest.java deleted file mode 100644 index 0f1cf5f..0000000 --- a/java/queue/QueueTest.java +++ /dev/null @@ -1,81 +0,0 @@ -package queue; - -import static org.junit.Assert.*; - -import org.junit.Before; -import org.junit.Test; - -public class QueueTest { - private static Queue q = new Queue(); - - @Before - public void setUp() throws Exception { - q = new Queue(); - } - - @Test - public void testEnqueue() { - q.enqueue("1"); - q.enqueue("2"); - - assertEquals(2, q.size()); - assertEquals(false, q.isEmpty()); - } - - @Test - public void testDequeue() { - q.enqueue("2"); - q.enqueue("3"); - - assertEquals("2", q.dequeue()); - assertEquals(1, q.size()); - } - - @Test - public void testSize() { - q.enqueue("3"); - q.enqueue("4"); - assertEquals(2, q.size()); - - q.dequeue(); - assertEquals(1, q.size()); - - q.dequeue(); - assertEquals(0, q.size()); - assertEquals(true, q.isEmpty()); - } - - @Test - public void testIsEmpty() { - assertEquals(true, q.isEmpty()); - q.enqueue("test"); - assertEquals(false, q.isEmpty()); - } - - @Test - public void testFont() { - q.enqueue("5"); - q.enqueue("6"); - q.enqueue("7"); - - assertEquals("5", q.font()); - } - - @Test - public void testEnd() { - q.enqueue("5"); - q.enqueue("6"); - q.enqueue("7"); - - assertEquals("7", q.end()); - } - - @Test - public void testClear() { - q.enqueue("5"); - q.enqueue("7"); - - q.clear(); - assertEquals(true, q.isEmpty()); - } -} diff --git a/java/stack/Stack.java b/java/stack/Stack.java deleted file mode 100644 index 535eb96..0000000 --- a/java/stack/Stack.java +++ /dev/null @@ -1,55 +0,0 @@ -package stack; - -public class Stack { - private static final int DEFAULT_CAPACITY = 10; - - private T[] dataStore; - private int top; - - public Stack() { - this.top = 0; - this.ensureCapacity(DEFAULT_CAPACITY); - } - - public int size() { - return this.top; - } - - public boolean isEmpty() { - return this.top == 0; - } - - public void push(T element) { - if (this.top == this.dataStore.length) { - this.ensureCapacity(2*this.size()); - } - this.dataStore[this.top++] = element; - } - - public T pop() { - if (this.isEmpty()) return null; - - return this.dataStore[--this.top]; - } - - public T peek() { - if (this.isEmpty()) return null; - - return this.dataStore[this.top-1]; - } - - public void clear() { - this.top = 0; - } - - @SuppressWarnings("unchecked") - private void ensureCapacity(int newCapacity) { - if (newCapacity < this.size()) return; - - T[] old = this.dataStore; - this.dataStore = (T[])new Object[newCapacity]; - for (int i = 0; i < this.size(); i++) { - this.dataStore[i] = old[i]; - } - } -} diff --git a/java/stack/StackTest.java b/java/stack/StackTest.java deleted file mode 100644 index 90bf525..0000000 --- a/java/stack/StackTest.java +++ /dev/null @@ -1,33 +0,0 @@ -package stack; - -import static org.junit.Assert.*; - -import org.junit.Test; - -public class StackTest { - - @Test - public void testStack() { - Stack stack = new Stack(); - assertTrue(stack.isEmpty()); - assertEquals(0, stack.size()); - - stack.push("1"); - stack.push("2"); - stack.push("3"); - assertEquals(3, stack.size()); - - String p1 = stack.pop(); - assertEquals("3", p1); - assertEquals(2, stack.size()); - - String p2 = stack.peek(); - assertEquals("2", p2); - assertEquals(2, stack.size()); - - stack.clear(); - assertTrue(stack.isEmpty()); - - } - -} diff --git a/javascript/arraylist/ArrayList.js b/javascript/arraylist/ArrayList.js deleted file mode 100644 index d8d5214..0000000 --- a/javascript/arraylist/ArrayList.js +++ /dev/null @@ -1,81 +0,0 @@ -// 迭代器 -function Iterator(list) { - this.list = list; - this.current = 0; -} - -Iterator.prototype.hasNext = function() { - return this.current < this.list.size(); -}; - -Iterator.prototype.next = function() { - if (!this.hasNext()) { - throw new Error('No Such Element Exception!'); - } - - return this.list.get(current++); -}; - -Iterator.prototype.remove = function() { - this.list.remove(--current); -}; - - -// ArrayList 构造函数 -function ArrayList() { - this.dataStore = []; -} - -ArrayList.prototype.size = function() { - return this.dataStore.length; -}; - -ArrayList.prototype.get = function(index) { - if (index < 0 || index >= this.size()) { - throw new Error('index:' + index + ' out of bounds exception!'); - } - - return this.dataStore[index]; -}; - -ArrayList.prototype.set = function(index, newVal) { - if (index < 0 || index >= this.size()) { - throw new Error('index:' + index + ' out of bounds exception!'); - } - - this.dataStore[index] = newVal; -}; - -ArrayList.prototype.insert = function(index, val) { - if (index < 0 || index >= this.size()) { - throw new Error('index:' + index + ' out of bounds exception!'); - } - - this.dataStore.splice(index, 0, val); -}; - -ArrayList.prototype.append = function(val) { - this.dataStore.push(val); -}; - -ArrayList.prototype.remove = function(index) { - if (index < 0 || index >= this.size()) { - throw new Error('index:' + index + ' out of bounds exception!'); - } - - this.dataStore.splice(index, 1); -}; - -ArrayList.prototype.clear = function() { - this.dataStore = []; -}; - -ArrayList.prototype.iterator = function() { - return new Iterator(this); -}; - -ArrayList.prototype.toString = function() { - return this.dataStore.join(); -}; - -module.exports = ArrayList; diff --git a/javascript/arraylist/ArrayList_test.js b/javascript/arraylist/ArrayList_test.js deleted file mode 100644 index e4c7b4a..0000000 --- a/javascript/arraylist/ArrayList_test.js +++ /dev/null @@ -1,123 +0,0 @@ -var assert = require('assert'); -var ArrayList = require('./ArrayList.js'); - -describe('ArrayList', function() { - describe('#size()', function() { - it('should return 0 when the ArrayList is empty', function() { - var arrayList = new ArrayList(); - assert.equal(0, arrayList.size()); - }); - - it('should return valid value', function() { - var arrayList = new ArrayList(); - arrayList.append(1); - arrayList.append(2); - arrayList.append(3); - assert.equal(3, arrayList.size()); - - arrayList.remove(2); - assert.equal(2, arrayList.size()); - }); - }); - - describe('#get(index)', function() { - it('should return valid value', function() { - var arrayList = new ArrayList(); - arrayList.append(1); - arrayList.append(2); - arrayList.append(3); - assert.equal(3, arrayList.get(2)); - assert.equal(2, arrayList.get(1)); - }); - }); - - describe('#set(index, newVal)', function() { - it('should set success and have no error', function() { - var arrayList = new ArrayList(); - arrayList.append(1); - arrayList.append(2); - arrayList.append(3); - assert.equal(3, arrayList.get(2)); - - assert.doesNotThrow(function() { - arrayList.set(2, 4); - }); - assert.equal(4, arrayList.get(2)); - - assert.throws(function() { - arrayList.set(3, 4); - }, Error); - }); - }); - - describe('#insert(index, val)', function() { - it('should insert success and have no error', function() { - var arrayList = new ArrayList(); - arrayList.append(1); - assert.doesNotThrow(function() { - arrayList.insert(0, 2); - }); - - assert.equal(2, arrayList.get(0)); - assert.equal(2, arrayList.size()); - }); - }); - - describe('#append(val)', function() { - it('should append success and have no error', function() { - var arrayList = new ArrayList(); - arrayList.append(1); - - assert.equal(1, arrayList.get(0)); - assert.equal(1, arrayList.size()); - }); - }); - - describe('#remove(index)', function() { - it('should remove success and have no error', function() { - var arrayList = new ArrayList(); - arrayList.append(1); - arrayList.append(2); - arrayList.append(3); - assert.equal(3, arrayList.size()); - - arrayList.remove(0); - assert.equal(2, arrayList.get(0)); - assert.equal(2, arrayList.size()); - }); - }); - - describe('#clear()', function() { - it('should clear success and have no error', function() { - var arrayList = new ArrayList(); - arrayList.append(1); - arrayList.append(2); - arrayList.append(3); - - arrayList.clear(); - assert.equal(0, arrayList.size()); - }); - }); - - describe('#iterator()', function() { - it('should return a valid value', function() { - var arrayList = new ArrayList(); - arrayList.append(1); - arrayList.append(2); - var iterator = arrayList.iterator(); - assert.notEqual(undefined, iterator); - assert.notEqual(undefined, iterator.next); - }); - }); - - describe('#toString()', function() { - it('should return a valid value', function() { - var arrayList = new ArrayList(); - arrayList.append(1); - arrayList.append(2); - - assert.equal('1,2', arrayList.toString()); - }); - }); - -}); diff --git a/javascript/linkedlist/LinkedList.js b/javascript/linkedlist/LinkedList.js deleted file mode 100644 index 21e7790..0000000 --- a/javascript/linkedlist/LinkedList.js +++ /dev/null @@ -1,84 +0,0 @@ -// Node构造函数 -function Node(element) { - this.element = element; - this.prev = null; - this.next = null; -} - -function LinkedList() { - this.head = new Node('head'); -} - -LinkedList.prototype.find = function(elem) { - var pos = this.head; - while (pos.next !== null) { - pos = pos.next; - if (pos.element === elem) return pos; - } - return null; -}; - -LinkedList.prototype.findLast = function() { - var pos = this.head; - while (pos.next !== null) { - pos = pos.next; - } - return pos; -}; - -LinkedList.prototype.display = function() { - var pos = this.head; - while (pos.next !== null) { - console.log(pos.next.element); - pos = pos.next; - } -}; - -LinkedList.prototype.dispReverse = function() { - var pos = this.findLast(); - while (pos.prev !== null) { - console.log(pos.prev.element); - pos = pos.prev; - } -}; - -LinkedList.prototype.insert = function(newElem) { - var node = new Node(newElem); - var last = this.findLast(); - if (last === null) { - this.head.next = node; - node.prev = this.head; - } - - last.next = node; - node.prev = last; -}; - -LinkedList.prototype.insertAfter = function(newElem, afterElem) { - var node = new Node(newElem); - var pos = this.find(afterElem); - - if (pos === null) return; - if (pos.next !== null) { - node.next = pos.next; - pos.next.prev = node; - } - - node.prev = pos; - pos.next = node; -}; - -LinkedList.prototype.remove = function(elem) { - var pos = this.find(elem); - if (pos === null) return; - - if (pos.next !== null) { - pos.next.prev = pos.prev; - } - pos.prev.next = pos.next; - pos.prev = null; - pos.next = null; -}; - - -module.exports = LinkedList; diff --git a/javascript/linkedlist/LinkedList_test.js b/javascript/linkedlist/LinkedList_test.js deleted file mode 100644 index 972aa6a..0000000 --- a/javascript/linkedlist/LinkedList_test.js +++ /dev/null @@ -1,25 +0,0 @@ -var assert = require('assert'); -var LinkedList = require('./LinkedList.js'); - -describe('Queue', function() { - describe('#insert, #insertAfter, #find, #findLast, #remove', function() { - it('should all success.', function() { - var list = new LinkedList(); - list.insert('1'); - list.insert('2'); - list.insert('3'); - assert.equal('2', list.find('3').prev.element); - assert.equal('3', list.findLast().element); - - list.insertAfter('4', '2'); - assert.equal('4', list.find('3').prev.element); - assert.equal('4', list.find('2').next.element); - assert.equal('3', list.findLast().element); - - list.remove('4'); - assert.equal('2', list.find('3').prev.element); - - }); - }); - -}); diff --git a/javascript/queue/Queue.js b/javascript/queue/Queue.js deleted file mode 100644 index 1172294..0000000 --- a/javascript/queue/Queue.js +++ /dev/null @@ -1,38 +0,0 @@ -// Queue构造函数 -function Queue() { - this.dataStore = []; -} - - -Queue.prototype.enqueue = function(element) { - this.dataStore.push(element); -}; - -Queue.prototype.dequeue = function() { - return this.dataStore.shift(); -}; - -Queue.prototype.size = function() { - return this.dataStore.length; -}; - -Queue.prototype.isEmpty = function() { - return this.dataStore.length == 0; -}; - -Queue.prototype.front = function() { - if (this.isEmpty()) return null; - return this.dataStore[0]; -}; - -Queue.prototype.end = function() { - if (this.isEmpty()) return null; - return this.dataStore[this.size()-1]; -}; - -Queue.prototype.clear = function() { - this.dataStore.length = 0; -}; - - -module.exports = Queue; diff --git a/javascript/queue/Queue_test.js b/javascript/queue/Queue_test.js deleted file mode 100644 index e553fbe..0000000 --- a/javascript/queue/Queue_test.js +++ /dev/null @@ -1,27 +0,0 @@ -var assert = require('assert'); -var Queue = require('./Queue.js'); - -describe('Queue', function() { - describe('#enqueue, #dequeue, #size, #isEmpty, #front, #end, #clear', function() { - it('should all success.', function() { - var q = new Queue(); - q.enqueue(1); - q.enqueue(2); - q.enqueue(3); - - assert.equal(3, q.size()); - assert.equal(false, q.isEmpty()); - - var d = q.dequeue(); - assert.equal(1, d); - assert.equal(2, q.size()); - assert.equal(3, q.end()); - assert.equal(2, q.front()); - - q.clear(); - assert.equal(true, q.isEmpty()); - - }); - }); - -}); diff --git a/javascript/stack/Stack.js b/javascript/stack/Stack.js deleted file mode 100644 index 709f6b2..0000000 --- a/javascript/stack/Stack.js +++ /dev/null @@ -1,30 +0,0 @@ -// Stack构造函数 -function Stack() { - this.dataStore = []; -} - -Stack.prototype.push = function(element) { - this.dataStore.push(element); -}; - -Stack.prototype.pop = function() { - return this.dataStore.pop(); -}; - -Stack.prototype.peek = function() { - return this.dataStore[this.size()-1]; -}; - -Stack.prototype.clear = function() { - this.dataStore.length = 0; -}; - -Stack.prototype.size = function() { - return this.dataStore.length; -}; - -Stack.prototype.isEmpty = function() { - return this.dataStore.length === 0; -}; - -module.exports = Stack; diff --git a/javascript/stack/Stack_test.js b/javascript/stack/Stack_test.js deleted file mode 100644 index f56863c..0000000 --- a/javascript/stack/Stack_test.js +++ /dev/null @@ -1,39 +0,0 @@ -var assert = require('assert'); -var Stack = require('./Stack.js'); - -describe('Stack', function() { - describe('#size(), #push()', function() { - it('should return 1 when push a element into Stack.', function() { - var stack = new Stack(); - stack.push('1'); - assert.equal(1, stack.size()); - }); - }); - - describe('#pop(), #peek()', function() { - it('should return valid value and length.', function() { - var stack = new Stack(); - stack.push('1'); - stack.push('2'); - stack.push('3'); - stack.push('4'); - - assert.equal(4, stack.pop()); - assert.equal(3, stack.size()); - assert.equal('3', stack.peek()); - }); - }); - - describe('#clear(), #isEmpty()', function() { - it('should return 0, when Stack called clear()', function() { - var stack = new Stack(); - assert(stack.isEmpty()); - stack.push('1'); - stack.push('2'); - stack.push('3'); - stack.clear(); - assert.equal(0, stack.size()); - }); - }); - -}); diff --git a/rust/Cargo.toml b/rust/Cargo.toml new file mode 100644 index 0000000..76256b7 --- /dev/null +++ b/rust/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "the_algorithms" +edition = "2021" +version = "0.1.0" +authors = ["Lance Li "] + +[dependencies] + +[dev-dependencies] diff --git a/rust/src/lib.rs b/rust/src/lib.rs new file mode 100644 index 0000000..e69de29 From c70357d7df8d1b4639d737e22a9c5858ae84b92b Mon Sep 17 00:00:00 2001 From: nange Date: Tue, 4 Jul 2023 19:46:22 +0800 Subject: [PATCH 02/50] update --- .gitignore | 2 +- README.md | 4 +++ go/go.mod | 5 +++ rust/src/lib.rs | 1 + rust/src/linkedlist/merge_two_lists.rs | 50 ++++++++++++++++++++++++++ rust/src/linkedlist/mod.rs | 1 + 6 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 go/go.mod create mode 100644 rust/src/linkedlist/merge_two_lists.rs create mode 100644 rust/src/linkedlist/mod.rs diff --git a/.gitignore b/.gitignore index 53149f7..80b4304 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ pkg .idea -java/.project +go.sum /rust/target **/*.rs.bk diff --git a/README.md b/README.md index d47be31..6b89a37 100644 --- a/README.md +++ b/README.md @@ -55,3 +55,7 @@ 二叉树可以看作是链表的二维扩展,链表的一个节点只有一个next指针,但是二叉树的一个节点可以有两个 next指针(通常被称作left,right),二维扩展后可以解决一维链表的一些问题,比如查找慢等。 二叉查找树可以把查找,添加,删除元素的时间复杂度降到O(logN)。 + +## 参考资料 + +本项目内容主要参考:[labuladong 的算法小抄](https://labuladong.github.io/algo/) diff --git a/go/go.mod b/go/go.mod new file mode 100644 index 0000000..baaf7ec --- /dev/null +++ b/go/go.mod @@ -0,0 +1,5 @@ +module the_algorithms + +go 1.20 + +require github.com/mitchellh/hashstructure v1.1.0 diff --git a/rust/src/lib.rs b/rust/src/lib.rs index e69de29..34f5394 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -0,0 +1 @@ +pub mod linkedlist; diff --git a/rust/src/linkedlist/merge_two_lists.rs b/rust/src/linkedlist/merge_two_lists.rs new file mode 100644 index 0000000..9464aa0 --- /dev/null +++ b/rust/src/linkedlist/merge_two_lists.rs @@ -0,0 +1,50 @@ +/// 合并两个有序链表:https://leetcode.cn/problems/merge-two-sorted-lists/ + +/// Definition for singly-linked list. +#[derive(PartialEq, Eq, Clone, Debug)] +pub struct ListNode { + pub val: i32, + pub next: Option>, +} + +impl ListNode { + fn new(val: i32) -> Self { + ListNode { next: None, val } + } +} + +pub fn merge_two_lists( + list1: Option>, + list2: Option>, +) -> Option> { + if list1.is_none() { + return list2; + } + if list2.is_none() { + return list1; + } + + let mut dummy = ListNode::new(-1); + let mut p = &mut dummy; + + let mut p1 = list1.unwrap(); + let mut p2 = list2.unwrap(); + + loop {} + + todo!() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_list_empty() { + let ret1 = merge_two_lists(None, None); + assert!(ret1.is_none()); + + let ret2 = merge_two_lists(None, Some(Box::new(ListNode::new(1)))); + assert_eq!(ret2, Some(Box::new(ListNode::new(1)))); + } +} diff --git a/rust/src/linkedlist/mod.rs b/rust/src/linkedlist/mod.rs new file mode 100644 index 0000000..1643a71 --- /dev/null +++ b/rust/src/linkedlist/mod.rs @@ -0,0 +1 @@ +mod merge_two_lists; From e7aba8c995b5738aa92a83c7b3cbce50c5a5acc7 Mon Sep 17 00:00:00 2001 From: nange Date: Wed, 5 Jul 2023 20:05:04 +0800 Subject: [PATCH 03/50] update --- README.md | 48 +++----- go/go.mod | 11 +- go/linkedlist/linkedlist.go | 159 ------------------------- go/linkedlist/linkedlist_test.go | 142 ---------------------- go/linkedlist/merge_two_lists.go | 47 ++++++++ go/linkedlist/merge_two_lists_test.go | 51 ++++++++ rust/src/linkedlist/merge_two_lists.rs | 61 ++++++---- 7 files changed, 162 insertions(+), 357 deletions(-) delete mode 100644 go/linkedlist/linkedlist.go delete mode 100644 go/linkedlist/linkedlist_test.go create mode 100644 go/linkedlist/merge_two_lists.go create mode 100644 go/linkedlist/merge_two_lists_test.go diff --git a/README.md b/README.md index 6b89a37..e58cecc 100644 --- a/README.md +++ b/README.md @@ -10,51 +10,33 @@ ## 目录 -1. [列表](#user-content-列表) -2. [栈](#user-content-栈) -3. [队列](#user-content-队列) -4. [链表](#user-content-链表) -5. [字典](#user-content-字典) -6. [二叉树和二叉查找树](#user-content-二叉树和二叉查找树) -7. [图和图算法](#user-content-图和图算法) -8. [排序算法](#user-content-排序算法) -9. [检索算法](#user-content-检索算法) +### 链表相关 -### 列表 +#### 双指针技巧 -列表是非常常见的一种数据结构,比如日常所见的购物清单、待办事项等等。 -它提供了对列表数据的一系列操作,比如:添加、删除、修改、遍历等功能。 -当我们把这样的具体问题抽象成用列表去解决的时候,往往可以简化问题。 +此技巧可用于解决一下一些问题: +1、合并两个有序链表 -### 栈 +2、链表的分解 -栈也是一种非常常见的数据结构,在计算机的世界里,在计算机的世界里, -栈是一种很高效的数据结构,因为数据只能在栈顶添加或者删除. -因此栈也被称为一种后入先出的数据结构.栈的使用遍布程序语言实现的方方面面, -从表达式求值到函数调用. +3、合并 k 个有序链表 -### 队列 +4、寻找单链表的倒数第 k 个节点 -队列是一种前进先出的数据结构. 在日常生活中非常常见:比如去银行排队办理业务. -在计算机中也极其常见, 很多情况下,当有大量任务需要完成时, 就会把任务暂时加入到 -任务队列中, 执行一个删除一个,继续执行下一个任务. +5、寻找单链表的中点 -### 链表 +6、判断单链表是否包含环并找出环起点 -有时候数组不一定是最佳的组织数据的数据结构,因为数组通常都是固定大小的,当数据填满时, -再加入新元素就变得很困难。在数组中,添加和删除元素也很麻烦,因为要移动数组中的其他元素。 -因此如果需要频繁的添加或者删除元素,可以考虑使用链表组织数据。 +7、判断两个单链表是否相交并找出交点 -### 字典 +##### 合并两个有序链表 -字典类型(map),是一种KV结构,通过key能够找到对应的value,map类型的特点是查询非常快(O(1)), -原理是通过一个hash函数能快速的找到对应的key的位置,在实际编程中,map类型几乎无处不在。 +[Go实现](/go/linkedlist/merge_two_lists.go)、[Rust实现](/rust/src/linkedlist/merge_two_lists.rs) -### 二叉树和二叉查找(搜索)树 +代码中用到了`p1`, `p2`两个指针用于比较两个链表节点的推进,`p`指针用于生成新链表节点的推进。在这个过程中没有数据的复制,只有对指针指向的改变,效率很高。 -二叉树可以看作是链表的二维扩展,链表的一个节点只有一个next指针,但是二叉树的一个节点可以有两个 -next指针(通常被称作left,right),二维扩展后可以解决一维链表的一些问题,比如查找慢等。 -二叉查找树可以把查找,添加,删除元素的时间复杂度降到O(logN)。 +代码中还用到一个链表的算法题中是很常见的「虚拟头结点」技巧,也就是 `dummy` 节点。如果不使用 dummy 虚拟节点,代码会复杂一些,需要额外处理指针 `p` 为空的情况。 +> 什么时候需要用虚拟头结点?当需要创造一条新链表的时候,可以使用虚拟头结点简化边界情况的处理。 ## 参考资料 diff --git a/go/go.mod b/go/go.mod index baaf7ec..eef2336 100644 --- a/go/go.mod +++ b/go/go.mod @@ -2,4 +2,13 @@ module the_algorithms go 1.20 -require github.com/mitchellh/hashstructure v1.1.0 +require ( + github.com/mitchellh/hashstructure v1.1.0 + github.com/stretchr/testify v1.8.4 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go/linkedlist/linkedlist.go b/go/linkedlist/linkedlist.go deleted file mode 100644 index 036ceb4..0000000 --- a/go/linkedlist/linkedlist.go +++ /dev/null @@ -1,159 +0,0 @@ -package linkedlist - -type Lister interface { - Clear() - Len() int - Front() *Element - Back() *Element - Remove(e *Element) interface{} - PushFront(v interface{}) *Element - PushBack(v interface{}) *Element - InsertBefore(v interface{}, mark *Element) *Element - InsertAfter(v interface{}, mark *Element) *Element - MoveBefore(e, mark *Element) - MoveAfter(e, mark *Element) - MoveToFront(e *Element) -} - -type Element struct { - prev, next *Element - list *List - Value interface{} -} - -func (e *Element) Next() *Element { - if n := e.next; n != nil && n != &e.list.root { - return n - } - return nil -} - -func (e *Element) Prev() *Element { - if p := e.prev; p != nil && p != &e.list.root { - return p - } - return nil -} - -type List struct { - root Element - len int -} - -func New() *List { - l := new(List) - l.Clear() - return l -} - -func (l *List) Clear() { - if firstElem := l.root.next; firstElem != nil && firstElem.list == l { - firstElem.prev = nil - } - if lastElem := l.root.prev; lastElem != nil && lastElem.list == l { - lastElem.next = nil - } - l.root.next = &l.root - l.root.prev = &l.root - l.len = 0 -} - -func (l *List) Len() int { - return l.len -} - -func (l *List) Front() *Element { - if l.len == 0 { - return nil - } - return l.root.next -} - -func (l *List) Back() *Element { - if l.len == 0 { - return nil - } - return l.root.prev -} - -func (l *List) remove(e *Element) *Element { - e.prev.next = e.next - e.next.prev = e.prev - e.prev = nil - e.next = nil - e.list = nil - l.len-- - return e -} - -func (l *List) Remove(e *Element) interface{} { - if e.list == l { - l.remove(e) - } - return e.Value -} - -func (l *List) insert(e, at *Element) *Element { - n := at.next - at.next = e - e.prev = at - e.next = n - n.prev = e - e.list = l - l.len++ - return e -} - -func (l *List) insertValue(v interface{}, at *Element) *Element { - return l.insert(&Element{Value: v}, at) -} - -func (l *List) PushFront(v interface{}) *Element { - return l.insertValue(v, &l.root) -} - -func (l *List) PushBack(v interface{}) *Element { - return l.insertValue(v, l.root.prev) -} - -func (l *List) InsertBefore(v interface{}, mark *Element) *Element { - if mark.list != l { - return nil - } - return l.insertValue(v, mark.prev) -} - -func (l *List) InsertAfter(v interface{}, mark *Element) *Element { - if mark.list != l { - return nil - } - return l.insertValue(v, mark) -} - -func (l *List) MoveBefore(e, mark *Element) { - if e.list != l || mark.list != l || e == mark { - return - } - l.insert(l.remove(e), mark.prev) -} - -func (l *List) MoveAfter(e, mark *Element) { - if e.list != l || mark.list != l || e == mark { - return - } - l.insert(l.remove(e), mark) -} - -func (l *List) MoveToFront(e *Element) { - if e.list != l || l.root.next == e { - return - } - l.insert(l.remove(e), &l.root) -} - -func (l *List) MoveToBack(e *Element) { - if e.list != l || l.root.prev == e { - return - } - l.insert(l.remove(e), l.root.prev) -} diff --git a/go/linkedlist/linkedlist_test.go b/go/linkedlist/linkedlist_test.go deleted file mode 100644 index 4e8209a..0000000 --- a/go/linkedlist/linkedlist_test.go +++ /dev/null @@ -1,142 +0,0 @@ -package linkedlist - -import "testing" - -func checkListLen(t *testing.T, l *List, len int) bool { - if n := l.Len(); n != len { - t.Errorf("l.Len() = %d, want %d\n", n, len) - return false - } - return true -} - -func checkListPointers(t *testing.T, l *List, es []*Element) { - root := &l.root - - if !checkListLen(t, l, len(es)) { - return - } - - if len(es) == 0 { - if l.root.next != root || l.root.prev != root { - t.Errorf("l.root.next = %p, l.root.prev = %p; both should be %p\n", - l.root.next, l.root.prev, root) - } - return - } - - for i, e := range es { - prev := root - Prev := (*Element)(nil) - if i > 0 { - prev = es[i-1] - Prev = prev - } - if p := e.prev; p != prev { - t.Errorf("es[%d](%p).prev = %p, want %p", i, e.prev, prev) - } - if p := e.Prev(); p != Prev { - t.Errorf("es[%d](%p).Prev() = %p, want %p", i, e.Prev(), prev) - } - - next := root - Next := (*Element)(nil) - if i < len(es)-1 { - next = es[i+1] - Next = next - } - if n := e.next; n != next { - t.Errorf("es[%d](%p).next = %p, want %p", i, e, n, next) - } - if n := e.Next(); n != Next { - t.Errorf("es[%d](%p).Next() = %p, want %p", i, e, n, Next) - } - } -} - -func TestList(t *testing.T) { - l := New() - checkListPointers(t, l, []*Element{}) - - // Single element list - e := l.PushFront("a") - checkListPointers(t, l, []*Element{e}) - l.MoveToFront(e) - checkListPointers(t, l, []*Element{e}) - l.MoveToBack(e) - checkListPointers(t, l, []*Element{e}) - l.Remove(e) - checkListPointers(t, l, []*Element{}) - - // Bigger list - e2 := l.PushFront(2) - e1 := l.PushFront(1) - e3 := l.PushBack(3) - e4 := l.PushBack("banana") - checkListPointers(t, l, []*Element{e1, e2, e3, e4}) - - l.Remove(e2) - checkListPointers(t, l, []*Element{e1, e3, e4}) - - l.MoveToFront(e3) // move from middle - checkListPointers(t, l, []*Element{e3, e1, e4}) - - l.MoveToFront(e1) - l.MoveToBack(e3) // move from middle - checkListPointers(t, l, []*Element{e1, e4, e3}) - - l.MoveToFront(e3) // move from back - checkListPointers(t, l, []*Element{e3, e1, e4}) - l.MoveToFront(e3) // should be no-op - checkListPointers(t, l, []*Element{e3, e1, e4}) - - l.MoveToBack(e3) // move from front - checkListPointers(t, l, []*Element{e1, e4, e3}) - l.MoveToBack(e3) // should be no-op - checkListPointers(t, l, []*Element{e1, e4, e3}) - - e2 = l.InsertBefore(2, e1) // insert before front - checkListPointers(t, l, []*Element{e2, e1, e4, e3}) - l.Remove(e2) - e2 = l.InsertBefore(2, e4) // insert before middle - checkListPointers(t, l, []*Element{e1, e2, e4, e3}) - l.Remove(e2) - e2 = l.InsertBefore(2, e3) // insert before back - checkListPointers(t, l, []*Element{e1, e4, e2, e3}) - l.Remove(e2) - - e2 = l.InsertAfter(2, e1) // insert after front - checkListPointers(t, l, []*Element{e1, e2, e4, e3}) - l.Remove(e2) - e2 = l.InsertAfter(2, e4) // insert after middle - checkListPointers(t, l, []*Element{e1, e4, e2, e3}) - l.Remove(e2) - e2 = l.InsertAfter(2, e3) // insert after back - checkListPointers(t, l, []*Element{e1, e4, e3, e2}) - l.Remove(e2) - - // Check standard iteration. - sum := 0 - for e := l.Front(); e != nil; e = e.Next() { - if i, ok := e.Value.(int); ok { - sum += i - } - } - if sum != 4 { - t.Errorf("sum over l = %d, want 4", sum) - } - - // Clear all elements by iterating - var next *Element - for e := l.Front(); e != nil; e = next { - next = e.Next() - l.Remove(e) - } - checkListPointers(t, l, []*Element{}) - - l.PushBack(1) - l.PushBack(2) - l.PushBack(3) - l.Clear() - checkListPointers(t, l, []*Element{}) -} diff --git a/go/linkedlist/merge_two_lists.go b/go/linkedlist/merge_two_lists.go new file mode 100644 index 0000000..9ab80b0 --- /dev/null +++ b/go/linkedlist/merge_two_lists.go @@ -0,0 +1,47 @@ +package linkedlist + +// Ref: https://leetcode.cn/problems/merge-two-sorted-lists/ +// Definition for singly-linked list. +type ListNode struct { + Val int + Next *ListNode +} + +func MergeTwoLists(list1 *ListNode, list2 *ListNode) *ListNode { + if list1 == nil { + return list2 + } + if list2 == nil { + return list1 + } + + var p1 = list1 + var p2 = list2 + + var dummy = &ListNode{} + var p = dummy + + for { + if p1.Val >= p2.Val { + p.Next = p2 + p2 = p2.Next + } else { + p.Next = p1 + p1 = p1.Next + } + p = p.Next + + if p1 == nil || p2 == nil { + break + } + } + + if p1 != nil { + p.Next = p1 + } + if p2 != nil { + p.Next = p2 + } + + return dummy.Next +} diff --git a/go/linkedlist/merge_two_lists_test.go b/go/linkedlist/merge_two_lists_test.go new file mode 100644 index 0000000..d183f48 --- /dev/null +++ b/go/linkedlist/merge_two_lists_test.go @@ -0,0 +1,51 @@ +package linkedlist + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_MergeTwoLists(t *testing.T) { + l1 := MergeTwoLists(nil, nil) + assert.Nil(t, l1) + + l2 := MergeTwoLists(nil, &ListNode{}) + assert.Equal(t, l2, &ListNode{}) + + l3 := MergeTwoLists(&ListNode{ + Val: 1, + Next: &ListNode{ + Val: 3, + Next: &ListNode{ + Val: 5, + }, + }, + }, &ListNode{ + Val: 2, + Next: &ListNode{ + Val: 4, + Next: &ListNode{ + Val: 6, + }, + }, + }) + assert.Equal(t, l3, &ListNode{ + Val: 1, + Next: &ListNode{ + Val: 2, + Next: &ListNode{ + Val: 3, + Next: &ListNode{ + Val: 4, + Next: &ListNode{ + Val: 5, + Next: &ListNode{ + Val: 6, + }, + }, + }, + }, + }, + }) +} diff --git a/rust/src/linkedlist/merge_two_lists.rs b/rust/src/linkedlist/merge_two_lists.rs index 9464aa0..511455b 100644 --- a/rust/src/linkedlist/merge_two_lists.rs +++ b/rust/src/linkedlist/merge_two_lists.rs @@ -1,4 +1,4 @@ -/// 合并两个有序链表:https://leetcode.cn/problems/merge-two-sorted-lists/ +/// Ref:https://leetcode.cn/problems/merge-two-sorted-lists/ /// Definition for singly-linked list. #[derive(PartialEq, Eq, Clone, Debug)] @@ -7,36 +7,41 @@ pub struct ListNode { pub next: Option>, } -impl ListNode { - fn new(val: i32) -> Self { - ListNode { next: None, val } - } -} - pub fn merge_two_lists( list1: Option>, list2: Option>, ) -> Option> { - if list1.is_none() { - return list2; - } - if list2.is_none() { - return list1; - } + let mut dummy_head = Some(Box::new(ListNode { val: 0, next: None })); + let mut tail = &mut dummy_head; - let mut dummy = ListNode::new(-1); - let mut p = &mut dummy; - - let mut p1 = list1.unwrap(); - let mut p2 = list2.unwrap(); + let mut l1 = list1; + let mut l2 = list2; + while l1.is_some() && l2.is_some() { + if l1.as_ref().unwrap().val < l2.as_ref().unwrap().val { + let next = l1.as_mut().unwrap().next.take(); + tail.as_mut().unwrap().next = l1; + l1 = next; + } else { + let next = l2.as_mut().unwrap().next.take(); + tail.as_mut().unwrap().next = l2; + l2 = next; + } + tail = &mut tail.as_mut().unwrap().next; + } - loop {} + if l1.is_some() { + tail.as_mut().unwrap().next = l1; + } else { + tail.as_mut().unwrap().next = l2; + } - todo!() + dummy_head.unwrap().next } #[cfg(test)] mod tests { + use crate::linkedlist::merge_two_lists; + use super::*; #[test] @@ -44,7 +49,19 @@ mod tests { let ret1 = merge_two_lists(None, None); assert!(ret1.is_none()); - let ret2 = merge_two_lists(None, Some(Box::new(ListNode::new(1)))); - assert_eq!(ret2, Some(Box::new(ListNode::new(1)))); + let ret2 = merge_two_lists(None, Some(Box::new(ListNode { val: 1, next: None }))); + assert_eq!(ret2, Some(Box::new(ListNode { val: 1, next: None }))); + + let ret3 = merge_two_lists( + Some(Box::new(ListNode { val: 1, next: None })), + Some(Box::new(ListNode { val: 2, next: None })), + ); + assert_eq!( + ret3, + Some(Box::new(ListNode { + val: 1, + next: Some(Box::new(ListNode { val: 2, next: None })) + })) + ) } } From 3d89a9bf2faa2425d67b658a489bdfce489c252d Mon Sep 17 00:00:00 2001 From: nange Date: Tue, 11 Jul 2023 15:45:21 +0800 Subject: [PATCH 04/50] update --- rust/src/linkedlist/merge_two_lists.rs | 49 +++++++++++++++----------- 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/rust/src/linkedlist/merge_two_lists.rs b/rust/src/linkedlist/merge_two_lists.rs index 511455b..f9e568f 100644 --- a/rust/src/linkedlist/merge_two_lists.rs +++ b/rust/src/linkedlist/merge_two_lists.rs @@ -11,31 +11,34 @@ pub fn merge_two_lists( list1: Option>, list2: Option>, ) -> Option> { - let mut dummy_head = Some(Box::new(ListNode { val: 0, next: None })); - let mut tail = &mut dummy_head; + let mut dummy = Some(Box::new(ListNode { val: 0, next: None })); + let mut p = &mut dummy; - let mut l1 = list1; - let mut l2 = list2; - while l1.is_some() && l2.is_some() { - if l1.as_ref().unwrap().val < l2.as_ref().unwrap().val { - let next = l1.as_mut().unwrap().next.take(); - tail.as_mut().unwrap().next = l1; - l1 = next; + let mut p1 = list1; + let mut p2 = list2; + + while p1.is_some() && p2.is_some() { + if p1.as_ref().unwrap().val <= p2.as_ref().unwrap().val { + let next = p1.as_mut().unwrap().next.take(); + p.as_mut().unwrap().next = p1; + p1 = next; } else { - let next = l2.as_mut().unwrap().next.take(); - tail.as_mut().unwrap().next = l2; - l2 = next; + let next = p2.as_mut().unwrap().next.take(); + p.as_mut().unwrap().next = p2; + p2 = next; } - tail = &mut tail.as_mut().unwrap().next; + + p = &mut p.as_mut().unwrap().next; } - if l1.is_some() { - tail.as_mut().unwrap().next = l1; - } else { - tail.as_mut().unwrap().next = l2; + if p1.is_some() { + p.as_mut().unwrap().next = p1; + } + if p2.is_some() { + p.as_mut().unwrap().next = p2; } - dummy_head.unwrap().next + dummy.unwrap().next } #[cfg(test)] @@ -53,14 +56,20 @@ mod tests { assert_eq!(ret2, Some(Box::new(ListNode { val: 1, next: None }))); let ret3 = merge_two_lists( - Some(Box::new(ListNode { val: 1, next: None })), + Some(Box::new(ListNode { + val: 1, + next: Some(Box::new(ListNode { val: 3, next: None })), + })), Some(Box::new(ListNode { val: 2, next: None })), ); assert_eq!( ret3, Some(Box::new(ListNode { val: 1, - next: Some(Box::new(ListNode { val: 2, next: None })) + next: Some(Box::new(ListNode { + val: 2, + next: Some(Box::new(ListNode { val: 3, next: None })) + })) })) ) } From 26c4befb4b428f9a401efdbe2e3e10d6ea83f133 Mon Sep 17 00:00:00 2001 From: nange Date: Tue, 11 Jul 2023 17:32:31 +0800 Subject: [PATCH 05/50] update --- README.md | 5 +- go/linkedlist/merge_two_lists.go | 29 ++++++++-- go/linkedlist/merge_two_lists_test.go | 60 +++++++++++---------- rust/src/linkedlist/merge_two_lists.rs | 73 ++++++++++++++++++-------- 4 files changed, 113 insertions(+), 54 deletions(-) diff --git a/README.md b/README.md index e58cecc..b07597b 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,10 @@ 代码中用到了`p1`, `p2`两个指针用于比较两个链表节点的推进,`p`指针用于生成新链表节点的推进。在这个过程中没有数据的复制,只有对指针指向的改变,效率很高。 代码中还用到一个链表的算法题中是很常见的「虚拟头结点」技巧,也就是 `dummy` 节点。如果不使用 dummy 虚拟节点,代码会复杂一些,需要额外处理指针 `p` 为空的情况。 -> 什么时候需要用虚拟头结点?当需要创造一条新链表的时候,可以使用虚拟头结点简化边界情况的处理。 +> 什么时候需要用虚拟头结点?当需要创造一条新链表的时候,可以使用虚拟头结点简化边界情况的处理。比如说,把两条有序链表合并成一条新的有序链表,是不是要创造一条新链表?再比如想把一条链表分解成两条链表,是不是也在创造新链表?这些情况都可以使用虚拟头结点简化边界情况的处理。 +> 这个问题还有另外一种解法,就是利用递归,可以去掉两个指针,因为在递归栈中可以自动保存这两个状态。参考上面两个文件中的递归实现。通常来说,递归实现的代码都会更简单,但空间复杂度更高。 + +##### 单链表的分解 ## 参考资料 diff --git a/go/linkedlist/merge_two_lists.go b/go/linkedlist/merge_two_lists.go index 9ab80b0..32fce14 100644 --- a/go/linkedlist/merge_two_lists.go +++ b/go/linkedlist/merge_two_lists.go @@ -22,12 +22,12 @@ func MergeTwoLists(list1 *ListNode, list2 *ListNode) *ListNode { var p = dummy for { - if p1.Val >= p2.Val { - p.Next = p2 - p2 = p2.Next - } else { + if p1.Val <= p2.Val { p.Next = p1 p1 = p1.Next + } else { + p.Next = p2 + p2 = p2.Next } p = p.Next @@ -45,3 +45,24 @@ func MergeTwoLists(list1 *ListNode, list2 *ListNode) *ListNode { return dummy.Next } + +func MergeTwoListsWithRecursion(list1 *ListNode, list2 *ListNode) *ListNode { + if list1 == nil { + return list2 + } + if list2 == nil { + return list1 + } + + if list1.Val <= list2.Val { + return &ListNode{ + Val: list1.Val, + Next: MergeTwoListsWithRecursion(list1.Next, list2), + } + } else { + return &ListNode{ + Val: list2.Val, + Next: MergeTwoListsWithRecursion(list1, list2.Next), + } + } +} diff --git a/go/linkedlist/merge_two_lists_test.go b/go/linkedlist/merge_two_lists_test.go index d183f48..508eb0b 100644 --- a/go/linkedlist/merge_two_lists_test.go +++ b/go/linkedlist/merge_two_lists_test.go @@ -7,45 +7,51 @@ import ( ) func Test_MergeTwoLists(t *testing.T) { - l1 := MergeTwoLists(nil, nil) - assert.Nil(t, l1) + var fnList = []func(list1 *ListNode, list2 *ListNode) *ListNode{ + MergeTwoLists, MergeTwoListsWithRecursion, + } - l2 := MergeTwoLists(nil, &ListNode{}) - assert.Equal(t, l2, &ListNode{}) + for _, fn := range fnList { + l1 := fn(nil, nil) + assert.Nil(t, l1) - l3 := MergeTwoLists(&ListNode{ - Val: 1, - Next: &ListNode{ - Val: 3, + l2 := fn(nil, &ListNode{}) + assert.Equal(t, l2, &ListNode{}) + + l3 := fn(&ListNode{ + Val: 1, Next: &ListNode{ - Val: 5, + Val: 3, + Next: &ListNode{ + Val: 5, + }, }, - }, - }, &ListNode{ - Val: 2, - Next: &ListNode{ - Val: 4, + }, &ListNode{ + Val: 2, Next: &ListNode{ - Val: 6, + Val: 4, + Next: &ListNode{ + Val: 6, + }, }, - }, - }) - assert.Equal(t, l3, &ListNode{ - Val: 1, - Next: &ListNode{ - Val: 2, + }) + assert.Equal(t, l3, &ListNode{ + Val: 1, Next: &ListNode{ - Val: 3, + Val: 2, Next: &ListNode{ - Val: 4, + Val: 3, Next: &ListNode{ - Val: 5, + Val: 4, Next: &ListNode{ - Val: 6, + Val: 5, + Next: &ListNode{ + Val: 6, + }, }, }, }, }, - }, - }) + }) + } } diff --git a/rust/src/linkedlist/merge_two_lists.rs b/rust/src/linkedlist/merge_two_lists.rs index f9e568f..fd0aabe 100644 --- a/rust/src/linkedlist/merge_two_lists.rs +++ b/rust/src/linkedlist/merge_two_lists.rs @@ -41,36 +41,65 @@ pub fn merge_two_lists( dummy.unwrap().next } +pub fn merge_two_lists_with_recursion( + list1: Option>, + list2: Option>, +) -> Option> { + match (list1, list2) { + (Some(node1), Some(node2)) => { + if node1.val <= node2.val { + Some(Box::new(ListNode { + val: node1.val, + next: merge_two_lists_with_recursion(node1.next, Some(node2)), + })) + } else { + Some(Box::new(ListNode { + val: node2.val, + next: merge_two_lists_with_recursion(Some(node1), node2.next), + })) + } + } + (Some(node1), None) => Some(node1), + (None, Some(node2)) => Some(node2), + (None, None) => None, + } +} + #[cfg(test)] mod tests { - use crate::linkedlist::merge_two_lists; - use super::*; #[test] fn test_list_empty() { - let ret1 = merge_two_lists(None, None); - assert!(ret1.is_none()); + let list_fn: [fn( + list1: Option>, + list2: Option>, + ) -> Option>; 2] = [merge_two_lists, merge_two_lists_with_recursion]; + + for f in list_fn { + let ret1 = f(None, None); + assert!(ret1.is_none()); - let ret2 = merge_two_lists(None, Some(Box::new(ListNode { val: 1, next: None }))); - assert_eq!(ret2, Some(Box::new(ListNode { val: 1, next: None }))); + let ret2 = f(None, Some(Box::new(ListNode { val: 1, next: None }))); + assert_eq!(ret2, Some(Box::new(ListNode { val: 1, next: None }))); - let ret3 = merge_two_lists( - Some(Box::new(ListNode { - val: 1, - next: Some(Box::new(ListNode { val: 3, next: None })), - })), - Some(Box::new(ListNode { val: 2, next: None })), - ); - assert_eq!( - ret3, - Some(Box::new(ListNode { - val: 1, - next: Some(Box::new(ListNode { - val: 2, - next: Some(Box::new(ListNode { val: 3, next: None })) + let ret3 = f( + Some(Box::new(ListNode { + val: 1, + next: Some(Box::new(ListNode { val: 3, next: None })), + })), + Some(Box::new(ListNode { val: 2, next: None })), + ); + assert_eq!( + ret3, + Some(Box::new(ListNode { + val: 1, + next: Some(Box::new(ListNode { + val: 2, + next: Some(Box::new(ListNode { val: 3, next: None })) + })) })) - })) - ) + ) + } } } From 51a5e3965c64f932a63b22a56a7132e32cb64cbe Mon Sep 17 00:00:00 2001 From: nange Date: Tue, 11 Jul 2023 17:34:33 +0800 Subject: [PATCH 06/50] update --- README.md | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index b07597b..6957740 100644 --- a/README.md +++ b/README.md @@ -15,19 +15,14 @@ #### 双指针技巧 此技巧可用于解决一下一些问题: -1、合并两个有序链表 -2、链表的分解 - -3、合并 k 个有序链表 - -4、寻找单链表的倒数第 k 个节点 - -5、寻找单链表的中点 - -6、判断单链表是否包含环并找出环起点 - -7、判断两个单链表是否相交并找出交点 +1. 合并两个有序链表 +2. 链表的分解 +3. 合并 k 个有序链表 +4. 寻找单链表的倒数第 k 个节点 +5. 寻找单链表的中点 +6. 判断单链表是否包含环并找出环起点 +7. 判断两个单链表是否相交并找出交点 ##### 合并两个有序链表 From da2686c9f0caa72777b5cc9bf9e9f1d9927b232b Mon Sep 17 00:00:00 2001 From: nange Date: Tue, 11 Jul 2023 17:35:15 +0800 Subject: [PATCH 07/50] update --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 6957740..18f766a 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ 代码中还用到一个链表的算法题中是很常见的「虚拟头结点」技巧,也就是 `dummy` 节点。如果不使用 dummy 虚拟节点,代码会复杂一些,需要额外处理指针 `p` 为空的情况。 > 什么时候需要用虚拟头结点?当需要创造一条新链表的时候,可以使用虚拟头结点简化边界情况的处理。比如说,把两条有序链表合并成一条新的有序链表,是不是要创造一条新链表?再比如想把一条链表分解成两条链表,是不是也在创造新链表?这些情况都可以使用虚拟头结点简化边界情况的处理。 +> > 这个问题还有另外一种解法,就是利用递归,可以去掉两个指针,因为在递归栈中可以自动保存这两个状态。参考上面两个文件中的递归实现。通常来说,递归实现的代码都会更简单,但空间复杂度更高。 ##### 单链表的分解 From 65e3859c03ecc49049d72e7f6d2bd159395fc297 Mon Sep 17 00:00:00 2001 From: nange Date: Wed, 12 Jul 2023 16:06:55 +0800 Subject: [PATCH 08/50] add linkedlist/partition --- README.md | 8 ++++++++ go/linkedlist/linkedlist.go | 7 +++++++ go/linkedlist/merge_two_lists.go | 5 ----- go/linkedlist/partition.go | 31 ++++++++++++++++++++++++++++ go/linkedlist/partition_test.go | 35 ++++++++++++++++++++++++++++++++ rust/src/linkedlist/mod.rs | 1 + rust/src/linkedlist/partition.rs | 0 7 files changed, 82 insertions(+), 5 deletions(-) create mode 100644 go/linkedlist/linkedlist.go create mode 100644 go/linkedlist/partition.go create mode 100644 go/linkedlist/partition_test.go create mode 100644 rust/src/linkedlist/partition.rs diff --git a/README.md b/README.md index 18f766a..f593063 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,8 @@ ##### 合并两个有序链表 +Ref: + [Go实现](/go/linkedlist/merge_two_lists.go)、[Rust实现](/rust/src/linkedlist/merge_two_lists.rs) 代码中用到了`p1`, `p2`两个指针用于比较两个链表节点的推进,`p`指针用于生成新链表节点的推进。在这个过程中没有数据的复制,只有对指针指向的改变,效率很高。 @@ -37,6 +39,12 @@ ##### 单链表的分解 +Ref: + +[Go实现](/go/linkedlist/partition.go)、[Rust实现](/rust/src/linkedlist/partition.rs) + +我们可以把原链表分成两个小链表,一个链表中的元素大小都小于 x,另一个链表中的元素都大于等于 x,最后再把这两条链表接到一起,就得到了题目想要的结果。 + ## 参考资料 本项目内容主要参考:[labuladong 的算法小抄](https://labuladong.github.io/algo/) diff --git a/go/linkedlist/linkedlist.go b/go/linkedlist/linkedlist.go new file mode 100644 index 0000000..60c7b04 --- /dev/null +++ b/go/linkedlist/linkedlist.go @@ -0,0 +1,7 @@ +package linkedlist + +// Definition for singly-linked list. +type ListNode struct { + Val int + Next *ListNode +} diff --git a/go/linkedlist/merge_two_lists.go b/go/linkedlist/merge_two_lists.go index 32fce14..54ce7fc 100644 --- a/go/linkedlist/merge_two_lists.go +++ b/go/linkedlist/merge_two_lists.go @@ -1,11 +1,6 @@ package linkedlist // Ref: https://leetcode.cn/problems/merge-two-sorted-lists/ -// Definition for singly-linked list. -type ListNode struct { - Val int - Next *ListNode -} func MergeTwoLists(list1 *ListNode, list2 *ListNode) *ListNode { if list1 == nil { diff --git a/go/linkedlist/partition.go b/go/linkedlist/partition.go new file mode 100644 index 0000000..0b3bd85 --- /dev/null +++ b/go/linkedlist/partition.go @@ -0,0 +1,31 @@ +package linkedlist + +// Ref: https://leetcode.cn/problems/partition-list/ + +func Partition(head *ListNode, x int) *ListNode { + // 左节点用于存储小于x的值, 右节点用于存储大于x的值 + var dummyLeft, dummyRight = &ListNode{}, &ListNode{} + // p1, p2分别用生成新的左右链表节点 + var p1, p2 = dummyLeft, dummyRight + + // p指针用于遍历head链表 + var p = head + + for p != nil { + if p.Val < x { + p1.Next = p + p1 = p1.Next + } else { + p2.Next = p + p2 = p2.Next + } + // 断开新生成的p1,p2指针和原链表的连接关系 + tmp := p.Next + p.Next = nil + p = tmp + } + + p1.Next = dummyRight.Next + + return dummyLeft.Next +} diff --git a/go/linkedlist/partition_test.go b/go/linkedlist/partition_test.go new file mode 100644 index 0000000..cebfe68 --- /dev/null +++ b/go/linkedlist/partition_test.go @@ -0,0 +1,35 @@ +package linkedlist + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_Partition(t *testing.T) { + ret1 := Partition(nil, 0) + assert.Nil(t, ret1) + + ret2 := Partition(&ListNode{ + Val: 3, + Next: &ListNode{ + Val: 0, + Next: &ListNode{ + Val: 1, + Next: &ListNode{ + Val: -1, + }}, + }}, 1) + assert.Equal(t, ret2, &ListNode{ + Val: 0, + Next: &ListNode{ + Val: -1, + Next: &ListNode{ + Val: 3, + Next: &ListNode{ + Val: 1, + }, + }, + }, + }) +} diff --git a/rust/src/linkedlist/mod.rs b/rust/src/linkedlist/mod.rs index 1643a71..1749dc1 100644 --- a/rust/src/linkedlist/mod.rs +++ b/rust/src/linkedlist/mod.rs @@ -1 +1,2 @@ mod merge_two_lists; +mod partition; diff --git a/rust/src/linkedlist/partition.rs b/rust/src/linkedlist/partition.rs new file mode 100644 index 0000000..e69de29 From 1d8825e0cbe36ebc8b55e49cd9c8c62ea052e20b Mon Sep 17 00:00:00 2001 From: nange Date: Wed, 12 Jul 2023 17:42:45 +0800 Subject: [PATCH 09/50] update --- go/linkedlist/merge_two_lists.go | 2 - go/linkedlist/partition.go | 2 - rust/src/linkedlist/linkedlist.rs | 6 ++ rust/src/linkedlist/merge_two_lists.rs | 9 +-- rust/src/linkedlist/mod.rs | 3 + rust/src/linkedlist/partition.rs | 84 ++++++++++++++++++++++++++ 6 files changed, 94 insertions(+), 12 deletions(-) create mode 100644 rust/src/linkedlist/linkedlist.rs diff --git a/go/linkedlist/merge_two_lists.go b/go/linkedlist/merge_two_lists.go index 54ce7fc..1ca470a 100644 --- a/go/linkedlist/merge_two_lists.go +++ b/go/linkedlist/merge_two_lists.go @@ -1,7 +1,5 @@ package linkedlist -// Ref: https://leetcode.cn/problems/merge-two-sorted-lists/ - func MergeTwoLists(list1 *ListNode, list2 *ListNode) *ListNode { if list1 == nil { return list2 diff --git a/go/linkedlist/partition.go b/go/linkedlist/partition.go index 0b3bd85..f70891c 100644 --- a/go/linkedlist/partition.go +++ b/go/linkedlist/partition.go @@ -1,7 +1,5 @@ package linkedlist -// Ref: https://leetcode.cn/problems/partition-list/ - func Partition(head *ListNode, x int) *ListNode { // 左节点用于存储小于x的值, 右节点用于存储大于x的值 var dummyLeft, dummyRight = &ListNode{}, &ListNode{} diff --git a/rust/src/linkedlist/linkedlist.rs b/rust/src/linkedlist/linkedlist.rs new file mode 100644 index 0000000..1c93b1f --- /dev/null +++ b/rust/src/linkedlist/linkedlist.rs @@ -0,0 +1,6 @@ +/// Definition for singly-linked list. +#[derive(PartialEq, Eq, Clone, Debug)] +pub struct ListNode { + pub val: i32, + pub next: Option>, +} diff --git a/rust/src/linkedlist/merge_two_lists.rs b/rust/src/linkedlist/merge_two_lists.rs index fd0aabe..ac6449b 100644 --- a/rust/src/linkedlist/merge_two_lists.rs +++ b/rust/src/linkedlist/merge_two_lists.rs @@ -1,11 +1,4 @@ -/// Ref:https://leetcode.cn/problems/merge-two-sorted-lists/ - -/// Definition for singly-linked list. -#[derive(PartialEq, Eq, Clone, Debug)] -pub struct ListNode { - pub val: i32, - pub next: Option>, -} +use super::ListNode; pub fn merge_two_lists( list1: Option>, diff --git a/rust/src/linkedlist/mod.rs b/rust/src/linkedlist/mod.rs index 1749dc1..0097b0b 100644 --- a/rust/src/linkedlist/mod.rs +++ b/rust/src/linkedlist/mod.rs @@ -1,2 +1,5 @@ +mod linkedlist; mod merge_two_lists; mod partition; + +pub use linkedlist::ListNode; diff --git a/rust/src/linkedlist/partition.rs b/rust/src/linkedlist/partition.rs index e69de29..dc41a77 100644 --- a/rust/src/linkedlist/partition.rs +++ b/rust/src/linkedlist/partition.rs @@ -0,0 +1,84 @@ +use super::ListNode; + +pub fn partition(head: Option>, x: i32) -> Option> { + // 左节点用于存储小于x的值 + let mut dummy_left = Some(Box::new(ListNode { val: 0, next: None })); + // 右节点用于存储大于x的值 + let mut dummy_right = Some(Box::new(ListNode { val: 0, next: None })); + + // p1, p2引用分别用生成新的左右链表节点 + let mut p1 = &mut dummy_left; + let mut p2 = &mut dummy_right; + + // p用于遍历head链表 + let mut p = head; + + while p.is_some() { + // 断开新生成的p1,p2引用和原链表的连接关系 + let tmp = p.as_mut().unwrap().next.take(); + if p.as_ref().unwrap().val < x { + // 设置左链表的next为当前p节点 + p1.as_mut().unwrap().next = p; + // 移动p1引用到最新位置 + p1 = &mut p1.as_mut().unwrap().next; + } else { + p2.as_mut().unwrap().next = p; + p2 = &mut p2.as_mut().unwrap().next; + } + p = tmp; + } + + p1.as_mut().unwrap().next = dummy_right.as_mut().unwrap().next.take(); + + dummy_left.unwrap().next +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_partition() { + let ret1 = partition(None, 0); + assert!(ret1.is_none()); + + let ret2 = partition( + Some(Box::new(ListNode { + val: 1, + next: Some(Box::new(ListNode { + val: 4, + next: Some(Box::new(ListNode { + val: 3, + next: Some(Box::new(ListNode { + val: 2, + next: Some(Box::new(ListNode { + val: 5, + next: Some(Box::new(ListNode { val: 2, next: None })), + })), + })), + })), + })), + })), + 3, + ); + assert_eq!( + ret2, + Some(Box::new(ListNode { + val: 1, + next: Some(Box::new(ListNode { + val: 2, + next: Some(Box::new(ListNode { + val: 2, + next: Some(Box::new(ListNode { + val: 4, + next: Some(Box::new(ListNode { + val: 3, + next: Some(Box::new(ListNode { val: 5, next: None })), + })), + })), + })), + })), + })) + ) + } +} From ee08b43c4de43e33ec25ff84468e42b89af89cfd Mon Sep 17 00:00:00 2001 From: nange Date: Fri, 14 Jul 2023 18:22:03 +0800 Subject: [PATCH 10/50] update go/merge_k_lists --- README.md | 9 ++++ go/linkedlist/merge_k_lists.go | 59 +++++++++++++++++++++++++ go/linkedlist/merge_k_lists_test.go | 66 ++++++++++++++++++++++++++++ rust/src/linkedlist/merge_k_lists.rs | 0 rust/src/linkedlist/mod.rs | 1 + 5 files changed, 135 insertions(+) create mode 100644 go/linkedlist/merge_k_lists.go create mode 100644 go/linkedlist/merge_k_lists_test.go create mode 100644 rust/src/linkedlist/merge_k_lists.rs diff --git a/README.md b/README.md index f593063..47c593d 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,15 @@ Ref: 我们可以把原链表分成两个小链表,一个链表中的元素大小都小于 x,另一个链表中的元素都大于等于 x,最后再把这两条链表接到一起,就得到了题目想要的结果。 +##### 合并K个有序链表 + +Ref: + +[Go实现](/go/linkedlist/merge_k_lists.go)、[Rust实现](/rust/src/linkedlist/merge_k_lists.rs) + +合并 k 个有序链表的逻辑类似合并两个有序链表,难点在于,如何快速得到 k 个节点中的最小节点,接到结果链表上? 通常的一种想法是可以把链表头放到数组里,可以写一个循环判断,每次找出一个最小值,这样的时间复杂度是O(N*K),N是所有链表的节点数量,K是链表个数。不过还有一种更高效的方式,可以把K个链表头放入 +**优先级队列(二叉堆)**中,放入一个最小堆中,每次就能方便的获得K个节点中的最小值,算法复杂度是:O(N*Log(K))。 + ## 参考资料 本项目内容主要参考:[labuladong 的算法小抄](https://labuladong.github.io/algo/) diff --git a/go/linkedlist/merge_k_lists.go b/go/linkedlist/merge_k_lists.go new file mode 100644 index 0000000..c28af6f --- /dev/null +++ b/go/linkedlist/merge_k_lists.go @@ -0,0 +1,59 @@ +package linkedlist + +import "container/heap" + +func MergeKLists(lists []*ListNode) *ListNode { + // 虚拟头结点 + dummy := &ListNode{} + // 指向虚拟头节点最后的节点,用于设置新节点 + p := dummy + + pq := make(PriorityQueue, 0) + heap.Init(&pq) + for _, head := range lists { + // 将K个链表头节点加入优先级队列 + if head != nil { + heap.Push(&pq, head) + } + } + + // 从优先级队列中逐个取出节点加入新链表中, + // 并把新的链表头节点加入优先级队列中 + for pq.Len() > 0 { + node := heap.Pop(&pq).(*ListNode) + p.Next = node + if node.Next != nil { + heap.Push(&pq, node.Next) + } + p = p.Next + } + + return dummy.Next +} + +type PriorityQueue []*ListNode + +func (pq PriorityQueue) Len() int { return len(pq) } + +func (pq PriorityQueue) Less(i, j int) bool { + // We want Pop to give us the highest, not lowest, priority so we use greater than here. + return pq[i].Val < pq[j].Val +} + +func (pq PriorityQueue) Swap(i, j int) { + pq[i], pq[j] = pq[j], pq[i] +} + +func (pq *PriorityQueue) Push(x any) { + item := x.(*ListNode) + *pq = append(*pq, item) +} + +func (pq *PriorityQueue) Pop() any { + old := *pq + n := len(old) + item := old[n-1] + old[n-1] = nil // avoid memory leak + *pq = old[0 : n-1] + return item +} diff --git a/go/linkedlist/merge_k_lists_test.go b/go/linkedlist/merge_k_lists_test.go new file mode 100644 index 0000000..e0703d4 --- /dev/null +++ b/go/linkedlist/merge_k_lists_test.go @@ -0,0 +1,66 @@ +package linkedlist + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_MergeKLists(t *testing.T) { + ret1 := MergeKLists(nil) + assert.Nil(t, ret1) + ret2 := MergeKLists([]*ListNode{}) + assert.Nil(t, ret2) + + arrList := []*ListNode{ + { + Val: 1, + Next: &ListNode{ + Val: 4, + Next: &ListNode{ + Val: 5, + }, + }, + }, + { + Val: 1, + Next: &ListNode{ + Val: 3, + Next: &ListNode{ + Val: 4, + }, + }, + }, + { + Val: 2, + Next: &ListNode{ + Val: 6, + }, + }, + } + ret3 := MergeKLists(arrList) + assert.Equal(t, ret3, &ListNode{ + Val: 1, + Next: &ListNode{ + Val: 1, + Next: &ListNode{ + Val: 2, + Next: &ListNode{ + Val: 3, + Next: &ListNode{ + Val: 4, + Next: &ListNode{ + Val: 4, + Next: &ListNode{ + Val: 5, + Next: &ListNode{ + Val: 6, + }, + }, + }, + }, + }, + }, + }, + }) +} diff --git a/rust/src/linkedlist/merge_k_lists.rs b/rust/src/linkedlist/merge_k_lists.rs new file mode 100644 index 0000000..e69de29 diff --git a/rust/src/linkedlist/mod.rs b/rust/src/linkedlist/mod.rs index 0097b0b..f0cc2be 100644 --- a/rust/src/linkedlist/mod.rs +++ b/rust/src/linkedlist/mod.rs @@ -1,4 +1,5 @@ mod linkedlist; +mod merge_k_lists; mod merge_two_lists; mod partition; From b4eb611c10e140ac31975157c04fbebf9047796b Mon Sep 17 00:00:00 2001 From: nange Date: Fri, 14 Jul 2023 20:31:54 +0800 Subject: [PATCH 11/50] update rust/merge_k_lists --- rust/src/linkedlist/linkedlist.rs | 4 +- rust/src/linkedlist/merge_k_lists.rs | 88 ++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 1 deletion(-) diff --git a/rust/src/linkedlist/linkedlist.rs b/rust/src/linkedlist/linkedlist.rs index 1c93b1f..78570e3 100644 --- a/rust/src/linkedlist/linkedlist.rs +++ b/rust/src/linkedlist/linkedlist.rs @@ -1,5 +1,7 @@ +use std::cmp::Ord; + /// Definition for singly-linked list. -#[derive(PartialEq, Eq, Clone, Debug)] +#[derive(PartialEq, PartialOrd, Ord, Eq, Clone, Debug)] pub struct ListNode { pub val: i32, pub next: Option>, diff --git a/rust/src/linkedlist/merge_k_lists.rs b/rust/src/linkedlist/merge_k_lists.rs index e69de29..aebb4f1 100644 --- a/rust/src/linkedlist/merge_k_lists.rs +++ b/rust/src/linkedlist/merge_k_lists.rs @@ -0,0 +1,88 @@ +use super::ListNode; +use std::cmp::Reverse; +use std::collections::BinaryHeap; + +pub fn merge_k_lists(lists: Vec>>) -> Option> { + // 将链表头节点加入堆 + let mut heap = BinaryHeap::new(); + for node in lists { + if let Some(boxed_node) = node { + heap.push(Reverse(boxed_node)); + } + } + + // 虚拟头结点 + let mut dummy = Box::new(ListNode { val: 0, next: None }); + // 指向虚拟头节点尾部节点,用于设置新节点 + let mut tail = &mut dummy; + + // 从堆中逐个取出节点,设置到新链表上, + // 并把取出节点的next节点重新加入堆中 + while let Some(Reverse(mut node)) = heap.pop() { + if let Some(next) = node.next.take() { + heap.push(Reverse(next)); + } + tail.next = Some(node); + tail = tail.next.as_mut().unwrap(); + } + + dummy.next +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_merge_k_lists() { + let ret1 = merge_k_lists(Vec::new()); + assert!(ret1.is_none()); + + let lists = vec![ + Some(Box::new(ListNode { + val: 1, + next: Some(Box::new(ListNode { + val: 4, + next: Some(Box::new(ListNode { val: 5, next: None })), + })), + })), + Some(Box::new(ListNode { + val: 1, + next: Some(Box::new(ListNode { + val: 3, + next: Some(Box::new(ListNode { val: 4, next: None })), + })), + })), + Some(Box::new(ListNode { + val: 2, + next: Some(Box::new(ListNode { val: 6, next: None })), + })), + ]; + let ret2 = merge_k_lists(lists); + assert_eq!( + ret2, + Some(Box::new(ListNode { + val: 1, + next: Some(Box::new(ListNode { + val: 1, + next: Some(Box::new(ListNode { + val: 2, + next: Some(Box::new(ListNode { + val: 3, + next: Some(Box::new(ListNode { + val: 4, + next: Some(Box::new(ListNode { + val: 4, + next: Some(Box::new(ListNode { + val: 5, + next: Some(Box::new(ListNode { val: 6, next: None })) + })) + })) + })) + })) + })) + })) + })) + ); + } +} From e4a46701ced7632576eff9f6c37ffcf010263823 Mon Sep 17 00:00:00 2001 From: nange Date: Fri, 14 Jul 2023 20:34:57 +0800 Subject: [PATCH 12/50] update --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 47c593d..76db88f 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ Ref: [Go实现](/go/linkedlist/merge_k_lists.go)、[Rust实现](/rust/src/linkedlist/merge_k_lists.rs) 合并 k 个有序链表的逻辑类似合并两个有序链表,难点在于,如何快速得到 k 个节点中的最小节点,接到结果链表上? 通常的一种想法是可以把链表头放到数组里,可以写一个循环判断,每次找出一个最小值,这样的时间复杂度是O(N*K),N是所有链表的节点数量,K是链表个数。不过还有一种更高效的方式,可以把K个链表头放入 -**优先级队列(二叉堆)**中,放入一个最小堆中,每次就能方便的获得K个节点中的最小值,算法复杂度是:O(N*Log(K))。 + **优先级队列(二叉堆)** 中,放入一个最小堆中,每次就能方便的获得K个节点中的最小值,算法复杂度是:O(N*Log(K))。 ## 参考资料 From 6f4fd060f5ada267f05d5ceea474d2680ab08b7f Mon Sep 17 00:00:00 2001 From: nange Date: Fri, 14 Jul 2023 20:37:54 +0800 Subject: [PATCH 13/50] update --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 76db88f..4c10966 100644 --- a/README.md +++ b/README.md @@ -51,8 +51,10 @@ Ref: [Go实现](/go/linkedlist/merge_k_lists.go)、[Rust实现](/rust/src/linkedlist/merge_k_lists.rs) -合并 k 个有序链表的逻辑类似合并两个有序链表,难点在于,如何快速得到 k 个节点中的最小节点,接到结果链表上? 通常的一种想法是可以把链表头放到数组里,可以写一个循环判断,每次找出一个最小值,这样的时间复杂度是O(N*K),N是所有链表的节点数量,K是链表个数。不过还有一种更高效的方式,可以把K个链表头放入 - **优先级队列(二叉堆)** 中,放入一个最小堆中,每次就能方便的获得K个节点中的最小值,算法复杂度是:O(N*Log(K))。 +合并 k 个有序链表的逻辑类似合并两个有序链表,难点在于,如何快速得到 k 个节点中的最小节点,接到结果链表上? +通常的一种想法是可以把链表头放到数组里,可以写一个循环判断,每次找出一个最小值,这样的时间复杂度是`O(N*K)`,N是所有链表的节点数量,K 是链表个数。 + +不过还有一种更高效的方式,可以把K个链表头放入 **优先级队列(二叉堆)** 中,放入一个最小堆中,每次就能方便的获得K个节点中的最小值,算法复杂度是:`O(N*Log(K))`。 ## 参考资料 From 9736bcdfd4e7b9f35ad6102d3f09ad19c4459503 Mon Sep 17 00:00:00 2001 From: nange Date: Sat, 15 Jul 2023 20:34:28 +0800 Subject: [PATCH 14/50] add linkedlist/remove_nth_from_end --- README.md | 13 ++++ go/linkedlist/remove_nth_from_end.go | 25 ++++++ go/linkedlist/remove_nth_from_end_test.go | 46 +++++++++++ rust/src/linkedlist/linkedlist.rs | 14 +++- rust/src/linkedlist/mod.rs | 1 + rust/src/linkedlist/remove_nth_from_end.rs | 90 ++++++++++++++++++++++ 6 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 go/linkedlist/remove_nth_from_end.go create mode 100644 go/linkedlist/remove_nth_from_end_test.go create mode 100644 rust/src/linkedlist/remove_nth_from_end.rs diff --git a/README.md b/README.md index 4c10966..c1c444a 100644 --- a/README.md +++ b/README.md @@ -52,10 +52,23 @@ Ref: [Go实现](/go/linkedlist/merge_k_lists.go)、[Rust实现](/rust/src/linkedlist/merge_k_lists.rs) 合并 k 个有序链表的逻辑类似合并两个有序链表,难点在于,如何快速得到 k 个节点中的最小节点,接到结果链表上? + 通常的一种想法是可以把链表头放到数组里,可以写一个循环判断,每次找出一个最小值,这样的时间复杂度是`O(N*K)`,N是所有链表的节点数量,K 是链表个数。 不过还有一种更高效的方式,可以把K个链表头放入 **优先级队列(二叉堆)** 中,放入一个最小堆中,每次就能方便的获得K个节点中的最小值,算法复杂度是:`O(N*Log(K))`。 +##### 单链表倒数第K个节点 + +Ref: + +[Go实现](/go/linkedlist/remove_nth_from_end.go)、[Rust实现](/rust/src/linkedlist/remove_nth_from_end.rs) + +要找到链表倒数第k个节点,一般的想法是得知道链表的长度`n`,再用`n-k+1`就得到倒数第k个节点,但是这样需要遍历两次链表,如何在只遍历一次的情况下找到呢? + +同样使用双指针技巧,先让一个指针先走`k`步,然后第二个指针开始和第一个指针一起走,第一个指针走完这个整个链表,第二个指针的位置刚好就在`n-k+1`的位置上。 + +> 另一种方法是使用递归,先一次性递归到链表结尾,递归出栈过程中对计数器加一,当计数器达到k时,此时的节点就是要被删除的节点。 + ## 参考资料 本项目内容主要参考:[labuladong 的算法小抄](https://labuladong.github.io/algo/) diff --git a/go/linkedlist/remove_nth_from_end.go b/go/linkedlist/remove_nth_from_end.go new file mode 100644 index 0000000..25d2021 --- /dev/null +++ b/go/linkedlist/remove_nth_from_end.go @@ -0,0 +1,25 @@ +package linkedlist + +func RemoveNthFromEnd(head *ListNode, n int) *ListNode { + dummy := &ListNode{0, head} + first, second := dummy, dummy + + // 第一步,让first节点先向前移动n+1步 + for i := 0; i < n+1; i++ { + first = first.Next + if first == nil && i < n { // 链表长度小于n,不需要删除任何节点 + return head + } + } + + // 第二步,让first和second节点同时向前移动,直到first节点到达链表的末尾 + for first != nil { + first = first.Next + second = second.Next + } + + // 第三步,删除second节点的下一个节点 + second.Next = second.Next.Next + + return dummy.Next +} diff --git a/go/linkedlist/remove_nth_from_end_test.go b/go/linkedlist/remove_nth_from_end_test.go new file mode 100644 index 0000000..7965946 --- /dev/null +++ b/go/linkedlist/remove_nth_from_end_test.go @@ -0,0 +1,46 @@ +package linkedlist + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_RemoveNthFromEnd(t *testing.T) { + ret1 := RemoveNthFromEnd(nil, 1) + assert.Nil(t, ret1) + + lists := &ListNode{ + Val: 1, + Next: &ListNode{ + Val: 4, + Next: &ListNode{ + Val: 5, + }, + }, + } + ret2 := RemoveNthFromEnd(lists, 1) + assert.Equal(t, ret2, &ListNode{ + Val: 1, + Next: &ListNode{ + Val: 4, + }, + }) + + lists2 := &ListNode{ + Val: 1, + Next: &ListNode{ + Val: 4, + Next: &ListNode{ + Val: 5, + }, + }, + } + ret3 := RemoveNthFromEnd(lists2, 3) + assert.Equal(t, ret3, &ListNode{ + Val: 4, + Next: &ListNode{ + Val: 5, + }, + }) +} diff --git a/rust/src/linkedlist/linkedlist.rs b/rust/src/linkedlist/linkedlist.rs index 78570e3..dfa4ad8 100644 --- a/rust/src/linkedlist/linkedlist.rs +++ b/rust/src/linkedlist/linkedlist.rs @@ -1,8 +1,20 @@ use std::cmp::Ord; /// Definition for singly-linked list. -#[derive(PartialEq, PartialOrd, Ord, Eq, Clone, Debug)] +#[derive(PartialEq, Eq, Clone, Debug)] pub struct ListNode { pub val: i32, pub next: Option>, } + +impl Ord for ListNode { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.val.cmp(&other.val) + } +} + +impl PartialOrd for ListNode { + fn partial_cmp(&self, other: &Self) -> Option { + self.val.partial_cmp(&other.val) + } +} diff --git a/rust/src/linkedlist/mod.rs b/rust/src/linkedlist/mod.rs index f0cc2be..223f173 100644 --- a/rust/src/linkedlist/mod.rs +++ b/rust/src/linkedlist/mod.rs @@ -2,5 +2,6 @@ mod linkedlist; mod merge_k_lists; mod merge_two_lists; mod partition; +mod remove_nth_from_end; pub use linkedlist::ListNode; diff --git a/rust/src/linkedlist/remove_nth_from_end.rs b/rust/src/linkedlist/remove_nth_from_end.rs new file mode 100644 index 0000000..6858868 --- /dev/null +++ b/rust/src/linkedlist/remove_nth_from_end.rs @@ -0,0 +1,90 @@ +use super::ListNode; + +pub fn remove_nth_from_end(head: Option>, n: i32) -> Option> { + let mut dummy_clone = Some(Box::new(ListNode { + val: 0, + next: head.clone(), + })); + let mut dummy = Some(Box::new(ListNode { val: 0, next: head })); + + let mut first = &mut dummy_clone; + let mut second = &mut dummy; + + // 第一步,让first节点先向前移动n+1步 + for i in 0..n + 1 { + first = &mut first.as_mut().unwrap().next; + if first.is_none() && i < n { + return dummy.unwrap().next; + } + } + + // 第二步,让first和second节点同时向前移动,直到first节点到达链表的末尾 + while first.is_some() { + first = &mut first.as_mut().unwrap().next; + second = &mut second.as_mut().unwrap().next; + } + + // 第三步,删除second节点的下一个节点 + second.as_mut().unwrap().next = second.as_mut().unwrap().next.as_mut().unwrap().next.take(); + + dummy.unwrap().next +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_remove_nth_from_end() { + let ret1 = remove_nth_from_end(None, 1); + assert!(ret1.is_none()); + + let lists = Some(Box::new(ListNode { + val: 1, + next: Some(Box::new(ListNode { + val: 4, + next: Some(Box::new(ListNode { val: 5, next: None })), + })), + })); + let ret2 = remove_nth_from_end(lists, 1); + assert_eq!( + ret2, + Some(Box::new(ListNode { + val: 1, + next: Some(Box::new(ListNode { val: 4, next: None })) + })) + ); + + let lists = Some(Box::new(ListNode { + val: 1, + next: Some(Box::new(ListNode { + val: 4, + next: Some(Box::new(ListNode { val: 5, next: None })), + })), + })); + let ret2 = remove_nth_from_end(lists, 2); + assert_eq!( + ret2, + Some(Box::new(ListNode { + val: 1, + next: Some(Box::new(ListNode { val: 5, next: None })) + })) + ); + + let lists = Some(Box::new(ListNode { + val: 1, + next: Some(Box::new(ListNode { + val: 4, + next: Some(Box::new(ListNode { val: 5, next: None })), + })), + })); + let ret2 = remove_nth_from_end(lists, 3); + assert_eq!( + ret2, + Some(Box::new(ListNode { + val: 4, + next: Some(Box::new(ListNode { val: 5, next: None })) + })) + ); + } +} From 3217edb686bf0ace66e5a205182c1e369bbefcb2 Mon Sep 17 00:00:00 2001 From: nange Date: Sun, 16 Jul 2023 15:53:08 +0800 Subject: [PATCH 15/50] update --- go/linkedlist/remove_nth_from_end.go | 20 ++++++++ go/linkedlist/remove_nth_from_end_test.go | 60 ++++++++++++++++++++++- 2 files changed, 79 insertions(+), 1 deletion(-) diff --git a/go/linkedlist/remove_nth_from_end.go b/go/linkedlist/remove_nth_from_end.go index 25d2021..8dd016a 100644 --- a/go/linkedlist/remove_nth_from_end.go +++ b/go/linkedlist/remove_nth_from_end.go @@ -23,3 +23,23 @@ func RemoveNthFromEnd(head *ListNode, n int) *ListNode { return dummy.Next } + +type recursion struct { + // 记录递归栈退出的次数,用于和n比较 + count int +} + +func (r *recursion) RemoveNthFromEndWithRecursion(head *ListNode, n int) *ListNode { + if head == nil { + return nil + } + + head.Next = r.RemoveNthFromEndWithRecursion(head.Next, n) + + r.count++ + if r.count == n { + return head.Next + } + + return head +} diff --git a/go/linkedlist/remove_nth_from_end_test.go b/go/linkedlist/remove_nth_from_end_test.go index 7965946..54423f5 100644 --- a/go/linkedlist/remove_nth_from_end_test.go +++ b/go/linkedlist/remove_nth_from_end_test.go @@ -36,11 +36,69 @@ func Test_RemoveNthFromEnd(t *testing.T) { }, }, } - ret3 := RemoveNthFromEnd(lists2, 3) + ret3 := RemoveNthFromEnd(lists2, 2) assert.Equal(t, ret3, &ListNode{ + Val: 1, + Next: &ListNode{ + Val: 5, + }, + }) + +} + +func Test_RemoveNthFromEndWithRecursion(t *testing.T) { + ret1 := (&recursion{}).RemoveNthFromEndWithRecursion(nil, 1) + assert.Nil(t, ret1) + + lists := &ListNode{ + Val: 1, + Next: &ListNode{ + Val: 4, + Next: &ListNode{ + Val: 5, + }, + }, + } + ret2 := (&recursion{}).RemoveNthFromEndWithRecursion(lists, 1) + assert.Equal(t, ret2, &ListNode{ + Val: 1, + Next: &ListNode{ + Val: 4, + }, + }) + + lists2 := &ListNode{ + Val: 1, + Next: &ListNode{ + Val: 4, + Next: &ListNode{ + Val: 5, + }, + }, + } + ret3 := (&recursion{}).RemoveNthFromEndWithRecursion(lists2, 2) + assert.Equal(t, ret3, &ListNode{ + Val: 1, + Next: &ListNode{ + Val: 5, + }, + }) + + lists3 := &ListNode{ + Val: 1, + Next: &ListNode{ + Val: 4, + Next: &ListNode{ + Val: 5, + }, + }, + } + ret4 := (&recursion{}).RemoveNthFromEndWithRecursion(lists3, 3) + assert.Equal(t, ret4, &ListNode{ Val: 4, Next: &ListNode{ Val: 5, }, }) + } From 7d36cec9f1fd6e0c72dfb785a2866a5ca54851e8 Mon Sep 17 00:00:00 2001 From: nange Date: Sat, 22 Jul 2023 18:30:00 +0800 Subject: [PATCH 16/50] add linkedlist/middle_node --- README.md | 8 +++++ go/linkedlist/middle_node.go | 13 +++++++ go/linkedlist/middle_node_test.go | 44 +++++++++++++++++++++++ rust/src/linkedlist/middle_node.rs | 57 ++++++++++++++++++++++++++++++ rust/src/linkedlist/mod.rs | 1 + 5 files changed, 123 insertions(+) create mode 100644 go/linkedlist/middle_node.go create mode 100644 go/linkedlist/middle_node_test.go create mode 100644 rust/src/linkedlist/middle_node.rs diff --git a/README.md b/README.md index c1c444a..d062aa3 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,14 @@ Ref: > 另一种方法是使用递归,先一次性递归到链表结尾,递归出栈过程中对计数器加一,当计数器达到k时,此时的节点就是要被删除的节点。 +##### 单链表中点 + +Ref: + +[Go实现](/go/linkedlist/middle_node.go)、[Rust实现](/rust/src/linkedlist/middle_node.rs) + +这个和上面一题类似,只是这次是找中点。同样使用快慢指针技巧,每当慢指针 slow 前进一步,快指针 fast 就前进两步,这样,当 fast 走到链表末尾时,slow 就指向了链表中点。 + ## 参考资料 本项目内容主要参考:[labuladong 的算法小抄](https://labuladong.github.io/algo/) diff --git a/go/linkedlist/middle_node.go b/go/linkedlist/middle_node.go new file mode 100644 index 0000000..eb4bbf4 --- /dev/null +++ b/go/linkedlist/middle_node.go @@ -0,0 +1,13 @@ +package linkedlist + +func MiddleNode(head *ListNode) *ListNode { + slow := head + fast := head + + for fast != nil && fast.Next != nil { + slow = slow.Next + fast = fast.Next.Next + } + + return slow +} diff --git a/go/linkedlist/middle_node_test.go b/go/linkedlist/middle_node_test.go new file mode 100644 index 0000000..ea05a58 --- /dev/null +++ b/go/linkedlist/middle_node_test.go @@ -0,0 +1,44 @@ +package linkedlist + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_MiddleNode(t *testing.T) { + ret1 := MiddleNode(nil) + assert.Nil(t, ret1) + + ret2 := MiddleNode(&ListNode{ + Val: 1, + Next: nil, + }) + assert.Equal(t, ret2, &ListNode{Val: 1, Next: nil}) + + ret3 := MiddleNode(&ListNode{ + Val: 1, + Next: &ListNode{ + Val: 2, + Next: &ListNode{ + Val: 3, + Next: nil, + }, + }, + }) + assert.Equal(t, ret3, &ListNode{Val: 2, Next: &ListNode{Val: 3, Next: nil}}) + + ret4 := MiddleNode(&ListNode{ + Val: 1, + Next: &ListNode{ + Val: 2, + Next: &ListNode{ + Val: 3, + Next: &ListNode{ + Val: 4, + }, + }, + }, + }) + assert.Equal(t, ret4, &ListNode{Val: 3, Next: &ListNode{Val: 4, Next: nil}}) +} diff --git a/rust/src/linkedlist/middle_node.rs b/rust/src/linkedlist/middle_node.rs new file mode 100644 index 0000000..9c25e40 --- /dev/null +++ b/rust/src/linkedlist/middle_node.rs @@ -0,0 +1,57 @@ +use super::ListNode; + +pub fn middle_node(head: Option>) -> Option> { + let mut slow = &head; + let mut fast = &head; + + while fast.is_some() && fast.as_ref().unwrap().next.is_some() { + slow = &slow.as_ref().unwrap().next; + fast = &fast.as_ref().unwrap().next.as_ref().unwrap().next; + } + + slow.clone() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_middle_node() { + let ret1 = middle_node(None); + assert!(ret1.is_none()); + + let ret2 = middle_node(Some(Box::new(ListNode { + val: 1, + next: Some(Box::new(ListNode { + val: 2, + next: Some(Box::new(ListNode { val: 3, next: None })), + })), + }))); + assert_eq!( + ret2, + Some(Box::new(ListNode { + val: 2, + next: Some(Box::new(ListNode { val: 3, next: None })) + })) + ); + + let ret3 = middle_node(Some(Box::new(ListNode { + val: 1, + next: Some(Box::new(ListNode { + val: 2, + next: Some(Box::new(ListNode { + val: 3, + next: Some(Box::new(ListNode { val: 4, next: None })), + })), + })), + }))); + assert_eq!( + ret3, + Some(Box::new(ListNode { + val: 3, + next: Some(Box::new(ListNode { val: 4, next: None })) + })) + ) + } +} diff --git a/rust/src/linkedlist/mod.rs b/rust/src/linkedlist/mod.rs index 223f173..dd6db57 100644 --- a/rust/src/linkedlist/mod.rs +++ b/rust/src/linkedlist/mod.rs @@ -1,6 +1,7 @@ mod linkedlist; mod merge_k_lists; mod merge_two_lists; +mod middle_node; mod partition; mod remove_nth_from_end; From 831da5bf2704ac367e6d41259844b2da2cc73c02 Mon Sep 17 00:00:00 2001 From: nange Date: Wed, 26 Jul 2023 16:59:13 +0800 Subject: [PATCH 17/50] add linkedlist/detect_cycle --- README.md | 12 ++++++ go/linkedlist/detect_cycle.go | 25 ++++++++++++ go/linkedlist/detect_cycle_test.go | 37 ++++++++++++++++++ rust/src/linkedlist/detect_cycle.rs | 59 +++++++++++++++++++++++++++++ rust/src/linkedlist/mod.rs | 1 + 5 files changed, 134 insertions(+) create mode 100644 go/linkedlist/detect_cycle.go create mode 100644 go/linkedlist/detect_cycle_test.go create mode 100644 rust/src/linkedlist/detect_cycle.rs diff --git a/README.md b/README.md index d062aa3..8cbb35f 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,18 @@ Ref: 这个和上面一题类似,只是这次是找中点。同样使用快慢指针技巧,每当慢指针 slow 前进一步,快指针 fast 就前进两步,这样,当 fast 走到链表末尾时,slow 就指向了链表中点。 +##### 判断链表是否包含环,并找出环起点 + +Ref: + +[Go实现](/go/linkedlist/detect_cycle.go)、[Rust实现](/rust/src/linkedlist/detect_cycle.rs) + +首先我们得知道链表是否有环,实现方法和上面一题类似,通过快慢指针,慢指针走一步,快指针走两步,如果最终两个指针走到了一起,则证明有环,如果快指针为null,则证明无环。 + +为什么有环快指针就一定会碰到慢指针?我们随便构造一个有环的链表,就会发现,每次走两步,每经过一次有环处,下一次快指针经过的节点都和上一次不同,所以快慢指针早晚一定会遇到。 + +如何找到环的起点呢?关键在于当快慢指针相遇时,让其中任一个指针指向头节点,然后让它俩以相同速度前进,再次相遇时所在的节点位置就是环开始的位置。 + ## 参考资料 本项目内容主要参考:[labuladong 的算法小抄](https://labuladong.github.io/algo/) diff --git a/go/linkedlist/detect_cycle.go b/go/linkedlist/detect_cycle.go new file mode 100644 index 0000000..3352b46 --- /dev/null +++ b/go/linkedlist/detect_cycle.go @@ -0,0 +1,25 @@ +package linkedlist + +func DetectCycle(head *ListNode) *ListNode { + slow := head + fast := head + + for fast != nil && fast.Next != nil { + slow = slow.Next + fast = fast.Next.Next + if slow == fast { + break + } + } + if fast == nil || fast.Next == nil { + return nil + } + + slow = head + for slow != fast { + slow = slow.Next + fast = fast.Next + } + + return slow +} diff --git a/go/linkedlist/detect_cycle_test.go b/go/linkedlist/detect_cycle_test.go new file mode 100644 index 0000000..7cb3353 --- /dev/null +++ b/go/linkedlist/detect_cycle_test.go @@ -0,0 +1,37 @@ +package linkedlist + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_DetectCycle(t *testing.T) { + ret1 := DetectCycle(nil) + assert.Nil(t, ret1) + + ret2 := DetectCycle(&ListNode{ + Val: 1, + Next: &ListNode{}, + }) + assert.Nil(t, ret2) + + head := &ListNode{ + Val: 1, + Next: &ListNode{ + Val: 2, + }, + } + node1 := &ListNode{ + Val: 3, + } + node2 := &ListNode{ + Val: 4, + } + head.Next = node1 + node1.Next = node2 + node2.Next = node1 + + ret3 := DetectCycle(head) + assert.Equal(t, ret3.Val, 3) +} diff --git a/rust/src/linkedlist/detect_cycle.rs b/rust/src/linkedlist/detect_cycle.rs new file mode 100644 index 0000000..06ad614 --- /dev/null +++ b/rust/src/linkedlist/detect_cycle.rs @@ -0,0 +1,59 @@ +use std::{cell::RefCell, rc::Rc}; + +#[derive(PartialEq, Eq, Clone, Debug)] +pub struct ListNode { + pub val: i32, + pub next: Option>>, +} + +pub fn detect_cycle(head: Option>>) -> Option>> { + let mut slow = head.clone(); + let mut fast = head.clone(); + + while fast.is_some() && fast.as_ref().unwrap().borrow().next.is_some() { + slow = slow.unwrap().borrow().next.clone(); + fast = fast + .unwrap() + .borrow() + .next + .as_ref() + .unwrap() + .borrow() + .next + .clone(); + + if Rc::ptr_eq(slow.as_ref().unwrap(), fast.as_ref().unwrap()) { + let mut head = head; + while !Rc::ptr_eq(head.as_ref().unwrap(), slow.as_ref().unwrap()) { + slow = slow.unwrap().borrow().next.clone(); + head = head.unwrap().borrow().next.clone(); + } + return head; + } + } + + None +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_detect_cycle() { + let ret = detect_cycle(None); + assert!(ret.is_none()); + + let head = Some(Rc::new(RefCell::new(ListNode { val: 1, next: None }))); + let node1 = Some(Rc::new(RefCell::new(ListNode { val: 2, next: None }))); + let node2 = Some(Rc::new(RefCell::new(ListNode { val: 3, next: None }))); + let node3 = Some(Rc::new(RefCell::new(ListNode { val: 4, next: None }))); + head.as_ref().unwrap().borrow_mut().next = node1.clone(); + node1.as_ref().unwrap().borrow_mut().next = node2.clone(); + node2.as_ref().unwrap().borrow_mut().next = node3.clone(); + node3.as_ref().unwrap().borrow_mut().next = node2.clone(); + + let ret = detect_cycle(head); + assert_eq!(ret, node2); + } +} diff --git a/rust/src/linkedlist/mod.rs b/rust/src/linkedlist/mod.rs index dd6db57..deca801 100644 --- a/rust/src/linkedlist/mod.rs +++ b/rust/src/linkedlist/mod.rs @@ -1,3 +1,4 @@ +mod detect_cycle; mod linkedlist; mod merge_k_lists; mod merge_two_lists; From 90571b2effd47c708840736fa7cfef1cfa82b429 Mon Sep 17 00:00:00 2001 From: nange Date: Wed, 26 Jul 2023 17:14:01 +0800 Subject: [PATCH 18/50] add go action --- .github/workflows/go.yml | 44 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 .github/workflows/go.yml diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml new file mode 100644 index 0000000..723f07b --- /dev/null +++ b/.github/workflows/go.yml @@ -0,0 +1,44 @@ +# This workflow will build a golang project +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go + +name: Dev + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +jobs: + lint-test-build-linux: + name: Lint-Test-Linux + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: '^1.20' + check-latest: true + + - name: Cache go module + uses: actions/cache@v3 + with: + path: | + ~/.cache/go-build + ~/go/pkg/mod + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + + - name: golangci-lint + uses: golangci/golangci-lint-action@v3 + with: + version: latest + args: --timeout 10m0s --verbose + + - name: Test + run: cd go && go test -v ./... From 2c0054765b6afba4e636fb1ad28546b352707b16 Mon Sep 17 00:00:00 2001 From: nange Date: Wed, 26 Jul 2023 17:16:45 +0800 Subject: [PATCH 19/50] fix go action --- .github/workflows/go.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 723f07b..9cc1531 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -39,6 +39,7 @@ jobs: with: version: latest args: --timeout 10m0s --verbose + working-directory: ./go - name: Test - run: cd go && go test -v ./... + run: cd ./go && go test -v ./... From 3a53f500b55da027dab8c4010c726dd7aa859f38 Mon Sep 17 00:00:00 2001 From: nange Date: Wed, 26 Jul 2023 17:26:27 +0800 Subject: [PATCH 20/50] fix go action --- go/arraylist/arraylist_test.go | 2 +- go/go.mod | 2 +- go/hashmap/hashmap.go | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/go/arraylist/arraylist_test.go b/go/arraylist/arraylist_test.go index 703b7b4..4d3eea4 100644 --- a/go/arraylist/arraylist_test.go +++ b/go/arraylist/arraylist_test.go @@ -21,7 +21,7 @@ func TestGet(t *testing.T) { list.Append("world") v, err := list.Get(1) if sv, ok := v.(string); !ok || sv != "world" || err != nil { - t.Errorf("arraylist#Get() failed. get for error value:%s, expected value:world.") + t.Errorf("arraylist#Get() failed. get for error value:%s, expected value:world.", sv) } } diff --git a/go/go.mod b/go/go.mod index eef2336..68c4699 100644 --- a/go/go.mod +++ b/go/go.mod @@ -3,7 +3,7 @@ module the_algorithms go 1.20 require ( - github.com/mitchellh/hashstructure v1.1.0 + github.com/mitchellh/hashstructure/v2 v2.0.2 github.com/stretchr/testify v1.8.4 ) diff --git a/go/hashmap/hashmap.go b/go/hashmap/hashmap.go index 0a19b87..34376dd 100644 --- a/go/hashmap/hashmap.go +++ b/go/hashmap/hashmap.go @@ -3,7 +3,7 @@ package hashmap import ( "fmt" - "github.com/mitchellh/hashstructure" + "github.com/mitchellh/hashstructure/v2" ) const ratio = .75 // ratio sets the capacity the hashmap has to be at before it expands @@ -32,7 +32,7 @@ func hash(key uint64) uint64 { } func hashAny(data interface{}) int64 { - h, _ := hashstructure.Hash(data, nil) + h, _ := hashstructure.Hash(data, hashstructure.FormatV2, nil) return int64(hash(h)) } From 3f09f7128c739658e479935d34a2023e7e5dcd64 Mon Sep 17 00:00:00 2001 From: nange Date: Wed, 26 Jul 2023 17:27:27 +0800 Subject: [PATCH 21/50] update go action --- .github/workflows/go.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 9cc1531..24f40f2 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -1,7 +1,7 @@ # This workflow will build a golang project # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go -name: Dev +name: Go on: push: From 3f55aa0d7c7456c9a9d1a389143329ac5b31c02a Mon Sep 17 00:00:00 2001 From: nange Date: Wed, 26 Jul 2023 17:29:39 +0800 Subject: [PATCH 22/50] fix go lint --- go/arraylist/arraylist.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/go/arraylist/arraylist.go b/go/arraylist/arraylist.go index 5b1317c..f237a2a 100644 --- a/go/arraylist/arraylist.go +++ b/go/arraylist/arraylist.go @@ -50,14 +50,14 @@ func (list *ArrayList) Size() int { func (list *ArrayList) Get(index int) (interface{}, error) { if index < 0 || index >= list.Size() { - return nil, errors.New("Index out of range.") + return nil, errors.New("index out of range") } return list.dataStore[index], nil } func (list *ArrayList) Set(index int, newVal interface{}) error { if index < 0 || index >= list.Size() { - return errors.New("Index out of range.") + return errors.New("index out of range") } list.dataStore[index] = newVal @@ -71,7 +71,7 @@ func (list *ArrayList) Append(val interface{}) { func (list *ArrayList) Insert(index int, val interface{}) error { if index < 0 || index >= list.Size() { - return errors.New("Index out of range.") + return errors.New("index out of range") } list.ensureCapacity() list.dataStore = list.dataStore[:list.Size()+1] @@ -87,7 +87,7 @@ func (list *ArrayList) Insert(index int, val interface{}) error { func (list *ArrayList) Remove(index int) error { if index < 0 || index >= list.Size() { - return errors.New("Index out of range.") + return errors.New("index out of range") } list.dataStore = append(list.dataStore[:index], list.dataStore[index+1:]...) list.theSize-- @@ -124,7 +124,7 @@ func (it *arrayListIterator) HasNext() bool { func (it *arrayListIterator) Next() (interface{}, error) { if !it.HasNext() { - return nil, errors.New("No Such Element.") + return nil, errors.New("no such element") } v, err := it.list.Get(it.current) it.current++ @@ -133,5 +133,5 @@ func (it *arrayListIterator) Next() (interface{}, error) { func (it *arrayListIterator) Remove() { it.current-- - it.list.Remove(it.current) + _ = it.list.Remove(it.current) } From 759097b9b692f73598940d5864604ade70224ef8 Mon Sep 17 00:00:00 2001 From: nange Date: Wed, 26 Jul 2023 17:57:10 +0800 Subject: [PATCH 23/50] update go action --- .github/workflows/go.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 24f40f2..4bde067 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -39,7 +39,7 @@ jobs: with: version: latest args: --timeout 10m0s --verbose - working-directory: ./go + working-directory: go - name: Test - run: cd ./go && go test -v ./... + run: cd go && go test -v ./... From 675c45df05a2ce7e0be51ef3db1f57c74348c3cf Mon Sep 17 00:00:00 2001 From: nange Date: Wed, 26 Jul 2023 18:03:30 +0800 Subject: [PATCH 24/50] update go action --- .github/workflows/go.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 4bde067..12f334f 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -11,7 +11,7 @@ on: jobs: lint-test-build-linux: - name: Lint-Test-Linux + name: test-lint runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 @@ -34,12 +34,12 @@ jobs: restore-keys: | ${{ runner.os }}-go- + - name: Test + run: cd go && go test -v ./... + - name: golangci-lint uses: golangci/golangci-lint-action@v3 with: version: latest args: --timeout 10m0s --verbose working-directory: go - - - name: Test - run: cd go && go test -v ./... From 37481a6b84b2b4ea830d76758b1484368ef1f070 Mon Sep 17 00:00:00 2001 From: nange Date: Wed, 26 Jul 2023 18:05:04 +0800 Subject: [PATCH 25/50] add go.sum --- .gitignore | 1 - go/go.sum | 12 ++++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 go/go.sum diff --git a/.gitignore b/.gitignore index 80b4304..6d23611 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ pkg .idea -go.sum /rust/target **/*.rs.bk diff --git a/go/go.sum b/go/go.sum new file mode 100644 index 0000000..c68149c --- /dev/null +++ b/go/go.sum @@ -0,0 +1,12 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4= +github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 3d9865d6c70458983a61e6a3b6cd3851462a1321 Mon Sep 17 00:00:00 2001 From: nange Date: Wed, 26 Jul 2023 18:31:07 +0800 Subject: [PATCH 26/50] add rust action --- .github/workflows/rust.yml | 33 ++++++++++++++++++++++++++ rust/src/linkedlist/linkedlist.rs | 20 ---------------- rust/src/linkedlist/merge_k_lists.rs | 6 ++--- rust/src/linkedlist/merge_two_lists.rs | 3 ++- rust/src/linkedlist/mod.rs | 29 ++++++++++++++++++++-- 5 files changed, 64 insertions(+), 27 deletions(-) create mode 100644 .github/workflows/rust.yml delete mode 100644 rust/src/linkedlist/linkedlist.rs diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 0000000..5dc7754 --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,33 @@ +name: Rust + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Update local toolchain + run: | + rustup update + + - name: Toolchain info + run: | + cargo --version + rustc --version + cargo clippy --version + + - name: Run lint + run: cd rust && cargo clippy + + - name: Run tests + run: cargo test \ No newline at end of file diff --git a/rust/src/linkedlist/linkedlist.rs b/rust/src/linkedlist/linkedlist.rs deleted file mode 100644 index dfa4ad8..0000000 --- a/rust/src/linkedlist/linkedlist.rs +++ /dev/null @@ -1,20 +0,0 @@ -use std::cmp::Ord; - -/// Definition for singly-linked list. -#[derive(PartialEq, Eq, Clone, Debug)] -pub struct ListNode { - pub val: i32, - pub next: Option>, -} - -impl Ord for ListNode { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - self.val.cmp(&other.val) - } -} - -impl PartialOrd for ListNode { - fn partial_cmp(&self, other: &Self) -> Option { - self.val.partial_cmp(&other.val) - } -} diff --git a/rust/src/linkedlist/merge_k_lists.rs b/rust/src/linkedlist/merge_k_lists.rs index aebb4f1..d547bb0 100644 --- a/rust/src/linkedlist/merge_k_lists.rs +++ b/rust/src/linkedlist/merge_k_lists.rs @@ -5,10 +5,8 @@ use std::collections::BinaryHeap; pub fn merge_k_lists(lists: Vec>>) -> Option> { // 将链表头节点加入堆 let mut heap = BinaryHeap::new(); - for node in lists { - if let Some(boxed_node) = node { - heap.push(Reverse(boxed_node)); - } + for node in lists.into_iter().flatten() { + heap.push(Reverse(node)); } // 虚拟头结点 diff --git a/rust/src/linkedlist/merge_two_lists.rs b/rust/src/linkedlist/merge_two_lists.rs index ac6449b..b3cf72c 100644 --- a/rust/src/linkedlist/merge_two_lists.rs +++ b/rust/src/linkedlist/merge_two_lists.rs @@ -34,7 +34,8 @@ pub fn merge_two_lists( dummy.unwrap().next } -pub fn merge_two_lists_with_recursion( +#[allow(dead_code)] +fn merge_two_lists_with_recursion( list1: Option>, list2: Option>, ) -> Option> { diff --git a/rust/src/linkedlist/mod.rs b/rust/src/linkedlist/mod.rs index deca801..8806991 100644 --- a/rust/src/linkedlist/mod.rs +++ b/rust/src/linkedlist/mod.rs @@ -1,9 +1,34 @@ mod detect_cycle; -mod linkedlist; mod merge_k_lists; mod merge_two_lists; mod middle_node; mod partition; mod remove_nth_from_end; -pub use linkedlist::ListNode; +pub use detect_cycle::detect_cycle; +pub use merge_k_lists::merge_k_lists; +pub use merge_two_lists::merge_two_lists; +pub use middle_node::middle_node; +pub use partition::partition; +pub use remove_nth_from_end::remove_nth_from_end; + +use std::cmp::Ord; + +/// Definition for singly-linked list. +#[derive(PartialEq, Eq, Clone, Debug)] +pub struct ListNode { + pub val: i32, + pub next: Option>, +} + +impl Ord for ListNode { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.val.cmp(&other.val) + } +} + +impl PartialOrd for ListNode { + fn partial_cmp(&self, other: &Self) -> Option { + self.val.partial_cmp(&other.val) + } +} From ec071caf2560669c0e20279eaa342c2af32e63bd Mon Sep 17 00:00:00 2001 From: nange Date: Wed, 26 Jul 2023 18:33:43 +0800 Subject: [PATCH 27/50] update rust action --- .github/workflows/rust.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 5dc7754..401ab40 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -10,7 +10,8 @@ env: CARGO_TERM_COLOR: always jobs: - build: + test-lint: + name: test-lint runs-on: ubuntu-latest steps: @@ -26,8 +27,8 @@ jobs: rustc --version cargo clippy --version + - name: Run tests + run: cd rust && cargo test + - name: Run lint run: cd rust && cargo clippy - - - name: Run tests - run: cargo test \ No newline at end of file From ec0a6a26d807b9f0838cb225c443f620fb3ae43b Mon Sep 17 00:00:00 2001 From: nange Date: Wed, 26 Jul 2023 19:52:55 +0800 Subject: [PATCH 28/50] update --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index 8cbb35f..2d06215 100644 --- a/README.md +++ b/README.md @@ -89,6 +89,20 @@ Ref: 如何找到环的起点呢?关键在于当快慢指针相遇时,让其中任一个指针指向头节点,然后让它俩以相同速度前进,再次相遇时所在的节点位置就是环开始的位置。 +##### 两个链表是否相交 + +Ref: + +[Go实现](/go/linkedlist/get_intersection_node.go)、[Rust实现](/rust/src/linkedlist/get_intersection_node.rs) + +这题最简单的思路是给每个节点构造HashSet,然后通过HashSet判断是否存在相同节点,但这样空间复杂度较高。 + +想要通过O(1)的空间复杂度完成就要充分观察相交链表的特点:我们可以通过让两个指针遍历两个链表,想办法让两个指针在相交处相遇,这样就找到了相交节点。 + +方法一:先分别遍历两个链表,计算出链表长度,将两个长度相减即得到长链表比短链表多出来的长度N,然后让长链表指针先走N步,然后两个指针再一起往前走,两个指针相同了,则找到相交节点。 + +方法二:分别定义两个指针,用于遍历两个链表,指针1遍历完链表1,则将指针1切换到链表2,对指针2做同样的逻辑,最终当两个指针相同时,则就是相交链表节点。这种方法技巧性更强,不容易想到。 + ## 参考资料 本项目内容主要参考:[labuladong 的算法小抄](https://labuladong.github.io/algo/) From 3c6d90ec8b3e799d4518c8bc04c8e3d5391f8499 Mon Sep 17 00:00:00 2001 From: nange Date: Thu, 27 Jul 2023 14:35:03 +0800 Subject: [PATCH 29/50] add linkedlist/detect_cycle --- go/linkedlist/get_intersection_node.go | 39 ++++++++++ go/linkedlist/get_intersection_node_test.go | 26 +++++++ rust/src/linkedlist/detect_cycle.rs | 16 ++-- rust/src/linkedlist/get_intersection_node.rs | 81 ++++++++++++++++++++ rust/src/linkedlist/mod.rs | 10 ++- 5 files changed, 161 insertions(+), 11 deletions(-) create mode 100644 go/linkedlist/get_intersection_node.go create mode 100644 go/linkedlist/get_intersection_node_test.go create mode 100644 rust/src/linkedlist/get_intersection_node.rs diff --git a/go/linkedlist/get_intersection_node.go b/go/linkedlist/get_intersection_node.go new file mode 100644 index 0000000..a16655d --- /dev/null +++ b/go/linkedlist/get_intersection_node.go @@ -0,0 +1,39 @@ +package linkedlist + +func GetIntersectionNode(headA, headB *ListNode) *ListNode { + p1 := headA + p2 := headB + lenA := 0 + lenB := 0 + + for p1 != nil { + p1 = p1.Next + lenA++ + } + for p2 != nil { + p2 = p2.Next + lenB++ + } + if lenA == 0 || lenB == 0 { + return nil + } + + p1 = headA + p2 = headB + if lenA >= lenB { // A 比 B 长,则A先走 lenA - lenB 步 + for i := 0; i < lenA-lenB; i++ { + p1 = p1.Next + } + } else { // B 比 A 长,则B先走 lenB - lenA 步 + for i := 0; i < lenB-lenA; i++ { + p2 = p2.Next + } + } + // 再一起往后走,直到相遇 + for p1 != p2 { + p1 = p1.Next + p2 = p2.Next + } + + return p1 +} diff --git a/go/linkedlist/get_intersection_node_test.go b/go/linkedlist/get_intersection_node_test.go new file mode 100644 index 0000000..b0e4e5f --- /dev/null +++ b/go/linkedlist/get_intersection_node_test.go @@ -0,0 +1,26 @@ +package linkedlist + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_GetIntersectionNode(t *testing.T) { + ret1 := GetIntersectionNode(nil, nil) + assert.Nil(t, ret1) + + head1 := &ListNode{Val: 1} + head2 := &ListNode{Val: 10} + node1 := &ListNode{Val: 2} + node2 := &ListNode{Val: 11} + nodeInte := &ListNode{Val: 3, Next: &ListNode{Val: 4}} + + head1.Next = node1 + node1.Next = nodeInte + head2.Next = node2 + node2.Next = nodeInte + + ret2 := GetIntersectionNode(head1, head2) + assert.Equal(t, ret2, nodeInte) +} diff --git a/rust/src/linkedlist/detect_cycle.rs b/rust/src/linkedlist/detect_cycle.rs index 06ad614..190106c 100644 --- a/rust/src/linkedlist/detect_cycle.rs +++ b/rust/src/linkedlist/detect_cycle.rs @@ -1,12 +1,8 @@ use std::{cell::RefCell, rc::Rc}; -#[derive(PartialEq, Eq, Clone, Debug)] -pub struct ListNode { - pub val: i32, - pub next: Option>>, -} +use super::ListNode2; -pub fn detect_cycle(head: Option>>) -> Option>> { +pub fn detect_cycle(head: Option>>) -> Option>> { let mut slow = head.clone(); let mut fast = head.clone(); @@ -44,10 +40,10 @@ mod tests { let ret = detect_cycle(None); assert!(ret.is_none()); - let head = Some(Rc::new(RefCell::new(ListNode { val: 1, next: None }))); - let node1 = Some(Rc::new(RefCell::new(ListNode { val: 2, next: None }))); - let node2 = Some(Rc::new(RefCell::new(ListNode { val: 3, next: None }))); - let node3 = Some(Rc::new(RefCell::new(ListNode { val: 4, next: None }))); + let head = Some(Rc::new(RefCell::new(ListNode2 { val: 1, next: None }))); + let node1 = Some(Rc::new(RefCell::new(ListNode2 { val: 2, next: None }))); + let node2 = Some(Rc::new(RefCell::new(ListNode2 { val: 3, next: None }))); + let node3 = Some(Rc::new(RefCell::new(ListNode2 { val: 4, next: None }))); head.as_ref().unwrap().borrow_mut().next = node1.clone(); node1.as_ref().unwrap().borrow_mut().next = node2.clone(); node2.as_ref().unwrap().borrow_mut().next = node3.clone(); diff --git a/rust/src/linkedlist/get_intersection_node.rs b/rust/src/linkedlist/get_intersection_node.rs new file mode 100644 index 0000000..5b4cb43 --- /dev/null +++ b/rust/src/linkedlist/get_intersection_node.rs @@ -0,0 +1,81 @@ +use std::{cell::RefCell, rc::Rc}; + +use super::ListNode2; + +pub fn get_intersection_node( + head1: Option>>, + head2: Option>>, +) -> Option>> { + let mut p1 = head1.clone(); + let mut p2 = head2.clone(); + let mut len_a = 0; + let mut len_b = 0; + + while p1.is_some() { + p1 = p1.unwrap().borrow().next.clone(); + len_a += 1; + } + while p2.is_some() { + p2 = p2.unwrap().borrow().next.clone(); + len_b += 1; + } + if len_a == 0 || len_b == 0 { + return None; + } + + p1 = head1.clone(); + p2 = head2.clone(); + if len_a >= len_b { + // A 比 B 长,则A先走 lenA - lenB 步 + for _ in 0..(len_a - len_b) { + p1 = p1.unwrap().borrow().next.clone(); + } + } else { + // B 比 A 长,则B先走 lenB - lenA 步 + for _ in 0..(len_b - len_a) { + p2 = p2.unwrap().borrow().next.clone(); + } + } + + // 再一起往后走,直到相遇 + while !Rc::ptr_eq(p1.as_ref().unwrap(), p2.as_ref().unwrap()) { + p1 = p1.unwrap().borrow().next.clone(); + p2 = p2.unwrap().borrow().next.clone(); + } + + p1 +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_get_intersection_node() { + let ret = get_intersection_node(None, None); + assert!(ret.is_none()); + + let head1 = Some(Rc::new(RefCell::new(ListNode2 { val: 1, next: None }))); + let head2 = Some(Rc::new(RefCell::new(ListNode2 { + val: 10, + next: None, + }))); + let node1 = Some(Rc::new(RefCell::new(ListNode2 { val: 2, next: None }))); + let node2 = Some(Rc::new(RefCell::new(ListNode2 { + val: 11, + next: None, + }))); + let node_inte = Some(Rc::new(RefCell::new(ListNode2 { + val: 3, + next: Some(Rc::new(RefCell::new(ListNode2 { val: 4, next: None }))), + }))); + + head1.as_ref().unwrap().borrow_mut().next = node1.clone(); + head2.as_ref().unwrap().borrow_mut().next = node2.clone(); + node1.as_ref().unwrap().borrow_mut().next = node_inte.clone(); + node2.as_ref().unwrap().borrow_mut().next = node_inte.clone(); + + let ret = get_intersection_node(head1, head2); + assert_eq!(ret, node_inte.clone()); + } +} diff --git a/rust/src/linkedlist/mod.rs b/rust/src/linkedlist/mod.rs index 8806991..b06d9b1 100644 --- a/rust/src/linkedlist/mod.rs +++ b/rust/src/linkedlist/mod.rs @@ -1,4 +1,5 @@ mod detect_cycle; +mod get_intersection_node; mod merge_k_lists; mod merge_two_lists; mod middle_node; @@ -6,13 +7,14 @@ mod partition; mod remove_nth_from_end; pub use detect_cycle::detect_cycle; +pub use get_intersection_node::get_intersection_node; pub use merge_k_lists::merge_k_lists; pub use merge_two_lists::merge_two_lists; pub use middle_node::middle_node; pub use partition::partition; pub use remove_nth_from_end::remove_nth_from_end; -use std::cmp::Ord; +use std::{cell::RefCell, cmp::Ord, rc::Rc}; /// Definition for singly-linked list. #[derive(PartialEq, Eq, Clone, Debug)] @@ -32,3 +34,9 @@ impl PartialOrd for ListNode { self.val.partial_cmp(&other.val) } } + +#[derive(PartialEq, Eq, Clone, Debug)] +pub struct ListNode2 { + pub val: i32, + pub next: Option>>, +} From d3c253fd888a380e679b59c1c33de31cef7534c2 Mon Sep 17 00:00:00 2001 From: nange Date: Sat, 29 Jul 2023 19:25:01 +0800 Subject: [PATCH 30/50] add linkedlist/reverse_list --- README.md | 11 +++++ go/linkedlist/reverse_list.go | 18 ++++++++ go/linkedlist/reverse_list_test.go | 33 +++++++++++++++ rust/src/linkedlist/mod.rs | 2 + rust/src/linkedlist/reverse_list.rs | 66 +++++++++++++++++++++++++++++ 5 files changed, 130 insertions(+) create mode 100644 go/linkedlist/reverse_list.go create mode 100644 go/linkedlist/reverse_list_test.go create mode 100644 rust/src/linkedlist/reverse_list.rs diff --git a/README.md b/README.md index 2d06215..1eeb6e0 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,17 @@ Ref: + +[Go实现](/go/linkedlist/reverse_list.go)、[Rust实现](/rust/src/linkedlist/reverse_list.rs) + +这个题目明显可以使用迭代方法,但需要小心处理临时对象的保存和边界条件的处理。 +更简单的方式是使用递归法,通过递归的方式找到最后一个节点,之后从倒数第二个节点开始逐个反转链表。 + ## 参考资料 本项目内容主要参考:[labuladong 的算法小抄](https://labuladong.github.io/algo/) diff --git a/go/linkedlist/reverse_list.go b/go/linkedlist/reverse_list.go new file mode 100644 index 0000000..f58a028 --- /dev/null +++ b/go/linkedlist/reverse_list.go @@ -0,0 +1,18 @@ +package linkedlist + +func ReverseList(head *ListNode) *ListNode { + // 定义递归退出条件 + if head == nil || head.Next == nil { + return head + } + // 递归返回最后一个节点作为头结点返回 + last := ReverseList(head.Next) + + // 从倒数第二个节点开始,对其后面的一个节点执行链表反转 + head.Next.Next = head + // 从倒数第二个节点开始,将自身的Next指针递归指向Nil + //(中间节点其实不用这一步也可以,但是因为最后一个head节点必须要这样做,因此统一做最好) + head.Next = nil + + return last +} diff --git a/go/linkedlist/reverse_list_test.go b/go/linkedlist/reverse_list_test.go new file mode 100644 index 0000000..4c3e6c8 --- /dev/null +++ b/go/linkedlist/reverse_list_test.go @@ -0,0 +1,33 @@ +package linkedlist + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_ReverseList(t *testing.T) { + ret1 := ReverseList(nil) + assert.Nil(t, ret1) + + ret2 := ReverseList(&ListNode{ + Val: 1, + Next: &ListNode{ + Val: 2, + Next: &ListNode{ + Val: 3, + Next: nil, + }, + }, + }) + assert.Equal(t, ret2, &ListNode{ + Val: 3, + Next: &ListNode{ + Val: 2, + Next: &ListNode{ + Val: 1, + Next: nil, + }, + }, + }) +} diff --git a/rust/src/linkedlist/mod.rs b/rust/src/linkedlist/mod.rs index b06d9b1..a5287d7 100644 --- a/rust/src/linkedlist/mod.rs +++ b/rust/src/linkedlist/mod.rs @@ -5,6 +5,7 @@ mod merge_two_lists; mod middle_node; mod partition; mod remove_nth_from_end; +mod reverse_list; pub use detect_cycle::detect_cycle; pub use get_intersection_node::get_intersection_node; @@ -13,6 +14,7 @@ pub use merge_two_lists::merge_two_lists; pub use middle_node::middle_node; pub use partition::partition; pub use remove_nth_from_end::remove_nth_from_end; +pub use reverse_list::reverse_list; use std::{cell::RefCell, cmp::Ord, rc::Rc}; diff --git a/rust/src/linkedlist/reverse_list.rs b/rust/src/linkedlist/reverse_list.rs new file mode 100644 index 0000000..0bcd5fd --- /dev/null +++ b/rust/src/linkedlist/reverse_list.rs @@ -0,0 +1,66 @@ +use super::ListNode; + +pub fn reverse_list(head: Option>) -> Option> { + // 定义helper函数,帮助递归状态的保存。 + // 因为递归的时候除了需要head参数外,还需要一个状态就是原始链表当前节点的前一个节点, + // 因为我们要把当前节点的next节点设置为前一个节点 + // 这是由于Rust的所有权生命周期规则限制造成的,如果不这样做,则需要使用RefCell和Rc来维护和更新状态 + fn helper(head: Option>, prev: Option>) -> Option> { + match head { + Some(mut node) => { + let next = node.next.take(); + node.next = prev; + helper(next, Some(node)) + } + None => prev, + } + } + + helper(head, None) // head没有前一个节点,所以是None +} + +#[allow(dead_code)] +fn reverse_list_with_loop(head: Option>) -> Option> { + let mut head = head; + let mut prev: Option> = None; + + while let Some(mut node) = head { + head = node.next.take(); + node.next = prev; + prev = Some(node); + } + + prev +} + +#[cfg(test)] +mod tests { + use super::super::ListNode; + use super::*; + + #[test] + fn test_reverse_list() { + let ret = reverse_list(None); + assert!(ret.is_none()); + + let head = Some(Box::new(ListNode { + val: 1, + next: Some(Box::new(ListNode { + val: 2, + next: Some(Box::new(ListNode { val: 3, next: None })), + })), + })); + let expect = Some(Box::new(ListNode { + val: 3, + next: Some(Box::new(ListNode { + val: 2, + next: Some(Box::new(ListNode { val: 1, next: None })), + })), + })); + let ret = reverse_list(head.clone()); + assert_eq!(ret, expect.clone()); + + let ret = reverse_list_with_loop(head.clone()); + assert_eq!(ret, expect.clone()); + } +} From 83170ac8e0fcfde18dad0d08de57605dbe2a940c Mon Sep 17 00:00:00 2001 From: nange Date: Thu, 3 Aug 2023 20:00:56 +0800 Subject: [PATCH 31/50] add go/linkedlist/reverse_list_n --- README.md | 8 ++++ go/linkedlist/reverse_list_n.go | 25 +++++++++++++ go/linkedlist/reverse_list_n_test.go | 55 ++++++++++++++++++++++++++++ 3 files changed, 88 insertions(+) create mode 100644 go/linkedlist/reverse_list_n.go create mode 100644 go/linkedlist/reverse_list_n_test.go diff --git a/README.md b/README.md index 1eeb6e0..6c85291 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,14 @@ Ref: 这个题目明显可以使用迭代方法,但需要小心处理临时对象的保存和边界条件的处理。 更简单的方式是使用递归法,通过递归的方式找到最后一个节点,之后从倒数第二个节点开始逐个反转链表。 +##### 反转链表前N个节点 + +[Go实现](/go/linkedlist/reverse_list_n.go)、[Rust实现](/rust/src/linkedlist/reverse_list_n.rs) + +这个题目和上一个的区别是:只反转前N个,然后第N个作为新的头节点,原来的头结点的Next节点指向原来的第N+1个节点。 + +方法是递归到第N个节点开始返回,像之前的逻辑一样反转链表,不同的是需要返回第N个和第N+1个节点,第N个节点作为新的头节点,第N+1个节点用于后续反转的节点指向它。 + ## 参考资料 本项目内容主要参考:[labuladong 的算法小抄](https://labuladong.github.io/algo/) diff --git a/go/linkedlist/reverse_list_n.go b/go/linkedlist/reverse_list_n.go new file mode 100644 index 0000000..0937024 --- /dev/null +++ b/go/linkedlist/reverse_list_n.go @@ -0,0 +1,25 @@ +package linkedlist + +func ReverseN(head *ListNode, n int) *ListNode { + h, _ := helper(head, n) + return h +} + +func helper(node *ListNode, n int) (*ListNode, *ListNode) { + if n <= 0 { + return node, node + } + if (node == nil || node.Next == nil) && n > 1 { + return node, node + } + if n == 1 { + return node, node.Next + } + + last, successor := helper(node.Next, n-1) + + node.Next.Next = node + node.Next = successor + + return last, successor +} diff --git a/go/linkedlist/reverse_list_n_test.go b/go/linkedlist/reverse_list_n_test.go new file mode 100644 index 0000000..d489cde --- /dev/null +++ b/go/linkedlist/reverse_list_n_test.go @@ -0,0 +1,55 @@ +package linkedlist + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_ReverseN(t *testing.T) { + ret1 := ReverseN(nil, 0) + assert.Nil(t, ret1) + + ret2 := ReverseN(&ListNode{ + Val: 1, + Next: nil, + }, 2) + assert.Equal(t, ret2, &ListNode{ + Val: 1, + Next: nil, + }) + + list := &ListNode{ + Val: 1, + Next: &ListNode{ + Val: 2, + Next: &ListNode{ + Val: 3, + Next: &ListNode{ + Val: 4, + Next: &ListNode{ + Val: 5, + Next: nil, + }, + }, + }, + }, + } + ret3 := ReverseN(list, 3) + assert.Equal(t, ret3, &ListNode{ + Val: 3, + Next: &ListNode{ + Val: 2, + Next: &ListNode{ + Val: 1, + Next: &ListNode{ + Val: 4, + Next: &ListNode{ + Val: 5, + Next: nil, + }, + }, + }, + }, + }) +} From 42574eec96710eafd3449993fc171df9609b96dd Mon Sep 17 00:00:00 2001 From: nange Date: Sat, 5 Aug 2023 18:07:31 +0800 Subject: [PATCH 32/50] add rust/linkedlist/reverse_list_n --- go/linkedlist/reverse_list_n.go | 8 +-- rust/src/linkedlist/mod.rs | 2 + rust/src/linkedlist/reverse_list_n.rs | 82 +++++++++++++++++++++++++++ 3 files changed, 87 insertions(+), 5 deletions(-) create mode 100644 rust/src/linkedlist/reverse_list_n.rs diff --git a/go/linkedlist/reverse_list_n.go b/go/linkedlist/reverse_list_n.go index 0937024..3f3e1c8 100644 --- a/go/linkedlist/reverse_list_n.go +++ b/go/linkedlist/reverse_list_n.go @@ -6,13 +6,11 @@ func ReverseN(head *ListNode, n int) *ListNode { } func helper(node *ListNode, n int) (*ListNode, *ListNode) { - if n <= 0 { + if n <= 0 || (node == nil || node.Next == nil) { // 边界条件 return node, node } - if (node == nil || node.Next == nil) && n > 1 { - return node, node - } - if n == 1 { + + if n == 1 { // 递归退出条件 return node, node.Next } diff --git a/rust/src/linkedlist/mod.rs b/rust/src/linkedlist/mod.rs index a5287d7..cddf3af 100644 --- a/rust/src/linkedlist/mod.rs +++ b/rust/src/linkedlist/mod.rs @@ -6,6 +6,7 @@ mod middle_node; mod partition; mod remove_nth_from_end; mod reverse_list; +mod reverse_list_n; pub use detect_cycle::detect_cycle; pub use get_intersection_node::get_intersection_node; @@ -15,6 +16,7 @@ pub use middle_node::middle_node; pub use partition::partition; pub use remove_nth_from_end::remove_nth_from_end; pub use reverse_list::reverse_list; +pub use reverse_list_n::reverse_list_n; use std::{cell::RefCell, cmp::Ord, rc::Rc}; diff --git a/rust/src/linkedlist/reverse_list_n.rs b/rust/src/linkedlist/reverse_list_n.rs new file mode 100644 index 0000000..c75dab8 --- /dev/null +++ b/rust/src/linkedlist/reverse_list_n.rs @@ -0,0 +1,82 @@ +use super::ListNode2; +use std::cell::RefCell; +use std::rc::Rc; + +pub fn reverse_list_n( + head: Option>>, + n: i32, +) -> Option>> { + let (last, _) = helper(head, n); + + last +} + +fn helper( + node: Option>>, + n: i32, +) -> ( + Option>>, + Option>>, +) { + if n <= 0 || (node.is_none() || node.as_ref().unwrap().borrow().next.is_none()) { + // 边界条件 + return (node.clone(), node.clone()); + } + + if n == 1 { + // 递归退出条件 + return (node.clone(), node.as_ref().unwrap().borrow().next.clone()); + } + + let (last, successor) = helper(node.as_ref().unwrap().borrow().next.clone(), n - 1); + node.as_ref() + .unwrap() + .borrow() + .next + .as_ref() + .unwrap() + .borrow_mut() + .next = node.clone(); + node.as_ref().unwrap().borrow_mut().next = successor.clone(); + + (last, successor) +} + +#[cfg(test)] +mod tests { + use super::super::ListNode2; + use super::*; + + #[test] + fn test_reverse_list_n() { + let ret = reverse_list_n(None, 0); + assert!(ret.is_none()); + + let ret = reverse_list_n( + Some(Rc::new(RefCell::new(ListNode2 { + val: 1, + next: Some(Rc::new(RefCell::new(ListNode2 { + val: 2, + next: Some(Rc::new(RefCell::new(ListNode2 { + val: 3, + next: Some(Rc::new(RefCell::new(ListNode2 { val: 4, next: None }))), + }))), + }))), + }))), + 2, + ); + assert_eq!( + ret, + Some(Rc::new(RefCell::new(ListNode2 { + val: 2, + next: Some(Rc::new(RefCell::new(ListNode2 { + val: 1, + next: Some(Rc::new(RefCell::new(ListNode2 { + val: 3, + next: Some(Rc::new(RefCell::new(ListNode2 { val: 4, next: None }))), + }))), + }))), + }))) + ) + } +} From e7f7c17f317275cabdc553504328cb3faf183b51 Mon Sep 17 00:00:00 2001 From: nange Date: Sat, 2 Sep 2023 20:26:13 +0800 Subject: [PATCH 33/50] add linkedlist/reverse_k_group --- README.md | 12 ++++++++++ go/linkedlist/reverse_k_group.go | 38 ++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 go/linkedlist/reverse_k_group.go diff --git a/README.md b/README.md index 6c85291..d7e08ad 100644 --- a/README.md +++ b/README.md @@ -122,6 +122,18 @@ Ref: 方法是递归到第N个节点开始返回,像之前的逻辑一样反转链表,不同的是需要返回第N个和第N+1个节点,第N个节点作为新的头节点,第N+1个节点用于后续反转的节点指向它。 +##### K个一组翻转链表 + +Ref: + +[Go实现](/go/linkedlist/reverse_k_group.go)、[Rust实现](/rust/src/linkedlist/reverse_k_group.rs) + +链表是一种兼具递归好迭代性质的结构,分析这个问题可以发现此问题具有递归性质。 + +比如对这个链表调用 `reverseKGroup(head, 2)`,即以 2 个节点为一组反转链表,如果我们可以把前两个节点翻转,那后面的节点该怎么处理?后面的节点还是相似的一条链表,而且规模(长度)比原来的这条链表更小,这就是**子问题**。 + +我们只需要2个一组翻转后的结果再指向下一个`reverseKGroup(head, 2)`的结果,这样就递归实现了翻转,递归退出条件是:最后的元素不足 k 个,就保持不变。 + ## 参考资料 本项目内容主要参考:[labuladong 的算法小抄](https://labuladong.github.io/algo/) diff --git a/go/linkedlist/reverse_k_group.go b/go/linkedlist/reverse_k_group.go new file mode 100644 index 0000000..904e914 --- /dev/null +++ b/go/linkedlist/reverse_k_group.go @@ -0,0 +1,38 @@ +package linkedlist + +// 翻转[a, b)的元素 +func reverse(a, b *ListNode) *ListNode { + var pre, curr, next *ListNode = nil, a, a + // 移动到b指针时,停止 + for curr != b { + next = curr.Next + curr.Next = pre + pre = curr + curr = next + } + // 返回翻转后的头结点 + return pre +} + +func ReverseKGroup(head *ListNode, k int) *ListNode { + if head == nil || head.Next == nil { + return head + } + + // 找到K的翻转区间 + a, b := head, head + for i := 0; i < k; i++ { + if b == nil { // 不足k个节点时,直接返回head + return head + } + b = b.Next + } + + // 翻转前k个元素 + newHead := reverse(a, b) + + // 递归执行此操作 + a.Next = ReverseKGroup(b, k) + + return newHead +} From 81341db7678c2b5ea79955614fdbbfaafe474931 Mon Sep 17 00:00:00 2001 From: nange Date: Mon, 4 Sep 2023 12:19:20 +0800 Subject: [PATCH 34/50] fix lint && add test --- go/linkedlist/reverse_k_group.go | 4 +-- go/linkedlist/reverse_k_group_test.go | 44 +++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 go/linkedlist/reverse_k_group_test.go diff --git a/go/linkedlist/reverse_k_group.go b/go/linkedlist/reverse_k_group.go index 904e914..f8af05d 100644 --- a/go/linkedlist/reverse_k_group.go +++ b/go/linkedlist/reverse_k_group.go @@ -2,10 +2,10 @@ package linkedlist // 翻转[a, b)的元素 func reverse(a, b *ListNode) *ListNode { - var pre, curr, next *ListNode = nil, a, a + var pre, curr *ListNode = nil, a // 移动到b指针时,停止 for curr != b { - next = curr.Next + next := curr.Next curr.Next = pre pre = curr curr = next diff --git a/go/linkedlist/reverse_k_group_test.go b/go/linkedlist/reverse_k_group_test.go new file mode 100644 index 0000000..d239ed9 --- /dev/null +++ b/go/linkedlist/reverse_k_group_test.go @@ -0,0 +1,44 @@ +package linkedlist + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_ReverseKGroup(t *testing.T) { + ret1 := ReverseKGroup(nil, 2) + assert.Nil(t, ret1) + + list := &ListNode{ + Val: 1, + Next: &ListNode{ + Val: 2, + Next: &ListNode{ + Val: 3, + Next: &ListNode{ + Val: 4, + Next: &ListNode{ + Val: 5, + }, + }, + }, + }, + } + ret2 := ReverseKGroup(list, 2) + assert.Equal(t, ret2, &ListNode{ + Val: 2, + Next: &ListNode{ + Val: 1, + Next: &ListNode{ + Val: 4, + Next: &ListNode{ + Val: 3, + Next: &ListNode{ + Val: 5, + }, + }, + }, + }, + }) +} From e0f5e824f7776190e30f35d1c022a51110fadea0 Mon Sep 17 00:00:00 2001 From: nange Date: Mon, 4 Sep 2023 20:44:47 +0800 Subject: [PATCH 35/50] update linkedlist/reverse_k_group --- rust/src/linkedlist/mod.rs | 9 +++ rust/src/linkedlist/reverse_k_group.rs | 92 ++++++++++++++++++++++++++ 2 files changed, 101 insertions(+) create mode 100644 rust/src/linkedlist/reverse_k_group.rs diff --git a/rust/src/linkedlist/mod.rs b/rust/src/linkedlist/mod.rs index cddf3af..adfb9e4 100644 --- a/rust/src/linkedlist/mod.rs +++ b/rust/src/linkedlist/mod.rs @@ -5,6 +5,7 @@ mod merge_two_lists; mod middle_node; mod partition; mod remove_nth_from_end; +mod reverse_k_group; mod reverse_list; mod reverse_list_n; @@ -15,6 +16,7 @@ pub use merge_two_lists::merge_two_lists; pub use middle_node::middle_node; pub use partition::partition; pub use remove_nth_from_end::remove_nth_from_end; +pub use reverse_k_group::reverse_k_group; pub use reverse_list::reverse_list; pub use reverse_list_n::reverse_list_n; @@ -27,6 +29,13 @@ pub struct ListNode { pub next: Option>, } +impl ListNode { + #[inline] + fn new(val: i32) -> Self { + ListNode { next: None, val } + } +} + impl Ord for ListNode { fn cmp(&self, other: &Self) -> std::cmp::Ordering { self.val.cmp(&other.val) diff --git a/rust/src/linkedlist/reverse_k_group.rs b/rust/src/linkedlist/reverse_k_group.rs new file mode 100644 index 0000000..a67362d --- /dev/null +++ b/rust/src/linkedlist/reverse_k_group.rs @@ -0,0 +1,92 @@ +use std::{cell::RefCell, rc::Rc}; + +use super::{ListNode, ListNode2}; + +pub fn reverse_k_group(head: Option>, k: i32) -> Option> { + if k <= 1 { + return head; + } + + // 1. 从head开始,确认其后至少包含k个节点,否则直接返回head + let mut head = head; + let mut curr = &mut head; + for _ in 0..k - 1 { + if curr.is_none() || curr.as_ref().unwrap().next.is_none() { + return head; + } + curr = &mut curr.as_mut().unwrap().next; + } + + // 2. 反转前k个节点 + let mut prev: Option> = None; + let mut curr = head; + for _ in 0..k { + let next = curr.as_mut().unwrap().next.take(); + curr.as_mut().unwrap().next = prev.take(); + prev = curr; + curr = next; + } + + // 3. 找到新的末尾节点,并设置其next为后续新递归结果的头节点 + // 注意:这种算法明显不是最优的,因为每次都需要在这里循环找到尾节点,更好的方式可能是使用ListNode2这样的结构 + let mut tail = &mut prev; + for i in 0..k - 1 { + tail = &mut tail.as_mut().unwrap().next; + if i == k - 2 { + break; + } + } + tail.as_mut().unwrap().next = reverse_k_group(curr, k); + + prev +} + +// TODO: 另一种实现 +pub fn reverse_k_group2( + head: Option>>, + k: i32, +) -> Option>> { + todo!() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_reverse_k_group() { + let ret = reverse_k_group(None, 2); + assert!(ret.is_none()); + + let head = Some(Box::new(ListNode { + val: 1, + next: Some(Box::new(ListNode { + val: 2, + next: Some(Box::new(ListNode { + val: 3, + next: Some(Box::new(ListNode { + val: 4, + next: Some(Box::new(ListNode { val: 5, next: None })), + })), + })), + })), + })); + let ret = reverse_k_group(head, 2); + assert_eq!( + ret, + Some(Box::new(ListNode { + val: 2, + next: Some(Box::new(ListNode { + val: 1, + next: Some(Box::new(ListNode { + val: 4, + next: Some(Box::new(ListNode { + val: 3, + next: Some(Box::new(ListNode { val: 5, next: None })), + })), + })), + })), + })) + ); + } +} From 352f6047b62ff9f70d1f9a498616b50f7ec74ec7 Mon Sep 17 00:00:00 2001 From: nange Date: Tue, 5 Sep 2023 11:54:54 +0800 Subject: [PATCH 36/50] update rust/linkedlist/reverse_k_group --- rust/src/linkedlist/reverse_k_group.rs | 72 +++++++++++++++++++++++++- 1 file changed, 70 insertions(+), 2 deletions(-) diff --git a/rust/src/linkedlist/reverse_k_group.rs b/rust/src/linkedlist/reverse_k_group.rs index a67362d..0cd7262 100644 --- a/rust/src/linkedlist/reverse_k_group.rs +++ b/rust/src/linkedlist/reverse_k_group.rs @@ -41,12 +41,41 @@ pub fn reverse_k_group(head: Option>, k: i32) -> Option>>, k: i32, ) -> Option>> { - todo!() + if k <= 1 { + return head; + } + + // 1. 从head开始,确认其后至少包含k个节点,否则直接返回head + let mut curr = head.clone(); + for _ in 0..k - 1 { + if curr.is_none() || curr.as_ref().unwrap().borrow().next.is_none() { + return head.clone(); + } + let next = curr.as_deref().unwrap().borrow().next.clone(); + curr = next; + } + + // 2. 反转前k个节点 + let mut curr = head.clone(); + let mut prev = None; + for _ in 0..k { + let next = curr.as_ref().unwrap().borrow_mut().next.take(); + curr.as_ref().unwrap().borrow_mut().next = prev.take(); + prev = curr; + curr = next; + } + + // 3. 将翻转后的尾节点的next指向后续递归翻转后的头节点 + let new_tail = head.clone(); + new_tail.as_ref().unwrap().borrow_mut().next = reverse_k_group2(curr, k); + + // 返回翻转后的头节点 + prev } #[cfg(test)] @@ -71,6 +100,7 @@ mod tests { })), })), })); + let ret = reverse_k_group(head, 2); assert_eq!( ret, @@ -89,4 +119,42 @@ mod tests { })) ); } + + #[test] + fn test_reverse_k_group2() { + let ret = reverse_k_group2(None, 2); + assert!(ret.is_none()); + + let head = Some(Rc::new(RefCell::new(ListNode2 { + val: 1, + next: Some(Rc::new(RefCell::new(ListNode2 { + val: 2, + next: Some(Rc::new(RefCell::new(ListNode2 { + val: 3, + next: Some(Rc::new(RefCell::new(ListNode2 { + val: 4, + next: Some(Rc::new(RefCell::new(ListNode2 { val: 5, next: None }))), + }))), + }))), + }))), + }))); + + let ret = reverse_k_group2(head, 2); + assert_eq!( + ret, + Some(Rc::new(RefCell::new(ListNode2 { + val: 2, + next: Some(Rc::new(RefCell::new(ListNode2 { + val: 1, + next: Some(Rc::new(RefCell::new(ListNode2 { + val: 4, + next: Some(Rc::new(RefCell::new(ListNode2 { + val: 3, + next: Some(Rc::new(RefCell::new(ListNode2 { val: 5, next: None }))), + }))), + }))), + }))), + }))) + ); + } } From 955f629cbe9e642ab9278319ac92c09459306be0 Mon Sep 17 00:00:00 2001 From: nange Date: Tue, 5 Sep 2023 11:56:12 +0800 Subject: [PATCH 37/50] update rust/linkedlist/reverse_k_group --- rust/src/linkedlist/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/rust/src/linkedlist/mod.rs b/rust/src/linkedlist/mod.rs index adfb9e4..41539c7 100644 --- a/rust/src/linkedlist/mod.rs +++ b/rust/src/linkedlist/mod.rs @@ -17,6 +17,7 @@ pub use middle_node::middle_node; pub use partition::partition; pub use remove_nth_from_end::remove_nth_from_end; pub use reverse_k_group::reverse_k_group; +pub use reverse_k_group::reverse_k_group2; pub use reverse_list::reverse_list; pub use reverse_list_n::reverse_list_n; From 7eca2089c91b5e52c148c367cb54564c8468bfc2 Mon Sep 17 00:00:00 2001 From: nange Date: Wed, 13 Sep 2023 20:06:38 +0800 Subject: [PATCH 38/50] add go/linkedlist/is_palindrome --- README.md | 15 +++++- go/linkedlist/is_palindrome.go | 84 +++++++++++++++++++++++++++++ go/linkedlist/is_palindrome_test.go | 57 ++++++++++++++++++++ 3 files changed, 154 insertions(+), 2 deletions(-) create mode 100644 go/linkedlist/is_palindrome.go create mode 100644 go/linkedlist/is_palindrome_test.go diff --git a/README.md b/README.md index d7e08ad..6ff7a05 100644 --- a/README.md +++ b/README.md @@ -122,7 +122,7 @@ Ref: 方法是递归到第N个节点开始返回,像之前的逻辑一样反转链表,不同的是需要返回第N个和第N+1个节点,第N个节点作为新的头节点,第N+1个节点用于后续反转的节点指向它。 -##### K个一组翻转链表 +#### K个一组翻转链表 Ref: @@ -134,6 +134,17 @@ Ref: 我们只需要2个一组翻转后的结果再指向下一个`reverseKGroup(head, 2)`的结果,这样就递归实现了翻转,递归退出条件是:最后的元素不足 k 个,就保持不变。 +#### 判断回文链表 + +[Go实现](/go/linkedlist/is_palindrome.go)、[Rust实现](/rust/src/linkedlist/is_palindrome.rs) + +回文链表是一个特殊类型的链表,其中链表的前半部分和后半部分在反转后是相同的。也就是说,从前向后读和从后向前读,链表中的元素顺序是相同的。 + +一种实现是可以遍历链表,将链表元素加入一个栈结构中,然后再将栈中元素和原始链表元素挨个比较,看是否相同 +(栈结构也可以采用后序递归遍历链表节点模拟,在递归调用中进行比较元素)。 这种方法唯一的缺点是空间复杂度为O(N),较高。 + +另一种更优的空间复杂度实现方式是,找到链表中点,把中点之后的部分反转,再和中点之前的部分进行比较,看元素是否相等。空间复杂度为O(1)。 + ## 参考资料 -本项目内容主要参考:[labuladong 的算法小抄](https://labuladong.github.io/algo/) +* [labuladong 的算法小抄](https://labuladong.github.io/algo/) diff --git a/go/linkedlist/is_palindrome.go b/go/linkedlist/is_palindrome.go new file mode 100644 index 0000000..176626c --- /dev/null +++ b/go/linkedlist/is_palindrome.go @@ -0,0 +1,84 @@ +package linkedlist + +// IsPalindromeV1 V1版本采用递归方式,代码简单,空间复杂度为O(N) +func IsPalindromeV1(head *ListNode) bool { + if head == nil || head.Next == nil { + return false + } + if head.Next.Next == nil { + return true + } + + h := head + + var traverse func(node *ListNode) bool + traverse = func(node *ListNode) bool { + if node == nil { + return true + } + + eq := traverse(node.Next) + eq = eq && h.Val == node.Val + h = h.Next + + return eq + } + + return traverse(head) +} + +// IsPalindromeV2 不用递归,使空间复杂度变为O(1) +func IsPalindromeV2(head *ListNode) bool { + if head == nil || head.Next == nil { + return false + } + if head.Next.Next == nil { + return true + } + + middle := findMiddle(head) + rev := reverseNode(middle) + + h := head + for rev != nil { + if rev.Val != h.Val { + return false + } + rev = rev.Next + h = h.Next + } + + return true +} + +func findMiddle(head *ListNode) *ListNode { + if head == nil { + return nil + } + + fast := head + slow := head + for fast != nil && fast.Next != nil { + fast = fast.Next.Next + slow = slow.Next + } + + return slow +} + +func reverseNode(node *ListNode) *ListNode { + if node == nil || node.Next == nil { + return node + } + + prev := (*ListNode)(nil) + curr := node + for curr != nil { + next := curr.Next + curr.Next = prev + prev = curr + curr = next + } + + return prev +} diff --git a/go/linkedlist/is_palindrome_test.go b/go/linkedlist/is_palindrome_test.go new file mode 100644 index 0000000..dde1b8e --- /dev/null +++ b/go/linkedlist/is_palindrome_test.go @@ -0,0 +1,57 @@ +package linkedlist + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_IsPalindrome(t *testing.T) { + fns := []func(node *ListNode) bool{IsPalindromeV1, IsPalindromeV2} + for _, fn := range fns { + ret1 := fn(nil) + assert.False(t, ret1) + + ret2 := fn(&ListNode{ + Val: 1, + Next: &ListNode{ + Val: 2, + Next: &ListNode{ + Val: 1, + Next: nil, + }, + }, + }) + assert.True(t, ret2) + + ret3 := fn(&ListNode{ + Val: 1, + Next: &ListNode{ + Val: 2, + Next: &ListNode{ + Val: 1, + Next: &ListNode{ + Val: 3, + Next: nil, + }, + }, + }, + }) + assert.False(t, ret3) + + ret4 := fn(&ListNode{ + Val: 1, + Next: &ListNode{ + Val: 2, + Next: &ListNode{ + Val: 2, + Next: &ListNode{ + Val: 1, + Next: nil, + }, + }, + }, + }) + assert.True(t, ret4) + } +} From 1b3a9ecee1e7c8cdacf3a57b996f4ecb263b8362 Mon Sep 17 00:00:00 2001 From: nange Date: Wed, 13 Sep 2023 20:15:28 +0800 Subject: [PATCH 39/50] update go/linkedlist/is_palindrome --- go/linkedlist/is_palindrome.go | 8 ++++---- go/linkedlist/is_palindrome_test.go | 9 +++++++++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/go/linkedlist/is_palindrome.go b/go/linkedlist/is_palindrome.go index 176626c..7dfe8eb 100644 --- a/go/linkedlist/is_palindrome.go +++ b/go/linkedlist/is_palindrome.go @@ -2,10 +2,10 @@ package linkedlist // IsPalindromeV1 V1版本采用递归方式,代码简单,空间复杂度为O(N) func IsPalindromeV1(head *ListNode) bool { - if head == nil || head.Next == nil { + if head == nil { return false } - if head.Next.Next == nil { + if head.Next == nil { return true } @@ -29,10 +29,10 @@ func IsPalindromeV1(head *ListNode) bool { // IsPalindromeV2 不用递归,使空间复杂度变为O(1) func IsPalindromeV2(head *ListNode) bool { - if head == nil || head.Next == nil { + if head == nil { return false } - if head.Next.Next == nil { + if head.Next == nil { return true } diff --git a/go/linkedlist/is_palindrome_test.go b/go/linkedlist/is_palindrome_test.go index dde1b8e..bc1b065 100644 --- a/go/linkedlist/is_palindrome_test.go +++ b/go/linkedlist/is_palindrome_test.go @@ -53,5 +53,14 @@ func Test_IsPalindrome(t *testing.T) { }, }) assert.True(t, ret4) + + ret5 := fn(&ListNode{ + Val: 1, + Next: &ListNode{ + Val: 2, + Next: nil, + }, + }) + assert.False(t, ret5) } } From fd44ccf7ff5407f95c42245093ae3a33a43987f3 Mon Sep 17 00:00:00 2001 From: nange Date: Thu, 14 Sep 2023 14:48:18 +0800 Subject: [PATCH 40/50] add rust/linkedlist/is_palindrome --- rust/src/linkedlist/is_palindrome.rs | 121 +++++++++++++++++++++++++++ rust/src/linkedlist/mod.rs | 33 +++++--- 2 files changed, 143 insertions(+), 11 deletions(-) create mode 100644 rust/src/linkedlist/is_palindrome.rs diff --git a/rust/src/linkedlist/is_palindrome.rs b/rust/src/linkedlist/is_palindrome.rs new file mode 100644 index 0000000..53af7bb --- /dev/null +++ b/rust/src/linkedlist/is_palindrome.rs @@ -0,0 +1,121 @@ +use std::cell::RefCell; +use std::rc::Rc; + +use super::ListNode2; + +pub fn is_palindrome(head: Option>>) -> bool { + if head.is_none() { + return false; + } + if head.as_ref().unwrap().borrow().next.is_none() { + return true; + } + // 1. 找到链表中点 + let mut slow = head.clone(); + let mut fast = head.clone(); + while !fast.is_none() && !fast.as_ref().unwrap().borrow().next.is_none() { + let next = slow.as_ref().unwrap().borrow().next.clone(); + slow = next; + let next2 = fast.as_ref().unwrap().borrow().next.as_ref().unwrap().borrow().next.clone(); + fast = next2; + } + let middle = slow; + + // 2. 反转中点之后的链表 + let mut pre = None; + let mut curr = middle.clone(); + while !curr.is_none() { + let next = curr.as_ref().unwrap().borrow().next.clone(); + curr.as_ref().unwrap().borrow_mut().next = pre.take(); + pre = curr.clone(); + curr = next; + } + + + // 3. 比较新节点`new`和老节点`head`是否一致 + let mut new = pre; + let mut old = head.clone(); + while !new.is_none() { + if new.as_ref().unwrap().borrow().val != old.as_ref().unwrap().borrow().val { + return false; + } + let new_next = new.as_ref().unwrap().borrow().next.clone(); + let old_next = old.as_ref().unwrap().borrow().next.clone(); + new = new_next; + old = old_next; + } + + true +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_is_palindrome_v1() { + let res = is_palindrome(None); + assert!(!res); + + let res = is_palindrome(Some(Rc::new(RefCell::new(ListNode2::new(0))))); + assert!(res); + + let head = Some(Rc::new(RefCell::new(ListNode2 { + val: 1, + next: Some(Rc::new(RefCell::new(ListNode2 { + val: 1, + next: None, + }))), + }))); + let res = is_palindrome(head); + assert!(res); + + let head = Some(Rc::new(RefCell::new(ListNode2 { + val: 1, + next: Some(Rc::new(RefCell::new(ListNode2 { + val: 2, + next: Some(Rc::new(RefCell::new(ListNode2 { + val: 1, + next: None, + }))), + }))), + }))); + let res = is_palindrome(head); + assert!(res); + + let head = Some(Rc::new(RefCell::new(ListNode2 { + val: 1, + next: Some(Rc::new(RefCell::new(ListNode2 { + val: 2, + next: Some(Rc::new(RefCell::new(ListNode2 { + val: 2, + next: Some(Rc::new(RefCell::new(ListNode2 { + val: 1, + next: None, + }))), + }))), + }))), + }))); + let res = is_palindrome(head); + assert!(res); + + let head = Some(Rc::new(RefCell::new(ListNode2 { + val: 1, + next: Some(Rc::new(RefCell::new(ListNode2 { + val: 2, + next: Some(Rc::new(RefCell::new(ListNode2 { + val: 2, + next: Some(Rc::new(RefCell::new(ListNode2 { + val: 1, + next: Some(Rc::new(RefCell::new(ListNode2 { + val: 1, + next: None, + }))), + }))), + }))), + }))), + }))); + let res = is_palindrome(head); + assert!(!res); + } +} \ No newline at end of file diff --git a/rust/src/linkedlist/mod.rs b/rust/src/linkedlist/mod.rs index 41539c7..63c4b9f 100644 --- a/rust/src/linkedlist/mod.rs +++ b/rust/src/linkedlist/mod.rs @@ -1,16 +1,8 @@ -mod detect_cycle; -mod get_intersection_node; -mod merge_k_lists; -mod merge_two_lists; -mod middle_node; -mod partition; -mod remove_nth_from_end; -mod reverse_k_group; -mod reverse_list; -mod reverse_list_n; +use std::{cell::RefCell, cmp::Ord, rc::Rc}; pub use detect_cycle::detect_cycle; pub use get_intersection_node::get_intersection_node; +pub use is_palindrome::is_palindrome; pub use merge_k_lists::merge_k_lists; pub use merge_two_lists::merge_two_lists; pub use middle_node::middle_node; @@ -21,7 +13,17 @@ pub use reverse_k_group::reverse_k_group2; pub use reverse_list::reverse_list; pub use reverse_list_n::reverse_list_n; -use std::{cell::RefCell, cmp::Ord, rc::Rc}; +mod detect_cycle; +mod get_intersection_node; +mod merge_k_lists; +mod merge_two_lists; +mod middle_node; +mod partition; +mod remove_nth_from_end; +mod reverse_k_group; +mod reverse_list; +mod reverse_list_n; +mod is_palindrome; /// Definition for singly-linked list. #[derive(PartialEq, Eq, Clone, Debug)] @@ -32,6 +34,7 @@ pub struct ListNode { impl ListNode { #[inline] + #[allow(dead_code)] fn new(val: i32) -> Self { ListNode { next: None, val } } @@ -54,3 +57,11 @@ pub struct ListNode2 { pub val: i32, pub next: Option>>, } + +impl ListNode2 { + #[inline] + #[allow(dead_code)] + fn new(val: i32) -> Self { + ListNode2 { next: None, val } + } +} \ No newline at end of file From 9415bba01919a49d8cb7322f9d14857304dbe500 Mon Sep 17 00:00:00 2001 From: nange Date: Thu, 14 Sep 2023 14:57:22 +0800 Subject: [PATCH 41/50] update readme.md --- README.md | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 6ff7a05..63a0bd5 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,8 @@ 所有代码都经过具体测试运行,具体环境为: - 操作系统:Ubuntu-latest 64位 -- Go语言版本: Go 1.20 -- Rust语言版本: Rust 1.70 +- Go语言版本: Go 1.21 +- Rust语言版本: Rust 1.72 ## 目录 @@ -14,16 +14,6 @@ #### 双指针技巧 -此技巧可用于解决一下一些问题: - -1. 合并两个有序链表 -2. 链表的分解 -3. 合并 k 个有序链表 -4. 寻找单链表的倒数第 k 个节点 -5. 寻找单链表的中点 -6. 判断单链表是否包含环并找出环起点 -7. 判断两个单链表是否相交并找出交点 - ##### 合并两个有序链表 Ref: @@ -32,8 +22,10 @@ Ref: 代码中用到了`p1`, `p2`两个指针用于比较两个链表节点的推进,`p`指针用于生成新链表节点的推进。在这个过程中没有数据的复制,只有对指针指向的改变,效率很高。 -代码中还用到一个链表的算法题中是很常见的「虚拟头结点」技巧,也就是 `dummy` 节点。如果不使用 dummy 虚拟节点,代码会复杂一些,需要额外处理指针 `p` 为空的情况。 -> 什么时候需要用虚拟头结点?当需要创造一条新链表的时候,可以使用虚拟头结点简化边界情况的处理。比如说,把两条有序链表合并成一条新的有序链表,是不是要创造一条新链表?再比如想把一条链表分解成两条链表,是不是也在创造新链表?这些情况都可以使用虚拟头结点简化边界情况的处理。 +代码中还用到一个链表的算法题中是很常见的「虚拟头结点」技巧,也就是 `dummy` 节点。如果不使用 dummy +虚拟节点,代码会复杂一些,需要额外处理指针 `p` 为空的情况。 +> +什么时候需要用虚拟头结点?当需要创造一条新链表的时候,可以使用虚拟头结点简化边界情况的处理。比如说,把两条有序链表合并成一条新的有序链表,是不是要创造一条新链表?再比如想把一条链表分解成两条链表,是不是也在创造新链表?这些情况都可以使用虚拟头结点简化边界情况的处理。 > > 这个问题还有另外一种解法,就是利用递归,可以去掉两个指针,因为在递归栈中可以自动保存这两个状态。参考上面两个文件中的递归实现。通常来说,递归实现的代码都会更简单,但空间复杂度更高。 @@ -53,9 +45,11 @@ Ref: 合并 k 个有序链表的逻辑类似合并两个有序链表,难点在于,如何快速得到 k 个节点中的最小节点,接到结果链表上? -通常的一种想法是可以把链表头放到数组里,可以写一个循环判断,每次找出一个最小值,这样的时间复杂度是`O(N*K)`,N是所有链表的节点数量,K 是链表个数。 +通常的一种想法是可以把链表头放到数组里,可以写一个循环判断,每次找出一个最小值,这样的时间复杂度是`O(N*K)`,N是所有链表的节点数量,K +是链表个数。 -不过还有一种更高效的方式,可以把K个链表头放入 **优先级队列(二叉堆)** 中,放入一个最小堆中,每次就能方便的获得K个节点中的最小值,算法复杂度是:`O(N*Log(K))`。 +不过还有一种更高效的方式,可以把K个链表头放入 **优先级队列(二叉堆)** +中,放入一个最小堆中,每次就能方便的获得K个节点中的最小值,算法复杂度是:`O(N*Log(K))`。 ##### 单链表倒数第K个节点 @@ -65,7 +59,8 @@ Ref: 要找到链表倒数第k个节点,一般的想法是得知道链表的长度`n`,再用`n-k+1`就得到倒数第k个节点,但是这样需要遍历两次链表,如何在只遍历一次的情况下找到呢? -同样使用双指针技巧,先让一个指针先走`k`步,然后第二个指针开始和第一个指针一起走,第一个指针走完这个整个链表,第二个指针的位置刚好就在`n-k+1`的位置上。 +同样使用双指针技巧,先让一个指针先走`k` +步,然后第二个指针开始和第一个指针一起走,第一个指针走完这个整个链表,第二个指针的位置刚好就在`n-k+1`的位置上。 > 另一种方法是使用递归,先一次性递归到链表结尾,递归出栈过程中对计数器加一,当计数器达到k时,此时的节点就是要被删除的节点。 @@ -75,7 +70,8 @@ Ref: [Go实现](/go/linkedlist/middle_node.go)、[Rust实现](/rust/src/linkedlist/middle_node.rs) -这个和上面一题类似,只是这次是找中点。同样使用快慢指针技巧,每当慢指针 slow 前进一步,快指针 fast 就前进两步,这样,当 fast 走到链表末尾时,slow 就指向了链表中点。 +这个和上面一题类似,只是这次是找中点。同样使用快慢指针技巧,每当慢指针 slow 前进一步,快指针 fast 就前进两步,这样,当 fast +走到链表末尾时,slow 就指向了链表中点。 ##### 判断链表是否包含环,并找出环起点 @@ -130,9 +126,12 @@ Ref: 链表是一种兼具递归好迭代性质的结构,分析这个问题可以发现此问题具有递归性质。 -比如对这个链表调用 `reverseKGroup(head, 2)`,即以 2 个节点为一组反转链表,如果我们可以把前两个节点翻转,那后面的节点该怎么处理?后面的节点还是相似的一条链表,而且规模(长度)比原来的这条链表更小,这就是**子问题**。 +比如对这个链表调用 `reverseKGroup(head, 2)`,即以 2 +个节点为一组反转链表,如果我们可以把前两个节点翻转,那后面的节点该怎么处理?后面的节点还是相似的一条链表,而且规模(长度)比原来的这条链表更小,这就是 +**子问题**。 -我们只需要2个一组翻转后的结果再指向下一个`reverseKGroup(head, 2)`的结果,这样就递归实现了翻转,递归退出条件是:最后的元素不足 k 个,就保持不变。 +我们只需要2个一组翻转后的结果再指向下一个`reverseKGroup(head, 2)`的结果,这样就递归实现了翻转,递归退出条件是:最后的元素不足 +k 个,就保持不变。 #### 判断回文链表 @@ -143,7 +142,8 @@ Ref: 一种实现是可以遍历链表,将链表元素加入一个栈结构中,然后再将栈中元素和原始链表元素挨个比较,看是否相同 (栈结构也可以采用后序递归遍历链表节点模拟,在递归调用中进行比较元素)。 这种方法唯一的缺点是空间复杂度为O(N),较高。 -另一种更优的空间复杂度实现方式是,找到链表中点,把中点之后的部分反转,再和中点之前的部分进行比较,看元素是否相等。空间复杂度为O(1)。 +另一种更优的空间复杂度实现方式是,找到链表中点,把中点之后的部分反转,再和中点之前的部分进行比较,看元素是否相等。空间复杂度为O( +1)。 ## 参考资料 From a028cc3b8ade668039a859e9c5481f2cbcb4cc8e Mon Sep 17 00:00:00 2001 From: nange Date: Thu, 14 Sep 2023 14:58:29 +0800 Subject: [PATCH 42/50] update readme.md --- README.md | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 63a0bd5..fcd134d 100644 --- a/README.md +++ b/README.md @@ -8,13 +8,11 @@ - Go语言版本: Go 1.21 - Rust语言版本: Rust 1.72 -## 目录 +## 链表相关 -### 链表相关 +### 双指针技巧 -#### 双指针技巧 - -##### 合并两个有序链表 +#### 合并两个有序链表 Ref: @@ -29,7 +27,7 @@ Ref: > > 这个问题还有另外一种解法,就是利用递归,可以去掉两个指针,因为在递归栈中可以自动保存这两个状态。参考上面两个文件中的递归实现。通常来说,递归实现的代码都会更简单,但空间复杂度更高。 -##### 单链表的分解 +#### 单链表的分解 Ref: @@ -37,7 +35,7 @@ Ref: 我们可以把原链表分成两个小链表,一个链表中的元素大小都小于 x,另一个链表中的元素都大于等于 x,最后再把这两条链表接到一起,就得到了题目想要的结果。 -##### 合并K个有序链表 +#### 合并K个有序链表 Ref: @@ -51,7 +49,7 @@ Ref: 不过还有一种更高效的方式,可以把K个链表头放入 **优先级队列(二叉堆)** 中,放入一个最小堆中,每次就能方便的获得K个节点中的最小值,算法复杂度是:`O(N*Log(K))`。 -##### 单链表倒数第K个节点 +#### 单链表倒数第K个节点 Ref: @@ -64,7 +62,7 @@ Ref: > 另一种方法是使用递归,先一次性递归到链表结尾,递归出栈过程中对计数器加一,当计数器达到k时,此时的节点就是要被删除的节点。 -##### 单链表中点 +#### 单链表中点 Ref: @@ -73,7 +71,7 @@ Ref: 这个和上面一题类似,只是这次是找中点。同样使用快慢指针技巧,每当慢指针 slow 前进一步,快指针 fast 就前进两步,这样,当 fast 走到链表末尾时,slow 就指向了链表中点。 -##### 判断链表是否包含环,并找出环起点 +#### 判断链表是否包含环,并找出环起点 Ref: @@ -85,7 +83,7 @@ Ref: 如何找到环的起点呢?关键在于当快慢指针相遇时,让其中任一个指针指向头节点,然后让它俩以相同速度前进,再次相遇时所在的节点位置就是环开始的位置。 -##### 两个链表是否相交 +#### 两个链表是否相交 Ref: @@ -99,9 +97,9 @@ Ref: @@ -110,7 +108,7 @@ Ref: 这个题目明显可以使用迭代方法,但需要小心处理临时对象的保存和边界条件的处理。 更简单的方式是使用递归法,通过递归的方式找到最后一个节点,之后从倒数第二个节点开始逐个反转链表。 -##### 反转链表前N个节点 +#### 反转链表前N个节点 [Go实现](/go/linkedlist/reverse_list_n.go)、[Rust实现](/rust/src/linkedlist/reverse_list_n.rs) @@ -118,7 +116,7 @@ Ref: 方法是递归到第N个节点开始返回,像之前的逻辑一样反转链表,不同的是需要返回第N个和第N+1个节点,第N个节点作为新的头节点,第N+1个节点用于后续反转的节点指向它。 -#### K个一组翻转链表 +### K个一组翻转链表 Ref: @@ -133,7 +131,7 @@ Ref: 我们只需要2个一组翻转后的结果再指向下一个`reverseKGroup(head, 2)`的结果,这样就递归实现了翻转,递归退出条件是:最后的元素不足 k 个,就保持不变。 -#### 判断回文链表 +### 判断回文链表 [Go实现](/go/linkedlist/is_palindrome.go)、[Rust实现](/rust/src/linkedlist/is_palindrome.rs) From fb2b188ce77d186aa1677c65317a7995002cf201 Mon Sep 17 00:00:00 2001 From: nange Date: Sat, 16 Sep 2023 16:23:23 +0800 Subject: [PATCH 43/50] add go/arraylist/remove_duplicates --- README.md | 21 +++++++++++++++++++++ go/arraylist/remove_duplicates.go | 18 ++++++++++++++++++ go/arraylist/remove_duplicates_test.go | 17 +++++++++++++++++ 3 files changed, 56 insertions(+) create mode 100644 go/arraylist/remove_duplicates.go create mode 100644 go/arraylist/remove_duplicates_test.go diff --git a/README.md b/README.md index fcd134d..36ffe7e 100644 --- a/README.md +++ b/README.md @@ -133,6 +133,8 @@ k 个,就保持不变。 ### 判断回文链表 +Ref: + [Go实现](/go/linkedlist/is_palindrome.go)、[Rust实现](/rust/src/linkedlist/is_palindrome.rs) 回文链表是一个特殊类型的链表,其中链表的前半部分和后半部分在反转后是相同的。也就是说,从前向后读和从后向前读,链表中的元素顺序是相同的。 @@ -143,6 +145,25 @@ k 个,就保持不变。 另一种更优的空间复杂度实现方式是,找到链表中点,把中点之后的部分反转,再和中点之前的部分进行比较,看元素是否相等。空间复杂度为O( 1)。 +## 数组相关 + +### 双指针技巧 + +双指针技巧主要分为两类:左右指针和快慢指针。 + +#### 删除有序数组中的重复项 + +Ref: + +[Go实现](/go/arraylist/remove_duplicates.go)、[Rust实现](/rust/src/linkedlist/remove_duplicates.rs) + +由于数组是有序的,重复的元素必然是连续的,这很容易就能找到重复的元素。 +难点主要有两个:一是需要原地删除,不能用新数组;由此引出第二个难点,如果每次发现相同元素就地删除,这样的时间复杂度非常高(N^2), +因为每删除一个元素就需要将后面的元素往前移动。 + +高效实现此算法是通过快慢指针技巧:让慢指针 `slow` 走在后面,快指针 `fast` 走在前面探路,找到一个不重复的元素就让 `slow` 前进一步并把元素赋值给 `slow` 。 +这样,就保证了 `nums[0..slow]` 都是无重复的元素,当 `fast` 指针遍历完整个数组 `nums` 后,`nums[0..slow]` 就是整个数组去重之后的结果。 + ## 参考资料 * [labuladong 的算法小抄](https://labuladong.github.io/algo/) diff --git a/go/arraylist/remove_duplicates.go b/go/arraylist/remove_duplicates.go new file mode 100644 index 0000000..394a180 --- /dev/null +++ b/go/arraylist/remove_duplicates.go @@ -0,0 +1,18 @@ +package arraylist + +func RemoveDuplicates(nums []int) int { + if len(nums) == 0 { + return 0 + } + + slow, fast := 0, 0 + for fast < len(nums) { + if nums[slow] != nums[fast] { + slow++ + nums[slow] = nums[fast] + } + fast++ + } + + return slow + 1 +} diff --git a/go/arraylist/remove_duplicates_test.go b/go/arraylist/remove_duplicates_test.go new file mode 100644 index 0000000..582d9da --- /dev/null +++ b/go/arraylist/remove_duplicates_test.go @@ -0,0 +1,17 @@ +package arraylist + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_RemoveDuplicates(t *testing.T) { + var res1 = RemoveDuplicates(nil) + assert.Equal(t, 0, res1) + + var arr = []int{1, 2, 2, 3, 3, 3, 4, 4, 4, 4} + var res2 = RemoveDuplicates(arr) + assert.Equal(t, 4, res2) + assert.Equal(t, []int{1, 2, 3, 4}, arr[0:res2]) +} From 233e26f6e4184b1084fd6fc68f71e6dbfdab81b4 Mon Sep 17 00:00:00 2001 From: nange Date: Sat, 16 Sep 2023 16:37:51 +0800 Subject: [PATCH 44/50] add rust/arraylist/remove_duplicates --- README.md | 2 +- rust/src/arraylist/mod.rs | 3 +++ rust/src/arraylist/remove_duplicates.rs | 33 +++++++++++++++++++++++++ rust/src/lib.rs | 1 + 4 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 rust/src/arraylist/mod.rs create mode 100644 rust/src/arraylist/remove_duplicates.rs diff --git a/README.md b/README.md index 36ffe7e..ab4745c 100644 --- a/README.md +++ b/README.md @@ -155,7 +155,7 @@ Ref: Ref: -[Go实现](/go/arraylist/remove_duplicates.go)、[Rust实现](/rust/src/linkedlist/remove_duplicates.rs) +[Go实现](/go/arraylist/remove_duplicates.go)、[Rust实现](/rust/src/arraylist/remove_duplicates.rs) 由于数组是有序的,重复的元素必然是连续的,这很容易就能找到重复的元素。 难点主要有两个:一是需要原地删除,不能用新数组;由此引出第二个难点,如果每次发现相同元素就地删除,这样的时间复杂度非常高(N^2), diff --git a/rust/src/arraylist/mod.rs b/rust/src/arraylist/mod.rs new file mode 100644 index 0000000..764c641 --- /dev/null +++ b/rust/src/arraylist/mod.rs @@ -0,0 +1,3 @@ +pub use remove_duplicates::remove_duplicates; + +mod remove_duplicates; diff --git a/rust/src/arraylist/remove_duplicates.rs b/rust/src/arraylist/remove_duplicates.rs new file mode 100644 index 0000000..64715fc --- /dev/null +++ b/rust/src/arraylist/remove_duplicates.rs @@ -0,0 +1,33 @@ +pub fn remove_duplicates(nums: &mut Vec) -> i32 { + if nums.is_empty() { + return 0; + } + + let (mut slow, mut fast) = (0, 0); + while fast < nums.len() { + if nums[slow] != nums[fast] { + slow += 1; + nums[slow] = nums[fast] + } + fast += 1; + } + + slow as i32 + 1 +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_remove_duplicates() { + let mut arr = vec![]; + let res = remove_duplicates(&mut arr); + assert_eq!(res, 0); + + arr = vec![1, 2, 2, 3, 3, 3, 4, 4, 4, 4]; + let res = remove_duplicates(&mut arr); + assert_eq!(res, 4); + assert_eq!(arr[..4], vec![1, 2, 3, 4]); + } +} diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 34f5394..fde2434 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -1 +1,2 @@ pub mod linkedlist; +pub mod arraylist; From 1157b64d25b2d2c318ee2deb8089c023431ff092 Mon Sep 17 00:00:00 2001 From: nange Date: Mon, 9 Oct 2023 20:15:08 +0800 Subject: [PATCH 45/50] add arraylist/remove_element.go --- README.md | 14 +++++++++++--- go/arraylist/remove_element.go | 17 +++++++++++++++++ go/arraylist/remove_element_test.go | 17 +++++++++++++++++ 3 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 go/arraylist/remove_element.go create mode 100644 go/arraylist/remove_element_test.go diff --git a/README.md b/README.md index ab4745c..c03a446 100644 --- a/README.md +++ b/README.md @@ -149,7 +149,7 @@ Ref: ### 双指针技巧 -双指针技巧主要分为两类:左右指针和快慢指针。 +双指针技巧主要分为两类:左右指针和快慢指针。 #### 删除有序数组中的重复项 @@ -157,13 +157,21 @@ Ref: [Go实现](/go/arraylist/remove_duplicates.go)、[Rust实现](/rust/src/arraylist/remove_duplicates.rs) -由于数组是有序的,重复的元素必然是连续的,这很容易就能找到重复的元素。 +由于数组是有序的,重复的元素必然是连续的,这很容易就能找到重复的元素。 难点主要有两个:一是需要原地删除,不能用新数组;由此引出第二个难点,如果每次发现相同元素就地删除,这样的时间复杂度非常高(N^2), 因为每删除一个元素就需要将后面的元素往前移动。 高效实现此算法是通过快慢指针技巧:让慢指针 `slow` 走在后面,快指针 `fast` 走在前面探路,找到一个不重复的元素就让 `slow` 前进一步并把元素赋值给 `slow` 。 这样,就保证了 `nums[0..slow]` 都是无重复的元素,当 `fast` 指针遍历完整个数组 `nums` 后,`nums[0..slow]` 就是整个数组去重之后的结果。 +#### 从数组中删除指定元素 + +Ref: + +[Go实现](/go/arraylist/remove_element.go) + +算法和上面类似,还是使用快慢指针。差别是快指针的值不等于指定值时,将值赋值给慢指针,再将慢指针往后走一步。(赋值前可以进一步判断快慢指针是否相同,相同则不用赋值,进一步提高性能) + ## 参考资料 -* [labuladong 的算法小抄](https://labuladong.github.io/algo/) +- [labuladong 的算法小抄](https://labuladong.github.io/algo/) diff --git a/go/arraylist/remove_element.go b/go/arraylist/remove_element.go new file mode 100644 index 0000000..4a810f2 --- /dev/null +++ b/go/arraylist/remove_element.go @@ -0,0 +1,17 @@ +package arraylist + +func RemoveElement(nums []int, val int) int { + slow, fast := 0, 0 + + for fast < len(nums) { + if nums[fast] != val { + if slow != fast { + nums[slow] = nums[fast] + } + slow++ + } + fast++ + } + + return slow +} diff --git a/go/arraylist/remove_element_test.go b/go/arraylist/remove_element_test.go new file mode 100644 index 0000000..9ad4ca5 --- /dev/null +++ b/go/arraylist/remove_element_test.go @@ -0,0 +1,17 @@ +package arraylist + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestRemoveElement(t *testing.T) { + nums := []int{3, 2, 2, 3} + assert.Equal(t, 4, RemoveElement(nums, 4)) + + val := 3 + res := RemoveElement(nums, val) + assert.Equal(t, 2, res) + assert.Equal(t, []int{2, 2}, nums[:res]) +} From dd47897abae9be08910f723c921af19d67f9950f Mon Sep 17 00:00:00 2001 From: nange Date: Mon, 9 Oct 2023 20:26:00 +0800 Subject: [PATCH 46/50] add rust/arraylist/remove_element.rs --- README.md | 2 +- rust/src/arraylist/mod.rs | 2 ++ rust/src/arraylist/remove_element.rs | 28 ++++++++++++++++++++++++++++ 3 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 rust/src/arraylist/remove_element.rs diff --git a/README.md b/README.md index c03a446..286bc60 100644 --- a/README.md +++ b/README.md @@ -168,7 +168,7 @@ Ref: Ref: -[Go实现](/go/arraylist/remove_element.go) +[Go实现](/go/arraylist/remove_element.go)、[Rust实现](/rust/src/arraylist/remove_element.rs) 算法和上面类似,还是使用快慢指针。差别是快指针的值不等于指定值时,将值赋值给慢指针,再将慢指针往后走一步。(赋值前可以进一步判断快慢指针是否相同,相同则不用赋值,进一步提高性能) diff --git a/rust/src/arraylist/mod.rs b/rust/src/arraylist/mod.rs index 764c641..5ed4671 100644 --- a/rust/src/arraylist/mod.rs +++ b/rust/src/arraylist/mod.rs @@ -1,3 +1,5 @@ pub use remove_duplicates::remove_duplicates; +pub use remove_element::remove_element; mod remove_duplicates; +mod remove_element; diff --git a/rust/src/arraylist/remove_element.rs b/rust/src/arraylist/remove_element.rs new file mode 100644 index 0000000..29bb241 --- /dev/null +++ b/rust/src/arraylist/remove_element.rs @@ -0,0 +1,28 @@ +pub fn remove_element(nums: &mut Vec, val: i32) -> i32 { + let mut slow = 0; + let mut fast = 0; + + while fast < nums.len() { + if nums[fast] != val { + if slow != fast { + nums[slow] = nums[fast]; + } + slow += 1; + } + fast += 1; + } + + slow as i32 +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_remove_element() { + let mut nums = vec![3, 2, 2, 3]; + assert_eq!(remove_element(&mut nums, 3), 2); + assert_eq!(remove_element(&mut nums, 4), 4); + } +} From dd87a1f6fb2c3333684bc597043aa377eadef658 Mon Sep 17 00:00:00 2001 From: nange Date: Mon, 9 Oct 2023 20:35:36 +0800 Subject: [PATCH 47/50] fix rust lint --- rust/src/linkedlist/is_palindrome.rs | 40 +++++++++++++--------------- rust/src/linkedlist/mod.rs | 6 ++--- 2 files changed, 21 insertions(+), 25 deletions(-) diff --git a/rust/src/linkedlist/is_palindrome.rs b/rust/src/linkedlist/is_palindrome.rs index 53af7bb..73365e7 100644 --- a/rust/src/linkedlist/is_palindrome.rs +++ b/rust/src/linkedlist/is_palindrome.rs @@ -13,10 +13,19 @@ pub fn is_palindrome(head: Option>>) -> bool { // 1. 找到链表中点 let mut slow = head.clone(); let mut fast = head.clone(); - while !fast.is_none() && !fast.as_ref().unwrap().borrow().next.is_none() { + while fast.is_some() && fast.as_ref().unwrap().borrow().next.is_some() { let next = slow.as_ref().unwrap().borrow().next.clone(); slow = next; - let next2 = fast.as_ref().unwrap().borrow().next.as_ref().unwrap().borrow().next.clone(); + let next2 = fast + .as_ref() + .unwrap() + .borrow() + .next + .as_ref() + .unwrap() + .borrow() + .next + .clone(); fast = next2; } let middle = slow; @@ -24,18 +33,17 @@ pub fn is_palindrome(head: Option>>) -> bool { // 2. 反转中点之后的链表 let mut pre = None; let mut curr = middle.clone(); - while !curr.is_none() { + while curr.is_some() { let next = curr.as_ref().unwrap().borrow().next.clone(); curr.as_ref().unwrap().borrow_mut().next = pre.take(); pre = curr.clone(); curr = next; } - // 3. 比较新节点`new`和老节点`head`是否一致 let mut new = pre; let mut old = head.clone(); - while !new.is_none() { + while new.is_some() { if new.as_ref().unwrap().borrow().val != old.as_ref().unwrap().borrow().val { return false; } @@ -62,10 +70,7 @@ mod tests { let head = Some(Rc::new(RefCell::new(ListNode2 { val: 1, - next: Some(Rc::new(RefCell::new(ListNode2 { - val: 1, - next: None, - }))), + next: Some(Rc::new(RefCell::new(ListNode2 { val: 1, next: None }))), }))); let res = is_palindrome(head); assert!(res); @@ -74,10 +79,7 @@ mod tests { val: 1, next: Some(Rc::new(RefCell::new(ListNode2 { val: 2, - next: Some(Rc::new(RefCell::new(ListNode2 { - val: 1, - next: None, - }))), + next: Some(Rc::new(RefCell::new(ListNode2 { val: 1, next: None }))), }))), }))); let res = is_palindrome(head); @@ -89,10 +91,7 @@ mod tests { val: 2, next: Some(Rc::new(RefCell::new(ListNode2 { val: 2, - next: Some(Rc::new(RefCell::new(ListNode2 { - val: 1, - next: None, - }))), + next: Some(Rc::new(RefCell::new(ListNode2 { val: 1, next: None }))), }))), }))), }))); @@ -107,10 +106,7 @@ mod tests { val: 2, next: Some(Rc::new(RefCell::new(ListNode2 { val: 1, - next: Some(Rc::new(RefCell::new(ListNode2 { - val: 1, - next: None, - }))), + next: Some(Rc::new(RefCell::new(ListNode2 { val: 1, next: None }))), }))), }))), }))), @@ -118,4 +114,4 @@ mod tests { let res = is_palindrome(head); assert!(!res); } -} \ No newline at end of file +} diff --git a/rust/src/linkedlist/mod.rs b/rust/src/linkedlist/mod.rs index 63c4b9f..933be47 100644 --- a/rust/src/linkedlist/mod.rs +++ b/rust/src/linkedlist/mod.rs @@ -15,6 +15,7 @@ pub use reverse_list_n::reverse_list_n; mod detect_cycle; mod get_intersection_node; +mod is_palindrome; mod merge_k_lists; mod merge_two_lists; mod middle_node; @@ -23,7 +24,6 @@ mod remove_nth_from_end; mod reverse_k_group; mod reverse_list; mod reverse_list_n; -mod is_palindrome; /// Definition for singly-linked list. #[derive(PartialEq, Eq, Clone, Debug)] @@ -48,7 +48,7 @@ impl Ord for ListNode { impl PartialOrd for ListNode { fn partial_cmp(&self, other: &Self) -> Option { - self.val.partial_cmp(&other.val) + Some(self.cmp(other)) } } @@ -64,4 +64,4 @@ impl ListNode2 { fn new(val: i32) -> Self { ListNode2 { next: None, val } } -} \ No newline at end of file +} From cf0748dea339596c172be25959c8976cdd9b6191 Mon Sep 17 00:00:00 2001 From: nange Date: Tue, 10 Oct 2023 16:42:40 +0800 Subject: [PATCH 48/50] add arraylist/move_zeroes --- README.md | 8 ++++++++ go/arraylist/move_zeroes.go | 9 +++++++++ go/arraylist/move_zeroes_test.go | 13 +++++++++++++ rust/src/arraylist/mod.rs | 2 ++ rust/src/arraylist/move_zeroes.rs | 21 +++++++++++++++++++++ 5 files changed, 53 insertions(+) create mode 100644 go/arraylist/move_zeroes.go create mode 100644 go/arraylist/move_zeroes_test.go create mode 100644 rust/src/arraylist/move_zeroes.rs diff --git a/README.md b/README.md index 286bc60..52346f7 100644 --- a/README.md +++ b/README.md @@ -172,6 +172,14 @@ Ref: 算法和上面类似,还是使用快慢指针。差别是快指针的值不等于指定值时,将值赋值给慢指针,再将慢指针往后走一步。(赋值前可以进一步判断快慢指针是否相同,相同则不用赋值,进一步提高性能) +#### 移动数组中的零到末尾 + +Ref: + +[Go实现](/go/arraylist/move_zeroes.go)、[Rust实现](/rust/src/arraylist/move_zeroes.rs) + +由于不能改变非零元素的相对顺序,基本上就只能从前往后顺序遍历,加上末尾都会是零,也就是前面的零都会被删除,因此可以基于上面的算法基础上,把零删除,然后再把尾部全部设置为0,即可。 + ## 参考资料 - [labuladong 的算法小抄](https://labuladong.github.io/algo/) diff --git a/go/arraylist/move_zeroes.go b/go/arraylist/move_zeroes.go new file mode 100644 index 0000000..6564a50 --- /dev/null +++ b/go/arraylist/move_zeroes.go @@ -0,0 +1,9 @@ +package arraylist + +func MoveZeroes(nums []int) { + res := RemoveElement(nums, 0) + for res < len(nums) { + nums[res] = 0 + res++ + } +} diff --git a/go/arraylist/move_zeroes_test.go b/go/arraylist/move_zeroes_test.go new file mode 100644 index 0000000..67ea662 --- /dev/null +++ b/go/arraylist/move_zeroes_test.go @@ -0,0 +1,13 @@ +package arraylist + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestMoveZeroes(t *testing.T) { + nums := []int{0, 1, 0, 3, 12} + MoveZeroes(nums) + assert.Equal(t, []int{1, 3, 12, 0, 0}, nums) +} diff --git a/rust/src/arraylist/mod.rs b/rust/src/arraylist/mod.rs index 5ed4671..3cc7ccd 100644 --- a/rust/src/arraylist/mod.rs +++ b/rust/src/arraylist/mod.rs @@ -1,5 +1,7 @@ +pub use move_zeroes::move_zeroes; pub use remove_duplicates::remove_duplicates; pub use remove_element::remove_element; +mod move_zeroes; mod remove_duplicates; mod remove_element; diff --git a/rust/src/arraylist/move_zeroes.rs b/rust/src/arraylist/move_zeroes.rs new file mode 100644 index 0000000..c4f7bcc --- /dev/null +++ b/rust/src/arraylist/move_zeroes.rs @@ -0,0 +1,21 @@ +use super::remove_element; + +pub fn move_zeroes(nums: &mut Vec) { + let mut res = remove_element(nums, 0) as usize; + while res < nums.len() { + nums[res] = 0; + res += 1; + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_move_zeroes() { + let mut nums = vec![0, 1, 0, 3, 12]; + move_zeroes(&mut nums); + assert_eq!(nums, vec![1, 3, 12, 0, 0]); + } +} From db7f30cd5197bb01c8d3f2d6e7cfca4c41f83447 Mon Sep 17 00:00:00 2001 From: nange Date: Mon, 18 Dec 2023 16:08:39 +0800 Subject: [PATCH 49/50] add arraylist/reverse_string --- .github/workflows/go.yml | 2 +- README.md | 10 +++++++++- go/arraylist/reverse_string.go | 10 ++++++++++ go/arraylist/reverse_string_test.go | 13 +++++++++++++ rust/src/arraylist/mod.rs | 1 + rust/src/arraylist/reverse_string.rs | 23 +++++++++++++++++++++++ 6 files changed, 57 insertions(+), 2 deletions(-) create mode 100644 go/arraylist/reverse_string.go create mode 100644 go/arraylist/reverse_string_test.go create mode 100644 rust/src/arraylist/reverse_string.rs diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 12f334f..c397d61 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -21,7 +21,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v4 with: - go-version: '^1.20' + go-version: '^1.21' check-latest: true - name: Cache go module diff --git a/README.md b/README.md index 52346f7..ce42446 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ - 操作系统:Ubuntu-latest 64位 - Go语言版本: Go 1.21 -- Rust语言版本: Rust 1.72 +- Rust语言版本: Rust 1.74 ## 链表相关 @@ -180,6 +180,14 @@ Ref: 由于不能改变非零元素的相对顺序,基本上就只能从前往后顺序遍历,加上末尾都会是零,也就是前面的零都会被删除,因此可以基于上面的算法基础上,把零删除,然后再把尾部全部设置为0,即可。 +#### 反转字符串 + +Ref: + +[Go实现](/go/arraylist/reverse_string.go)、[Rust实现](/rust/src/arraylist/reverse_string.rs) + +反转字符串实际上非常简单,在数组前后使用双指针,交换指针的值即可。 + ## 参考资料 - [labuladong 的算法小抄](https://labuladong.github.io/algo/) diff --git a/go/arraylist/reverse_string.go b/go/arraylist/reverse_string.go new file mode 100644 index 0000000..b5af015 --- /dev/null +++ b/go/arraylist/reverse_string.go @@ -0,0 +1,10 @@ +package arraylist + +func ReverseString(s []byte) { + p1, p2 := 0, len(s)-1 + for p1 < p2 { + s[p1], s[p2] = s[p2], s[p1] + p1++ + p2-- + } +} diff --git a/go/arraylist/reverse_string_test.go b/go/arraylist/reverse_string_test.go new file mode 100644 index 0000000..27ad28d --- /dev/null +++ b/go/arraylist/reverse_string_test.go @@ -0,0 +1,13 @@ +package arraylist + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestReverseString(t *testing.T) { + s := []byte("hello world") + ReverseString(s) + assert.Equal(t, []byte("dlrow olleh"), s) +} diff --git a/rust/src/arraylist/mod.rs b/rust/src/arraylist/mod.rs index 3cc7ccd..e6645c3 100644 --- a/rust/src/arraylist/mod.rs +++ b/rust/src/arraylist/mod.rs @@ -5,3 +5,4 @@ pub use remove_element::remove_element; mod move_zeroes; mod remove_duplicates; mod remove_element; +mod reverse_string; diff --git a/rust/src/arraylist/reverse_string.rs b/rust/src/arraylist/reverse_string.rs new file mode 100644 index 0000000..2b1e018 --- /dev/null +++ b/rust/src/arraylist/reverse_string.rs @@ -0,0 +1,23 @@ +pub fn reverse_string(s: &mut Vec) { + let mut p1 = 0_usize; + let mut p2 = s.len()-1; + while p1 < p2 { + let tmp = s[p1]; + s[p1] = s[p2]; + s[p2] = tmp; + p1 += 1; + p2 -= 1 + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_reverse_string() { + let mut s = vec!['h', 'e', 'l', 'l', 'o']; + reverse_string(&mut s); + assert_eq!(vec!['o', 'l', 'l', 'e', 'h'], s) + } +} \ No newline at end of file From 9642e4bad8d62bd29f1249ffe12ee715be517d14 Mon Sep 17 00:00:00 2001 From: nange Date: Mon, 18 Dec 2023 18:49:42 +0800 Subject: [PATCH 50/50] add arraylist/longest_palindrome --- README.md | 9 +++++ go/arraylist/longest_palindrome.go | 31 +++++++++++++++++ go/arraylist/longest_palindrome_test.go | 17 +++++++++ rust/src/arraylist/longest_palindrome.rs | 44 ++++++++++++++++++++++++ rust/src/arraylist/mod.rs | 1 + 5 files changed, 102 insertions(+) create mode 100644 go/arraylist/longest_palindrome.go create mode 100644 go/arraylist/longest_palindrome_test.go create mode 100644 rust/src/arraylist/longest_palindrome.rs diff --git a/README.md b/README.md index ce42446..61e4e47 100644 --- a/README.md +++ b/README.md @@ -188,6 +188,15 @@ Ref: 反转字符串实际上非常简单,在数组前后使用双指针,交换指针的值即可。 +#### 寻找最长回文子串 + +Ref: + +[Go实现](/go/arraylist/longest_palindrome.go)、[Rust实现](/rust/src/arraylist/longest_palindrome.rs) + +实现思路是挨个从每个元素出发判断,寻找最长回文,找出最长的;但回文有两种形式需要考虑到,即中间位置是一个相同的元素和两个相同的元素 +(也可认为是奇数个回文和偶数个回文),因此每次判断是需要把两种情况都考虑进去。 + ## 参考资料 - [labuladong 的算法小抄](https://labuladong.github.io/algo/) diff --git a/go/arraylist/longest_palindrome.go b/go/arraylist/longest_palindrome.go new file mode 100644 index 0000000..9eb111c --- /dev/null +++ b/go/arraylist/longest_palindrome.go @@ -0,0 +1,31 @@ +package arraylist + +func LongestPalindrome(s string) string { + res := "" + for i := 0; i < len(s); i++ { + // 以s[i]为中心的最长回文子串 + s1 := palindrome(s, i, i) + // 以s[i]和s[i+1]为中心的最长回文子串 + s2 := palindrome(s, i, i+1) + + // 取最长回文串 + if len(s1) > len(res) { + res = s1 + } + if len(s2) > len(res) { + res = s2 + } + } + + return res +} + +func palindrome(s string, l, r int) string { + for l >= 0 && r < len(s) && s[l] == s[r] { + // 当前是回文,向两边展开,继续判断 + l-- + r++ + } + // 返回 l, r之间的最长回文 + return s[l+1 : r] +} diff --git a/go/arraylist/longest_palindrome_test.go b/go/arraylist/longest_palindrome_test.go new file mode 100644 index 0000000..74f0bc3 --- /dev/null +++ b/go/arraylist/longest_palindrome_test.go @@ -0,0 +1,17 @@ +package arraylist + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestLongestPalindrome(t *testing.T) { + s1 := "babad" + res := LongestPalindrome(s1) + assert.Equal(t, "bab", res) + + s2 := "cbbd" + res = LongestPalindrome(s2) + assert.Equal(t, "bb", res) +} diff --git a/rust/src/arraylist/longest_palindrome.rs b/rust/src/arraylist/longest_palindrome.rs new file mode 100644 index 0000000..ceaf8c8 --- /dev/null +++ b/rust/src/arraylist/longest_palindrome.rs @@ -0,0 +1,44 @@ +pub fn longest_palindrome(s: String) -> String { + let mut res = ""; + for i in 1..s.len() { + let s1 = palindrome(&s, i as isize , i as isize); + let s2 = palindrome(&s, i as isize, (i+1) as isize); + + if s1.len() > res.len() { + res = s1; + } + if s2.len() > res.len() { + res = s2; + } + } + + res.to_string() +} + +fn palindrome(s: &str, l: isize, r: isize) -> &str { + let mut l = l; + let mut r = r; + let bytes = s.as_bytes(); + while l >= 0 && r < s.len() as isize && bytes[l as usize] == bytes[r as usize] { + l -= 1; + r += 1; + } + + &s[(l+1) as usize..r as usize] +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_longest_palindrome() { + let s = "babad"; + let res = longest_palindrome(s.to_string()); + assert_eq!("bab".to_string(), res); + + let s = "cbbd"; + let res = longest_palindrome(s.to_string()); + assert_eq!("bb".to_string(), res); + } +} diff --git a/rust/src/arraylist/mod.rs b/rust/src/arraylist/mod.rs index e6645c3..e156010 100644 --- a/rust/src/arraylist/mod.rs +++ b/rust/src/arraylist/mod.rs @@ -6,3 +6,4 @@ mod move_zeroes; mod remove_duplicates; mod remove_element; mod reverse_string; +mod longest_palindrome;