本文主要是对《代码随想录》中数据结构部分的知识点做简要的笔记。
1、数组 1.1、数组理论基础 概念: 数组是存放在连续内存空间上的相同类型数据的集合。
注意:
数组下标从 0 开始;
数组内存空间的地址是连续的。
数组操作的复杂性: 正是由于数组内存地址是连续的,所以在对数组进行删除和添加的操作时,难免需要移动其他元素的地址。
不可删除性: 数组的元素是不能删除的,只能覆盖。
C++ 中的 vector 和 array: vector 底层实现是 array,本质不是数组而是容器。
C++ 二维数组的地址空间: C++ 二维数组在地址空间上是连续的。
1.2、二分查找 二分查找的主要问题在于边界的确定 。一半采用两种写法:
左闭右闭
1 2 3 4 5 6 7 8 9 10 11 12 13 14 int left = 0 ;int right = nums.size () - 1 ; while (left <= right){ int middle = left + (right - left) / 2 ; if (nums[middle] > target){ right = middle - 1 ; } else if (nums[middle] < target){ left = middle + 1 ; } else { return middle; } }
左闭右开
1 2 3 4 5 6 7 8 9 10 11 12 int left = 0 ;int right = nums.size (); while (left < right) { int middle = left + ((right - left) >> 1 ); if (nums[middle] > target) { right = middle; } else if (nums[middle] < target) { left = middle + 1 ; } else { return middle; } }
运用二分查找的关键词:有序数组、元素唯一 。
1.3、删除元素 题意:删除数组中指定的某个值的元素。
暴力解法:for循环遍历
双指针解法:重点在于理解双指针中快慢指针的意义 :
快指针:寻找新数组的元素,新数组就是不含有目标元素的数组;
慢指针:指向更新新数组的下标位置 。
1.4、有序数组的平方 LeetCode997.有序数组的平方
将双指针思想应用在相向 的思想。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution {public : vector<int > sortedSquares (vector<int >& A) { int k = A.size () - 1 ; vector<int > result (A.size(), 0 ) ; for (int i = 0 , j = A.size () - 1 ; i <= j;) { if (A[i] * A[i] < A[j] * A[j]) { result[k--] = A[j] * A[j]; j--; } else { result[k--] = A[i] * A[i]; i++; } } return result; } };
1.5、长度最小的子数组 LeetCode209.长度最小的子数组
本题最主要的是滑动窗口的思想 。
滑动窗口: 不断调节子序列的起始位置和终止位置,从而得出我们想要的结果;
for循环中的移动变量: 移动变量应该是终止位置,如果是起始位置开始遍历,跟双for循环无异。
滑动窗口需要确定如下三点:
窗口内是什么?
如何移动窗口的起始位置?
如何移动窗口的结束位置?
精髓: 不断调节子序列的起始位置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Solution {public : int minSubArrayLen (int s, vector<int >& nums) { int result = INT32_MAX; int sum = 0 ; int i = 0 ; int subLength = 0 ; for (int j = 0 ; j < nums.size (); j++) { sum += nums[j]; while (sum >= s) { subLength = (j - i + 1 ); result = result < subLength ? result : subLength; sum -= nums[i++]; } } return result == INT32_MAX ? 0 : result; } };
最小滑窗模板和最大滑窗模板
参考:
LeetCode904.水果成蓝
【深度解析】这道题和76. 最小覆盖子串的区别
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution {public : int totalFruit (vector<int >& fruits) { int n = fruits.size (); unordered_map<int , int > cnt; int left = 0 , ans = 0 ; for (int right = 0 ; right < n; right++){ ++cnt[fruits[right]]; while (cnt.size () > 2 ){ auto it = cnt.find (fruits[left]); --it->second; if (it->second == 0 ){ cnt.erase (it); } ++left; } ans = max (ans, right - left + 1 ); } return ans; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 while j < len (nums): 判断[i, j]是否满足条件 while 满足条件: 不断更新结果(注意在while 内更新!) i += 1 (最大程度的压缩i,使得滑窗尽可能的小) j += 1 while j < len (nums): 判断[i, j]是否满足条件 while 不满足条件: i += 1 (最保守的压缩i,一旦满足条件了就退出压缩i的过程,使得滑窗尽可能的大) 不断更新结果(注意在while 外更新!) j += 1
1.6、螺旋矩阵II LeetCode59.螺旋矩阵II
核心所在: 与二分法一样要坚持 循环不变量原则 ,具体而言就是边界信息不变,是左闭右开还是左闭右闭。确认好后依次循环。
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 class Solution {public : vector<vector<int >> generateMatrix (int n) { vector<vector<int >> res (n, vector <int >(n, 0 )); int startx = 0 , starty = 0 ; int loop = n / 2 ; int mid = n / 2 ; int count = 1 ; int offset = 1 ; int i,j; while (loop --) { i = startx; j = starty; for (j = starty; j < n - offset; j++) { res[startx][j] = count++; } for (i = startx; i < n - offset; i++) { res[i][j] = count++; } for (; j > starty; j--) { res[i][j] = count++; } for (; i > startx; i--) { res[i][j] = count++; } startx++; starty++; offset += 1 ; } if (n % 2 ) { res[mid][mid] = count; } return res; } };
2、链表 2.1、链表理论基础 概念: 链表是一种通过指针串联在一起的线性结构,每一个节点由两部分组成,一个是数据域一个是指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针的意思)。
链表的类型:
链表的存储方式: 链表是通过指针域的指针链接在内存中各个节点。所以链表中的节点在内存中不是连续分布的,而是散乱分布在内存中的某地址上,分配机制取决于操作系统的内存管理。
2.2、删除链表元素 LeetCode203.移除链表元素
操作链表主要涉及两种操作 :
直接使用 原来的链表来进行删除操作。
设置一个虚拟头结点 在进行删除操作。
在直接使用原链表的操作中,需要对头节点和一般节点做区别操作,故采用虚拟头节点可以使用统一的方式 去对链表进行操作。
2.3、设计链表 LeetCode707.设计链表
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 class MyLinkedList {public : struct LinkedNode { int val; LinkedNode* next; LinkedNode (int val):val (val), next (nullptr ){} }; MyLinkedList () { _dummyHead = new LinkedNode (0 ); _size = 0 ; } int get (int index) { if (index > (_size - 1 ) || index < 0 ) { return -1 ; } LinkedNode* cur = _dummyHead->next; while (index--){ cur = cur->next; } return cur->val; } void addAtHead (int val) { LinkedNode* newNode = new LinkedNode (val); newNode->next = _dummyHead->next; _dummyHead->next = newNode; _size++; } void addAtTail (int val) { LinkedNode* newNode = new LinkedNode (val); LinkedNode* cur = _dummyHead; while (cur->next != nullptr ){ cur = cur->next; } cur->next = newNode; _size++; } void addAtIndex (int index, int val) { if (index > _size) return ; if (index < 0 ) index = 0 ; LinkedNode* newNode = new LinkedNode (val); LinkedNode* cur = _dummyHead; while (index--) { cur = cur->next; } newNode->next = cur->next; cur->next = newNode; _size++; } void deleteAtIndex (int index) { if (index >= _size || index < 0 ) { return ; } LinkedNode* cur = _dummyHead; while (index--) { cur = cur ->next; } LinkedNode* tmp = cur->next; cur->next = cur->next->next; delete tmp; tmp=nullptr ; _size--; } void printLinkedList () { LinkedNode* cur = _dummyHead; while (cur->next != nullptr ) { cout << cur->next->val << " " ; cur = cur->next; } cout << endl; } private : int _size; LinkedNode* _dummyHead; };
2.4、翻转链表 LeetCode206.翻转链表
主要采用双指针的思想 进行求解,其中重点在于:
采用递归的写法其思想也是双指针思想,重点在于传入参数:把xx值赋给xx,决定了递归的传参
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution {public : ListNode* reverse (ListNode* pre,ListNode* cur) { if (cur == NULL ) return pre; ListNode* temp = cur->next; cur->next = pre; return reverse (cur,temp); } ListNode* reverseList (ListNode* head) { return reverse (NULL , head); } };