《数据结构与算法之美》学习指导手册

《数据结构与算法之美》学习指导手册

复杂度分析

1:复杂度分为:时间复杂度、空间复杂度。

2:时间复杂度的计算和代码的结构,代码的执行次数有关系。

3:空间复杂度的计算和数据结构,存储资源有关系。

4:复杂度和常系数无关如:O(n)、O(2n)、O(n/2)都表示O(n)复杂度

5:多个复杂度相加,取高次项复杂度为最终结果,如:O(n^3+n^2+n)那么这个复杂度就是O(n^3)

6:常见的一些时间复杂度有:(1)对一个数组遍历循环,若数组长度为n,那么时间复杂度为O(n)。(2)嵌套遍历,若外层遍历n次,内层遍历m次,则时间复杂度为O(m*n)。(3)若两个遍历不是嵌套的,还是顺序的,那么时间复杂度依然是O(n)+O(n) = O(2n) = O(n),最终依然为O(n)。(4)若n和程序执行的次数无关,那么时间复杂度始终为O(1);

数组、栈、队列

数组(Array)是一种线性表数据结构。它用一组连续的内存空间,来存储一组具有相同类型的数据。

寻址公式:a[i]_address = base_address + i * data_type_size

为什么大多数编程语言中,数组要从 0 开始编号,而不是从 1 开始呢?

从数组存储的内存模型上来看,“下标”最确切的定义应该是“偏移(offset)”。前面也讲到,如果用 a 来表示数组的首地址,a[0]就是偏移为 0 的位置,也就是首地址,a[k]就表示偏移 k 个 type_size 的位置,所以计算 a[k]的内存地址只需要用这个公式:a[k]_address = base_address + k * type_size但是,如果数组从 1 开始计数,那我们计算数组元素 a[k]的内存地址就会变为:a[k]_address = base_address + (k-1)*type_size对比两个公式,我们不难发现,从 1 开始编号,每次随机访问数组元素都多了一次减法运算,对于 CPU 来说,就是多了一次减法指令。

链表

常见的三种缓存淘汰策略:

  1. 先进先出策略 FIFO(First In,First Out)
  2. 最少使用策略 LFU(Least Frequently Used)
  3. 最近最少使用策略 LRU(Least Recently Used)

单链表反转

1
2
3
4
5
6
7
8
9
10
11
12
13
public ListNode reverseList(ListNode head) {
ListNode next = null;
ListNode prev = null;
ListNode curr = head;

while(curr!=null){
next = curr.next;
curr.next = prev;
prev = curr;
curr = next;
}
return prev;
}

链表中环的检测

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public ListNode detectCycle(ListNode head) {
ListNode fast= head;
ListNode slow = head;

while(fast!=null&&fast.next!=null){
slow = slow.next;
fast = fast.next.next;
if(slow==fast){
fast = head;
while(slow!=fast){
slow = slow.next;
fast = fast.next;
}
return slow;
}
}

return null;
}

两个有序的链表合并

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//递归 
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
if(l1 == null) return l2;
if(l2 == null) return l1;
ListNode temp = l1.val<l2.val?l1:l2;
temp.next = mergeTwoLists(temp.next,l1.val>=l2.val?l1:l2);
return temp;
}

private ListNode mergeTwoLists(ListNode l1,ListNode l2){
if( l1==null)return l2;
if( l2==null)return l1;
ListNode res = l1.val<l2.val?l1:l2;
res.next = mergeTwoLists(res.next,l1.val>=l2.val?l1:l2);
return res;
}

删除链表倒数第 n 个结点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//设置快和慢两个指针,初始化时快指针比慢指针多走k-1步,然后两个指针每次都走一步,当快指针到达终点时,慢指针正好处在倒数第k的位置
public int kthToLast(ListNode head, int k) {
ListNode fast = head;
for(int i = 0; i <= k; i++){
fast = fast.next;
}
ListNode slow = head;
while(fast!=null){
fast = fast.next;
slow = slow.next;
}
return slow.val;
}

删除给定的节点

1
2
node.val = node.next.val;
node.next = node.next.next;

求链表的中间结点

1
2
3
4
5
6
7
8
9
10
public int middle(NodeList head){
NodeList fast = head;
NodeList slow = head;

while(fast!=null&&fast.next!=null){
fast = fast.next.next;
slow = slow.next;
}
return slow.val;
}

删除链表中等于给定值 *val* 的所有节点

1
2
3
4
5
6
7
public ListNode delete(NodeList head , int val){
if(head==null){
return head;
}
head.next = delete(head.next,val);
return head.val==val?head.next:head;
}

两数相加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
Stack<Integer> stack1 = new Stack<>();
Stack<Integer> stack2 = new Stack<>();
while (l1 != null) {
stack1.push(l1.val);
l1 = l1.next;
}
while (l2 != null) {
stack2.push(l2.val);
l2 = l2.next;
}

int carry = 0;
ListNode head = null;
while (!stack1.isEmpty() || !stack2.isEmpty() || carry > 0) {
int sum = carry;
sum += stack1.isEmpty()? 0: stack1.pop();
sum += stack2.isEmpty()? 0: stack2.pop();

ListNode node = new ListNode(sum % 10);
node.next = head;
head = node;

carry = sum / 10;
}
return head;


递归

排序、二分查找

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 二分搜索函数的定义里,除了要指定数组 nums 和目标查找数 target 之外,还要指定查找区间的起点和终点位置,分别用 low 和 high 去表示。
int binarySearch(int[] nums, int target, int low, int high) {
       // 为了避免无限循环,先判断,如果起点位置大于终点位置,表明这是一个非法的区间,已经尝试了所有的搜索区间还是没能找到结果,返回 -1。

if (low > high) {
        return -1;
   }
   // 取正中间那个数的下标 middle。
   int middle = low + (high - low) / 2;
   
   // 判断一下正中间的那个数是不是要找的目标数 target,是,就返回下标 middle。    
    if (nums[middle] == target) {
        return middle;
   }
   
    // 如果发现目标数在左边,就递归地从左半边进行二分搜索。
   if (target < nums[middle]) {
        return binarySearch(nums, target, low, middle - 1);
      } else {
        return binarySearch(nums, target, middle + 1, high);
   }//否则从右半边递归地进行二分搜索。
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int binarySearch(int[] nums, int target, int low, int high) {
   // 在 while 循环里,判断搜索的区间范围是否有效
   while (low <= high) {
        // 计算正中间的数的下标
        int middle = low + (high - low) / 2;
    
    // 判断正中间的那个数是不是要找的目标数 target。如果是,就返回下标 middle
    if (nums[middle] == target) {
        return middle;
    }
    
    // 如果发现目标数在左边,调整搜索区间的终点为 middle - 1;否则,调整搜索区间的起点为 middle + 1
    if (target < nums[middle]) {
        high = middle - 1;
      } else {
       low = middle + 1;
     }
   }

   // 如果超出了搜索区间,表明无法找到目标数,返回 -1  
   return -1;
}

颜色分类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public void sortColors(int[] nums){
int numsLength = nums.length;
int ptr = 0;
for(int i=0; i<numsLength;i++){
if(nums[i]==0){
int temp = nums[i];
nums[i]=nums[ptr];
nums[ptr]=temp;
++ptr;
}
}
for(int i=0; i<numsLength;i++){
if(nums[i]==1){
int temp = nums[i];
nums[i]=nums[ptr];
nums[ptr]=temp;
++ptr;
}
}
}

散列表

二叉树

堆和堆排序

BF/RK字符串匹配算法

Trie树

图的表示

深度广度优先搜索

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
boolean dfs(int maze[][], int x, int y) {
    // 第一步:判断是否找到了B
    if (x == B[0] && y == B[1]) {
        return true;
    } 

    // 第二步:标记当前的点已经被访问过
    maze[x][y] = -1;

    // 第三步:在四个方向上尝试
    for (int d = 0; d < 4; d++) {
        int i = x + dx[d], j = y + dy[d];

        // 第四步:如果有一条路径被找到了,返回true
        if (isSafe(maze, i, j) && dfs(maze, i, j)) {
            return true;
        }
    }

    // 付出了所有的努力还是没能找到B,返回false
    return false;
  
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
boolean dfs(int maze[][], int x, int y) {
    // 创建一个Stack
    Stack<Integer[]> stack = new Stack<>();

    // 将起始点压入栈,标记它访问过
    stack.push(new Integer[] {x, y});
    maze[x][y] = -1;
    
    while (!stack.isEmpty()) {
        // 取出当前点
        Integer[] pos = stack.pop();
        x = pos[0]; y = pos[1];
      
        // 判断是否找到了目的地
        if (x == B[0] && y == B[1]) {
          return true;
        }
    
        // 在四个方向上尝试  
        for (int d = 0; d < 4; d++) {
            int i = x + dx[d], j = y + dy[d];
            
        if (isSafe(maze, i, j)) {
            stack.push(new Integer[] {i, j});
            maze[i][j] = -1;
            }
        }
    }
    return false;
}

四种算法原理

跳表

拓扑排序、最短路径、A* 算法

B+树

位图

BM、KMP、AC自动机

红黑树

哈希算法

高级篇、实战等其他内容

文章目录
  1. 1. 《数据结构与算法之美》学习指导手册
    1. 1.1. 复杂度分析
    2. 1.2. 数组、栈、队列
    3. 1.3. 链表
    4. 1.4. 递归
    5. 1.5. 排序、二分查找
      1. 1.5.1. 颜色分类
    6. 1.6. 散列表
    7. 1.7. 二叉树
    8. 1.8. 堆和堆排序
    9. 1.9. BF/RK字符串匹配算法
    10. 1.10. Trie树
    11. 1.11. 图的表示
    12. 1.12. 深度广度优先搜索
    13. 1.13. 四种算法原理
    14. 1.14. 跳表
    15. 1.15. 拓扑排序、最短路径、A* 算法
    16. 1.16. B+树
    17. 1.17. 位图
    18. 1.18. BM、KMP、AC自动机
    19. 1.19. 红黑树
    20. 1.20. 哈希算法
    21. 1.21. 高级篇、实战等其他内容
|