diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml new file mode 100644 index 0000000..c397d61 --- /dev/null +++ b/.github/workflows/go.yml @@ -0,0 +1,45 @@ +# 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: Go + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +jobs: + lint-test-build-linux: + name: test-lint + 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.21' + 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: 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 diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 0000000..401ab40 --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,34 @@ +name: Rust + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +env: + CARGO_TERM_COLOR: always + +jobs: + test-lint: + name: test-lint + 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 tests + run: cd rust && cargo test + + - name: Run lint + run: cd rust && cargo clippy diff --git a/.gitignore b/.gitignore index 3ae055a..6d23611 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,8 @@ pkg .idea -java/.project + +/rust/target +**/*.rs.bk +Cargo.lock +/.idea/ +.vscode diff --git a/README.md b/README.md index 194f91f..61e4e47 100644 --- a/README.md +++ b/README.md @@ -1,76 +1,202 @@ -# 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.21 +- Rust语言版本: Rust 1.74 -## 目录 -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-检索算法) (待处理...) +## 链表相关 +### 双指针技巧 -### 列表 -列表是非常常见的一种数据结构,比如日常所见的购物清单、待办事项等等。 -它提供了对列表数据的一系列操作,比如:添加、删除、修改、遍历等功能。 -当我们把这样的具体问题抽象成用列表去解决的时候,往往可以简化问题。 +#### 合并两个有序链表 -具体实现: [Go](go/arraylist) 、 [Javascript](javascript/arraylist) 、 -[Java](java/arraylist) 、 [C](c/arraylist) +Ref: +[Go实现](/go/linkedlist/merge_two_lists.go)、[Rust实现](/rust/src/linkedlist/merge_two_lists.rs) -### 栈 -栈也是一种非常常见的数据结构,在计算机的世界里,在计算机的世界里, -栈是一种很高效的数据结构,因为数据只能在栈顶添加或者删除. -因此栈也被称为一种后入先出的数据结构.栈的使用遍布程序语言实现的方方面面, -从表达式求值到函数调用. +代码中用到了`p1`, `p2`两个指针用于比较两个链表节点的推进,`p`指针用于生成新链表节点的推进。在这个过程中没有数据的复制,只有对指针指向的改变,效率很高。 -具体实现: [Go](go/stack) 、 [Javascript](javascript/stack) 、 -[Java](java/stack) 、 [C](c/stack) +代码中还用到一个链表的算法题中是很常见的「虚拟头结点」技巧,也就是 `dummy` 节点。如果不使用 dummy +虚拟节点,代码会复杂一些,需要额外处理指针 `p` 为空的情况。 +> +什么时候需要用虚拟头结点?当需要创造一条新链表的时候,可以使用虚拟头结点简化边界情况的处理。比如说,把两条有序链表合并成一条新的有序链表,是不是要创造一条新链表?再比如想把一条链表分解成两条链表,是不是也在创造新链表?这些情况都可以使用虚拟头结点简化边界情况的处理。 +> +> 这个问题还有另外一种解法,就是利用递归,可以去掉两个指针,因为在递归栈中可以自动保存这两个状态。参考上面两个文件中的递归实现。通常来说,递归实现的代码都会更简单,但空间复杂度更高。 +#### 单链表的分解 -### 队列 -队列是一种前进先出的数据结构. 在日常生活中非常常见:比如去银行排队办理业务. -在计算机中也极其常见, 很多情况下,当有大量任务需要完成时, 就会把任务暂时加入到 -任务队列中, 执行一个删除一个,继续执行下一个任务. +Ref: -具体实现: [Go](go/queue) 、[Javascript](javascript/queue) 、 -[Java](java/queue) 、 [C](c/queue) +[Go实现](/go/linkedlist/partition.go)、[Rust实现](/rust/src/linkedlist/partition.rs) +我们可以把原链表分成两个小链表,一个链表中的元素大小都小于 x,另一个链表中的元素都大于等于 x,最后再把这两条链表接到一起,就得到了题目想要的结果。 -### 链表 -有时候数组不一定是最佳的组织数据的数据结构,因为数组通常都是固定大小的,当数据填满时, -再加入新元素就变得很困难。在数组中,添加和删除元素也很麻烦,因为要移动数组中的其他元素。 -因此如果需要频繁的添加或者删除元素,可以考虑使用链表组织数据。 +#### 合并K个有序链表 -具体实现: [Go](go/linkedlist) 、[Javascript](javascript/linkedlist) 、 -[Java](java/linkedlist) 、 [C](c/linkedlist) +Ref: +[Go实现](/go/linkedlist/merge_k_lists.go)、[Rust实现](/rust/src/linkedlist/merge_k_lists.rs) -### 字典 -字典类型(map),是一种KV结构,通过key能够找到对应的value,map类型的特点是查询非常快(O(1)), -原理是通过一个hash函数能快速的找到对应的key的位置,在实际编程中,map类型几乎无处不在。 +合并 k 个有序链表的逻辑类似合并两个有序链表,难点在于,如何快速得到 k 个节点中的最小节点,接到结果链表上? -具体实现: [Go](go/hashmap) 、[Javascript](javascript/hashmap) 、 -[Java](java/hashmap) 、 [C](c/hashmap) +通常的一种想法是可以把链表头放到数组里,可以写一个循环判断,每次找出一个最小值,这样的时间复杂度是`O(N*K)`,N是所有链表的节点数量,K +是链表个数。 +不过还有一种更高效的方式,可以把K个链表头放入 **优先级队列(二叉堆)** +中,放入一个最小堆中,每次就能方便的获得K个节点中的最小值,算法复杂度是:`O(N*Log(K))`。 -### 二叉树和二叉查找(搜索)树 -二叉树可以看作是链表的二维扩展,链表的一个节点只有一个next指针,但是二叉树的一个节点可以有两个 -next指针(通常被称作left,right),二维扩展后可以解决一维链表的一些问题,比如查找慢等。 -二叉查找树可以把查找,添加,删除元素的时间复杂度降到O(logN)。 +#### 单链表倒数第K个节点 -具体实现: [Go](go/binarytree) 、[Javascript](javascript/binarytree) 、 -[Java](java/binarytree) 、 [C](c/binarytree) +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时,此时的节点就是要被删除的节点。 + +#### 单链表中点 + +Ref: + +[Go实现](/go/linkedlist/middle_node.go)、[Rust实现](/rust/src/linkedlist/middle_node.rs) + +这个和上面一题类似,只是这次是找中点。同样使用快慢指针技巧,每当慢指针 slow 前进一步,快指针 fast 就前进两步,这样,当 fast +走到链表末尾时,slow 就指向了链表中点。 + +#### 判断链表是否包含环,并找出环起点 + +Ref: + +[Go实现](/go/linkedlist/detect_cycle.go)、[Rust实现](/rust/src/linkedlist/detect_cycle.rs) + +首先我们得知道链表是否有环,实现方法和上面一题类似,通过快慢指针,慢指针走一步,快指针走两步,如果最终两个指针走到了一起,则证明有环,如果快指针为null,则证明无环。 + +为什么有环快指针就一定会碰到慢指针?我们随便构造一个有环的链表,就会发现,每次走两步,每经过一次有环处,下一次快指针经过的节点都和上一次不同,所以快慢指针早晚一定会遇到。 + +如何找到环的起点呢?关键在于当快慢指针相遇时,让其中任一个指针指向头节点,然后让它俩以相同速度前进,再次相遇时所在的节点位置就是环开始的位置。 + +#### 两个链表是否相交 + +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做同样的逻辑,最终当两个指针相同时,则就是相交链表节点。这种方法技巧性更强,不容易想到。 + +### 递归魔法 + +#### 反转链表 + +Ref: + +[Go实现](/go/linkedlist/reverse_list.go)、[Rust实现](/rust/src/linkedlist/reverse_list.rs) + +这个题目明显可以使用迭代方法,但需要小心处理临时对象的保存和边界条件的处理。 +更简单的方式是使用递归法,通过递归的方式找到最后一个节点,之后从倒数第二个节点开始逐个反转链表。 + +#### 反转链表前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个节点用于后续反转的节点指向它。 + +### 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 个,就保持不变。 + +### 判断回文链表 + +Ref: + +[Go实现](/go/linkedlist/is_palindrome.go)、[Rust实现](/rust/src/linkedlist/is_palindrome.rs) + +回文链表是一个特殊类型的链表,其中链表的前半部分和后半部分在反转后是相同的。也就是说,从前向后读和从后向前读,链表中的元素顺序是相同的。 + +一种实现是可以遍历链表,将链表元素加入一个栈结构中,然后再将栈中元素和原始链表元素挨个比较,看是否相同 +(栈结构也可以采用后序递归遍历链表节点模拟,在递归调用中进行比较元素)。 这种方法唯一的缺点是空间复杂度为O(N),较高。 + +另一种更优的空间复杂度实现方式是,找到链表中点,把中点之后的部分反转,再和中点之前的部分进行比较,看元素是否相等。空间复杂度为O( +1)。 + +## 数组相关 + +### 双指针技巧 + +双指针技巧主要分为两类:左右指针和快慢指针。 + +#### 删除有序数组中的重复项 + +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)、[Rust实现](/rust/src/arraylist/remove_element.rs) + +算法和上面类似,还是使用快慢指针。差别是快指针的值不等于指定值时,将值赋值给慢指针,再将慢指针往后走一步。(赋值前可以进一步判断快慢指针是否相同,相同则不用赋值,进一步提高性能) + +#### 移动数组中的零到末尾 + +Ref: + +[Go实现](/go/arraylist/move_zeroes.go)、[Rust实现](/rust/src/arraylist/move_zeroes.rs) + +由于不能改变非零元素的相对顺序,基本上就只能从前往后顺序遍历,加上末尾都会是零,也就是前面的零都会被删除,因此可以基于上面的算法基础上,把零删除,然后再把尾部全部设置为0,即可。 + +#### 反转字符串 + +Ref: + +[Go实现](/go/arraylist/reverse_string.go)、[Rust实现](/rust/src/arraylist/reverse_string.rs) + +反转字符串实际上非常简单,在数组前后使用双指针,交换指针的值即可。 + +#### 寻找最长回文子串 + +Ref: + +[Go实现](/go/arraylist/longest_palindrome.go)、[Rust实现](/rust/src/arraylist/longest_palindrome.rs) + +实现思路是挨个从每个元素出发判断,寻找最长回文,找出最长的;但回文有两种形式需要考虑到,即中间位置是一个相同的元素和两个相同的元素 +(也可认为是奇数个回文和偶数个回文),因此每次判断是需要把两种情况都考虑进去。 + +## 参考资料 + +- [labuladong 的算法小抄](https://labuladong.github.io/algo/) 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/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) } 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/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/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/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]) +} 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]) +} 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/go/go.mod b/go/go.mod new file mode 100644 index 0000000..68c4699 --- /dev/null +++ b/go/go.mod @@ -0,0 +1,14 @@ +module the_algorithms + +go 1.20 + +require ( + github.com/mitchellh/hashstructure/v2 v2.0.2 + 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/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= 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)) } 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/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/go/linkedlist/is_palindrome.go b/go/linkedlist/is_palindrome.go new file mode 100644 index 0000000..7dfe8eb --- /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 { + return false + } + if head.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 { + return false + } + if head.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..bc1b065 --- /dev/null +++ b/go/linkedlist/is_palindrome_test.go @@ -0,0 +1,66 @@ +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) + + ret5 := fn(&ListNode{ + Val: 1, + Next: &ListNode{ + Val: 2, + Next: nil, + }, + }) + assert.False(t, ret5) + } +} diff --git a/go/linkedlist/linkedlist.go b/go/linkedlist/linkedlist.go index 036ceb4..60c7b04 100644 --- a/go/linkedlist/linkedlist.go +++ b/go/linkedlist/linkedlist.go @@ -1,159 +1,7 @@ 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) +// Definition for singly-linked list. +type ListNode struct { + Val int + Next *ListNode } 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_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/go/linkedlist/merge_two_lists.go b/go/linkedlist/merge_two_lists.go new file mode 100644 index 0000000..1ca470a --- /dev/null +++ b/go/linkedlist/merge_two_lists.go @@ -0,0 +1,61 @@ +package linkedlist + +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 = p1 + p1 = p1.Next + } else { + p.Next = p2 + p2 = p2.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 +} + +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 new file mode 100644 index 0000000..508eb0b --- /dev/null +++ b/go/linkedlist/merge_two_lists_test.go @@ -0,0 +1,57 @@ +package linkedlist + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_MergeTwoLists(t *testing.T) { + var fnList = []func(list1 *ListNode, list2 *ListNode) *ListNode{ + MergeTwoLists, MergeTwoListsWithRecursion, + } + + for _, fn := range fnList { + l1 := fn(nil, nil) + assert.Nil(t, l1) + + l2 := fn(nil, &ListNode{}) + assert.Equal(t, l2, &ListNode{}) + + l3 := fn(&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/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/go/linkedlist/partition.go b/go/linkedlist/partition.go new file mode 100644 index 0000000..f70891c --- /dev/null +++ b/go/linkedlist/partition.go @@ -0,0 +1,29 @@ +package linkedlist + +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/go/linkedlist/remove_nth_from_end.go b/go/linkedlist/remove_nth_from_end.go new file mode 100644 index 0000000..8dd016a --- /dev/null +++ b/go/linkedlist/remove_nth_from_end.go @@ -0,0 +1,45 @@ +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 +} + +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 new file mode 100644 index 0000000..54423f5 --- /dev/null +++ b/go/linkedlist/remove_nth_from_end_test.go @@ -0,0 +1,104 @@ +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, 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, + }, + }) + +} diff --git a/go/linkedlist/reverse_k_group.go b/go/linkedlist/reverse_k_group.go new file mode 100644 index 0000000..f8af05d --- /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 *ListNode = nil, 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 +} 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, + }, + }, + }, + }, + }) +} 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_n.go b/go/linkedlist/reverse_list_n.go new file mode 100644 index 0000000..3f3e1c8 --- /dev/null +++ b/go/linkedlist/reverse_list_n.go @@ -0,0 +1,23 @@ +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 || (node == nil || node.Next == nil) { // 边界条件 + 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, + }, + }, + }, + }, + }) +} 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/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/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 new file mode 100644 index 0000000..e156010 --- /dev/null +++ b/rust/src/arraylist/mod.rs @@ -0,0 +1,9 @@ +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; +mod reverse_string; +mod longest_palindrome; 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]); + } +} 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/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); + } +} 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 diff --git a/rust/src/lib.rs b/rust/src/lib.rs new file mode 100644 index 0000000..fde2434 --- /dev/null +++ b/rust/src/lib.rs @@ -0,0 +1,2 @@ +pub mod linkedlist; +pub mod arraylist; diff --git a/rust/src/linkedlist/detect_cycle.rs b/rust/src/linkedlist/detect_cycle.rs new file mode 100644 index 0000000..190106c --- /dev/null +++ b/rust/src/linkedlist/detect_cycle.rs @@ -0,0 +1,55 @@ +use std::{cell::RefCell, rc::Rc}; + +use super::ListNode2; + +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(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(); + node3.as_ref().unwrap().borrow_mut().next = node2.clone(); + + let ret = detect_cycle(head); + assert_eq!(ret, node2); + } +} 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/is_palindrome.rs b/rust/src/linkedlist/is_palindrome.rs new file mode 100644 index 0000000..73365e7 --- /dev/null +++ b/rust/src/linkedlist/is_palindrome.rs @@ -0,0 +1,117 @@ +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_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(); + fast = next2; + } + let middle = slow; + + // 2. 反转中点之后的链表 + let mut pre = None; + let mut curr = middle.clone(); + 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_some() { + 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); + } +} diff --git a/rust/src/linkedlist/merge_k_lists.rs b/rust/src/linkedlist/merge_k_lists.rs new file mode 100644 index 0000000..d547bb0 --- /dev/null +++ b/rust/src/linkedlist/merge_k_lists.rs @@ -0,0 +1,86 @@ +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.into_iter().flatten() { + heap.push(Reverse(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 })) + })) + })) + })) + })) + })) + })) + })) + ); + } +} diff --git a/rust/src/linkedlist/merge_two_lists.rs b/rust/src/linkedlist/merge_two_lists.rs new file mode 100644 index 0000000..b3cf72c --- /dev/null +++ b/rust/src/linkedlist/merge_two_lists.rs @@ -0,0 +1,99 @@ +use super::ListNode; + +pub fn merge_two_lists( + list1: Option>, + list2: Option>, +) -> Option> { + let mut dummy = Some(Box::new(ListNode { val: 0, next: None })); + let mut p = &mut dummy; + + 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 = p2.as_mut().unwrap().next.take(); + p.as_mut().unwrap().next = p2; + p2 = next; + } + + p = &mut p.as_mut().unwrap().next; + } + + if p1.is_some() { + p.as_mut().unwrap().next = p1; + } + if p2.is_some() { + p.as_mut().unwrap().next = p2; + } + + dummy.unwrap().next +} + +#[allow(dead_code)] +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 super::*; + + #[test] + fn test_list_empty() { + 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 = f(None, Some(Box::new(ListNode { val: 1, next: None }))); + assert_eq!(ret2, Some(Box::new(ListNode { val: 1, 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 })) + })) + })) + ) + } + } +} 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 new file mode 100644 index 0000000..933be47 --- /dev/null +++ b/rust/src/linkedlist/mod.rs @@ -0,0 +1,67 @@ +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; +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; + +mod detect_cycle; +mod get_intersection_node; +mod is_palindrome; +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; + +/// Definition for singly-linked list. +#[derive(PartialEq, Eq, Clone, Debug)] +pub struct ListNode { + pub val: i32, + pub next: Option>, +} + +impl ListNode { + #[inline] + #[allow(dead_code)] + 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) + } +} + +impl PartialOrd for ListNode { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +#[derive(PartialEq, Eq, Clone, Debug)] +pub struct ListNode2 { + pub val: i32, + pub next: Option>>, +} + +impl ListNode2 { + #[inline] + #[allow(dead_code)] + fn new(val: i32) -> Self { + ListNode2 { next: None, val } + } +} diff --git a/rust/src/linkedlist/partition.rs b/rust/src/linkedlist/partition.rs new file mode 100644 index 0000000..dc41a77 --- /dev/null +++ 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 })), + })), + })), + })), + })), + })) + ) + } +} 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 })) + })) + ); + } +} diff --git a/rust/src/linkedlist/reverse_k_group.rs b/rust/src/linkedlist/reverse_k_group.rs new file mode 100644 index 0000000..0cd7262 --- /dev/null +++ b/rust/src/linkedlist/reverse_k_group.rs @@ -0,0 +1,160 @@ +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 +} + +/// 另一种通过`Rc`, `RefCell`实现的版本 +pub fn reverse_k_group2( + head: Option>>, + k: i32, +) -> Option>> { + 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)] +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 })), + })), + })), + })), + })) + ); + } + + #[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 }))), + }))), + }))), + }))), + }))) + ); + } +} 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()); + } +} 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 }))), + }))), + }))), + }))) + ) + } +}