目录

第一章 序章

第二章 数据结构

数据结构(data structure):是数据的组织、管理和存储格式,其使用的目的是为了高效地访问和修改数据。
数据结构组成方式

  1. 线性结构

线性结构是最简单的数据结构,包括数组、链表、以及由他们衍生出来的栈、队列、哈希表。

    1. 树是相对复杂的数据结构,其中比较有代表性的是二叉树,由它衍生出了二叉堆之类的数据结构。

图是更为复杂的数据结构,因为在图中会呈现出多对多的关联关系。

  1. 其他数据结构

除次之外,还有很多其他的数据结构。但是他们都是由基本数据结构变形而来,用于解决某些特定的关系,如跳表、哈希链表、位图等。
物理结构和逻辑结构

一维

数组(array)

适用用查询,时间复杂度为O(1)

1. 什么是数组

  1. 数组 array 是由相同类型的变量所组成的有序集合,数组中的每个变量称为元素,数组是最基本最简单的数据结构
  2. 内存是由一个个连续内存单元组成的,每一个内存单元都有自己的地址。数组中的每一个元素,都存储在小小的内存单元中,并且元素直接紧密排列,既不能打乱元素的存储顺序,也不能跳过某个存储单元进行存储。

    2. 数组的创建

    1. int[] array1 = new int[3];
    2. int[] array2 = new int[]{1,2,3};

    3. Arrays

    1)sort()
    sort()方法用来对数组进行排序,该方法要求数组的元素要实现Comparable接口。如果排序的元素不是String或者基本数据类型,就需要主动实现Comparable接口(基本数据类型和String本身已经实现了Comparable接口)。 ```java String[] strs = {“沉”, “默”,”王”, “二”};

Arrays.sort(strs); System.out.println(Arrays.toString(strs)); // 输出[二, 沉, 王, 默]

  1. 2binarySearch()<br />binarySearch()方法用来对数组进行二分查找(返回值所在的下标,未找到的话返回-1),调用该方法之前必须要先排序。
  2. ```java
  3. String[] strs = {"沉", "默","王", "二"};
  4. Arrays.sort(strs);
  5. System.out.println(Arrays.binarySearch(strs, "二"));
  6. // 排序后的结果为[二, 沉, 王, 默]
  7. // 二分查找的结果范围0

由于sort()方法排序后的结果为[二, 沉, 王, 默],所以Arrays.binarySearch(strs, “二”)返回下标值0。

4. array & list 相互转换

  1. // List to Array
  2. List<String> stringList = new ArrayList<>();
  3. // 此方法存在强制转换时会抛异常
  4. final Object[] objects = stringList.toArray();
  5. // 推荐使用此方法进行list to array 操作
  6. final String[] strings = stringList.toArray(new String[stringList.size()]);
  7. // Array to List
  8. String[] stingArray = {"array,list"};
  9. // 错误操作:返回的list是Arrays里面的一个静态内部类,该类并未实现add,remove方法,因此在使用时存在局限性
  10. List<String> list = Arrays.asList(stingArray);
  11. // 正确转换
  12. List<String> list1 = new ArrayList<>(Arrays.asList(stingArray));
  13. List<String> list2 = new ArrayList<String>(stingArray.length);
  14. Collections.addAll(list2,stingArray);

5. 数组基本操作

数组遍历:通过数组下标直接获取元素
数组更新:直接根据数组下标修改元素
数组新增:中间插入,需要把插入位置到元素全部后移
数组删除:中间删除,需要把元素从删除位置每个向前移动一个

  1. /**
  2. * 数据结构 —— 数组
  3. * 数组的基本 操作
  4. * 数组获取 lookup O(1)
  5. * 数组新增 insert O(n)
  6. * 尾部插入 lookup O(1)
  7. * 中间插入 O(n)
  8. * 超范围插入 O(n)
  9. * 数组删除 delete O(n)
  10. * 数组更新 update O(1)
  11. */
  12. public class ArrayDemo {
  13. private int[] array;
  14. private int size;
  15. private ArrayDemo(int capacity) {
  16. this.array = new int[capacity];
  17. size = 0;
  18. }
  19. /**
  20. * 获取数组元素 直接通过下标获取即可,时间复杂度O(1)
  21. *
  22. * @param array
  23. * @param i
  24. * @return
  25. */
  26. public int getElement(int[] array, int i) {
  27. if (size <= 0)
  28. throw new IndexOutOfBoundsException("超出数组实际 元素范围!");
  29. return array[i];
  30. }
  31. /**
  32. * 尾部插入数组添加元素
  33. *
  34. * @param element
  35. * @param index
  36. * @return
  37. */
  38. public void add(int element, int index) {
  39. if (index < 0 || index > size) {
  40. throw new RuntimeException("");
  41. }
  42. for (int i = size - 1; i >= index; i--) {
  43. array[i + 1] = array[i];
  44. }
  45. array[index] = element;
  46. size++;
  47. }
  48. /**
  49. * 超出范围新增
  50. *
  51. * @param element
  52. * @param index
  53. */
  54. public void addOutArray(int element, int index) {
  55. if (index < 0 || index > size) {
  56. throw new IndexOutOfBoundsException("超出数组实际 元素范围!");
  57. }
  58. if (size > array.length) {
  59. grow();
  60. }
  61. for (int i = size - 1; i >= index; i--) {
  62. array[i + 1] = array[i];
  63. }
  64. array[index] = element;
  65. size++;
  66. }
  67. /**
  68. * 数组扩容
  69. */
  70. private void grow() {
  71. // 扩容2倍
  72. int[] newArray = new int[array.length >> 1];
  73. // 从旧数组复制到新数组
  74. System.arraycopy(array, 0, newArray, 0, array.length);
  75. array = newArray;
  76. }
  77. /**
  78. * 删除元素
  79. *
  80. * @param index
  81. * @return
  82. */
  83. private int delete(int index) {
  84. if (index < size || index > size) {
  85. throw new IndexOutOfBoundsException("超出数组实际 元素范围!");
  86. }
  87. //从左往右遍历,将数组元素向前移动
  88. int deleteElement = array[index];
  89. for (int i = index; i < size; i++) {
  90. array[i] = array[i + 1];
  91. }
  92. size--;
  93. return deleteElement;
  94. }
  95. /**
  96. * list 和 array 转换
  97. */
  98. public void ListToArray() {
  99. // List to Array
  100. List<String> stringList = new ArrayList<>();
  101. // 此方法存在强制转换时会抛异常
  102. final Object[] objects = stringList.toArray();
  103. // 推荐使用此方法进行list to array 操作
  104. final String[] strings = stringList.toArray(new String[stringList.size()]);
  105. // Array to List
  106. String[] stingArray = {"array,list"};
  107. // 错误操作:返回的list是Arrays里面的一个静态内部类,该类并未实现add,remove方法,因此在使用时存在局限性
  108. List<String> list = Arrays.asList(stingArray);
  109. // 正确转换
  110. List<String> list1 = new ArrayList<>(Arrays.asList(stingArray));
  111. List<String> list2 = new ArrayList<String>(stingArray.length);
  112. Collections.addAll(list2, stingArray);
  113. }

6. array 常用API

  1. // 对数组进排序
  2. Arrays.sort()
  3. // 将一个数组复制到新的数组中
  4. Arrays.copyOf(int[] original, int newLength)
  5. //Object src : 原数组
  6. int srcPos : 从元数据的起始位置开始
  7.   Object dest : 目标数组
  8.   int destPos : 目标数组的开始起始位置
  9.   int length : copy的数组的长度
  10. System.arraycopy(Object src, int srcPos, Object dest, int destPos,int length)

链表(linked list)

适用新增和删除,时间复杂度为O(1), 遍历时间复杂度为O(n)

1.什么是链表

链表是一种物理上非连续、非顺序的数据结构,由若干个节点(Node)组成。

2.常见链表

单链表:单向链表的每一个节点包含两部分,一部分是存放数据的变量data,另一部分是指向下一个节点的next指针
image.png
双向链表:在单链表的基础上,每个节点多了指向前置节点到指针。
image.png
循环链表:

3.链表基本操作

查找节点:链表查找不像数组那样可以直接通过数组下标获取元素,只能从头节点开始向后一个一个节点逐一查找
更新节点:如果不考虑查找节点的过程,链表的更新过程会像数组那样简单,直接把旧数据替换成新数据即可
image.png
插入节点
和数组类似,链表插入主要分为三种情况:头部插入、中间插入、尾部插入
尾部插入:是最简单的操作,将最后一个节点的next指针指向新插入节点
image.png
头部插入:

  1. 把新节点的next 指针指向原先的头节点
  2. 把新节点变为链表的头节点

    1. ![image.png](https://cdn.nlark.com/yuque/0/2021/png/12747730/1635669485205-1e8515ab-96ff-4c51-88e9-51e501fd793f.png#clientId=ua9f2d3fc-2a64-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=224&id=u1fdd1bf6&margin=%5Bobject%20Object%5D&name=image.png&originHeight=448&originWidth=1254&originalType=binary&ratio=1&rotation=0&showTitle=false&size=377212&status=done&style=none&taskId=u2299d9bb-0f35-4d97-82fe-be10c64e036&title=&width=627)<br />中间插入: 中间插入主要分为两步<br />第一步:新节点的next指针,指向插⼊位置的节点<br />第二步:插⼊位置前置节点的next指针,指向新节点。<br /> ![image.png](https://cdn.nlark.com/yuque/0/2021/png/12747730/1635669897757-6b7d5105-f096-4a9e-80b5-304179e50c1e.png#clientId=ua9f2d3fc-2a64-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=321&id=u05a8f405&margin=%5Bobject%20Object%5D&name=image.png&originHeight=642&originWidth=1100&originalType=binary&ratio=1&rotation=0&showTitle=false&size=372968&status=done&style=none&taskId=uc2d34858-0889-4ed4-9b78-fd071f14277&title=&width=550)<br />**删除节点:**<br />头部删除:把链表的头节点设为原先头节点的next指针即可。<br /> ![image.png](https://cdn.nlark.com/yuque/0/2021/png/12747730/1635670337650-057b07c1-208d-4555-9922-d29731af4c0d.png#clientId=ua9f2d3fc-2a64-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=188&id=u3b430070&margin=%5Bobject%20Object%5D&name=image.png&originHeight=376&originWidth=1166&originalType=binary&ratio=1&rotation=0&showTitle=false&size=377166&status=done&style=none&taskId=uc6c43c57-aad3-4070-b3c9-84300439169&title=&width=583)

尾部删除:最简单的操作,把链表倒数第2个节点的next指针指向空即可
image.png
中间删除:把要删除节点的前置节点的next指针,指向要删除元素的下⼀个节点即可。
image.png

4.代码操作

  1. /**
  2. * 单链表基本定义
  3. */
  4. public class ListNode {
  5. int val;
  6. ListNode next;
  7. ListNode() {
  8. }
  9. ListNode(int val) {
  10. this.val = val;
  11. }
  12. ListNode(int val, ListNode next) {
  13. this.val = val;
  14. this.next = next;
  15. }
  16. // 头节点
  17. ListNode head;
  18. // 尾节点
  19. ListNode tail;
  20. // 链表大小
  21. int size;
  22. /**
  23. * 新增
  24. *
  25. * @param data
  26. * @param index
  27. */
  28. private void insert(int data, int index) {
  29. if (index < size || index > size) {
  30. throw new IndexOutOfBoundsException("越界");
  31. }
  32. ListNode newListNode = new ListNode(data);
  33. if (size == 0) {
  34. // 空链表
  35. head = newListNode;
  36. tail = newListNode;
  37. } else if (index == 0) {
  38. // 头部插入
  39. newListNode.next = head;
  40. head = newListNode;
  41. } else if (index == size) {
  42. // 尾部插入
  43. tail.next = newListNode;
  44. tail = newListNode;
  45. } else {
  46. // 插入中间
  47. ListNode preNode = get(index - 1);
  48. newListNode.next = preNode.next;
  49. preNode.next = newListNode;
  50. }
  51. size++;
  52. }
  53. /**
  54. * 移除元素
  55. *
  56. * @param index
  57. */
  58. private ListNode remove(int index) {
  59. if (index < size || index > size) {
  60. throw new IndexOutOfBoundsException("越界");
  61. }
  62. ListNode removeNode = null;
  63. if (index == 0) {
  64. // 头部删除
  65. removeNode = head;
  66. head = head.next;
  67. } else if (index == size) {
  68. // 尾部删除
  69. final ListNode preNode = get(index - 1);
  70. removeNode = preNode.next;
  71. preNode.next = null;
  72. tail = preNode;
  73. } else {
  74. // 删除中间节点
  75. final ListNode preNode = get(index - 1);
  76. ListNode nextNode = preNode.next.next;
  77. removeNode = preNode.next;
  78. preNode.next = nextNode;
  79. }
  80. size--;
  81. return removeNode;
  82. }
  83. /**
  84. * 链表获取元素
  85. *
  86. * @param index
  87. * @return
  88. */
  89. private ListNode get(int index) {
  90. if (index < size || index > size) {
  91. throw new IndexOutOfBoundsException("越界");
  92. }
  93. ListNode temp = head;
  94. for (int i = 0; i < index; i++) {
  95. temp = temp.next;
  96. }
  97. return temp;
  98. }
  99. }

5.博客地址

leetcode: https://mp.weixin.qq.com/s/Ym7MhhJ—NcaRRnj1y1vFA

队列(queue)

双端队列(deque)

集合 set

集合 map

二维

树(tree)

图(graph)

二叉搜索树

堆(Heap)

并查集 disjoint set

字典树 Trie

特殊数据结构

位运算 Bitwise

布隆过滤器

LRU Cache

第三章 算法专题

一、数组

第四章 LeetCode题解

一、数组

1. 两数之和

题目
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
解题思路

  1. 暴力解决: 两次for 循环寻找和为target 的下标并返回
  2. 使用哈希表:利用空间换时间,提高效率。Map 存放数组元素和下标,通过判断key 是否存在目标值结束循环

代码

  1. /**
  2. * 解法一: 暴力解法
  3. * @param nums
  4. * @param target
  5. * @return
  6. */
  7. public int[] twoSum1(int[] nums, int target) {
  8. for (int i = 0; i < nums.length; i++) {
  9. for (int j = i+1; j < nums.length; j++) {
  10. if(nums[i]+nums[j] == target){
  11. return new int[]{i,j};
  12. }
  13. }
  14. }
  15. return new int[0];
  16. }
  17. /**
  18. * 解法二:使用hash 表
  19. * @param nums
  20. * @param target
  21. * @return
  22. */
  23. public int[] twoSum2(int[] nums, int target) {
  24. Map<Integer,Integer> map = new HashMap<>();
  25. for (int i = 0; i < nums.length; i++) {
  26. if(map.containsKey(target - nums[i])){
  27. return new int[]{map.get(target - nums[i]),i};
  28. }
  29. map.put(nums[i],i);
  30. }
  31. return new int[0];
  32. }

2.寻找两个正序数组的中位数

题目
给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。
示例 1:
输入:nums1 = [1,3], nums2 = [2]
输出:2.00000
解释:合并数组 = [1,2,3] ,中位数 2
解题思路

  1. 暴力解决: 合并两个数组,然后进行排序,最后对排序后的数组按照中位数计算公式进行计算
  2. 二分查找:
  3. 分治法:

代码

  1. /**
  2. * 暴力解法
  3. * 1.合并数组API
  4. * 2.排序数组API
  5. * 3.计算注意强转成double,不然返回整数
  6. * @param nums1
  7. * @param nums2
  8. * @return
  9. */
  10. public static double findMedianSortedArrays(int[] nums1, int[] nums2) {
  11. int size = nums1.length+nums2.length;
  12. if(size == 0){
  13. return 0;
  14. }
  15. int[] result = Arrays.copyOf(nums1, nums1.length + nums2.length);
  16. System.arraycopy(nums2, 0, result, nums1.length, nums2.length);
  17. if (size == 1){
  18. return result[0];
  19. }
  20. Arrays.sort(result);
  21. if( size % 2 == 0 ){
  22. return ((double)result[(size/2)-1] + result[size/2])/2;
  23. }else {
  24. return result[size/2];
  25. }
  26. }

3.删除有序数组中的重复项

题目
给你一个 升序排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。
由于在某些语言中不能改变数组的长度,所以必须将结果放在数组nums的第一部分。更规范地说,如果在删除重复项之后有 k 个元素,那么 nums 的前 k 个元素应该保存最终结果。
将最终结果插入 nums 的前 k 个位置后返回 k 。
不要使用额外的空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成
解题思路

  1. 快慢指针:


  2. 二、链表

  3. 两数相加

题目
给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
解题思路

  1. 同时遍历两个链表,逐位计算它们的和,并与当前位置的进位值相加。具体而言,如果当前两个链表处相应位置的数字为 n1,n2,进位值为 carry,则它们的和为 n1+n2+carry;其中,答案链表处相应位置的数字为 (n1+n2+carry) mod 10,而新的进位值为n1+n2+carry/10
  2. 如果两个链表的长度不同,则可以认为长度短的链表的后面有若干个 00 。
  3. 此外,如果链表遍历结束后,有carry>0,还需要在答案链表的后面附加一个节点,节点的值为 carry。

代码

  1. /**
  2. * Definition for singly-linked list.
  3. * public class ListNode {
  4. * int val;
  5. * ListNode next;
  6. * ListNode() {}
  7. * ListNode(int val) { this.val = val; }
  8. * ListNode(int val, ListNode next) { this.val = val; this.next = next; }
  9. * }
  10. */
  11. class Solution {
  12. public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
  13. ListNode tail=null, head = null;
  14. int carry = 0;
  15. while (l1 != null || l2 != null) {
  16. //1. 同时遍历两个 ListNode, 如果同位置链表为空用 0 代替
  17. int n1 = l1 != null ? l1.val : 0;
  18. int n2 = l2 != null ? l2.val : 0;
  19. int sum = n1 + n2 + carry;
  20. if (head == null) {
  21. head = tail = new ListNode(sum % 10);
  22. } else {
  23. tail.next = new ListNode(sum % 10);
  24. tail = tail.next;
  25. }
  26. carry = sum / 10;
  27. if (l1 != null) {
  28. l1 = l1.next;
  29. }
  30. if (l2 != null) {
  31. l2 = l2.next;
  32. }
  33. }
  34. // 3. 如果 carray >0 则
  35. if (carry > 0) {
  36. tail.next = new ListNode(carry);
  37. }
  38. return head;
  39. }
  40. }

第五章 算法

算法基础

注意:子头脑中回忆上面每种算法的思想和代码模版

if-else,switch -> branch

for, while loop -> Iteration

递归 Recursion

搜索 Search: 深度优先搜索 Depth first search, 广度优先搜索 Breadth first serarch

动态规划 Dynamic Programming

二分查找 Binary Search

贪心 Greedy

数学 Math,几何 Geometry

常用算法

1. 滑动窗口

什么是滑动窗口

滑动窗口算法是在给定特定窗口大小的数组或字符串上执行需求的操作。
该算法可以将一部分问题中的嵌套循环转变为一个单循环,因此减少时间复杂度。

基本示列

举个例子:如下图所示,设定滑动窗口(window)大小为 3,当滑动窗口每次划过数组时,计算当前滑动窗口中元素的和,得到结果 res。
image.png
可以用来解决一些查找满足一定条件的连续区间的性质(长度等)的问题。由于区间连续,因此当区间发生变化时,可以通过旧有的计算结果对搜索空间进行剪枝,这样便减少了重复计算,降低了时间复杂度。往往类似于“ 请找到满足 xx 的最 x 的区间(子串、子数组)的 xx ”这类问题都可以使用该方法进行解决。
需要注意的是,滑动窗口算法更多的是一种思想,而非某种数据结构的使用。

滑动窗口的基本框架

2. 动态规划

3. 中心扩展