├── README.md ├── images └── wechat_QRcode.jpg ├── leetcode算法之二分查找.md ├── leetcode算法之二叉搜索树.md ├── leetcode算法之位运算.md ├── leetcode算法之分治法.md ├── leetcode算法之前缀树(字典树).md ├── leetcode算法之动态规划.md ├── leetcode算法之动态规划(背包问题).md ├── leetcode算法之双指针.md ├── leetcode算法之哈希表.md ├── leetcode算法之回溯.md ├── leetcode算法之图.md ├── leetcode算法之数组.md ├── leetcode算法之栈和队列.md ├── leetcode算法之贪心.md ├── leetcode算法之递归.md ├── leetcode算法之遍历(BFS与DFS).md └── leetcode算法之链表.md /README.md: -------------------------------------------------------------------------------- 1 | 明年就是找工作了,又要开始刷题了,把之前做过的题目再梳理整理一遍,但愿明年不要那么拉跨,祈祷明年能找到工作,千万不能毕业就失业。 2 | 3 | 4 | 5 | 分类别解析leetcode上的一些相关的例题路,代码采用**C++**实现。 6 | 7 | 使用**python**刷题分类整理的笔记,请参考这个仓库的v1 tag: [https://github.com/lxztju/leetcode-algorithm/tree/v1](https://github.com/lxztju/leetcode-algorithm/tree/v1) 8 | 9 | 10 | 11 | ## 算法题目分类 12 | 13 | 按照不同的类别整理leetcode题目,每个类别大概整理10-20道题目,每个类别的题目均附上可通过的c++与python代码,并针对每个类别的典型题目,附上清晰的题目思路算法逻辑解析。 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | ## 更多信息交流请关注 22 | 23 | * 微信公众号: 小哲AI 24 | 25 | 26 | ![公众号二维码](https://xiaozheai111.oss-cn-beijing.aliyuncs.com/wechatimgs/公众号二维码.jpg) 27 | -------------------------------------------------------------------------------- /images/wechat_QRcode.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lxztju/leetcode-algorithm/7409ead410b2dcc9610e410ed916ea1e8812b5d7/images/wechat_QRcode.jpg -------------------------------------------------------------------------------- /leetcode算法之二分查找.md: -------------------------------------------------------------------------------- 1 | 今天来盘一盘 ** 二分查找 ** 这类题目 2 | 3 | 4 | 代码采用**C++**实现 5 | 6 | 使用**python**刷题分类整理的笔记,请参考: [https://github.com/lxztju/leetcode-algorithm/tree/v1](https://github.com/lxztju/leetcode-algorithm/tree/v1) 7 | 8 | 9 | 10 | ## 二分查找 11 | 二分查找是针对一个排序列表,每次利用中间元素折半去掉部分元素,减少重复的查找遍历. 12 | 13 | * 对于一个排序数组nums,查找指定的一个数字target,采用二分查找的解题思路 14 | * 利用target与nums数组的中间元素相比较, 15 | 1. 如果target> nums[mid],说明target在数组的后半部分, 16 | 2. 如果target < nums[mid], 说明target在数组的前半部分 17 | 3. 如果target == nums[mid], 找到target. 18 | 19 | 二分查找的典型解题思路模板代码: 20 | 21 | ```c++ 22 | int binary_search(vector& nums, int target){ 23 | int l = 0, r = nums.size() - 1; 24 | while (l <= r){ 25 | int mid = l + (r - l) / 2; // 直接采用(r+l)/2. 容易出现整形溢出 26 | // 找到对应的元素,返回索引 27 | if (nums[mid] == target) return mid; 28 | // target比中间值大,说明存在数组后半部分 29 | else if (nums[mid] < target) 30 | l = mid + 1; 31 | // target小, 说明存在数组的前半部分. 32 | else 33 | r = mid - 1; 34 | } 35 | return -1; 36 | } 37 | ``` 38 | 39 | **两个非常困扰而且易错的细节点:** 40 | * while循环的判断条件是`l(mid) * mid; 71 | double pow2 = static_cast(mid + 1) * (mid + 1); 72 | if (pow2 == x) return mid + 1; 73 | else if (pow1 == x || (pow1 < x && pow2 > x)) return mid; 74 | else if (pow1 > x) 75 | r = mid - 1; 76 | else 77 | l = mid + 1; 78 | } 79 | return l; 80 | } 81 | }; 82 | ``` 83 | 84 | 85 | #### 744 寻找比目标字母大的最小字母 (Easy) 86 | 87 | ```c++ 88 | class Solution { 89 | public: 90 | char nextGreatestLetter(vector& letters, char target) { 91 | if (target >= *(letters.end() - 1)) return *(letters.begin()); 92 | // 全闭区间 93 | int l = 0, r = letters.size() - 1; 94 | while (l <= r){ 95 | int mid = l + (r - l) / 2; 96 | if (target < letters[mid]) 97 | r = mid - 1; 98 | else if (target >= letters[mid]) 99 | l = mid + 1; 100 | } 101 | return letters[l]; 102 | } 103 | }; 104 | ``` 105 | #### 278 第一个错误的版本 (Easy) 106 | ```c++ 107 | // The API isBadVersion is defined for you. 108 | // bool isBadVersion(int version); 109 | 110 | class Solution { 111 | public: 112 | int firstBadVersion(int n) { 113 | // 直接二分即可,找到第一个为false的版本 114 | // 全闭区间 115 | int l = 1, r = n; 116 | while (l <= r){ 117 | int mid = l + (r - l) /2 ; 118 | if (isBadVersion(mid)) 119 | r = mid - 1; 120 | else 121 | l = mid + 1; 122 | } 123 | return l; 124 | } 125 | }; 126 | ``` 127 | 128 | #### 540 有序数组中的单一元素 (Medium) 129 | 130 | * 题解: 二分查找 131 | * 这道题直接判断中间元素左右两侧的子数组哪一部分是奇数,说明单一元素就存在对应的部分 132 | * 如果mid后半部分的数组是偶数: 133 | * 1. 那么如果mid与mid+1位置的元素相等, 那么去掉这俩元素后,后半部分就是奇数,说明单一元素存在右半部分, l = mid + 2 134 | * 2. 如果mid与mid - 1位置相等,那么单一元素存在左半部分, r = mid - 2 135 | * 同理分析mid的后半部分是奇数. 136 | * 如果两侧均为偶数,那么mid即为待查找的单一元素. 137 | 138 | 139 | ```c++ 140 | class Solution { 141 | public: 142 | int singleNonDuplicate(vector& nums) { 143 | // 二分查找 144 | // mid为数组中间的元素索引 145 | // 如果mid与后边或者前边的元素相等, 那么判断哪一侧是奇数就说明在哪一侧 146 | // 如果mid刚好就是单独的元素,那么直接返回即可 147 | int l = 0, r = nums.size() - 1; 148 | while (l < r){ 149 | int mid = l + (r - l) / 2; 150 | // 后半部分是偶数 151 | bool second_even = ((r - mid) % 2 == 0); 152 | 153 | if (nums[mid + 1] == nums[mid]){ 154 | if (second_even) 155 | l = mid + 2; 156 | else 157 | r = mid - 1; 158 | } 159 | else if (nums[mid] == nums[mid - 1]){ 160 | if (second_even) 161 | r = mid - 2; 162 | else 163 | l = mid + 1; 164 | } 165 | else 166 | return nums[mid]; 167 | } 168 | return nums[l]; 169 | } 170 | }; 171 | ``` 172 | 173 | 174 | #### 153 寻找旋转排序数组中的最小值 (Medium) 175 | * 题解1: 简单顺序查找 176 | * 遍历数组, 如果nums[i] > nums[i+1] return num[i+1] 177 | * 如果遍历完全没有找到,就说明nums[0]是最小的元素. 178 | 179 | 180 | ```c++ 181 | class Solution { 182 | public: 183 | int findMin(vector& nums) { 184 | // 顺序查找, 如果前边元素,大于后边,那么就找到了对应的元素 185 | for (int i = 0; i < nums.size() - 1; i++){ 186 | if (nums[i] > nums[i+1]) 187 | return nums[i+1]; 188 | } 189 | return nums[0]; 190 | } 191 | }; 192 | ``` 193 | 194 | * 题解2: 二分查找 195 | * 采用中间元素与首尾元素进行对比的方式 196 | * 如果中间元素大于数组尾元素,说明反转点在mid的右边 197 | * 如果中间元素小于数组的尾部元素. 说明反转点在mid或者在mid的左边 198 | 199 | ```c++ 200 | class Solution { 201 | public: 202 | int findMin(vector& nums) { 203 | int left = 0; 204 | int right = nums.size() - 1; 205 | while (left < right) { 206 | int mid = left + (right - left) / 2; 207 | if (nums[mid] > nums[right]) { 208 | left = mid + 1; 209 | } else { 210 | right = mid; 211 | } 212 | } 213 | return nums[left]; 214 | } 215 | }; 216 | ``` 217 | 218 | 219 | #### 34 在排序数组中查找元素的第一个和最后一个位置(medium) 220 | 221 | * 题解: 顺序查找 222 | 223 | ```c++ 224 | class Solution { 225 | public: 226 | vector searchRange(vector& nums, int target) { 227 | // 直接顺序查找 228 | int l = 0, r = nums.size() - 1; 229 | vector res; 230 | while (l 0 && nums[r] != target ) 233 | r--; 234 | if (l != nums.size()) 235 | return {l, r}; 236 | else 237 | return {-1, -1}; 238 | } 239 | }; 240 | ``` 241 | 242 | * 题解2: 二分查找 243 | * 首先利用二分查找,找到对应的target 244 | * 然后顺序往左右扩展找到对应的边界 245 | 246 | ```c++ 247 | class Solution { 248 | public: 249 | vector searchRange(vector& nums, int target) { 250 | int l = 0, r = nums.size() - 1; 251 | // 使用二分查找,找到对应元素, 然后左右遍历得到两个坐标 252 | int index = -1; 253 | while (l <= r){ 254 | int mid = l + ( r - l ) / 2; 255 | //找到对应的元素索引 256 | if (nums[mid] == target){ 257 | index = mid; 258 | break; 259 | } 260 | else if(nums[mid] < target) 261 | l = mid + 1; 262 | else 263 | r = mid - 1; 264 | } 265 | // 没有找到对应的索引 266 | if (index == -1) 267 | return {-1, -1}; 268 | 269 | int left = index, right = index; 270 | // 向左扩展,找到元素左边界 271 | while (left >= 0 && nums[left] == target ) 272 | left--; 273 | // 向右扩展找到有边界 274 | while (right < nums.size() && nums[right] == target) 275 | right++; 276 | return {left + 1, right - 1}; 277 | } 278 | }; 279 | ``` 280 | 281 | 282 | 283 | 284 | 285 | ## 更多分类刷题资料 286 | 287 | * 微信公众号: 小哲AI 288 | 289 | ![wechat_QRcode](https://img-blog.csdnimg.cn/20210104185413204.jpg) 290 | 291 | * GitHub地址: [https://github.com/lxztju/leetcode-algorithm](https://github.com/lxztju/leetcode-algorithm) 292 | * csdn博客: [https://blog.csdn.net/lxztju](https://blog.csdn.net/lxztju) 293 | * 知乎专栏: [小哲AI](https://www.zhihu.com/column/c_1101089619118026752) 294 | * AI研习社专栏:[小哲AI](https://www.yanxishe.com/column/109) 295 | 296 | -------------------------------------------------------------------------------- /leetcode算法之二叉搜索树.md: -------------------------------------------------------------------------------- 1 | 今天来盘一盘 **二叉搜索树 ** 这类题目 2 | 3 | 4 | 5 | 使用**python**刷题分类整理的笔记,请参考: [https://github.com/lxztju/leetcode-algorithm/tree/v1](https://github.com/lxztju/leetcode-algorithm/tree/v1) 6 | 7 | 8 | 9 | ## 二叉搜索树 10 | 二叉搜索树是指 **左子树的节点值均小于根节点的值, 右子树的节点值大于根节点的值(递归的概念,对于每个节点都成立)** 11 | 12 | 二叉搜索树的**中序遍历是排序数组** 13 | 14 | 15 | * 235 二叉搜索树的最近公共祖先 (Easy) 16 | * 108 将有序数组转换为二叉搜索树 (Easy) 17 | * 653 两数之和 IV - 输入 BST (Easy) 18 | * 530 二叉搜索树的最小绝对差 (Easy) 19 | * 501 二叉搜索树中的众数 (Easy) 20 | * 669 修剪二叉搜索树 (medium) 21 | * 230 二叉搜索树中第K小的元素 (Medium) 22 | * 538 把二叉搜索树转换为累加树 (medium) 23 | * 109 有序链表转换二叉搜索树 (Medium) 24 | * 236 二叉树的最近公共祖先 (Medium) 25 | 26 | 27 | 28 | #### 235 二叉搜索树的最近公共祖先 (Easy) 29 | 30 | * 如果当前节点值大于pq,那么说明分叉点存在于当前节点的左子树中 31 | * 如果当前节点值小于pq, 那么说明分叉点存在当前节点的右子树中 32 | * 否则,说明找到分叉点。 33 | 34 | ```c++ 35 | class Solution { 36 | public: 37 | TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) { 38 | if (root == nullptr) return nullptr; 39 | // 大于pq,搜索左子树 40 | if(root->val > p->val && root->val > q->val) 41 | return lowestCommonAncestor(root->left, p, q); 42 | // 小于pq,搜索右子树 43 | else if (root->val < p->val && root->val < q->val) 44 | return lowestCommonAncestor(root->right, p, q); 45 | // 找到分叉点。 46 | else 47 | return root; 48 | } 49 | }; 50 | ``` 51 | 52 | 53 | #### 108 将有序数组转换为二叉搜索树 (Easy) 54 | * 二叉搜索树的**中序遍历是有序数组** 55 | * 根据这个性质,排序数组的中间元素是根节点,左半部分是左子树,右半部分是右子树,递归构建 56 | 57 | ```c++ 58 | class Solution { 59 | public: 60 | TreeNode* sortedArrayToBST(vector& nums) { 61 | return sortedArray2BST(nums, 0, nums.size()-1); 62 | } 63 | TreeNode* sortedArray2BST(vector& nums, int left, int right){ 64 | if (left > right) return nullptr; 65 | int middle = left + (right - left) / 2; 66 | TreeNode* root = new TreeNode(nums[middle]); 67 | root->left = sortedArray2BST(nums, left, middle-1); 68 | root->right = sortedArray2BST(nums, middle+1, right); 69 | return root; 70 | } 71 | }; 72 | ``` 73 | 74 | 75 | 76 | #### 653 两数之和 IV - 输入 BST (Easy) 77 | * 中序遍历得到排序数组 78 | * 然后双指针 79 | 80 | ```c++ 81 | class Solution { 82 | public: 83 | bool findTarget(TreeNode* root, int k) { 84 | vector nums; 85 | inorder(root, nums); 86 | int l = 0, r = nums.size() -1; 87 | while (l < r){ 88 | int lrSum = nums[l] + nums[r]; 89 | if (lrSum == k) 90 | return true; 91 | else if (lrSum > k) 92 | r--; 93 | else 94 | l++; 95 | } 96 | return false; 97 | } 98 | //中序遍历 99 | void inorder(TreeNode* root, vector& nums){ 100 | if (root == nullptr) return; 101 | inorder(root->left, nums); 102 | nums.push_back(root->val); 103 | inorder(root->right, nums); 104 | } 105 | }; 106 | ``` 107 | 108 | 109 | 110 | #### 530 二叉搜索树的最小绝对差 (Easy) 111 | * 依然利用中序遍历为有序数组的特点。 112 | 113 | ```c++ 114 | class Solution { 115 | public: 116 | int getMinimumDifference(TreeNode* root) { 117 | vector nums; 118 | inorder(root, nums); 119 | int res = nums[1] - nums[0]; 120 | for (int i = 1; i < nums.size(); i++){ 121 | res = min(res, nums[i] - nums[i-1]); 122 | } 123 | return res; 124 | } 125 | //中序遍历 126 | void inorder(TreeNode* root, vector& nums){ 127 | if (root == nullptr) return; 128 | inorder(root->left, nums); 129 | nums.push_back(root->val); 130 | inorder(root->right, nums); 131 | } 132 | }; 133 | ``` 134 | 135 | #### 501 二叉搜索树中的众数 (Easy) 136 | ```c++ 137 | class Solution { 138 | public: 139 | vector findMode(TreeNode* root) { 140 | // 直接使用unordered_map,没有用到二叉搜索树的特定 141 | unordered_map nodeFreq; 142 | vector res; 143 | traversal(root, nodeFreq); 144 | int freq = 0; 145 | for (auto e = nodeFreq.begin(); e != nodeFreq.end(); e++){ 146 | freq = max(freq, e->second); 147 | } 148 | for (auto e = nodeFreq.begin(); e != nodeFreq.end(); e++){ 149 | if (e->second == freq) 150 | res.push_back(e->first); 151 | } 152 | return res; 153 | } 154 | void traversal(TreeNode* root, unordered_map& nodeFreq){ 155 | if (root == nullptr) return; 156 | nodeFreq[root->val]++; 157 | traversal(root->left, nodeFreq); 158 | traversal(root->right, nodeFreq); 159 | } 160 | }; 161 | ``` 162 | 163 | 如果不使用额外的空间。 164 | * 针对二叉搜索树而言,其中序遍历是一个有序数组, 所有相等的元素均在一起出现 165 | * 利用cnt变量来统计当前元素出现的次数, maxcnt为最大频率的元素出现的次数,res保存众数 166 | 167 | ```c++ 168 | class Solution { 169 | public: 170 | vector findMode(TreeNode* root) { 171 | vector res; 172 | int cnt = 1; 173 | int maxcnt = 1; 174 | int base; 175 | inorder(root, cnt, maxcnt, res, base); 176 | return res; 177 | } 178 | void inorder(TreeNode* root, int& cnt, int& maxcnt, vector& res, int& base){ 179 | if (root == nullptr) return; 180 | 181 | inorder(root->left, cnt, maxcnt, res, base); 182 | 183 | if (root->val == base){ 184 | cnt++; 185 | } 186 | else{ 187 | cnt = 1; 188 | base = root->val; 189 | } 190 | if (cnt > maxcnt){ 191 | res.clear(); 192 | res.push_back(base); 193 | maxcnt = cnt; 194 | } 195 | else if (cnt == maxcnt){ 196 | res.push_back(base); 197 | } 198 | 199 | inorder(root->right, cnt, maxcnt, res, base); 200 | } 201 | }; 202 | ``` 203 | 204 | #### 669 修剪二叉搜索树 (medium) 205 | 206 | ```c++ 207 | class Solution { 208 | public: 209 | TreeNode* trimBST(TreeNode* root, int low, int high) { 210 | if(root == nullptr) return nullptr; 211 | if (root->val < low) 212 | return trimBST(root->right, low, high); 213 | else if (root->val > high) 214 | return trimBST(root->left, low, high); 215 | else{ 216 | root->left = trimBST(root->left, low, high); 217 | root->right = trimBST(root->right, low, high); 218 | } 219 | return root; 220 | } 221 | }; 222 | ``` 223 | 224 | #### 230 二叉搜索树中第K小的元素 (Medium) 225 | 226 | * 最直观的想法,二叉搜索树的中序遍历为排序数组 227 | ```c++ 228 | class Solution { 229 | public: 230 | int kthSmallest(TreeNode* root, int k) { 231 | vector nums; 232 | inorder(root, nums); 233 | return nums[k-1]; 234 | } 235 | 236 | //中序遍历 237 | void inorder(TreeNode* root, vector& nums){ 238 | if (root == nullptr) return; 239 | inorder(root->left, nums); 240 | nums.push_back(root->val); 241 | inorder(root->right, nums); 242 | } 243 | }; 244 | ``` 245 | 246 | #### 538 把二叉搜索树转换为累加树 (medium) 247 | * 反中序遍历 248 | * 从最右侧看,根节点的值为根+=右子树的值 249 | * 左子树的值为左子树的值+=根的值 250 | 251 | ```c++ 252 | class Solution { 253 | public: 254 | TreeNode* convertBST(TreeNode* root) { 255 | int val = 0; 256 | inverseInorder(root, val); 257 | return root; 258 | } 259 | // 反中序遍历 260 | void inverseInorder(TreeNode* root, int& val){ 261 | if (root == nullptr) return; 262 | inverseInorder(root->right, val); 263 | val += root->val; 264 | root->val = val; 265 | inverseInorder(root->left, val); 266 | } 267 | }; 268 | ``` 269 | 270 | 271 | #### 109 有序链表转换二叉搜索树 (Medium) 272 | * 有序链表转换为二叉搜索树与数组的转换方式基本一致 273 | * 只需要将链表分为左右两部分分别构成BST的左右子树即可 274 | 275 | ```c++ 276 | class Solution { 277 | public: 278 | TreeNode* sortedListToBST(ListNode* head) { 279 | if (head == nullptr) return nullptr; 280 | // 返回链表中间节点的前驱节点 281 | ListNode* premiddle = premiddleList(head); 282 | cout<< (premiddle->val) << endl; 283 | // 链表的中间节点 284 | int val = 0; 285 | ListNode* middle = premiddle->next; 286 | if (middle == nullptr) { 287 | TreeNode* root = new TreeNode(premiddle->val); 288 | return root; 289 | } 290 | else{ 291 | TreeNode* root = new TreeNode(middle->val); 292 | premiddle->next = nullptr; 293 | root->left = sortedListToBST(head); 294 | root->right = sortedListToBST(middle->next); 295 | return root; 296 | } 297 | } 298 | 299 | 300 | // 获得链表的中间节点的前一个节点。 301 | ListNode* premiddleList(ListNode* head){ 302 | if ( head == nullptr) return nullptr; 303 | ListNode* slow = head; 304 | ListNode* pre = slow; 305 | ListNode* fast = head->next; 306 | while (fast != nullptr && fast->next != nullptr){ 307 | pre = slow; 308 | slow = slow->next; 309 | fast = fast->next->next; 310 | } 311 | return pre; 312 | } 313 | }; 314 | ``` 315 | 316 | 317 | #### 236 二叉树的最近公共祖先 (Medium) 318 | * 直接遍历查找,如果一棵树的左右子树中分别存在p与q,那么直接返回这个结点 319 | 320 | ```c++ 321 | class Solution { 322 | public: 323 | TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) { 324 | if (root == nullptr) return nullptr; 325 | if ( root == p || root == q) return root; 326 | TreeNode* left = lowestCommonAncestor(root->left, p, q); 327 | TreeNode* right = lowestCommonAncestor(root->right, p, q); 328 | if (left != nullptr && right != nullptr) return root; 329 | else if (left == nullptr) 330 | return right; 331 | else if (right == nullptr) 332 | return left; 333 | else return nullptr; 334 | } 335 | }; 336 | ``` 337 | 338 | 339 | 340 | 341 | ## 更多分类刷题资料 342 | 343 | * 微信公众号: 小哲AI 344 | 345 | ![wechat_QRcode](https://img-blog.csdnimg.cn/20210104185413204.jpg) 346 | 347 | * GitHub地址: [https://github.com/lxztju/leetcode-algorithm](https://github.com/lxztju/leetcode-algorithm) 348 | * csdn博客: [https://blog.csdn.net/lxztju](https://blog.csdn.net/lxztju) 349 | * 知乎专栏: [小哲AI](https://www.zhihu.com/column/c_1101089619118026752) 350 | * AI研习社专栏:[小哲AI](https://www.yanxishe.com/column/109) 351 | 352 | -------------------------------------------------------------------------------- /leetcode算法之位运算.md: -------------------------------------------------------------------------------- 1 | 今天来盘一盘 **位运算 ** 这类题目 2 | 3 | 4 | 5 | 使用**python**刷题分类整理的笔记,请参考: [https://github.com/lxztju/leetcode-algorithm/tree/v1](https://github.com/lxztju/leetcode-algorithm/tree/v1) 6 | 7 | ## 位运算 8 | 9 | * 461 汉明距离 (Easy) 10 | * 136 只出现一次的数字 (Easy) 11 | * 268 丢失的数字 (Easy) 12 | * 260 只出现一次的数字 III (Medium) 13 | * 190 颠倒二进制位 (Easy) 14 | * 231 2的幂 (Easy) 15 | * 342 4的幂 (Easy) 16 | * 693 交替位二进制数 (Easy) 17 | * 476 数字的补数 (Easy) 18 | * 371 两整数之和 (Easy) 19 | * 318 最大单词长度乘积 (Medium) 20 | * 338 比特位计数 (Medium) 21 | 22 | 23 | #### 461 汉明距离 (Easy) 24 | 25 | 26 | ```c++ 27 | class Solution { 28 | public: 29 | int hammingDistance(int x, int y) { 30 | // 统计不同的位数 31 | auto sx = bitTransfer(x); 32 | auto sy = bitTransfer(y); 33 | int cnt = 0; 34 | for ( int i = 0; i< 32; i++){ 35 | if (sy[i] != sx[i]) 36 | cnt++; 37 | } 38 | return cnt; 39 | 40 | } 41 | string bitTransfer(int x){ 42 | // 将转换为01的二进制字符串 43 | string s(32, '0'); 44 | if (x == 0) return s; 45 | int i = 31; 46 | while ( x > 0){ 47 | int res = x % 2; 48 | s[i] = (res + '0'); 49 | x /= 2; 50 | i--; 51 | } 52 | return s; 53 | } 54 | }; 55 | ``` 56 | 57 | * 异或操作 58 | 59 | ```c++ 60 | class Solution { 61 | public: 62 | int hammingDistance(int x, int y) { 63 | // 利用异或操作,不同的为1, 然后统计1的个数 64 | int res = x ^ y; 65 | int cnt = 0; 66 | while ( res > 0){ 67 | cnt += (res & 1); 68 | res >>= 1; 69 | } 70 | return cnt; 71 | 72 | } 73 | 74 | }; 75 | 76 | ``` 77 | 78 | 79 | #### 136 只出现一次的数字 (Easy) 80 | * 相同元素的异或值为0 81 | * 0与任何元素的异或值为其自身 82 | * 因此整个序列所有元素的异或值即为仅仅出现一次的元素 83 | 84 | ```c++ 85 | class Solution { 86 | public: 87 | int singleNumber(vector& nums) { 88 | int res = 0; 89 | for (auto num:nums){ 90 | res ^= num; 91 | } 92 | return res; 93 | } 94 | }; 95 | ``` 96 | 97 | * 这个题也可以排序,然后查找仅仅出现一次的元素 98 | 99 | * 也可以利用哈希表来查找。 100 | 101 | #### 268 丢失的数字 (Easy) 102 | * 方法1: 排序 103 | * 方法2: 哈希表 104 | 105 | 106 | * 方法3: 位运算 107 | * 与上一题的处理方法类似,新构建一个0-n的数组6与原来的数组结合形成大数组,缺失的数字就是在新数组中及你进出现一次的数字 108 | 109 | ```c++ 110 | class Solution { 111 | public: 112 | int missingNumber(vector& nums) { 113 | int res = 0; 114 | for ( int i=0; i< nums.size(); i++){ 115 | res ^= (nums[i] ^ i); 116 | } 117 | return res ^ nums.size(); 118 | } 119 | }; 120 | 121 | ``` 122 | 123 | #### 260 只出现一次的数字 III (Medium) 124 | 125 | ```c++ 126 | class Solution { 127 | public: 128 | vector singleNumber(vector& nums) { 129 | // 整个数组进行异或操作,得到两个单独出现的字符的异或值 130 | int numXor = 0; 131 | for (auto num: nums) 132 | numXor ^= num; 133 | 134 | // 按照这个异或者,将原始数组分为两部分,每个单独出现的数字分别位于其中的一部分。 135 | int split = 1; 136 | while ((numXor & split) == 0){ 137 | split <<= 1; 138 | } 139 | vector res(2, 0); 140 | for (auto num : nums){ 141 | if (num & split) 142 | res[0] ^= num; 143 | else 144 | res[1] ^= num; 145 | } 146 | return res; 147 | } 148 | }; 149 | 150 | ``` 151 | 152 | #### 190 颠倒二进制位 (Easy) 153 | 154 | * 直接颠倒 155 | 156 | 157 | ```c++ 158 | class Solution { 159 | public: 160 | uint32_t reverseBits(uint32_t n) { 161 | uint32_t res = 0, power = 31; 162 | while (n != 0) { 163 | res += (n & 1) << power; 164 | n >>= 1; 165 | power -= 1; 166 | } 167 | return res; 168 | } 169 | }; 170 | ``` 171 | 172 | #### 231 2的幂 (Easy) 173 | 174 | ```c++ 175 | class Solution { 176 | public: 177 | bool isPowerOfTwo(int n) { 178 | int res = 0; 179 | int cnt = 0; 180 | if ( n < 0) return false; 181 | while ( n != 0){ 182 | if ((n & 1) == 1) 183 | cnt++; 184 | n >>= 1; 185 | } 186 | return cnt == 1; 187 | } 188 | }; 189 | ``` 190 | 191 | 192 | ```c++ 193 | class Solution { 194 | public: 195 | bool isPowerOfTwo(int n) { 196 | return ( n > 0 && (( n & (n - 1) )== 0) ); 197 | } 198 | }; 199 | ``` 200 | 201 | #### 342 4的幂 (Easy) 202 | 203 | ```c++ 204 | class Solution { 205 | public: 206 | bool isPowerOfFour(int num) { 207 | // 2的幂 208 | if (num > 0 && (num & ( num - 1 )) == 0) 209 | // 判断是否是4的幂 210 | return (num & 0x55555555) == num; 211 | return false; 212 | } 213 | }; 214 | ``` 215 | 216 | #### 693 交替位二进制数 (Easy) 217 | * 直接模拟整个交替过程,判断是否交替。 218 | 219 | ```c++ 220 | class Solution { 221 | public: 222 | bool hasAlternatingBits(int n) { 223 | int tmp1 = n & 1; 224 | n >>= 1; 225 | while ( n != 0){ 226 | int tmp2 = n & 1; 227 | n >>= 1; 228 | if (tmp1 == tmp2) 229 | return false; 230 | tmp1 = tmp2; 231 | } 232 | return true; 233 | } 234 | }; 235 | ``` 236 | 237 | #### 476 数字的补数 (Easy) 238 | * 101(5)的补数为111(7)减101 239 | 240 | ```c++ 241 | class Solution { 242 | public: 243 | int findComplement(int num) { 244 | long tmp = 1; 245 | while( tmp <= num){ 246 | tmp <<= 1; 247 | } 248 | return tmp - 1 - num; 249 | } 250 | }; 251 | 252 | ``` 253 | 254 | #### 371 两整数之和 (Easy) 255 | 256 | ```c++ 257 | class Solution { 258 | public: 259 | int getSum(int a, int b) { 260 | return a + b; 261 | } 262 | }; 263 | ``` 264 | 265 | * 题目要求不使用+ -符号 266 | * 使用在数字电子电路中,加法电路的设计方式 267 | * 针对二进制位,进位位为采用两个二进制的与运算, 和为两个二进制的异或运算的结果。 268 | 269 | ```c++ 270 | class Solution { 271 | public: 272 | int getSum(int a, int b) { 273 | 274 | while (b){ 275 | auto carry = (static_cast (a & b) )<< 1; 276 | a = a ^ b; 277 | b = carry; 278 | } 279 | return a; 280 | } 281 | }; 282 | 283 | ``` 284 | 285 | #### 318 最大单词长度乘积 (Medium) 286 | * 使用位运算作为编码来统计出现字母的种类数。 287 | 288 | ```c++ 289 | class Solution { 290 | public: 291 | int maxProduct(vector& words) { 292 | // 使用一个整形数字来表每个单词所含有字母的种类。,这个整形数字的后26位,如果某一位为1,说明出现这个字母 293 | int n = words.size(); 294 | vector code(n, 0); 295 | for (int i = 0; i < n; i++){ 296 | for (int j=0; j < words[i].size(); j++){ 297 | code[i] |= 1 << (words[i][j] - 'a'); 298 | } 299 | } 300 | int res = 0; 301 | for ( int i = 0; i < n-1; i++){ 302 | for ( int j = i+1; j < n; j++){ 303 | if ((code[i] & code[j]) == 0){ 304 | int tmp = (words[i].size()) * (words[j].size()); 305 | res = max(res, tmp); 306 | } 307 | } 308 | } 309 | return res; 310 | } 311 | }; 312 | ``` 313 | 314 | #### 338 比特位计数 (Medium) 315 | 316 | * 直接统计 317 | 318 | ```c++ 319 | class Solution { 320 | public: 321 | vector countBits(int num) { 322 | // 直接统计 323 | vector res; 324 | for (int i = 0; i<=num; i++){ 325 | int tmp = 0; 326 | int tmp1 = i; 327 | while (tmp1 != 0){ 328 | tmp += (tmp1 & 1); 329 | tmp1 >>= 1; 330 | } 331 | res.push_back(tmp); 332 | } 333 | return res; 334 | } 335 | }; 336 | ``` 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | ## 更多分类刷题资料 345 | 346 | * 微信公众号: 小哲AI 347 | 348 | ![wechat_QRcode](https://img-blog.csdnimg.cn/20210104185413204.jpg) 349 | 350 | * GitHub地址: [https://github.com/lxztju/leetcode-algorithm](https://github.com/lxztju/leetcode-algorithm) 351 | * csdn博客: [https://blog.csdn.net/lxztju](https://blog.csdn.net/lxztju) 352 | * 知乎专栏: [https://www.zhihu.com/column/c_1101089619118026752](https://www.zhihu.com/column/c_1101089619118026752) 353 | * AI研习社专栏:[https://www.yanxishe.com/column/109](https://www.yanxishe.com/column/109) 354 | 355 | -------------------------------------------------------------------------------- /leetcode算法之分治法.md: -------------------------------------------------------------------------------- 1 | 今天来盘一盘 **分治法 ** 这类题目 2 | 3 | 4 | 5 | 使用**python**刷题分类整理的笔记,请参考: [https://github.com/lxztju/leetcode-algorithm/tree/v1](https://github.com/lxztju/leetcode-algorithm/tree/v1) 6 | 7 | ## 分治法 8 | **分而治之**: 就是把一个复杂的问题分成两个或更多的相同或相似的子问题,直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并 9 | 10 | 11 | * 241 为运算表达式设计优先级 (Medium) 12 | * 95 不同的二叉搜索树 II (Medium) 13 | 14 | 15 | #### 241 为运算表达式设计优先级 (Medium) 16 | 17 | * 以运算符为分界将计算过程分为两部分,左侧的计算结果与右侧的结果相互组合运算即可。 18 | 19 | ```c++ 20 | class Solution { 21 | public: 22 | vector diffWaysToCompute(string input) { 23 | int index = 0; 24 | int num = 0; 25 | while (index < input.size() && isdigit(input[index])){ 26 | num = num* 10 + input[index] - '0'; 27 | index++; 28 | } 29 | if (index == input.size()) 30 | return {num}; 31 | vector ans; 32 | for(int i = index; i< input.size(); i++){ 33 | if (input[i] == '+' || input[i] == '-' || input[i] == '*'){ 34 | auto left = diffWaysToCompute(input.substr(0, i)); 35 | auto right = diffWaysToCompute(input.substr(i+1)); 36 | for (auto l : left){ 37 | for (auto r: right){ 38 | if (input[i] == '+') 39 | ans.push_back(l + r); 40 | else if (input[i] == '-') 41 | ans.push_back(l - r); 42 | else 43 | ans.push_back(l * r); 44 | } 45 | } 46 | } 47 | } 48 | return ans; 49 | } 50 | }; 51 | ``` 52 | 53 | 54 | 55 | #### 95 不同的二叉搜索树 II (Medium) 56 | 57 | ```c++ 58 | class Solution { 59 | public: 60 | vector generateTrees(int n) { 61 | auto res = buildBST(1, n); 62 | return res; 63 | } 64 | vector buildBST(int left, int right){ 65 | if (left > right) return {nullptr}; 66 | vector trees; 67 | for (int i = left; i <= right; i++){ 68 | auto leftTrees = buildBST(left, i-1); 69 | auto rightTrees = buildBST(i+1, right); 70 | for (auto l : leftTrees){ 71 | for (auto r : rightTrees){ 72 | TreeNode* root = new TreeNode(i); 73 | root->left = l; 74 | root->right = r; 75 | trees.push_back(root); 76 | } 77 | } 78 | } 79 | return trees; 80 | } 81 | }; 82 | ``` 83 | 84 | 85 | 86 | 87 | 88 | ## 更多分类刷题资料 89 | 90 | * 微信公众号: 小哲AI 91 | 92 | ![wechat_QRcode](https://img-blog.csdnimg.cn/20210104185413204.jpg) 93 | 94 | * GitHub地址: [https://github.com/lxztju/leetcode-algorithm](https://github.com/lxztju/leetcode-algorithm) 95 | * csdn博客: [https://blog.csdn.net/lxztju](https://blog.csdn.net/lxztju) 96 | * 知乎专栏: [https://www.zhihu.com/column/c_1101089619118026752](https://www.zhihu.com/column/c_1101089619118026752) 97 | * AI研习社专栏:[https://www.yanxishe.com/column/109](https://www.yanxishe.com/column/109) 98 | 99 | -------------------------------------------------------------------------------- /leetcode算法之前缀树(字典树).md: -------------------------------------------------------------------------------- 1 | 今天来盘一盘 **前缀树(字典树) ** 这类题目 2 | 3 | 4 | 5 | 使用**python**刷题分类整理的笔记,请参考: [https://github.com/lxztju/leetcode-algorithm/tree/v1](https://github.com/lxztju/leetcode-algorithm/tree/v1) 6 | 7 | 8 | ## 前缀树(字典树) 9 | 前缀树是用来存储字符串的。前缀树的每一个节点代表一个字符串(前缀)。每一个节点会有多个子节点,通往不同子节点的路径上有着不同的字符。子节点代表的字符串是由节点本身的原始字符串 ,以及通往该子节点路径上所有的字符组成的。 10 | 11 | 前缀树的一个重要的特性是,节点所有的后代都与该节点相关的字符串有着共同的前缀。 12 | 13 | 14 | * 208 实现 Trie (前缀树)(medium) 15 | * 211 添加与搜索单词 - 数据结构设计(medium) 16 | 17 | 18 | #### 208 实现 Trie (前缀树) 19 | ```c++ 20 | class Trie { 21 | 22 | private: 23 | bool isEnd; 24 | Trie* nextNode[26]; 25 | 26 | public: 27 | /** Initialize your data structure here. */ 28 | Trie() { 29 | isEnd = false; 30 | memset(nextNode, 0, sizeof(nextNode)); 31 | } 32 | 33 | /** Inserts a word into the trie. */ 34 | void insert(string word) { 35 | Trie* node = this; 36 | for (char c : word){ 37 | if (node->nextNode[c - 'a'] == nullptr) 38 | node->nextNode[c - 'a'] = new Trie(); 39 | node = node->nextNode[c - 'a']; 40 | } 41 | node->isEnd = true; 42 | } 43 | 44 | /** Returns if the word is in the trie. */ 45 | bool search(string word) { 46 | Trie* node = this; 47 | for (char c: word){ 48 | node = node->nextNode[c - 'a']; 49 | if (node == nullptr) return false; 50 | } 51 | return node->isEnd; 52 | } 53 | 54 | /** Returns if there is any word in the trie that starts with the given prefix. */ 55 | bool startsWith(string prefix) { 56 | Trie* node = this; 57 | for (char c: prefix){ 58 | node = node->nextNode[c - 'a']; 59 | if (node == nullptr) return false; 60 | } 61 | return true; 62 | } 63 | }; 64 | ``` 65 | 66 | #### 211 添加与搜索单词 - 数据结构设计 67 | 68 | ```c++ 69 | class WordDictionary { 70 | 71 | private: 72 | bool isEnd; 73 | WordDictionary* nextNode[26]; 74 | public: 75 | /** Initialize your data structure here. */ 76 | WordDictionary() { 77 | isEnd = false; 78 | memset(nextNode, 0, sizeof(nextNode)); 79 | } 80 | 81 | void addWord(string word) { 82 | WordDictionary* node = this; 83 | for (char c : word){ 84 | int index = c - 'a'; 85 | if (node->nextNode[index] == nullptr) 86 | node->nextNode[index] = new WordDictionary(); 87 | node = node->nextNode[index]; 88 | } 89 | node->isEnd = true; 90 | 91 | } 92 | 93 | bool search(string word) { 94 | auto node = this; 95 | return match(word, node, 0); 96 | } 97 | bool match(string word, WordDictionary* node, int start){ 98 | if (node == nullptr) return false; 99 | if (start == word.size()) return node->isEnd; 100 | 101 | auto ch = word[start]; 102 | if (ch != '.'){ 103 | int index = ch - 'a'; 104 | if (node->nextNode[index] == nullptr) 105 | return false; 106 | node = node->nextNode[index]; 107 | return match(word, node, start + 1); 108 | } 109 | else{ 110 | for (auto ch = 'a'; ch <= 'z'; ch++){ 111 | int index = ch - 'a'; 112 | if (match(word, node->nextNode[index], start + 1)) 113 | return true; 114 | } 115 | } 116 | return false; 117 | } 118 | }; 119 | ``` 120 | 121 | 122 | 123 | ## 更多分类刷题资料 124 | 125 | * 微信公众号: 小哲AI 126 | 127 | ![wechat_QRcode](https://img-blog.csdnimg.cn/20210104185413204.jpg) 128 | 129 | * GitHub地址: [https://github.com/lxztju/leetcode-algorithm](https://github.com/lxztju/leetcode-algorithm) 130 | * csdn博客: [https://blog.csdn.net/lxztju](https://blog.csdn.net/lxztju) 131 | * 知乎专栏: [小哲AI](https://www.zhihu.com/column/c_1101089619118026752) 132 | * AI研习社专栏:[小哲AI](https://www.yanxishe.com/column/109) 133 | 134 | -------------------------------------------------------------------------------- /leetcode算法之动态规划.md: -------------------------------------------------------------------------------- 1 | 今天来盘一盘 **动态规划 ** 这类题目 2 | 3 | 这是最让我头疼的一类题目,一直不想做这类题目,这里开始学习一下。 4 | 5 | 使用**python**刷题分类整理的笔记,请参考: [https://github.com/lxztju/leetcode-algorithm/tree/v1](https://github.com/lxztju/leetcode-algorithm/tree/v1) 6 | 7 | ## 动态规划 8 | 动规就是**以空间换取时间。** 9 | 10 | 动态规划常常适用于有**重叠子问题和最优子结构**性质的问题。 11 | 12 | 一些思考的套路: 递归 (自顶向下)——> 记忆化递归(自顶向下消除重复) ——> 动态规划(自底而上) 13 | 14 | 1. 斐波那契数列 15 | 16 | * 70 爬楼梯(easy) 17 | * 198 打家劫舍(easy) 18 | * 213 打家劫舍 II(medium) 19 | 20 | 2. 矩阵路径 21 | 22 | * 64 最小路径和(medium) 23 | * 62 不同路径(medium) 24 | 25 | 3. 数组区间 26 | 27 | * 303 区域和检索 - 数组不可变(easy) 28 | * 413 等差数列划分(medium) 29 | 30 | 4. 分割整数 31 | 32 | * 343 整数拆分(medium) 33 | * 279 完全平方数(medium) 34 | * 91 解码方法(medium) 35 | 36 | 5. 最长递增子序列 37 | 38 | * 300 最长上升子序列(medium) 39 | * 646 最长数对链(medium) 40 | * 376 摆动序列(medium) 41 | 42 | 6. 最长公共子序列 43 | 44 | * 1143 最长公共子序列(medium) 45 | 46 | 47 | 7. 股票交易 48 | 49 | * 121 买卖股票的最佳时机(easy) 50 | * 122 买卖股票的最佳时机II(easy) 51 | * 123 买卖股票的最佳时机 III(hard) 52 | * 188 买卖股票的最佳时机 IV(hard) 53 | * 309 最佳买卖股票时机含冷冻期(medium) 54 | * 714 买卖股票的最佳时机含手续费(medium) 55 | 56 | 57 | 8. 字符串编辑 58 | 59 | * 583 两个字符串的删除操作(medium) 60 | * 72 编辑距离(hard) 61 | * 650 只有两个键的键盘(medium) 62 | 63 | 64 | ### 1. 斐波那契数列 65 | 这类题目是斐波那契数列是最简单的动态规划的问题,对于这类题目,我会**首先使用递归**直观解决问题, **然后利用记忆化递归**的方法去重,**最后使用动态规划**实现自底而上的方法。 66 | 67 | #### 70 爬楼梯(easy) 68 | * 递归 69 | * n-1到n有一种方法,第n-2到n有1种方法。 70 | * 因此到达第n阶楼梯的方法为到达第n-1阶的方法与第n-2阶楼梯的方法和 71 | 72 | ```c++ 73 | class Solution { 74 | public: 75 | int climbStairs(int n) { 76 | if (n <= 2) return n; 77 | return climbStairs(n-1) + climbStairs(n-2); 78 | } 79 | }; 80 | ``` 81 | 82 | * 记忆化递归 83 | * 因为在上述递归方法种 84 | * 例如要求 f(10) = f(9) + f(8),就要求f(9)与f(8) 85 | f(9) = f(8) + f(7) 86 | f(8) = f(7) + f(6) 87 | 从上边的分析,可以看出存在大量的重复计算,随着所求数字的增大,重复计算大量的增加,这里采用map来存储已经计算过的元素,例如在求f(9)时f(8)保存下来,然后遇到求f(8)的位置,不进行往下计算,实现递归树的剪枝 88 | 89 | ```c++ 90 | class Solution { 91 | public: 92 | unordered_map memo; 93 | int climbStairs(int n) { 94 | if (n <= 2) return n; 95 | if (memo.find(n) == memo.end()) 96 | memo.insert(make_pair(n, climbStairs(n-1) + climbStairs(n-2))); 97 | 98 | return memo[n]; 99 | } 100 | }; 101 | ``` 102 | 103 | * 记忆化递归时自上而下的方法, 动态规划是自下而上的方法 104 | 105 | ```c++ 106 | class Solution { 107 | public: 108 | int climbStairs(int n) { 109 | if (n <= 2) return n; 110 | vector dp(n+1, 0); 111 | dp[1] = 1; 112 | dp[2] = 2; 113 | for (int i = 3; i< n+1; i++){ 114 | dp[i] = dp[i-1] + dp[i-2]; 115 | } 116 | return dp[n]; 117 | } 118 | }; 119 | ``` 120 | 121 | 122 | #### 198 打家劫舍(easy) 123 | 124 | * 递归 125 | 126 | ```c++ 127 | class Solution { 128 | public: 129 | int rob(vector& nums) { 130 | if (nums.empty()) return 0; 131 | return helper(nums.size() - 1, nums); 132 | } 133 | int helper(int n, vector& nums){ 134 | if (n == 0) return nums[0]; 135 | if (n == 1) return max(nums[0], nums[1]); 136 | // 偷盗第n-1房,与偷盗n房的最值 137 | return max(helper(n-1, nums), helper(n-2, nums) + nums[n]); 138 | } 139 | }; 140 | ``` 141 | 142 | * 记忆化递归 143 | 144 | ```c++ 145 | class Solution { 146 | public: 147 | unordered_map memo; 148 | int rob(vector& nums) { 149 | if (nums.empty()) return 0; 150 | return helper(nums.size() - 1, nums); 151 | } 152 | int helper(int n, vector& nums){ 153 | if (n == 0) return nums[0]; 154 | if (n == 1) return max(nums[0], nums[1]); 155 | if (memo.find(n-1) == memo.end()) 156 | memo[n-1] = helper(n-1, nums); 157 | if (memo.find(n-2) == memo.end()) 158 | memo[n-2] = helper(n-2, nums); 159 | // 偷盗第n-1房,与偷盗n房的最值 160 | return max(memo[n-1], memo[n-2] + nums[n]); 161 | } 162 | }; 163 | ``` 164 | 165 | * 动态规划 166 | 167 | ```c++ 168 | class Solution { 169 | public: 170 | unordered_map memo; 171 | int rob(vector& nums) { 172 | if (nums.empty()) return 0; 173 | if (nums.size() == 1) return nums[0]; 174 | vector dp(nums.size(), 0); 175 | dp[0] = nums[0]; 176 | dp[1] = max(nums[0], nums[1]); 177 | for (int i = 2; i< nums.size(); i++){ 178 | dp[i] = max(dp[i-1], dp[i-2] + nums[i]); 179 | } 180 | return dp[nums.size() - 1]; 181 | } 182 | }; 183 | ``` 184 | 185 | 186 | #### 213 打家劫舍 II(medium) 187 | * 这个题与上一题的主要区别在于这里的数组是循环的。 188 | * 直观的思路可以分为两种情况,一种是选择第一间房丢掉最后一间房nums(nums.begin(), nums.end() - 1),第二种是选择最后一间房丢掉第一间房nums(nums.begin() + 1, nums.end())。在二者中取得最大值即可 189 | 190 | * 直接采用动态规划, 懒得用递归了 191 | 192 | ```c++ 193 | class Solution { 194 | public: 195 | int rob(vector& nums) { 196 | vector dp1(nums.size() - 1, 0); //丢掉最后一个 197 | vector dp2(nums.size() - 1, 0); // 丢掉第一个元素 198 | if (nums.size() == 1) return nums[0]; 199 | if (nums.size() == 2) return max(nums[0], nums[1]); 200 | dp1[0] = nums[0]; 201 | dp1[1] = max(nums[0], nums[1]); 202 | dp2[0] = nums[1]; 203 | dp2[1] = max(nums[1], nums[2]); 204 | for (int i = 2; i< nums.size()-1; i++){ 205 | dp1[i] = max(dp1[i-1], dp1[i-2] + nums[i]); 206 | dp2[i] = max(dp2[i-1], dp2[i-2] + nums[i+1]); 207 | } 208 | return max(dp1[nums.size()-2], dp2[nums.size()-2]); 209 | } 210 | }; 211 | ``` 212 | 213 | ### 2. 矩阵路径 214 | 215 | 这类题目是在一个矩阵中找寻满足题意的路径。 216 | 217 | 依然采用 递归-> 记忆化递归 -> 动态规划的三种方法来求解这个问题。 218 | 219 | #### 64 最小路径和(medium) 220 | * 递归 221 | * 假设f(i,j)为从起始位置到i, j位置的最小路径和,从上边和左边两个方向可以到达i j 。 222 | * 因此f(i,j) = min( f(i-1, j), f(i, j-1) ) + grid[i][j] 223 | 224 | ```c++ 225 | class Solution { 226 | public: 227 | int minPathSum(vector>& grid) { 228 | int m = grid.size(); 229 | int n = grid[0].size(); 230 | return pathsum(m-1, n-1, grid); 231 | } 232 | int pathsum(int i, int j, vector>& grid){ 233 | if (i == 0 && j == 0) return grid[0][0]; 234 | int left = i-1 >= 0 ? pathsum(i-1, j, grid) : INT_MAX; 235 | int up = j-1 >= 0 ? pathsum(i, j-1, grid) : INT_MAX; 236 | return min(left, up) + grid[i][j]; 237 | } 238 | }; 239 | ``` 240 | * 记忆化递归 241 | * 可以直观看出存在大量的重复运算,采用记忆化数组来消除重复计算 242 | 243 | ```c++ 244 | class Solution { 245 | public: 246 | int minPathSum(vector>& grid) { 247 | int m = grid.size(); 248 | int n = grid[0].size(); 249 | vector> memo(m, vector(n, -1)); 250 | return pathsum(m - 1, n - 1, grid, memo); 251 | } 252 | 253 | int pathsum(int i, int j, vector>& grid, vector>& memo){ 254 | if (i == 0 && j == 0) return grid[0][0]; 255 | 256 | int left = INT_MAX; 257 | if (i - 1 >= 0){ 258 | if (memo[i-1][j] == -1) 259 | memo[i-1][j] = pathsum(i-1, j, grid, memo); 260 | left = memo[i-1][j]; 261 | } 262 | 263 | int up = INT_MAX; 264 | if (j - 1 >= 0){ 265 | if (memo[i][j - 1] == -1) 266 | memo[i][j - 1] = pathsum(i, j - 1, grid, memo); 267 | up = memo[i][j-1]; 268 | } 269 | return min(left, up) + grid[i][j]; 270 | } 271 | }; 272 | ``` 273 | 274 | * 动态规划: 自底而上 275 | ```c++ 276 | class Solution { 277 | public: 278 | int minPathSum(vector>& grid) { 279 | int m = grid.size(); 280 | int n = grid[0].size(); 281 | vector> dp(grid); // dp[i][j]表示从起始点达到ij的最小的路径和。 282 | for (int i = 1; i< m; i++){ 283 | dp[i][0] +=dp[i-1][0]; 284 | } 285 | for (int j = 1; j < n; j++){ 286 | dp[0][j] += dp[0][j-1]; 287 | } 288 | for (int i = 1; i< m; i++){ 289 | for (int j = 1; j < n; j++){ 290 | dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + grid[i][j]; 291 | } 292 | } 293 | return dp[m-1][n-1]; 294 | } 295 | }; 296 | ``` 297 | 298 | #### 62 不同路径(medium) 299 | * 直接使用动态规划 300 | 301 | ```c++ 302 | class Solution { 303 | public: 304 | int uniquePaths(int m, int n) { 305 | vector> dp(m, vector(n, 1)); 306 | for (int i = 1; i< m; i++){ 307 | for (int j = 1; j < n; j++){ 308 | dp[i][j] = dp[i-1][j] + dp[i][j-1]; 309 | } 310 | } 311 | return dp[m-1][n-1]; 312 | } 313 | }; 314 | ``` 315 | 316 | 317 | 318 | ### 3. 数组区间 319 | 320 | 321 | #### 303 区域和检索 - 数组不可变(easy) 322 | 323 | * 直接使用dp[i][j]保存ij之间的距离, 每次需要的时候直接查找即可。 324 | ```c++ 325 | class NumArray { 326 | 327 | private: 328 | vector> dp; 329 | public: 330 | NumArray(vector& nums) { 331 | int n = nums.size(); 332 | dp = vector> (n, vector(n, 0)); 333 | for ( int i = 0; i < n; i++){ 334 | dp[i][i] = nums[i]; 335 | } 336 | for (int i = 0; i < n-1; i++){ 337 | for (int j = i+1; j < n; j++){ 338 | dp[i][j] = dp[i][j-1] + nums[j]; 339 | } 340 | } 341 | } 342 | 343 | int sumRange(int i, int j) { 344 | return dp[i][j]; 345 | } 346 | }; 347 | ``` 348 | 349 | 350 | * 也可以使用dp[i]保存起始位置到i的序列和 351 | * ij之间的距离采用dp[j] - dp[i]得到 352 | 353 | ```c++ 354 | class NumArray { 355 | 356 | private: 357 | vector dp; 358 | public: 359 | NumArray(vector& nums) { 360 | int n = nums.size(); 361 | dp = vector(n, 0); 362 | if ( n > 0){ 363 | dp[0] = nums[0]; 364 | for ( int i = 1; i < n; i++){ 365 | dp[i] = dp[i-1] + nums[i]; 366 | } 367 | } 368 | } 369 | 370 | int sumRange(int i, int j) { 371 | if (i == 0) return dp[j]; 372 | return dp[j] - dp[i - 1]; 373 | } 374 | }; 375 | ``` 376 | 377 | 378 | #### 413 等差数列划分(medium) 379 | 380 | ```c++ 381 | class Solution { 382 | public: 383 | int numberOfArithmeticSlices(vector& A) { 384 | vector dp(A.size(), 0); 385 | int res = 0; 386 | for (int i = 2; i< A.size(); i++){ 387 | if (A[i] + A[i-2] == 2 * A[i-1]){ 388 | dp[i] = dp[i-1] + 1; 389 | res += dp[i]; 390 | } 391 | } 392 | return res; 393 | } 394 | }; 395 | ``` 396 | 397 | 398 | 399 | ### 4. 分割整数 400 | 401 | 402 | #### 343 整数拆分(medium) 403 | * dp[i]表示i拆分后的最值, dp[i]可以拆分为j, i-j, 那么i-j可以选择继续拆分与否,取最值 404 | * 为什么不考虑j是否拆分呢,考虑递归树即可想明白。 405 | * 考虑一棵直观的递归树。 求dp[n], 可以分为1*dp[n-1],或者2* dp[n-2] ...... (n-1) * dp[1]。 406 | 407 | ```c++ 408 | class Solution { 409 | public: 410 | int integerBreak(int n) { 411 | vector dp(n + 1, 1); 412 | dp[0] = 0; 413 | for (int i = 2; i< n+1; i++){ 414 | for (int j = 1; j < i; j++){ 415 | dp[i] = max(dp[i], max(j * dp[i - j], j * (i-j))); 416 | } 417 | } 418 | return dp[n]; 419 | } 420 | }; 421 | ``` 422 | 423 | #### 279 完全平方数(medium) 424 | * dp[i]表示i为结尾的完全平方数 425 | * i分为j与i - j*j, 那么dp[i] = dp[i-j*j] + 1 426 | 427 | ```c++ 428 | class Solution { 429 | public: 430 | int numSquares(int n) { 431 | vector dp(n+1, INT_MAX); 432 | dp[0] = 0; 433 | dp[1] = 1; 434 | for(int i = 2; i< n+1; i++){ 435 | int tmp = sqrt(i); 436 | for (int j = 1; j < tmp + 1; j++){ 437 | dp[i] = min(dp[i], dp[i - j * j] + 1 ); 438 | } 439 | // cout< dp(n+1, 0); 460 | if (n == 0) return 0; 461 | if (s[0] - '0' == 0) return 0; 462 | dp[0] = 1; 463 | dp[1] = s[0] != '0' ? 1 : 0; 464 | for (int i = 1; i < s.size(); i++){ 465 | 466 | if (patch(s, i) && s[i] == '0') 467 | dp[i + 1] = dp[i-1]; 468 | else if (s[i] != '0' && patch(s, i)) 469 | dp[i+1] = dp[i] + dp[i-1]; 470 | else if (s[i] != '0' && ! patch(s, i)) 471 | dp[i+1] = dp[i]; 472 | } 473 | 474 | return dp[n]; 475 | } 476 | bool patch(string& s, int i){ 477 | int tmp1 = s[i-1] - '0'; 478 | int tmp2 = tmp1 * 10 + s[i] - '0'; 479 | if (tmp2 >=10 && tmp2 <= 26) 480 | return true; 481 | return false; 482 | } 483 | }; 484 | ``` 485 | 486 | 487 | 488 | ### 5. 最长递增子序列 489 | 490 | 491 | 492 | #### 300 最长上升子序列(medium) 493 | * dp[i]表示以第i元素为结尾的最长上升子列的长度 494 | * dp[i] = max(dp[j]) + 1 并且 nums[i] > nums[j],也就是针对所以小于i的dp子数组,如果nums[j] < nums[i] 那么选择其中最大的加一, 如果全部小于nums[i],那么dp[i] = 1 495 | 496 | ```c++ 497 | class Solution { 498 | public: 499 | int lengthOfLIS(vector& nums) { 500 | int n = nums.size(); 501 | vector dp(n, 0); 502 | dp[0] = 1; 503 | for (int i = 1; i < n; i++){ 504 | for (int j = 0; j < i; j++){ 505 | if (nums[i] > nums[j]){ 506 | dp[i] = max(dp[i], dp[j]); 507 | } 508 | } 509 | dp[i]++; 510 | } 511 | } 512 | }; 513 | ``` 514 | 515 | 516 | #### 646 最长数对链(medium) 517 | * 与上一题类似 518 | 519 | ```c++ 520 | class Solution { 521 | public: 522 | static bool cmp(const vector& a, const vector& b){ 523 | if (a[1] == b[1]) return a[0] < b[0]; 524 | return a[1] < b[1]; 525 | } 526 | 527 | int findLongestChain(vector>& pairs) { 528 | int n = pairs.size(); 529 | 530 | sort(pairs.begin(), pairs.end(), cmp); 531 | 532 | vector dp(n, 0); 533 | dp[0] = 1; 534 | for (int i = 1; i < n; i++){ 535 | for (int j = 0; j < i; j++){ 536 | if ( pairs[i][0] > pairs[j][1] ) 537 | dp[i] = max(dp[i], dp[j]); 538 | } 539 | dp[i]++; 540 | } 541 | 542 | return *max_element(dp.begin(), dp.end()); 543 | } 544 | }; 545 | ``` 546 | 547 | 548 | #### 376 摆动序列(medium) 549 | * 与前两题一致, 这里采用dp[i][0]表示以i为结尾的最长的序列最后一个差值为负数 550 | * dp[i][1]最后一个差值为正数 551 | 552 | ```c++ 553 | class Solution { 554 | public: 555 | int wiggleMaxLength(vector& nums) { 556 | int n = nums.size(); 557 | int res = 0; 558 | if ( n < 2) return n; 559 | vector> dp(n, vector(2, 0)); 560 | dp[0][0] = 1; 561 | dp[0][1] = 1; 562 | for ( int i = 1; i< n; i++){ 563 | for (int j = 0; j < n; j++){ 564 | if (nums[i] - nums[j] > 0){ 565 | dp[i][1] = max(dp[i][1], dp[j][0]); 566 | } 567 | else if (nums[i] - nums[j] < 0){ 568 | dp[i][0] = max(dp[i][0], dp[j][1]); 569 | } 570 | 571 | } 572 | dp[i][0]++; 573 | dp[i][1]++; 574 | res = max(res, max(dp[i][0], dp[i][1])); 575 | } 576 | return res; 577 | } 578 | }; 579 | ``` 580 | 581 | 582 | 583 | ### 6. 最长公共子序列 584 | 585 | #### 1143 最长公共子序列(medium) 586 | * dp[i][j]表示text1的前i个字符, text2的前j个字符中二者的公共子序列的长度 587 | * 如果text[i] == text2[j] 那么 dp[i][j] = dp[i-1][j-1]+1 588 | * 如果text1[i] != text2[j] 那么: dp[i][j] = max(dp[i-1][j], dp[i][j-1]) 589 | 590 | 591 | ```c++ 592 | class Solution { 593 | public: 594 | int longestCommonSubsequence(string text1, string text2) { 595 | int m = text1.size(); 596 | int n= text2.size(); 597 | 598 | vector>dp(m+1, vector(n+1, 0)); 599 | for (int i = 1; i <= m; i++ ){ 600 | for (int j = 1; j<=n; j++){ 601 | if (text1[i-1] == text2[j-1]) 602 | dp[i][j] = dp[i-1][j-1] + 1; 603 | else 604 | dp[i][j] = max(dp[i-1][j], dp[i][j-1]); 605 | } 606 | } 607 | return dp[m][n]; 608 | } 609 | }; 610 | ``` 611 | 612 | 613 | 614 | ### 7. 股票交易 615 | 对于这类股票交易的问题,可以使用一个同意的模板来解决这类问题。 616 | 617 | * 状态方程为: 618 | 619 | ``` 620 | 状态转移方程: 621 | 状态方程中的 i 表示第i天, k表示剩余的操作次数(这里以购买作为操作次数的记录), 0表示不持有股票,1表示持有股票 622 | 再第i天不持有股票, 那么其最大利润为上一天不持有股票与上一天持有股票卖掉二者的最大值 623 | dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i]) 624 | 在第i天持有股票,那么其最大利润为上一天持有股票与上一天不持有股票,然后重新购买二者的最大值 625 | dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i]) 626 | ``` 627 | 628 | * 边界条件: 629 | ``` 630 | base case: 631 | #时间从第一天开始 632 | dp[0][k][0] = 0 633 | dp[0][k][1] = -prices[0] 634 | 635 | # k为0表示不允许交易 636 | dp[i][0][0] = 0 637 | dp[i][0][1] = -infinity 638 | ``` 639 | 640 | 当然利用这种方法进行处理并不一定是最优的,有时候会存在大量的冗余, 这里主要引入这种统一的解决思路 641 | 642 | 643 | #### 121 买卖股票的最佳时机(easy) 644 | 645 | ```c++ 646 | class Solution { 647 | public: 648 | int maxProfit(vector& prices) { 649 | // 这里只能交易一次,因此k为1, 这里就只需要定义二维数组 650 | int n = prices.size(); 651 | vector> dp(n, vector(2, 0)); 652 | dp[0][0] = 0; 653 | dp[0][1] = -prices[0]; 654 | for (int i=1; i< n; i++){ 655 | dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i]); 656 | // 这里由于只能交易1次。dp[i][0][0] = 0; 657 | dp[i][1] = max(dp[i-1][1], -prices[i]); 658 | } 659 | return dp[n-1][0]; 660 | } 661 | }; 662 | ``` 663 | 664 | 665 | #### 122 买卖股票的最佳时机II(easy) 666 | 667 | ```c++ 668 | class Solution { 669 | public: 670 | int maxProfit(vector& prices) { 671 | // 这里可以交易无数次,因此k不做限制, 这里就只需要定义二维数组 672 | int n = prices.size(); 673 | vector> dp(n, vector(2, 0)); 674 | dp[0][0] = 0; 675 | dp[0][1] = -prices[0]; 676 | for (int i=1; i< n; i++){ 677 | dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i]); 678 | 679 | dp[i][1] = max(dp[i-1][1], dp[i-1][0]-prices[i]); 680 | } 681 | return dp[n-1][0]; 682 | } 683 | }; 684 | ``` 685 | 686 | 687 | #### 123 买卖股票的最佳时机 III(hard) 688 | 689 | ```c++ 690 | class Solution { 691 | public: 692 | int maxProfit(vector& prices) { 693 | // 这里最多能够完成两笔交易,因此k = 2,需要定义三维数组 694 | int n = prices.size(); 695 | vector>> dp(n, vector>(3, vector(2, 0))); 696 | 697 | for ( int k = 0; k < 3; k++){ 698 | dp[0][k][0] = 0; 699 | dp[0][k][1] = -prices[0]; 700 | } 701 | for (int i = 0; i < n; i++){ 702 | dp[i][0][0] = 0; 703 | dp[i][0][1] = -INT_MAX; 704 | } 705 | for ( int i = 1; i< n; i++){ 706 | for (int k = 1; k <3; k++){ 707 | dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i]); 708 | dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i]); 709 | } 710 | } 711 | return dp[n-1][2][0]; 712 | } 713 | }; 714 | ``` 715 | 716 | 717 | #### 188 买卖股票的最佳时机 IV(hard) 718 | 719 | ```c++ 720 | class Solution { 721 | public: 722 | int maxProfit(int k, vector& prices) { 723 | // 这里最多能够完成k笔交易,需要定义三维数组 724 | int n = prices.size(); 725 | if (n ==0 || k == 0) return 0; 726 | vector>> dp(n, vector>(k+1, vector(2, 0))); 727 | 728 | for ( int k1 = 0; k1 < k+1; k1++){ 729 | dp[0][k1][0] = 0; 730 | dp[0][k1][1] = -prices[0]; 731 | } 732 | for (int i = 0; i < n; i++){ 733 | dp[i][0][0] = 0; 734 | dp[i][0][1] = -INT_MAX; 735 | } 736 | for ( int i = 1; i< n; i++){ 737 | for (int k1 = 1; k1 & prices) { 754 | // 可以实现无数次的交易,定义二维数组作为dp 755 | int n = prices.size(); 756 | if ( n == 0 ) return 0; 757 | vector> dp(n, vector(2, 0)); 758 | dp[0][0] = 0; 759 | dp[0][1] = -prices[0]; 760 | if (n == 1 ) return 0; 761 | dp[1][0] = max(0, prices[1] - prices[0]); 762 | dp[1][1] = max(-prices[0], -prices[1]); 763 | for (int i = 2; i < n; i++){ 764 | dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i]); 765 | // 下次购买的时候只能在i-2天不持有的情况下购买 766 | dp[i][1] = max(dp[i-1][1], dp[i-2][0] - prices[i]); 767 | } 768 | return dp[n-1][0]; 769 | } 770 | }; 771 | ``` 772 | 773 | 774 | #### 714 买卖股票的最佳时机含手续费(medium) 775 | 776 | ```c++ 777 | class Solution { 778 | public: 779 | int maxProfit(vector& prices, int fee) { 780 | // 这里可以交易无数次,因此k不做限制, 这里就只需要定义二维数组 781 | int n = prices.size(); 782 | vector> dp(n, vector(2, 0)); 783 | dp[0][0] = 0; 784 | dp[0][1] = -prices[0]; 785 | for (int i=1; i< n; i++){ 786 | dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i] - fee); 787 | 788 | dp[i][1] = max(dp[i-1][1], dp[i-1][0]-prices[i]); 789 | } 790 | return dp[n-1][0]; 791 | } 792 | }; 793 | ``` 794 | 795 | 796 | 797 | 798 | ### 8. 字符串编辑 799 | 800 | #### 583 两个字符串的删除操作(medium) 801 | 802 | ```c++ 803 | class Solution { 804 | public: 805 | int minDistance(string word1, string word2) { 806 | // 实际就是求最长公共子序列, 剩余的部分为即为需要操作的部分 807 | int m = word1.size(); 808 | int n = word2.size(); 809 | vector> dp(m+1, vector(n+1, 0)); 810 | for (int i = 1; i <= m; i++){ 811 | for (int j = 1; j <= n; j++){ 812 | if (word1[i-1] == word2[j-1]) 813 | dp[i][j] = dp[i-1][j-1] + 1; 814 | else 815 | dp[i][j] = max(dp[i-1][j], dp[i][j-1]); 816 | } 817 | } 818 | return m + n-2 * dp[m][n]; 819 | } 820 | }; 821 | ``` 822 | 823 | 824 | #### 72 编辑距离(hard) 825 | * 设dp[i][j]为word1的前i个字符转换为word2的前j个字符所需要的操作数。 826 | * 当word1[i] == word2[j]时候,dp[i][j] = dp[i-1][j-1] 827 | * 当word1[i] != word2[j]时候 828 | * 如果此时word1[i-1]与 word[j]已经转换完成, 那么此时删除word1[i]即可 此时dp[i][j] = dp[i-1][j] + 1 829 | * 如果此时word1[i]与 word[j-1]已经转换完成, 那么此时插入word1[i]即可 此时dp[i][j] = dp[i][j-1] + 1 830 | * 如果此时word1[i-1]与 word[j-1]已经转换完成, 那么此时替换word1[i]即可 此时dp[i][j] = dp[i-1][j-1] + 1 831 | * 从这三种情况中取得最小值即可 832 | 833 | ```c++ 834 | class Solution { 835 | public: 836 | int minDistance(string word1, string word2) { 837 | int m = word1.size(); 838 | int n = word2.size(); 839 | 840 | vector> dp(m+1, vector(n+1, 0)); 841 | 842 | // 空字符转换为word2 843 | for (int j = 1; j <= n; j++) 844 | dp[0][j] = dp[0][j-1] + 1; 845 | // word1转换为空字符 846 | for (int i = 1; i <= m; i++) 847 | dp[i][0] = dp[i-1][0] + 1; 848 | 849 | for (int i = 1; i <= m; i++){ 850 | for (int j = 1; j <= n; j++ ){ 851 | if (word1[i-1] == word2[j-1]) 852 | dp[i][j] = dp[i-1][j-1]; 853 | else 854 | dp[i][j] = min(dp[i-1][j], min(dp[i-1][j-1], dp[i][j-1])) + 1; 855 | } 856 | } 857 | return dp[m][n]; 858 | } 859 | }; 860 | ``` 861 | 862 | 863 | #### 650 只有两个键的键盘(medium) 864 | 865 | * dp[i]表示打印i个A需要的最小的次数。 866 | 867 | ```c++ 868 | class Solution { 869 | public: 870 | int minSteps(int n) { 871 | vector dp(n+1, INT_MAX); 872 | dp[1] = 0; 873 | for (int i = 2; i <= n; i++){ 874 | for (int j = 1; j < i; j++){ 875 | if ( i % j == 0) 876 | dp[i] = min(dp[i], dp[j] + i / j); 877 | } 878 | } 879 | return dp[n]; 880 | } 881 | }; 882 | ``` 883 | 884 | 885 | 886 | 887 | 888 | 889 | 890 | 891 | 892 | 893 | ## 更多分类刷题资料 894 | 895 | * 微信公众号: 小哲AI 896 | 897 | ![wechat_QRcode](https://img-blog.csdnimg.cn/20210104185413204.jpg) 898 | 899 | * GitHub地址: [https://github.com/lxztju/leetcode-algorithm](https://github.com/lxztju/leetcode-algorithm) 900 | * csdn博客: [https://blog.csdn.net/lxztju](https://blog.csdn.net/lxztju) 901 | * 知乎专栏: [https://www.zhihu.com/column/c_1101089619118026752](https://www.zhihu.com/column/c_1101089619118026752) 902 | * AI研习社专栏:[https://www.yanxishe.com/column/109](https://www.yanxishe.com/column/109) 903 | 904 | -------------------------------------------------------------------------------- /leetcode算法之动态规划(背包问题).md: -------------------------------------------------------------------------------- 1 | 今天来盘一盘 **动态规划(背包问题) ** 这类题目 2 | 3 | 这是最让我头疼的一类题目,一直不想做这类题目,这里开始学习一下。 4 | 5 | 使用**python**刷题分类整理的笔记,请参考: [https://github.com/lxztju/leetcode-algorithm/tree/v1](https://github.com/lxztju/leetcode-algorithm/tree/v1) 6 | 7 | ## 动态规划(背包问题) 8 | 动规就是**以空间换取时间。** 9 | 10 | 0-1背包是背包问题的一个主要的表现形式,在**01背包**的基础上发展出来的还有**完全背包**以及**多维背包**问题。 11 | 12 | ### 0-1背包 13 | 14 | 问题描述为: 存在一个容量为 C 的背包,和N类物品。这些物品分别有两个属性,重量w 和价值 v,每个物品的重量为w[i], 价值为v[i],**每种物品只有一个**。在不超过背包容量的情况下能够装入最大的价值为多少?(这个背包可以不装满)。 15 | 16 | 如果采用暴力法,每个物品都有装入与不装入两种情况,复杂度为2的n次幂,复杂度为指数级别的复杂度。如果使用动态规划,时间复杂度会降低至O(N*C)。 17 | 18 | ``` 19 | dp[i][j] 为将前i件物品装进容量为j的背包可以获得的最大价值, 0<=i<=N, 0<=j<=C 20 | 21 | dp表为(N+1)x(C+1)维的二维数组 22 | ``` 23 | 24 | * 状态转移方程 25 | 26 | ``` 27 | 1. 不装入第i件物品时,dp[i][j] = dp[i-1][j]; 28 | 2. 装入第i件物品,dp[i][j] = dp[i-1][ j-w[i] ] + v[i]; (j > w[i] 背包的容量大于w[i]) 29 | 状态转移方程: 30 | dp[i][j] = max(dp[i-1][j], dp[i-1][j-w[i]]+v[i]) 31 | ``` 32 | 33 | * base case 34 | 35 | ``` 36 | 第0个物品时不存在的,价值为0。 37 | dp[0][:] = 0; 38 | ``` 39 | 40 | * 基本的实现过程: 41 | 42 | ``` 43 | // 定义dp 44 | vector> dp(N+1, vector(C+1, 0)) 45 | 46 | // 定义base case 47 | for (int j = 0; j < c+1; j++) 48 | dp[0][j] = 0; 49 | // 执行状态转移 50 | for (int i = 1; i < N+1; i++){ 51 | for (int j = C; j >= 0; j--){ 52 | dp[i][j] = dp[i-1][j]; 53 | if (j >= w[i]) 54 | dp[i][j] = max(dp[i][j], dp[i-1][j-w[i]] + v[i]); 55 | } 56 | } 57 | return dp[N][C]; 58 | ``` 59 | 60 | 61 | * **优化的实现过程(dp采用一维数组)** 62 | 63 | ```c++ 64 | 初始dp[j]均为0 65 | for (int i = 1; i < N+1; i++){ 66 | for (int j = C; j >= 0; j--){ 67 | if (j >= w[i]) 68 | dp[j] = max(dp[j], dp[j-w[i]] + v[i]); 69 | } 70 | } 71 | ``` 72 | 73 | 74 | ### 完全背包 75 | 问题描述为: 存在一个容量为 C 的背包,和N类物品。这些物品分别有两个属性,重量w 和价值 v,每种物品的重量为w[i], 价值为v[i],**每种物品有无穷多个**。在不超过背包容量的情况下能够装入最大的价值为多少?(这个背包可以不装满)。 76 | 77 | 78 | ``` 79 | dp[i][j] 为将前i件物品装进容量为j的背包可以获得的最大价值, 0<=i<=N, 0<=j<=C 80 | 81 | dp表为(N+1)x(C+1)维的二维数组 82 | ``` 83 | 84 | 完全背包与01背包的思路基本一致,只是每种物品可以有无限多个,因此每次装入第i种物品后,还能继续装入i种物品。 85 | 86 | 87 | * 状态转移方程 88 | 89 | ``` 90 | 1. 不装入第i种物品时,dp[i][j] = dp[i-1][j]; 91 | 2. 装入第i件物品,dp[i][j] = dp[i][ j-w[i] ] + v[i]; (j > w[i] 背包的容量大于w[i]) 92 | 状态转移方程: 93 | dp[i][j] = max(dp[i-1][j], dp[i][j-w[i]]+v[i]) 94 | ``` 95 | 96 | * base case 97 | 98 | ``` 99 | 第0个物品时不存在的,价值为0。 100 | dp[0][:] = 0; 101 | ``` 102 | 103 | 104 | * 基本的实现过程: 105 | 106 | ``` 107 | // 定义dp 108 | vector> dp(N+1, vector(C+1, 0)) 109 | 110 | // 定义base case 111 | for (int j = 0; j < c+1; j++) 112 | dp[0][j] = 0; 113 | // 执行状态转移 114 | for (int i = 1; i < N+1; i++){ 115 | for (int j = 0; j <=C ; j++){ 116 | dp[i][j] = dp[i-1][j]; 117 | if (j >= w[i]) 118 | dp[i][j] = max(dp[i][j], dp[i][j-w[i]] + v[i]); 119 | } 120 | } 121 | return dp[N][C]; 122 | ``` 123 | 124 | * **优化的实现过程(dp采用一维数组)** 125 | 126 | ```c++ 127 | 初始dp[j]均为0 128 | for (int i = 1; i < N+1; i++){ 129 | for (int j = 0; j <= C; j++){ 130 | if (j >= w[i]) 131 | dp[j] = max(dp[j], dp[j-w[i]] + v[i]); 132 | } 133 | } 134 | ``` 135 | 136 | ### 多重背包 137 | 138 | 问题描述为: 存在一个容量为 C 的背包,和N类物品。这些物品分别有三个属性,重量w ,价值 v和数量n,每种物品的重量为w[i], 价值为v[i],**每种物品分别有n[i]个**。在不超过背包容量的情况下能够装入最大的价值为多少?(这个背包可以不装满)。 139 | 140 | 141 | 与前边的01背包类似,不同之处在于第i件物品的数目为n[i],这里的分析思路就是针对第i种物品,分别可以装入0,1,2,3, ... ,n[i]件(同时还得满足不能超过背包的容量)。 142 | 143 | 144 | ``` 145 | dp[i][j] 为将前i件物品装进容量为j的背包可以获得的最大价值, 0<=i<=N, 0<=j<=C 146 | 147 | dp表为(N+1)x(C+1)维的二维数组 148 | ``` 149 | 150 | 151 | 152 | * 状态转移方程 153 | 154 | ``` 155 | 1. 不装入第i种物品时,dp[i][j] = dp[i-1][j]; 156 | 157 | 2. 对于第i种物品, k为装入第i种物品的件数, k <= min(n[i], j/w[i]) 158 | dp[i][j] = max{(dp[i-1][j − k*w[i]] + k*v[i]) for i in range(k)} 159 | 160 | dp[i][j] = max(dp[i][j], dp[i][j-k* w[i]]+ k* v[i]) 161 | ``` 162 | 163 | * base case 164 | 165 | ``` 166 | 第0个物品时不存在的,价值为0。 167 | dp[0][:] = 0; 168 | ``` 169 | 170 | 171 | * 基本的实现过程: 172 | 173 | ``` 174 | // 定义dp 175 | vector> dp(N+1, vector(C+1, 0)) 176 | 177 | // 定义base case 178 | for (int j = 0; j < c+1; j++) 179 | dp[0][j] = 0; 180 | 181 | // 执行状态转移 182 | for (int i = 1; i < N+1; i++){ 183 | for (int j = C; j >=0 ; j--){ 184 | dp[i][j] = dp[i-1][j]; 185 | int tmp = min(n[i], j / w[i]); 186 | for (int k = 0; k<=tmp; k++){ 187 | dp[i][j] = max(dp[i][j], dp[i][j-k*w[i]] + k*v[i]); 188 | } 189 | } 190 | } 191 | return dp[N][C]; 192 | ``` 193 | 194 | 195 | * **优化的实现过程(dp采用一维数组)** 196 | 197 | ```c++ 198 | 初始dp[j]均为0 199 | for (int i = 1; i < N+1; i++){ 200 | for (int j = C; j >= 0; j--){ 201 | 202 | int tmp = min(n[i], j / w[i]); 203 | 204 | for (int k = 0; k<=tmp; k++){ 205 | dp[j] = max(dp[j], dp[j-k*w[i]] + k*v[i]); 206 | } 207 | } 208 | } 209 | ``` 210 | 211 | 212 | * 416 分割等和子集(medium) 213 | * 494 目标和(medium) 214 | * 474 一和零(medium) 215 | * 322 零钱兑换(medium) 216 | * 518 零钱兑换 II(medium) 217 | * 139 单词拆分(medium) 218 | * 377 组合总和 Ⅳ(medium) 219 | 220 | 221 | 222 | #### 416 分割等和子集(medium) 223 | * 01背包问题 224 | 225 | ```c++ 226 | class Solution { 227 | public: 228 | bool canPartition(vector& nums) { 229 | int n = nums.size(); 230 | if ( n < 2) return false; 231 | int numsSum = 0; 232 | for (auto num: nums) numsSum += num; 233 | if (numsSum %2 != 0) return false; 234 | int target = numsSum / 2; 235 | // 这就是一个01背包问题, 背包的容量为target, 能否恰好装满这个背包 236 | // nums数组的每个元素就是一种物品,nums[i]即为第i种物品的重量。 237 | vector> dp(n+1, vector(target+1, false)); 238 | dp[0][0] = true; 239 | for(int i = 1; i <= n; i++){ 240 | for (int j = target; j>=0; j--){ 241 | dp[i][j] = dp[i-1][j]; 242 | if (j >= nums[i-1]) 243 | dp[i][j] = dp[i][j] || dp[i-1][j-nums[i-1]]; 244 | if (dp[i][target] == true) 245 | return true; 246 | } 247 | } 248 | return false; 249 | } 250 | }; 251 | ``` 252 | 253 | * 优化后的一维数组解决方案 254 | 255 | ```c++ 256 | class Solution { 257 | public: 258 | bool canPartition(vector& nums) { 259 | int n = nums.size(); 260 | if ( n < 2) return false; 261 | int numsSum = 0; 262 | for (auto num: nums) numsSum += num; 263 | if (numsSum %2 != 0) return false; 264 | int target = numsSum / 2; 265 | // 这就是一个01背包问题, 背包的容量为target, 能否恰好装满这个背包 266 | // nums数组的每个元素就是一种物品,nums[i]即为第i种物品的重量。 267 | vectordp (target+1, false); 268 | dp[0] = true; 269 | for(int i = 1; i <= n; i++){ 270 | for (int j = target; j>=0; j--){ 271 | if (j >= nums[i-1]) 272 | dp[j] = dp[j] || dp[j-nums[i-1]]; 273 | if (dp[target] == true) 274 | return true; 275 | } 276 | } 277 | return false; 278 | } 279 | }; 280 | ``` 281 | 282 | 283 | 284 | #### 494 目标和(medium) 285 | * 01 背包问题, 求方案数目 286 | * sum(P) - sum(N) = target 287 | * sum(P) + sum(N) + sum(P) - sum(N) = target + sum(P) + sum(N) 288 | * 2 * sum(P) = target + sum(nums) 289 | * target为计算后的target,找子序列和为target的个数 290 | * 背包容量为target,物品重量为元素的值 291 | 292 | ```c++ 293 | class Solution { 294 | public: 295 | int findTargetSumWays(vector& nums, int S) { 296 | int sum = 0; 297 | for(int &num: nums) sum += num; 298 | if(S > sum || sum < -S) return 0; 299 | if((S + sum) % 2 != 0) return 0; 300 | int target = (S + sum) / 2; 301 | 302 | vectordp(target + 1, 0); 303 | 304 | dp[0] = 1; 305 | for(int i = 1; i <= nums.size(); i++) 306 | for(int j = target; j >= nums[i-1]; j--) 307 | dp[j] = dp[j] + dp[j - nums[i-1]]; 308 | 309 | return dp[target]; 310 | } 311 | }; 312 | ``` 313 | 314 | 315 | #### 474 一和零(medium) 316 | * 01背包问题, 二维背包问题 317 | * 背包的容量为m与n 318 | * 每个物品的重量为0的个数与1的个数 319 | * 每个物品的价值均为1, 求价值最大问题。 320 | * dp[j][k] = max(dp[j][k], dp[j-nums[i][0]][k - nums[i][1]) 321 | 322 | ```c++ 323 | class Solution { 324 | public: 325 | int findMaxForm(vector& strs, int m, int n) { 326 | vector> nums(strs.size(), vector(2, 0)); 327 | int num0 = 0, num1 = 0; 328 | for(int i = 0; i> dp (m+1, vector(n+1, 0)); 340 | for (int i = 0; i < nums.size(); i++){ 341 | for (int j = m; j >= nums[i][0]; j--){ 342 | for (int k = n; k >= nums[i][1]; k--){ 343 | dp[j][k] = max(dp[j][k], dp[j - nums[i][0]][k - nums[i][1]] + 1); 344 | } 345 | } 346 | } 347 | return dp[m][n]; 348 | } 349 | }; 350 | ``` 351 | 352 | 353 | #### 322 零钱兑换(medium) 354 | * 完全背包问题 355 | * 背包容量为amount 356 | * 每件物品的重量为coins[i], 每件物品的价值为1, 求刚好装满背包的物品的价值最小。 357 | * dp[j] = min(dp[j], dp[j-coins[i-1]]+1) 358 | 359 | ```c++ 360 | class Solution { 361 | public: 362 | int coinChange(vector& coins, int amount) { 363 | 364 | vector dp(amount+1, INT_MAX); 365 | dp[0] = 0; 366 | for (int i = 1; i < coins.size() + 1; i++){ 367 | for (int j = coins[i-1]; j <= amount; j++){ 368 | if(dp[j] - 1 > dp[j - coins[i-1]]) 369 | dp[j] = min(dp[j], 1 + dp[j - coins[i-1]]); 370 | } 371 | } 372 | return dp[amount]==INT_MAX ? -1 : dp[amount]; 373 | } 374 | }; 375 | ``` 376 | 377 | 378 | #### 518 零钱兑换 II(medium) 379 | * 完全背包问题 380 | * 背包的容量为amount, 每个物品的重量为coins[i],求刚好装满背包的方案数 381 | * dp[j] = sum(dp[j] + dp[j-coins[i-1]]) 382 | 383 | ```c++ 384 | class Solution { 385 | public: 386 | int change(int amount, vector& coins) { 387 | int n = coins.size(); 388 | vector dp(amount+1, 0); 389 | dp[0] = 1; 390 | for (int i = 1; i< n+1; i++){ 391 | for (int j = coins[i-1]; j<= amount; j++) 392 | dp[j] = dp[j] + dp[j- coins[i-1]]; 393 | } 394 | return dp[amount]; 395 | } 396 | }; 397 | ``` 398 | 399 | 400 | 401 | * 接下来的这两道题,需要考虑输出的顺序,因此循环遍历的顺序与前边几道题不一致,外层遍历背包,内层遍历物品 402 | 403 | #### 139 单词拆分(medium) 404 | * 完全背包 405 | * 物品就是单词字典,每个物品可以有无数个, 背包就是字符串, 看能否完全装满背包 406 | 407 | ```c++ 408 | class Solution { 409 | public: 410 | bool wordBreak(string s, vector& wordDict) { 411 | unordered_set wordSet(wordDict.begin(), wordDict.end()); 412 | vector dp(s.size() + 1, false); 413 | dp[0] = true; 414 | for (int i = 1; i <= s.size(); i++){ // 遍历背包 415 | for (int j = 0; j < i; j++){ // 遍历物品 416 | string word = s.substr(j, i - j); // 417 | if (wordSet.find(word) != wordSet.end()) 418 | dp[i] = dp[i] || dp[j]; 419 | } 420 | } 421 | return dp[s.size()]; 422 | } 423 | }; 424 | ``` 425 | 426 | 427 | #### 377 组合总和 Ⅳ(medium) 428 | * 完全背包问题 429 | * 物品的重量为nums[i],背包容量为target, 刚好装满背包的组合数 430 | * dp[j] = sum(dp[j], dp[j-nums[i-1]]) 431 | 432 | ```c++ 433 | class Solution { 434 | public: 435 | int combinationSum4(vector& nums, int target) { 436 | int n = nums.size(); 437 | vector dp(target+1, 0); 438 | dp[0] = 1; 439 | for (int i = 0; i < target+1; i++){ // 遍历背包 440 | for (int j = 1; j < n+1; j++ ){ // 遍历物品 441 | if ( i >= nums[j-1]) 442 | dp[i] = (dp[i] >= INT_MAX - dp[i-nums[j-1]]) ? INT_MAX : dp[i] + dp[i-nums[j-1]]; 443 | } 444 | } 445 | return dp[target]; 446 | } 447 | }; 448 | ``` 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | ## 更多分类刷题资料 457 | 458 | * 微信公众号: 小哲AI 459 | 460 | ![wechat_QRcode](https://img-blog.csdnimg.cn/20210104185413204.jpg) 461 | 462 | * GitHub地址: [https://github.com/lxztju/leetcode-algorithm](https://github.com/lxztju/leetcode-algorithm) 463 | * csdn博客: [https://blog.csdn.net/lxztju](https://blog.csdn.net/lxztju) 464 | * 知乎专栏: [https://www.zhihu.com/column/c_1101089619118026752](https://www.zhihu.com/column/c_1101089619118026752) 465 | * AI研习社专栏:[https://www.yanxishe.com/column/109](https://www.yanxishe.com/column/109) 466 | 467 | -------------------------------------------------------------------------------- /leetcode算法之双指针.md: -------------------------------------------------------------------------------- 1 | 明年就是找工作了,又要开始刷题了,把之前做过的题目再梳理整理一遍,但愿明年不要那么拉跨,祈祷明年能找到工作,千万不能毕业就失业。 2 | 3 | 分类别解析leetcode上的一些相关的例题路,代码采用**C++与python**实现。 4 | 5 | 6 | 7 | ## 双指针 8 | 主要分为如下的三类题目: 对撞指针, 快慢指针, 其他双指针. 9 | 10 | 11 | ### 对撞指针 12 | 13 | > 对撞指针的问题,一般是数组首尾各有一个指针,这俩指针往中间移动过,解决相对应的问题 14 | 15 | * 167 有序数组的 Two Sum 2 (easy) 16 | * 633 两数的平方和(easy) 17 | * 345 反转元音字符(easy) 18 | * 125 验证回文串(easy) 19 | * 680 回文字符串(easy) 20 | * 344 反转字符串(easy) 21 | * 27 移除元素(easy) 22 | * 11 盛最多水容器(medium) 23 | 24 | 25 | 26 | #### 167 有序数组的 Two Sum 2 (easy) 27 | 28 | > 给定一个已按照**升序排列** 的有序数组,找到两个数使得它们相加之和等于目标数。 29 | 30 | * 题解思路: 31 | * 数组是升序排列, 首尾各设置一个指针, 查找两个指针的值的和为target. 32 | * 当二者之和大于target时,尾指针前移,这两个数的和减小; 小于target时,首指针后移,直到二者数组的和等于target. 33 | 34 | ```c++ 35 | class Solution { 36 | public: 37 | vector twoSum(vector& numbers, int target) { 38 | // 首尾指针 39 | int head = 0, tail = numbers.size() - 1; 40 | while (head <= tail){ 41 | int point_sum = numbers[head] + numbers[tail]; 42 | if ( point_sum== target) 43 | return {head + 1, tail + 1}; 44 | // 和小于target,首指针后移 45 | else if (point_sum < target) 46 | head++; 47 | // 和大于target, 尾指针前移 48 | else 49 | tail--; 50 | } 51 | return {}; 52 | } 53 | }; 54 | ``` 55 | 56 | #### 633 两数的平方和(easy) 57 | 58 | > 给定一个非负整数 c ,你要判断是否存在两个整数 a 和 b,使得 a2 + b2 = c 。 59 | 60 | * 题解: 61 | * 与上一题一样, 设定一个数组为[0 ~ int(sqrt(c)) + 1], 设定首尾指针, 首尾指针对应的元素的平方之和为c 62 | 63 | ```c++ 64 | class Solution { 65 | public: 66 | bool judgeSquareSum(int c) { 67 | int head = 0; 68 | int tail = static_cast(sqrt(c)) + 1; 69 | while (head <= tail){ 70 | long point_sum = pow(head, 2) + pow(tail, 2); 71 | if (point_sum == c) 72 | return true; 73 | else if (point_sum < c) 74 | head++; 75 | else 76 | tail--; 77 | } 78 | return false; 79 | } 80 | }; 81 | ``` 82 | 83 | #### 345 反转元音字符(easy) 84 | 85 | > 编写一个函数,以字符串作为输入,反转该字符串中的元音字母。 86 | 87 | * 题解: 88 | * 首尾指针, 首先首指针后移,直到找到元音字母,然后尾指针后移找到元音字母,将二者交换 89 | 90 | ```c++ 91 | class Solution { 92 | public: 93 | string reverseVowels(string s) { 94 | // 首尾指针 95 | int head = 0, tail = s.size() - 1; 96 | // 利用无序的关联容器,存储原因字母表. 97 | unordered_set vowels({'a', 'e', 'i', 'o', 'u', 'A', 'E', 'I', 'O', 'U'}); 98 | 99 | while(head < tail){ 100 | // 首指针找到元音字母 101 | if(vowels.find(s[head]) == vowels.end()) 102 | head++; 103 | //尾指针找到元音字母 104 | else if(vowels.find(s[tail]) == vowels.end()) 105 | tail--; 106 | else{ 107 | // 交换首尾指针对应的元音字母 108 | swap(s[head], s[tail]); 109 | // 移动首尾指针的值 110 | head++; 111 | tail--; 112 | } 113 | } 114 | return s; 115 | } 116 | }; 117 | ``` 118 | 119 | 120 | #### 125 验证回文串(easy) 121 | 122 | > 给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。 123 | 124 | ```c++ 125 | class Solution { 126 | public: 127 | bool isPalindrome(string s) { 128 | int head = 0, tail = s.size() -1; 129 | while (head < tail){ 130 | // isalnum() 数字或者字母时为真. 131 | if (isalnum(s[head]) && isalnum(s[tail])){ 132 | // 忽略大小写,直接将其都转化为小写进行比较 133 | if (tolower(s[head]) != tolower(s[tail])) 134 | return false; 135 | else 136 | head++; tail--; 137 | } 138 | else if (! isalnum(s[head])) 139 | head++; 140 | else if (! isalnum(s[tail])) 141 | tail--; 142 | } 143 | return true; 144 | } 145 | }; 146 | ``` 147 | #### 680 回文字符串(easy) 148 | 149 | > 给定一个非空字符串 s,最多删除一个字符。判断是否能成为回文字符串 150 | 151 | * 题解: 152 | * 与上边一道题不同的是,可以删除一个字符,然后确定是否为回文串. 153 | * 那么分为两种情况, 第一种情况: 直接就是回文串 154 | * 第二种情况: 遇到了不相等的字符,那么要么去掉首指针对应的字符此时中间的子串依然为回文串,或者去掉尾指针此时中间的字符对应的字符依然为回文串. 155 | 156 | ```c++ 157 | class Solution { 158 | public: 159 | 160 | bool palindrome(string& s){ 161 | // 验证一个字符串是否为回文串 162 | int head = 0, tail = s.size() - 1; 163 | while (head < tail){ 164 | if (s[head] != s[tail]) 165 | return false; 166 | else 167 | head++; tail--; 168 | } 169 | return true; 170 | } 171 | 172 | bool validPalindrome(string s) { 173 | int head = 0, tail = s.size() - 1; 174 | while (head < tail){ 175 | if (s[head] == s[tail]) { 176 | head++; tail--; 177 | continue; 178 | } 179 | else{ 180 | string str1(s, head + 1, tail- head); 181 | string str2(s, head, tail - head); 182 | return palindrome(str1) || palindrome(str2); 183 | } 184 | } 185 | return true; 186 | } 187 | }; 188 | ``` 189 | 190 | #### 344 反转字符串(easy) 191 | ```c++ 192 | class Solution { 193 | public: 194 | void reverseString(vector& s) { 195 | int head = 0, tail = s.size() -1; 196 | while (head < tail){ 197 | swap(s[head], s[tail]); 198 | head++; 199 | tail--; 200 | } 201 | } 202 | }; 203 | ``` 204 | 205 | 206 | #### 27 移除元素(easy) 207 | 208 | > 给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。 209 | > 210 | > 不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。 211 | > 212 | > 元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素 213 | 214 | * 题解 215 | * 这道题的主要的思路就是 将数值为val的元素移动到数组的末尾 216 | ```c++ 217 | class Solution { 218 | public: 219 | int removeElement(vector& nums, int val) { 220 | int head = 0, tail = nums.size() - 1; 221 | 222 | while (head <= tail){ 223 | while (head <= tail && nums[tail] == val) 224 | tail--; 225 | if (nums[head] == val && head <= tail){ 226 | swap(nums[head], nums[tail]); 227 | tail--; 228 | } 229 | head++; 230 | } 231 | return tail + 1; 232 | } 233 | }; 234 | ``` 235 | 236 | 237 | #### 11 盛最多水容器(medium) 238 | 239 | > 给你 n 个非负整数 a1,a2,...,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i的两个端点分别为 (i, ai) 和 (i, 0) 。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。 240 | 241 | * 题解: 242 | * 这道题依然采用首尾指针, 这个矩形的面积为`长x高`, 长为两个指针之间的距离, 高由两个指针所对应的元素中的最小值决定. 243 | * 两个指针中高度较小的指针向内移动,找对应的更高的值.例如左边低,那么左指针右移虽然长变小了,但是高可能变高,整体面积才可能变大. 如果右指针左移,那么高永远由左边决定,此时长也变小了,整体的面积更小. 244 | * 因此只能移动短边对应的指针. 245 | 246 | ```c++ 247 | class Solution { 248 | public: 249 | int maxArea(vector& height) { 250 | // 定义头尾指针 251 | int head = 0, tail = height.size() - 1; 252 | // 保存最大的矩形面积 253 | long maxarea = 0; 254 | while (head < tail){ 255 | // 左边低, 那么左指针右移找寻更高的 高 256 | // 如果右指针左移, 那么高还是左指针的值,但是长变小了,只能移动短边 257 | if (height[head] < height[tail]){ 258 | long area = height[head] * (tail - head); 259 | maxarea = max(maxarea, area); 260 | head++; 261 | } 262 | // 右边低,右指针左移 263 | else { 264 | long area = height[tail] * (tail - head); 265 | maxarea = max(maxarea, area); 266 | tail--; 267 | } 268 | } 269 | return maxarea; 270 | } 271 | }; 272 | ``` 273 | 274 | 275 | ### 快慢指针 276 | > 快慢指针是指两个指针从头开始一个快一个慢指针, 一般就是, 最经典的题目就是针对链表的问题(**快慢指针查找链表的中心点**). 277 | 278 | * 141 判断列表是否存在环(easy) 279 | * 283 移动零(easy) 280 | * 26 删除排序数组中的重复项(easy) 281 | * 80 删除排序数组中的重复项 II(medium) 282 | 283 | 284 | 285 | #### 141 判断列表是否存在环(easy) 286 | 287 | > 给定一个链表,判断链表中是否有环。 288 | > 289 | > 如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,我们使用整数 pos来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意:pos不作为参数进行传递,仅仅是为了标识链表的实际情况。 290 | > 291 | > 如果链表中存在环,则返回 true 。 否则,返回 false 。 292 | 293 | 294 | * 题解: 295 | * 使用快慢指针,就像赛跑,一个人跑得快一个人跑得慢两个人肯定会相遇. 296 | * 这道题快慢指针,如果二者相遇之后就说明二者有环,否则快指针就会首先达到链表末尾. 297 | 298 | ```c++ 299 | class Solution { 300 | public: 301 | bool hasCycle(ListNode *head) { 302 | if (head == nullptr || head->next == nullptr) return false; 303 | // 定义快慢指针 304 | ListNode* slow = head; 305 | ListNode* fast = head->next; 306 | // 如果fast指针没有到达链表的结尾,就持续移动快慢指针. 307 | while (fast != nullptr && fast->next != nullptr){ 308 | // 如果二者相遇,说明有环 309 | if (fast == slow) return true; 310 | slow = slow->next; 311 | fast = fast->next->next; 312 | } 313 | // 快指针到达链表的结尾,说明没有环. 314 | return false; 315 | } 316 | }; 317 | ``` 318 | 319 | #### 283 移动零(easy) 320 | 321 | > 给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。 322 | 323 | * 题解: 324 | * 使用快慢指针, 快指针向右遍历,快指针指向非零元素,慢指针指向待交换的元素. 325 | * 右指针向右遍历,如果遇到非零元素,左右指针的值进行交换,然后左右指针同时后移 326 | * 如果右指针遇到零元素, 那么左指针不变等待交换,右指针后移找寻非零元素. 327 | 328 | ```c++ 329 | class Solution { 330 | public: 331 | void moveZeroes(vector& nums) { 332 | // 快慢指针 333 | int slow=0, fast=0; 334 | 335 | while (fast< nums.size()){ 336 | if (nums[fast] != 0){ 337 | swap(nums[fast], nums[slow]); 338 | slow++; 339 | } 340 | fast++; 341 | } 342 | } 343 | }; 344 | ``` 345 | 346 | #### 26 删除排序数组中的重复项(easy) 347 | 348 | > 给定一个排序数组,你需要在 原地 删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。 349 | > 350 | > 不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。 351 | 352 | 353 | * 题解: 354 | * 使用快慢指针,慢指针指向最终保留的数字,也就是待交换的数字,快指针向后遍历找寻不重复的元素与慢指针的位置进行交换,从而首先将不重复的元素保留在数组的前部. 355 | * 快指针向后遍历,当快慢指针对应的值不相同的时候,说明出现了一个新的不重复数字,将其进行交换. 356 | 357 | ```c++ 358 | class Solution { 359 | public: 360 | int removeDuplicates(vector& nums) { 361 | // 快慢指针 362 | int slow = 0, fast = 0; 363 | if (nums.size() == 0) return 0; 364 | while (fast < nums.size()){ 365 | if (nums[fast] != nums[slow]){ 366 | slow++; 367 | swap(nums[fast], nums[slow]); 368 | } 369 | fast++; 370 | } 371 | return slow+1; 372 | } 373 | }; 374 | ``` 375 | 376 | #### 80 删除排序数组中的重复项 II(medium) 377 | 378 | > 给定一个增序排列数组 nums ,你需要在 原地 删除重复出现的元素,使得每个元素最多出现两次,返回移除后数组的新长度。 379 | > 380 | > 不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。 381 | 382 | 这道题与上一题相比难在重复的字符可以出现两次. 383 | 384 | * 题解: 385 | * 这里就需要在上一题的基础上加以改进,也就是要判断字符已经保存的次数cnt表示 386 | 387 | ```c++ 388 | class Solution { 389 | public: 390 | int removeDuplicates(vector& nums) { 391 | if (nums.empty()) return 0; 392 | if (nums.size() == 1) return 1; 393 | 394 | int slow = 0, fast = 1; 395 | int cnt = 1; //cnt表示某个字符出现的次数 396 | 397 | while (fast < nums.size() ) { 398 | if (nums[fast] == nums[slow]) { 399 | // 如果这个字符仅仅出现了一次, 那么慢指针后移并赋值 400 | if (cnt == 1) { 401 | slow++; 402 | nums[slow] = nums[fast]; 403 | cnt++; 404 | } 405 | //如果已经出现了两次,就直接快指针后移 406 | } 407 | // 如果不相等,那么直接复制,也就是字符仅仅出现一次 408 | else { 409 | slow++; 410 | nums[slow] = nums[fast]; 411 | cnt = 1; 412 | } 413 | fast++; 414 | } 415 | return slow + 1; 416 | } 417 | }; 418 | ``` 419 | 420 | 421 | 422 | ### 其他双指针 423 | 424 | * 88. 归并有序数组(easy) 425 | * 524. 通过删除字母匹配到字典里最长单词(medium) 426 | 427 | 428 | #### 88. 归并有序数组(easy) 429 | 430 | > 给你两个有序整数数组 nums1 和 nums2,请你将 nums2 合并到 nums1 中,使 nums1 成为一个有序数组。 431 | > 说明: 432 | > 433 | > 初始化 nums1 和 nums2 的元素数量分别为 m 和 n 。 你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素。 434 | 435 | * 题解: 436 | * 方法2: 直接另外开辟一个空间,将二者插入新的vector,然后再copy回nums1.这样显然不太合适 437 | * 方法2: 直接使用insert再nums1的对应位置插入元素,但是这样会造成元素的大量移动操作 438 | * 方法3: 从后往前复制,先将最大的放置在nums[m+n-1]的位置,然后逐个往前 439 | 440 | 方法3的代码如下: (也可以采用前两种方法试试,简单题应该也会通过) 441 | ```c++ 442 | class Solution { 443 | public: 444 | void merge(vector& nums1, int m, vector& nums2, int n) { 445 | 446 | int i = m-1, j = n-1, k = 0; 447 | while (i >= 0 && j >= 0){ 448 | if (nums1[i] > nums2[j]){ 449 | nums1[m + n - k - 1] = nums1[i]; 450 | i--; 451 | } 452 | else{ 453 | nums1[m + n - k - 1] = nums2[j]; 454 | j--; 455 | } 456 | k++; 457 | } 458 | if (j >= 0){ 459 | for (int s = 0; s <= j; s++){ 460 | nums1[s] = nums2[s]; 461 | } 462 | } 463 | } 464 | }; 465 | ``` 466 | 467 | 468 | #### 524. 通过删除字母匹配到字典里最长单词(medium) 469 | 470 | > 给定一个字符串和一个字符串字典,找到字典里面最长的字符串,该字符串可以通过删除给定字符串的某些字符来得到。如果答案不止一个,返回长度最长且字典顺序最小的字符串。如果答案不存在,则返回空字符串。 471 | 472 | * 题解: 473 | * 首先查找所有满足条件的字符串 474 | * 然后在这些字符串中保留最长的一些字符串(可能相同长度有多个) 475 | * 找到字典序最小的字符串 476 | 477 | 478 | ```c++ 479 | class Solution { 480 | public: 481 | string findLongestWord(string s, vector& d) { 482 | 483 | if (s.empty()) return ""; 484 | 485 | vector Words; // 存储所有满足条件的字串 486 | 487 | for (auto e : d){ 488 | int p1 = 0, p2 = 0; 489 | while (p1 < e.size() && p2 < s.size()){ 490 | if (e[p1] == s[p2]) 491 | {p1++;p2++;} 492 | else 493 | p2++; 494 | } 495 | if (p1 == e.size()) 496 | Words.push_back(e); 497 | } 498 | 499 | vector longestWord; // 保存满足条件的子串中最长的字串 500 | 501 | int maxSize = 0; 502 | for (auto e : Words){ 503 | if (e.size() == maxSize) 504 | longestWord.push_back(e); 505 | else if (e.size() > maxSize){ 506 | longestWord.clear(); 507 | longestWord.push_back(e); 508 | maxSize = e.size(); 509 | } 510 | } 511 | sort(longestWord.begin(), longestWord.end()); 512 | 513 | if (longestWord.empty()) return ""; 514 | else return longestWord[0]; 515 | 516 | } 517 | }; 518 | ``` 519 | 520 | ## 更多笔记资料 521 | 522 | * 微信公众号: 小哲AI 523 | 524 | ![wechat_QRcode](https://img-blog.csdnimg.cn/20210104185413204.jpg) 525 | 526 | * GitHub地址: [https://github.com/lxztju/leetcode-algorithm](https://github.com/lxztju/leetcode-algorithm) 527 | * csdn博客: [https://blog.csdn.net/lxztju](https://blog.csdn.net/lxztju) 528 | * 知乎专栏: [小哲AI](https://www.zhihu.com/column/c_1101089619118026752) 529 | * AI研习社专栏:[小哲AI](https://www.yanxishe.com/column/109) 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | -------------------------------------------------------------------------------- /leetcode算法之哈希表.md: -------------------------------------------------------------------------------- 1 | 今天来盘一盘 **哈希表** 这类题目 2 | 3 | 4 | 分类别解析leetcode上的一些相关的例题路,代码采用**C++与python**实现。 5 | 6 | 7 | 8 | ## 哈希表 9 | 哈希表是一种很有用的数据结构, 其作用主要是**以空间换时间**, 在c++中主要是unordered_set与unordered_map,在python中主要是set与dict. 10 | 11 | 12 | * 1 两数之和 (Easy) 13 | * 217 存在重复元素 (Easy) 14 | * 594 最长和谐子序列 (Easy) 15 | * 128 最长连续序列 (Hard) 16 | * 349 两个数组的交集(easy) 17 | * 350 两个数组的交集 II(easy) 18 | * 242 有效的字母异位词(easy) 19 | * 202 快乐数(easy) 20 | * 205 同构字符串(easy) 21 | * 451 根据字符出现频率排序(medium) 22 | * 15 三数之和(medium) 23 | * 18 四数之和(medium) 24 | * 454 四数相加 II(medium) 25 | * 49 字母异位词分组(medium) 26 | * 447 回旋镖的数量(easy) 27 | * 219 存在重复元素 II(easy) 28 | 29 | 30 | #### 1 两数之和 (Easy) 31 | 32 | > 给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 的那 两个 整数,并返回它们的数组下标。 33 | > 34 | > 你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍 35 | 36 | 37 | * 题解: 38 | * 1. 使用哈希表(unordered_map)存储访问过的元素,这道题需要返回索引,因此键为元素的值,值为其对应的索引. 39 | * 2. 遍历整个数组,对于nums[i], 如果target-nums[i]存在于哈希表中,那就说明找到了这两个数. 40 | * 3. 如果target - nums[i]不在哈希表中就将nums[i]存在哈希表中. 41 | 42 | ```c++ 43 | class Solution { 44 | public: 45 | vector twoSum(vector& nums, int target) { 46 | // 定义哈希表 47 | unordered_map visited; 48 | for(int i = 0; i < nums.size(); i++){ 49 | int tmp = target - nums[i]; 50 | if (visited.find(tmp) != visited.end()) 51 | return {i, visited[tmp]}; 52 | else 53 | visited.insert(make_pair(nums[i], i)); 54 | } 55 | return {}; 56 | } 57 | }; 58 | ``` 59 | 60 | 61 | #### 217 存在重复元素 (Easy) 62 | 63 | * 题解1: 64 | * 简单使用哈希表存储访问过的元素,然后依次判断遍历的元素是否被访问过. 65 | 66 | * 题解2: 67 | * 排序,然后查找是否有相邻的相等元素. 68 | 69 | ```c++ 70 | class Solution { 71 | public: 72 | bool containsDuplicate(vector& nums) { 73 | // 使用unordered_set存储访问过的元素. 74 | unordered_set visited; 75 | for (auto num: nums){ 76 | if (visited.find(num) != visited.end()) 77 | return true; 78 | visited.insert(num); 79 | } 80 | return false; 81 | } 82 | }; 83 | ``` 84 | 85 | ```c++ 86 | class Solution { 87 | public: 88 | bool containsDuplicate(vector& nums) { 89 | if(nums.size() < 2) return false; 90 | // 排序. 91 | sort(nums.begin(), nums.end()); 92 | for (int i = 1; i< nums.size(); i++){ 93 | if (nums[i] == nums[i-1]) 94 | return true; 95 | } 96 | return false; 97 | } 98 | }; 99 | ``` 100 | 101 | 102 | #### 594 最长和谐子序列 (Easy) 103 | 104 | > 和谐数组是指一个数组里元素的最大值和最小值之间的差别正好是1。 105 | > 106 | > 现在,给定一个整数数组,你需要在所有可能的子序列中找到最长的和谐子序列的长度 107 | 108 | * 题解: 109 | * 遍历一遍vector,使用哈希表存储每个数字出现的次数 110 | * 遍历哈希表,找相邻数字出现次数之和最大. 111 | 112 | 113 | ```c++ 114 | class Solution { 115 | public: 116 | int findLHS(vector& nums) { 117 | // 遍历一遍存储每个字符出现的次数 118 | // 判断相邻数目最大的和. 119 | unordered_map numCnt; 120 | int res = 0; 121 | for (auto num: nums){ 122 | if (numCnt.find(num) == numCnt.end()) 123 | numCnt.insert(make_pair(num, 1)); 124 | else 125 | numCnt[num]++; 126 | } 127 | for (auto e = numCnt.begin(); e != numCnt.end(); e++){ 128 | if (numCnt.find(e->first+1) == numCnt.end()) 129 | continue; 130 | else 131 | res = max(res, e->second + numCnt[e->first+1]); 132 | } 133 | return res; 134 | } 135 | }; 136 | ``` 137 | 138 | 139 | #### 128 最长连续序列 (Hard) 140 | 141 | * 题解1 : 哈希暴力法(超时) 142 | * 将数组中所有的元素存入unordered_set中 143 | * 遍历数组,以每个元素作为开头,找寻连续子列的长度 144 | * 保存最长的长度 145 | 146 | ```c++ 147 | class Solution { 148 | public: 149 | int longestConsecutive(vector& nums) { 150 | // 暴力法.直接以每个数字为开头,遍历所有的元素,找寻最长的子列, 超时 151 | unordered_set nums_set(nums.begin(), nums.end()); 152 | int max_length = 0; 153 | for (auto num: nums){ 154 | int length = 0; 155 | int tmp = num; 156 | while (nums_set.find(tmp) != nums_set.end()){ 157 | tmp++; 158 | length++; 159 | } 160 | max_length = max(max_length, length); 161 | } 162 | return max_length; 163 | } 164 | }; 165 | ``` 166 | 167 | * 题解2: 找寻合适开头去重 168 | * 上边的方法以数组中每个元素均作为开头进行处理,存在大量重复计算 169 | * 针对数组中的元素, 如果其减一之后的数字,依然存在数组中,说明其不适合作为开头,跳过这个元素. 170 | 171 | ```c++ 172 | class Solution { 173 | public: 174 | int longestConsecutive(vector& nums) { 175 | // 直接采用暴力法以每个字符为开头,期间包含很多重复不必要的工作 176 | // 因此需要找到一个合适的开头进行扫描,如果元素减一存在于数组中,那么说明不能以这个字符作为开头 177 | unordered_set nums_set(nums.begin(), nums.end()); 178 | int max_length = 0; 179 | for (auto num: nums){ 180 | int tmp = num; 181 | if (nums_set.find(--num) != nums_set.end()) continue; 182 | 183 | int length = 0; 184 | while (nums_set.find(tmp) != nums_set.end()){ 185 | tmp++; 186 | length++; 187 | } 188 | max_length = max(max_length, length); 189 | } 190 | return max_length; 191 | } 192 | }; 193 | ``` 194 | 195 | 196 | #### 349 两个数组的交集(easy) 197 | * 题解: 哈希表 198 | * 将其中较短的数组存入unordered_set中 199 | * 遍历另一个数组中的元素是否在这个哈希表中,记录存在的元素 200 | * 如果发现一个元素存在哈希表中需要删除这个元素,防止重复. 201 | 202 | ```c++ 203 | class Solution { 204 | public: 205 | vector intersection(vector& nums1, vector& nums2) { 206 | vector res; 207 | if (nums1.size() > nums2.size()){ 208 | unordered_set nums_set(nums2.begin(), nums2.end()); 209 | for (auto num: nums1){ 210 | if (nums_set.find(num) != nums_set.end()){ 211 | res.push_back(num); 212 | nums_set.erase(num); 213 | } 214 | } 215 | } 216 | else{ 217 | unordered_set nums_set(nums1.begin(), nums1.end()); 218 | for (auto num: nums2){ 219 | if (nums_set.find(num) != nums_set.end()){ 220 | res.push_back(num); 221 | nums_set.erase(num); 222 | } 223 | } 224 | } 225 | return res; 226 | } 227 | }; 228 | ``` 229 | 230 | #### 350 两个数组的交集 II(easy) 231 | * 题解: 232 | * 需要统计重复数字出现的数目,因此采用unordered_map统计数组中的元素数目 233 | 234 | ```c++ 235 | class Solution { 236 | public: 237 | vector intersect(vector& nums1, vector& nums2) { 238 | //使用两个unordered_map分别记录两个数组中出现的次数的最大值. 239 | unordered_map nums1_map; 240 | unordered_map nums2_map; 241 | vector res; 242 | for(auto num: nums1) 243 | nums1_map[num]++; 244 | for (auto num: nums2) 245 | nums2_map[num]++; 246 | 247 | for (auto e=nums1_map.begin(); e!= nums1_map.end(); e++){ 248 | if (nums2_map.find(e->first) != nums2_map.end()){ 249 | int cnt = min(nums2_map[e->first], e->second); 250 | for(int i = 0; i< cnt; i++) 251 | res.push_back(e->first); 252 | } 253 | } 254 | return res; 255 | } 256 | }; 257 | ``` 258 | #### 242 有效的字母异位词(easy) 259 | * 题解: 260 | * 使用哈希表统计两个字符串中每个字符出现的次数 261 | * 对比两个哈希表的内容是否相同 262 | 263 | ```c++ 264 | 265 | class Solution { 266 | public: 267 | bool isAnagram(string s, string t) { 268 | // 直接使用哈希表进行遍历对比即可. 269 | // 使用map统计么个字符出现的次数,可以仅仅使用一个map 270 | if (s.size() != t.size()) return false; 271 | unordered_map scnt; 272 | for (auto s_substring : s){ 273 | scnt[s_substring]++; 274 | } 275 | for (auto t_substring : t){ 276 | if (scnt[t_substring] < 0) return false; 277 | scnt[t_substring]--; 278 | } 279 | 280 | for (auto e=scnt.begin(); e!=scnt.end(); e++){ 281 | if (e->second != 0) return false; 282 | } 283 | return true; 284 | } 285 | }; 286 | ``` 287 | #### 202 快乐数(easy) 288 | * 题解: 289 | * 使用哈希表保存已经访问过的元素 290 | * 如果下一个的和,已经出现过,说明出现了死循环,这就不是快乐数 291 | 292 | ```c++ 293 | 294 | class Solution { 295 | public: 296 | bool isHappy(int n) { 297 | // 使用哈希表保存已经访问过的元素, 防止出现死循环 298 | unordered_set visited; 299 | int res = n; 300 | while (true){ 301 | if (res == 1) return true; 302 | else if (visited.find(res) != visited.end()){ 303 | return false; 304 | } 305 | int dec = res; 306 | visited.insert(res); 307 | int sum_values = 0; 308 | while (dec){ 309 | int re = dec % 10; 310 | dec /= 10; 311 | sum_values += pow(re, 2); 312 | res = sum_values; 313 | } 314 | } 315 | } 316 | 317 | }; 318 | ``` 319 | #### 205 同构字符串(easy) 320 | 321 | ```c++ 322 | 323 | class Solution { 324 | public: 325 | bool isIsomorphic(string s, string t) { 326 | if (s.size() != t.size()) return false; 327 | unordered_map s2t; 328 | unordered_map t2s; 329 | int len = s.size(); 330 | for (int i = 0; i < len; ++i) { 331 | char x = s[i], y = t[i]; 332 | if ((s2t.find(x)!= s2t.end() && s2t[x] != y) || (t2s.find(y) != t2s.end() && t2s[y] != x)) { 333 | return false; 334 | } 335 | s2t[x] = y; 336 | t2s[y] = x; 337 | } 338 | return true; 339 | } 340 | }; 341 | ``` 342 | #### 451 根据字符出现频率排序(medium) 343 | * 题解: 344 | * 使用unordered_map保存每个字符出现的次数 345 | * 将map中对应的pair保存至vector中 346 | * 根据pair的第二个参数(数字)进行sort排序 347 | * 保存string结果 348 | 349 | ```c++ 350 | 351 | class Solution { 352 | public: 353 | string frequencySort(string s) { 354 | // 使用map保存每个字符出现的次数 355 | // 采用vector保存结果并进行排序 356 | // 返回string格式的结果 357 | unordered_map snum; 358 | for (auto chr : s){ 359 | snum[chr]++; 360 | } 361 | string res; 362 | res = sortfreq(snum); 363 | return res; 364 | } 365 | string sortfreq(unordered_map& snum){ 366 | vector> snumpair; 367 | string s; 368 | for (auto e = snum.begin(); e!=snum.end(); e++){ 369 | snumpair.push_back(make_pair(e->first, e->second)); 370 | } 371 | sort(snumpair.begin(), snumpair.end(), freqdecend); 372 | for (auto spair: snumpair){ 373 | string c1(spair.second, spair.first); 374 | s += c1; 375 | } 376 | return s; 377 | } 378 | // 排序的谓词 379 | static bool freqdecend(pair&s1, pair& s2){ 380 | return s1.second > s2.second; 381 | } 382 | }; 383 | ``` 384 | #### 15 三数之和(medium) 385 | * 题解1:暴力法, 时间复杂度n^3 386 | * 直接三重循环,找到三个数字 387 | * 其中需要考虑重复的情况,因此在开始的时候将整个数组排序去重. 388 | ```c++ 389 | class Solution { 390 | public: 391 | vector> threeSum(vector& nums) { 392 | // 暴力法,三层循环 393 | vector< vector > res; 394 | if (nums.size() < 3) return {}; 395 | sort(nums.begin(), nums.end()); 396 | for (int i=0; i < nums.size()-2; i++){ 397 | if (i > 0 && nums[i] == nums[i-1]) continue; 398 | for (int j=i+1; j i+1 && nums[j] == nums[j-1]) continue; 400 | for (int k=j+1; k j+1 && nums[k] == nums[k-1]) continue; 402 | if (nums[i] + nums[j] + nums[k] == 0) 403 | res.push_back({nums[i], nums[j], nums[k]}); 404 | } 405 | } 406 | } 407 | return res; 408 | } 409 | }; 410 | ``` 411 | 412 | * 题解2: 排序后的双指针 413 | 414 | ```c++ 415 | class Solution { 416 | public: 417 | vector> threeSum(vector& nums) { 418 | // 哈希表,时间换空间. 419 | vector< vector > res; 420 | if (nums.size() < 3) return {}; 421 | 422 | sort(nums.begin(), nums.end()); 423 | 424 | for (int i=0; i < nums.size()-2; i++){ 425 | if (i > 0 && nums[i] == nums[i-1]) continue; 426 | 427 | int target = -nums[i]; // 这就变成了两数之和为target的问题. 428 | int l = i+1, r = nums.size() - 1; 429 | while (l < r){ 430 | // cout<< l<< " "<< r< i+1 && nums[l] == nums[l-1]) { 432 | l++; 433 | continue; 434 | } 435 | if (r < nums.size() - 1 && nums[r] == nums[r+1]){ 436 | r--; 437 | continue; 438 | } 439 | if (nums[l] + nums[r] == target){ 440 | res.push_back({nums[i], nums[l], nums[r]}); 441 | l++; r--; 442 | } 443 | else if (nums[l] + nums[r] < target) 444 | l++; 445 | else 446 | r--; 447 | } 448 | 449 | } 450 | return res; 451 | } 452 | }; 453 | 454 | ``` 455 | 456 | #### 18 四数之和(medium) 457 | * 题解: 458 | * 同上一题一样, 排序加上双指针 459 | 460 | ```c++ 461 | 462 | class Solution { 463 | public: 464 | vector> fourSum(vector& nums, int target) { 465 | if (nums.size() < 4) return {}; 466 | vector< vector > res; 467 | sort(nums.begin(), nums.end()); 468 | for (int i = 0; i < nums.size() - 3; i++){ 469 | if (i > 0 && nums[i] == nums[i-1]) continue; 470 | 471 | for (int j = i+1; j < nums.size() - 2; j++){ 472 | if (j > i+1 && nums[j] == nums[j-1]) continue; 473 | 474 | int t = target - nums[i] - nums[j]; 475 | int l = j+1, r = nums.size() -1; 476 | while (l < r){ 477 | if (l >j+1 && nums[l] == nums[l-1]){ 478 | l++; 479 | continue; 480 | } 481 | if (r < nums.size()-1 && nums[r] == nums[r+1]){ 482 | r--; 483 | continue; 484 | } 485 | if (nums[l] + nums[r] == t){ 486 | res.push_back({nums[i], nums[j], nums[l], nums[r]}); 487 | l++; 488 | r--; 489 | } 490 | else if(nums[l] + nums[r] < t) 491 | l++; 492 | else 493 | r--; 494 | } 495 | } 496 | } 497 | return res; 498 | } 499 | }; 500 | ``` 501 | #### 454 四数相加 II(medium) 502 | * 题解:暴力法,超时, 时间复杂度n^4 503 | 504 | ```c++ 505 | class Solution { 506 | public: 507 | int fourSumCount(vector& A, vector& B, vector& C, vector& D) { 508 | // 直接暴力法,先试试 509 | int res = 0; 510 | for (auto numA : A){ 511 | for (auto numB: B){ 512 | for (auto numC: C){ 513 | for (auto numD: D){ 514 | if(numA+ numB + numC+ numD == 0){ 515 | res++; 516 | } 517 | } 518 | } 519 | } 520 | } 521 | return res; 522 | } 523 | }; 524 | ``` 525 | 526 | 527 | * 题解: 528 | * 将AB作为一组,将CD作为一组, 529 | * 使用map分别保存每个组合的和与对应的数目 530 | * 最终和为0的总次数由两个map中互为相反数的键的乘积得到. 531 | ```c++ 532 | class Solution { 533 | public: 534 | int fourSumCount(vector& A, vector& B, vector& C, vector& D) { 535 | // 直接暴力法,先试试 536 | int res = 0; 537 | unordered_map AB_num; 538 | unordered_map CD_num; 539 | for (auto numA : A){ 540 | for (auto numB: B){ 541 | AB_num[numA+numB]++; 542 | } 543 | } 544 | for (auto numC : C){ 545 | for (auto numD: D){ 546 | CD_num[numC+numD]++; 547 | } 548 | } 549 | 550 | for (auto e = AB_num.begin(); e!=AB_num.end(); e++){ 551 | res += (e->second) * CD_num[(-(e->first))]; 552 | } 553 | 554 | return res; 555 | } 556 | }; 557 | ``` 558 | 559 | #### 49 字母异位词分组(medium) 560 | * 题解: 561 | * 排序, 然后直接使用map存储对应的string即可. 562 | ```c++ 563 | class Solution { 564 | public: 565 | vector> groupAnagrams(vector& strs) { 566 | unordered_map > code2s; 567 | vector> res; 568 | for (string& s: strs){ 569 | string key = s; 570 | sort(key.begin(), key.end()); 571 | code2s[key].push_back(s); 572 | } 573 | for (auto e = code2s.begin(); e!= code2s.end(); e++){ 574 | res.push_back(e->second); 575 | } 576 | return res; 577 | } 578 | }; 579 | ``` 580 | #### 447 回旋镖的数量(easy) 581 | * 题解: 582 | * 以每个点为起始点, 使用map统计这个点与其他点距离的数目. 583 | * 同一个距离的回旋镖的数目为n*(n-1), n为距离的数目. 584 | 585 | 586 | ```c++ 587 | 588 | class Solution { 589 | public: 590 | int numberOfBoomerangs(vector>& points) { 591 | int res = 0; 592 | for (auto p : points) { 593 | unordered_map dist2num; 594 | for (auto q : points) { 595 | int dx = p[0]-q[0]; 596 | int dy = p[1]-q[1]; 597 | dist2num[pow(dy, 2)+pow(dx, 2)]++; 598 | } 599 | 600 | for (auto x : dist2num) { 601 | res += x.second * (x.second-1); 602 | } 603 | } 604 | return res; 605 | } 606 | }; 607 | ``` 608 | 609 | #### 219 存在重复元素 II(easy) 610 | * 题解: 611 | * 使用unordered_set存储一个不超过k个元素的序列 612 | * 如果下一个元素存在于列表中,那就说明存在 613 | * 如果set中元素超过k个,移除最早的元素. 614 | 615 | ```c++ 616 | class Solution { 617 | public: 618 | bool containsNearbyDuplicate(vector& nums, int k) { 619 | // 维护一个哈希表,这个哈希表中最多出现k个元素 620 | // 如果下一个遍历的元素存在于哈希表中,那么说明存在 621 | if (nums.size() < 2 || k == 0) return false; 622 | 623 | unordered_set knums; 624 | for (int i =0; i < nums.size(); i++){ 625 | if (knums.find(nums[i]) != knums.end()) 626 | return true; 627 | knums.insert(nums[i]); 628 | 629 | if (knums.size() > k){ 630 | knums.erase(nums[i- k]); 631 | } 632 | } 633 | return false; 634 | } 635 | }; 636 | ``` 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | ## 更多分类刷题资料 649 | 650 | * 微信公众号: 小哲AI 651 | 652 | ![wechat_QRcode](https://img-blog.csdnimg.cn/20210104185413204.jpg) 653 | 654 | * GitHub地址: [https://github.com/lxztju/leetcode-algorithm](https://github.com/lxztju/leetcode-algorithm) 655 | * csdn博客: [https://blog.csdn.net/lxztju](https://blog.csdn.net/lxztju) 656 | * 知乎专栏: [小哲AI](https://www.zhihu.com/column/c_1101089619118026752) 657 | * AI研习社专栏:[小哲AI](https://www.yanxishe.com/column/109) 658 | 659 | -------------------------------------------------------------------------------- /leetcode算法之回溯.md: -------------------------------------------------------------------------------- 1 | 今天来盘一盘 **回溯 ** 这类题目 2 | 3 | 4 | 5 | 使用**python**刷题分类整理的笔记,请参考: [https://github.com/lxztju/leetcode-algorithm/tree/v1](https://github.com/lxztju/leetcode-algorithm/tree/v1) 6 | 7 | 8 | ## 回溯 9 | **回溯法其实就是暴力法**。经常使用的暴力法就是**多层循环**, 但是面对**树形结构**的话很难采用循环的方式进行遍历。这时就要采用**递归回溯**的方法实现暴力法。 10 | 11 | 这类题目看似比较难,但是其实时很简单规范的套路性的代码, 下边几道题会有一个**固定的模板套路**, 请耐心体会。 12 | 13 | * 17 电话号码的字母组合(medium) 14 | * 93 复原IP地址(medium) 15 | * 131 分割回文串(medium) 16 | * 46 全排列(medium) 17 | * 47 全排列 II(medium) 18 | * 77 组合(medium) 19 | * 39 组合总和(medium) 20 | * 40 组合总和 II 21 | * 216 组合总和 III 22 | * 78 子集(medium) 23 | * 90 子集II(medium) 24 | * 79 单词搜索(medicum) 25 | * 200 岛屿数量(medium) 26 | * 130 被围绕的区域(medium) 27 | * 417 太平洋大西洋水流问题(medium) 28 | * 51 N皇后(hard) 29 | * 52 N皇后 II(hard) 30 | * 37 解数独(hard) 31 | 32 | 33 | #### 17 电话号码的字母组合(medium) 34 | * 一个树形结构 35 | * 例如输入的数字为“23 36 | * 那么这棵树的第一层有三个节点是 abc, 每个节点的下边有三个节点def。遍历这棵树得到所有的路径。 37 | 38 | ```c++ 39 | class Solution { 40 | public: 41 | vector letterCombinations(string digits) { 42 | // 构建字典映射 43 | unordered_map character2Num({ 44 | {'2', "abc"}, 45 | {'3', "def"}, 46 | {'4', "ghi"}, 47 | {'5', "jkl"}, 48 | {'6', "mno"}, 49 | {'7', "pqrs"}, 50 | {'8', "tuv"}, 51 | {'9', "wxyz"} 52 | }); 53 | // 定义返回值 54 | vector res; 55 | string path; 56 | backtrace(digits, res, 0, path, character2Num); 57 | return res; 58 | } 59 | 60 | void backtrace(string digits, vector& res, int index, string path, unordered_map& character2Num){ 61 | // 回溯递归函数 62 | if (digits.empty()) return ; 63 | // 如果遍历完成所有的数字,就保存得到的字母组合。 64 | if (index == digits.size()) { 65 | res.push_back(path); 66 | return ; 67 | } 68 | // 得到某个数字映射得到的字符串 69 | auto characters = character2Num[digits[index]]; 70 | // 遍历这个字符串,相当于树形结构中的一层 71 | for (int i = 0; i < characters.size(); i++){ 72 | path += characters[i]; 73 | // 向树的下一层进行遍历 74 | backtrace(digits, res, index + 1, path, character2Num); 75 | // 回溯到上一层的时候,需要去除这一层的元素。 76 | path.pop_back(); 77 | } 78 | } 79 | }; 80 | ``` 81 | 82 | #### 93 复原IP地址(medium) 83 | * 这个题也可以直接四层循环来做, 因为限制了最多分为4部分 84 | * 这里依然采用回溯搜索法来处理。这种分割字符串的题目,使用回溯法是一个通用的套路。 85 | 86 | 87 | ```c++ 88 | class Solution { 89 | public: 90 | vector restoreIpAddresses(string s) { 91 | vector< string> res; 92 | string path; 93 | 94 | backtrace(s, res, path, 0, 0); 95 | return res; 96 | } 97 | void backtrace(string& s, vector& res, string path, int cnt, int startindex){ 98 | // cnt表示分割的部分的数目,一共需要分割为4部分, startindex为接下来这一部分的开始位置 99 | if (cnt == 4){ 100 | if (startindex == s.size()) 101 | res.push_back(path); 102 | return; 103 | } 104 | 105 | for (int i = startindex; i < s.size(); i++){ 106 | // 每一部分的最长长度为3 107 | if (i - startindex == 3) break; 108 | auto prepath = path; 109 | auto substring = s.substr(startindex, i - startindex + 1); 110 | // 如果长度部位0, 且开头的数字为0, 不合法的ip 111 | if ( i - startindex > 0 && s[startindex] == '0') break; 112 | 113 | // 如果大于255直接返回。 114 | if (stoi(substring) > 255 ) break; 115 | path += substring; 116 | if (i != s.size() - 1) 117 | path += "."; 118 | backtrace(s, res, path, cnt+1, i + 1); 119 | path = prepath; 120 | } 121 | } 122 | }; 123 | ``` 124 | 125 | 126 | #### 131 分割回文串(medium) 127 | 128 | * 与上一题思路差不多, 没有上一题那么多需要排除的边界条件。 129 | 130 | ```c++ 131 | class Solution { 132 | public: 133 | vector> partition(string s) { 134 | // 得到所有的子字符串,判断得到的是否都是回文串。 135 | vector> res; 136 | vector palstring; 137 | string path; 138 | 139 | backtrace(s, res, palstring, 0); 140 | return res; 141 | } 142 | void backtrace(string& s, vector>& res, vector& palstring, int startindex){ 143 | if (startindex == s.size()){ 144 | res.push_back(palstring); 145 | return ; 146 | } 147 | for (int i = startindex; i < s.size(); i++){ 148 | auto substring = s.substr(startindex, i - startindex + 1); 149 | if ( ! ispalindrome(substring)) continue; 150 | palstring.push_back(substring); 151 | backtrace(s, res, palstring, i + 1); 152 | palstring.pop_back(); 153 | } 154 | 155 | } 156 | bool ispalindrome(string s){ 157 | int l = 0, r = s.size() - 1; 158 | while ( l < r){ 159 | if (s[l] != s[r]) 160 | return false; 161 | l++; r--; 162 | } 163 | return true; 164 | } 165 | 166 | }; 167 | ``` 168 | 169 | 170 | #### 46 全排列(medium) 171 | * 依然是一个树形结构,标准的回溯法套路代码 172 | 173 | ```c++ 174 | class Solution { 175 | public: 176 | vector> permute(vector& nums) { 177 | vector> res; 178 | vector path; 179 | unordered_set visited; 180 | backtrace(nums, res, path, visited, 0); 181 | return res; 182 | } 183 | void backtrace(vector& nums, vector>& res, vector& path, unordered_set& visited, int index){ 184 | if (index == nums.size()){ 185 | res.push_back(path); 186 | return ; 187 | } 188 | for (int i = 0; i< nums.size(); i++){ 189 | if(visited.find(nums[i]) != visited.end()) continue; 190 | visited.insert(nums[i]); 191 | path.push_back(nums[i]); 192 | backtrace(nums, res, path, visited, index+1); 193 | visited.erase(nums[i]); 194 | path.pop_back(); 195 | } 196 | } 197 | }; 198 | ``` 199 | 200 | #### 47 全排列 II(medium) 201 | * 与上一题的不同之处在于,这里存在重复的数字 202 | * 也就是说如果在同一层中出现了相同的数字,那么会出现重复的情况。 203 | * 采用排序的方式去重。 204 | 205 | ```c++ 206 | class Solution { 207 | public: 208 | vector> permuteUnique(vector& nums) { 209 | sort(nums.begin(), nums.end()); 210 | vector path; 211 | vector< vector > res; 212 | unordered_set visited; 213 | backtrace(nums, res, path, visited, 0); 214 | return res; 215 | } 216 | void backtrace(vector& nums, vector>& res, vector& path, unordered_set& visited, int index){ 217 | if (index == nums.size()){ 218 | res.push_back(path); 219 | return ; 220 | } 221 | for (int i=0; i< nums.size(); i++){ 222 | if (visited.find(i) != visited.end()) continue; 223 | // 这里采用nums中第i-1个元素如果没有访问过,说明在之后依然会出现,造成重复。 224 | if (i > 0 && nums[i] == nums[i-1] && visited.find(i - 1) == visited.end()) continue; 225 | visited.insert(i); 226 | path.push_back(nums[i]); 227 | backtrace(nums, res, path, visited, index+1); 228 | path.pop_back(); 229 | visited.erase(i); 230 | } 231 | } 232 | }; 233 | ``` 234 | 235 | #### 77 组合(medium) 236 | ```c++ 237 | 238 | class Solution { 239 | public: 240 | vector> combine(int n, int k) { 241 | vector> res; 242 | vector path; 243 | baketrace(n, k, 0, res, path); 244 | return res; 245 | } 246 | void baketrace(int n, int k, int index, vector>& res, vector& path){ 247 | if (k == 0){ 248 | res.push_back(path); 249 | return; 250 | } 251 | // 如果剩余的数字不够组成k个数字的组合,那么直接返回 252 | if (n - index + 1 < k) return ; 253 | 254 | for(int i = 1; i <= n; i++){ 255 | if (i <= index) continue; 256 | path.push_back(i); 257 | baketrace(n, k-1, i, res, path); 258 | path.pop_back(); 259 | } 260 | } 261 | }; 262 | ``` 263 | 264 | 265 | #### 39 组合总和(medium) 266 | * 与上一题的主要不同点在于一个元素可以多次选择,因此这里层的起始索引不再是i+1,而是i 267 | 268 | ```c++ 269 | class Solution { 270 | public: 271 | vector> combinationSum(vector& candidates, int target) { 272 | vector> res; 273 | vector path; 274 | 275 | backtrace(candidates, res, path, target, 0); 276 | return res; 277 | } 278 | void backtrace(vector& candidates, vector>&res, vector& path, int target, int index){ 279 | if (target == 0){ 280 | res.push_back(path); 281 | return ; 282 | } 283 | for (int i = 0; i< candidates.size(); i++){ 284 | if (candidates[i] > target) continue; 285 | if (i < index) continue; 286 | path.push_back(candidates[i]); 287 | backtrace(candidates, res, path, target- candidates[i], i); 288 | path.pop_back(); 289 | } 290 | } 291 | }; 292 | ``` 293 | 294 | #### 40 组合总和 II 295 | 296 | ```c++ 297 | class Solution { 298 | public: 299 | vector> combinationSum2(vector& candidates, int target) { 300 | // 每个数字只可以使用一次,并且可能含有重复的数字 301 | vector> res; 302 | vector path; 303 | // 排序去重 304 | sort(candidates.begin(), candidates.end()); 305 | unordered_set visited; 306 | backtrace(candidates, res, path, visited, 0, target); 307 | return res; 308 | } 309 | void backtrace(vector& candidates, vector>&res, vector& path, unordered_set& visited, int index, int target){ 310 | if(target == 0){ 311 | res.push_back(path); 312 | return; 313 | } 314 | for (int i = index; i < candidates.size(); i++){ 315 | 316 | if (candidates[i] > target) break; 317 | if ( i > 0 && candidates[i] == candidates[i-1] && visited.find(i-1) == visited.end()) continue; 318 | path.push_back(candidates[i]); 319 | visited.insert(i); 320 | backtrace(candidates, res, path, visited, i+1, target - candidates[i]); 321 | path.pop_back(); 322 | visited.erase(i); 323 | } 324 | } 325 | }; 326 | ``` 327 | 328 | 329 | #### 216 组合总和 III 330 | 331 | ```c++ 332 | class Solution { 333 | public: 334 | vector> combinationSum3(int k, int n) { 335 | if ( k > 9 || n > 45) return {}; 336 | vector> res; 337 | vector path; 338 | vector candidates({1,2,3,4,5,6,7,8,9}); 339 | backtrace(candidates, res, path, 0, n, k); 340 | return res; 341 | } 342 | void backtrace(vector& candidates, vector>&res, vector& path, int index, int target, int depth){ 343 | if(target == 0 && depth == 0){ 344 | res.push_back(path); 345 | return; 346 | } 347 | for (int i = index; i < candidates.size(); i++){ 348 | if (candidates[i] > target) break; 349 | path.push_back(candidates[i]); 350 | backtrace(candidates, res, path, i+1, target - candidates[i], depth-1); 351 | path.pop_back(); 352 | } 353 | } 354 | }; 355 | ``` 356 | 357 | #### 78 子集(medium) 358 | 359 | ```c++ 360 | class Solution { 361 | public: 362 | vector> subsets(vector& nums) { 363 | vector> res; 364 | vector path; 365 | backtrace(nums, res, path, 0); 366 | return res; 367 | } 368 | void backtrace(vector& nums, vector>& res, vector& path, int index ){ 369 | 370 | res.push_back(path); 371 | 372 | for (int i = index; i < nums.size(); i++){ 373 | path.push_back(nums[i]); 374 | backtrace(nums, res, path, i+1); 375 | path.pop_back(); 376 | } 377 | } 378 | }; 379 | ``` 380 | 381 | #### 90 子集II(medium) 382 | ```c++ 383 | 384 | class Solution { 385 | public: 386 | vector> subsetsWithDup(vector& nums) { 387 | // 同一层中不能有相同的元素, 排序去重 388 | sort(nums.begin(), nums.end()); 389 | vector> res; 390 | vector path; 391 | unordered_set visited; 392 | backtrace(nums, res, path, visited, 0); 393 | return res; 394 | } 395 | void backtrace(vector& nums, vector>& res, vector& path,unordered_set& visited, int index){ 396 | res.push_back(path); 397 | for (int i = index; i< nums.size(); i++){ 398 | if (i > 0 && nums[i] == nums[i-1] && visited.find(i - 1) == visited.end()) continue; 399 | visited.insert(i); 400 | path.push_back(nums[i]); 401 | backtrace(nums, res, path, visited, i + 1); 402 | path.pop_back(); 403 | visited.erase(i); 404 | } 405 | } 406 | }; 407 | ``` 408 | 409 | 410 | * **下边的几道搜索的问题,也是一类非常经典的回溯问题。** 411 | #### 79 单词搜索(medicum) 412 | ```c++ 413 | 414 | class Solution { 415 | public: 416 | bool exist(vector>& board, string word) { 417 | vector> offsets({ {-1, 0}, {0, 1}, {1, 0}, {0 ,-1} }); 418 | vector> visited(board.size(), vector(board[0].size(), 0)); 419 | 420 | for (int i = 0; i< board.size(); i++){ 421 | for (int j = 0; j< board[0].size(); j++){ 422 | if (board[i][j] == word[0]){ 423 | if (backtrace(board, word, visited, offsets, i, j, 1)) 424 | return true; 425 | } 426 | } 427 | } 428 | return false; 429 | } 430 | 431 | bool backtrace(vector>& board, string& word, vector>& visited, vector>& offsets, int x, int y, int index){ 432 | // if (board[x][y] != word[index]) return false; 433 | visited[x][y] = 1; 434 | 435 | if (index == word.size()) return true; 436 | 437 | for (auto off :offsets){ 438 | 439 | int new_x = x + off.first; 440 | int new_y = y + off.second; 441 | 442 | if (!inboard(board, new_x, new_y)) continue; 443 | 444 | if (visited[new_x][new_y] == 1) continue; 445 | 446 | if (board[new_x][new_y] != word[index]) continue; 447 | 448 | if (backtrace(board, word, visited, offsets, new_x, new_y, index + 1)) 449 | return true; 450 | } 451 | visited[x][y] = 0; 452 | return false; 453 | } 454 | bool inboard(vector>& board, int x, int y){ 455 | return (x >= 0 && x < board.size()) && ( y >= 0 && y < board[0].size()); 456 | } 457 | }; 458 | ``` 459 | 460 | #### 200 岛屿数量(medium) 461 | 462 | ```c++ 463 | class Solution { 464 | public: 465 | int numIslands(vector>& grid) { 466 | int m = grid.size(); 467 | int n = grid[0].size(); 468 | int res = 0; 469 | vector> offsets({ {-1, 0},{1, 0}, {0, 1}, {0, -1} }); 470 | for (int i = 0; i < m; i ++){ 471 | for (int j = 0; j < n; j++){ 472 | if (grid[i][j] == '1'){ 473 | res++; 474 | backtrace(grid, i, j, m, n, offsets); 475 | } 476 | } 477 | } 478 | return res; 479 | } 480 | void backtrace(vector>& grid, int x, int y, int m, int n, vector>& offsets){ 481 | grid[x][y] = '0'; 482 | for (auto off: offsets){ 483 | int new_x = x + off.first; 484 | int new_y = y + off.second; 485 | if (!inboard(m, n, new_x, new_y) || grid[new_x][new_y] == '0') continue; 486 | backtrace(grid, new_x, new_y, m, n, offsets); 487 | } 488 | } 489 | 490 | bool inboard(int m, int n, int x, int y){ 491 | return (x >= 0 && x < m) && (y >= 0 && y < n); 492 | } 493 | }; 494 | ``` 495 | 496 | 497 | #### 130 被围绕的区域(medium) 498 | ```c++ 499 | class Solution { 500 | public: 501 | void solve(vector>& board) { 502 | //找到与边界联通的O,标记为o,然后将剩余的O变为x,o变为O即可 503 | if (board.empty()) return ; 504 | vector> offsets({{1, 0},{-1, 0}, {0, 1}, {0, -1}}); 505 | int m = board.size(), n = board[0].size(); 506 | // 遍历边界 507 | for ( int i = 0; i< m; i++){ 508 | if (board[i][0] == 'O') 509 | backtrace(board, i, 0, offsets); 510 | if (board[i][n-1] == 'O') 511 | backtrace(board, i, n-1, offsets); 512 | } 513 | for (int j = 0; j < n; j++){ 514 | if (board[0][j] == 'O') 515 | backtrace(board, 0, j, offsets); 516 | if (board[m- 1][j] == 'O') 517 | backtrace(board, m-1, j, offsets); 518 | } 519 | // 替换内部的O 520 | for ( int i = 0;i< m; i++){ 521 | for (int j = 0; j < n; j++){ 522 | if (board[i][j] == 'O') 523 | board[i][j] = 'X'; 524 | } 525 | } 526 | // 将边界的o换回O 527 | for ( int i = 0;i< m; i++){ 528 | for (int j = 0; j < n; j++){ 529 | if (board[i][j] == 'o') 530 | board[i][j] = 'O'; 531 | } 532 | } 533 | } 534 | 535 | void backtrace(vector>& board, int x, int y, vector>& offsets){ 536 | board[x][y] = 'o'; 537 | for (auto off: offsets ){ 538 | int new_x = x + off.first; 539 | int new_y = y + off.second; 540 | if (! inboard(board, new_x, new_y) || board[new_x][new_y] != 'O') continue; 541 | backtrace(board, new_x, new_y, offsets); 542 | } 543 | } 544 | 545 | bool inboard(vector>& board, int x, int y){ 546 | return (x >= 0 && x < board.size()) && ( y >= 0 && y < board[0].size()); 547 | } 548 | }; 549 | ``` 550 | 551 | #### 417 太平洋大西洋水流问题(medium) 552 | 553 | ```c++ 554 | class Solution { 555 | public: 556 | vector> pacificAtlantic(vector>& matrix) { 557 | //使用两个数组分别表示能流到大西洋atlantic与太平洋pacific的位置 558 | if (matrix.empty()) return {}; 559 | int m = matrix.size(), n = matrix[0].size(); 560 | vector> atlantic(m, vector(n, false)); 561 | vector> pacific(m, vector(n, false)); 562 | 563 | vector> offsets({ {-1, 0},{0, -1},{1, 0},{0, 1}}); 564 | vector> res; 565 | 566 | for (int i = 0; i< m; i++){ 567 | if (! pacific[i][0]) 568 | backtrace(matrix, i, 0, pacific, offsets); 569 | if (! atlantic[i][n-1]) 570 | backtrace(matrix, i, n-1, atlantic, offsets); 571 | } 572 | for (int j = 0; j< n; j++){ 573 | if (! pacific[0][j]) 574 | backtrace(matrix, 0, j, pacific, offsets); 575 | if (! atlantic[m-1][j]) 576 | backtrace(matrix, m-1, j, atlantic, offsets); 577 | } 578 | for (int i =0; i>& matrix, int x, int y, vector>& ocean, vector>& offsets){ 588 | ocean[x][y] = true; 589 | for (auto off: offsets){ 590 | int new_x = x + off.first; 591 | int new_y = y + off.second; 592 | if (!inboard(matrix, new_x, new_y) || matrix[new_x][new_y] < matrix[x][y] || ocean[new_x][new_y]) continue; 593 | backtrace(matrix, new_x, new_y, ocean, offsets); 594 | } 595 | } 596 | 597 | bool inboard(vector>& matrix, int x, int y){ 598 | return (x >= 0 && x < matrix.size()) && ( y >= 0 && y < matrix[0].size()); 599 | } 600 | }; 601 | ``` 602 | 603 | #### 51 N皇后(hard) 604 | ```c++ 605 | class Solution { 606 | public: 607 | vector> queues; 608 | vector> solveNQueens(int n) { 609 | //主对角线i- j为常数,范围为1-n到n-1 610 | // 副对角线i+j为常数,范围为0到2*n-2 611 | vector diag(2*n-1, false); //主对角线,i - j + n - 1为其索引对应的对角线 612 | vector otherdiag(2*n-1, false); // 副对角线i + j 为其索引对应的对角线。 613 | vector queue(n, -1); //表示每行放置在哪一列。 614 | unordered_set cols; // 表示已经放置的列 615 | 616 | 617 | backtrace(n, diag, otherdiag, queue, cols, 0); 618 | 619 | return queues; 620 | 621 | } 622 | void backtrace(int n, vector& diag, vector& otherdiag, vector& queue, unordered_set cols, int row){ 623 | if (row == n){ 624 | queues.push_back(generateString(queue, n)); 625 | return ; 626 | } 627 | for (int j = 0; j < n; j++){ 628 | if (diag[row-j+n-1] || otherdiag[row+j] || cols.find(j) != cols.end()) continue; 629 | diag[row-j+n-1] = true; 630 | otherdiag[row+j] = true; 631 | cols.insert(j); 632 | queue[row] = j; 633 | backtrace(n, diag, otherdiag, queue, cols, row + 1); 634 | diag[row-j+n-1] = false; 635 | otherdiag[row+j] = false; 636 | queue[row] = -1; 637 | cols.erase(j); 638 | } 639 | } 640 | vector generateString(vector& queue, int n){ 641 | vector res(n, string(n, '.')); 642 | 643 | for (int i=0; i< n; i++) 644 | res[i][queue[i]] = 'Q'; 645 | 646 | return res; 647 | } 648 | }; 649 | 650 | ``` 651 | 652 | 653 | #### 52 N皇后 II(hard) 654 | 655 | ```c++ 656 | class Solution { 657 | public: 658 | int totalNQueens(int n) { 659 | vector diag(2*n - 1, false); // 主对角线是否占用, i-j+n-1为其主对角线的索引 660 | vector otherdiag(2*n-1, false); //副对角线,i+j为其索引 661 | unordered_set cols; // 保存已经被占用的列 662 | vector queue(n, -1); //表示每个元素分别放置在第j列的位置。 663 | int res = 0; 664 | backtrace(n, diag, otherdiag, cols, queue, res, 0); 665 | return res; 666 | } 667 | void backtrace(int n, vector& diag, vector& otherdiag, unordered_set& cols, vector& queue, int& res, int row){ 668 | if (row == n){ 669 | res++; 670 | return ; 671 | } 672 | // 第row行可以放的列的位置,遍历查找 673 | for (int i=0; i< n; i++){ 674 | if(diag[row - i + n - 1] || otherdiag[row + i] || cols.find(i) != cols.end()) continue; 675 | diag[row - i + n - 1 ] = true; 676 | otherdiag[row + i] = true; 677 | cols.insert(i); 678 | queue[row] = i; 679 | backtrace(n, diag, otherdiag, cols, queue, res, row+1); 680 | diag[row - i + n - 1 ] = false; 681 | otherdiag[row + i] = false; 682 | cols.erase(i); 683 | queue[row] = -1; 684 | } 685 | } 686 | }; 687 | ``` 688 | 689 | 690 | #### 37 解数独(hard) 691 | 692 | 693 | 694 | ```c++ 695 | class Solution { 696 | public: 697 | bool valid = false; 698 | void solveSudoku(vector>& board) { 699 | vector> rows(9, unordered_set()); //每行出现过的数字 700 | vector> cols(9, unordered_set()); //每列出现过的数字 701 | vector< unordered_set > grids(9, unordered_set()); // 第i个3x3的格子出现过的数字,其索引为(i/3)*3 + j/3 702 | vector> spaces; 703 | for ( int i = 0; i <9; i++){ 704 | for (int j = 0; j < 9; j++){ 705 | if (board[i][j] == '.') 706 | spaces.push_back(make_pair(i, j)); 707 | else{ 708 | int digit = int(board[i][j] - '0'); 709 | rows[i].insert(digit); 710 | cols[j].insert(digit); 711 | grids[(i/3)*3+j/3].insert(digit); 712 | } 713 | } 714 | } 715 | backtrace(board, rows, cols, grids, spaces, 0); 716 | return; 717 | } 718 | void backtrace(vector>& board, vector>& rows, vector>& cols, vector>& grids, vector>& spaces, int index){ 719 | if ( index == spaces.size()) { 720 | valid = true; 721 | return ; 722 | } 723 | int i = spaces[index].first; 724 | int j = spaces[index].second; 725 | // 放置 k 这个数字 726 | for (int k = 1; k <= 9; k++){ 727 | if (valid) break; 728 | if ( rows[i].find(k) != rows[i].end() || cols[j].find(k) != cols[j].end() || grids[(i/3)*3+j/3] .find(k) != grids[(i/3)*3+j/3].end() ) 729 | continue; 730 | rows[i].insert(k); 731 | cols[j].insert(k); 732 | grids[(i/3)*3+j/3].insert(k); 733 | board[i][j] = (char) ('0' + k); 734 | backtrace(board, rows, cols, grids, spaces, index+1); 735 | rows[i].erase(k); 736 | cols[j].erase(k); 737 | grids[(i/3)*3+j/3].erase(k); 738 | // board[i][j] = '.'; 739 | } 740 | } 741 | }; 742 | ``` 743 | 744 | 745 | 746 | 747 | 748 | ## 更多分类刷题资料 749 | 750 | * 微信公众号: 小哲AI 751 | 752 | ![wechat_QRcode](https://img-blog.csdnimg.cn/20210104185413204.jpg) 753 | 754 | * GitHub地址: [https://github.com/lxztju/leetcode-algorithm](https://github.com/lxztju/leetcode-algorithm) 755 | * csdn博客: [https://blog.csdn.net/lxztju](https://blog.csdn.net/lxztju) 756 | * 知乎专栏: [小哲AI](https://www.zhihu.com/column/c_1101089619118026752) 757 | * AI研习社专栏:[小哲AI](https://www.yanxishe.com/column/109) 758 | 759 | -------------------------------------------------------------------------------- /leetcode算法之图.md: -------------------------------------------------------------------------------- 1 | 今天来盘一盘 **图 ** 这类题目 2 | 3 | 这类题目对我来说也是比较难的,比较难搞。 4 | 5 | ``` 6 | 有向无环图的拓扑排序(BFS, DFS) 7 | 8 | 利用并查集处理连通域问题 9 | ``` 10 | 11 | 使用**python**刷题分类整理的笔记,请参考: [https://github.com/lxztju/leetcode-algorithm/tree/v1](https://github.com/lxztju/leetcode-algorithm/tree/v1) 12 | 13 | ## 图 14 | 15 | ### BFS与DFS 16 | 17 | * 785 判断二分图 (Medium) 18 | * 207 课程表 (Medium) 19 | * 210 课程表 II (Medium) 20 | 21 | ### 并查集 22 | 23 | 并查集是一种树型的数据结构,主要用于处理连通域的问题,用于处理一些不交集的合并及查询问题。主要的操作有如下的步骤: 24 | 25 | Find:确定元素属于哪一个子集。它可以被用来确定两个元素是否属于同一子集。 26 | Union:将两个子集合并成同一个集合。 27 | 28 | * 684 冗余连接 (Medium) 29 | 30 | 31 | ### BFS与DFS 32 | 33 | 34 | 35 | #### 785 判断二分图 (Medium) 36 | 使用染色法,0代表一种颜色,1代表另外一种颜色, -1表示未染色,相邻的元素不能染成同样的颜色。 37 | 38 | * DFS 39 | 40 | ```c++ 41 | class Solution { 42 | public: 43 | bool isBipartite(vector>& graph) { 44 | int n = graph.size(); 45 | vector colors(n, -1); 46 | for (int i = 0; i< n; i++){ 47 | if (colors[i] == -1){ 48 | if (!dfs(graph, colors, i, 0)) 49 | return false; 50 | } 51 | } 52 | return true; 53 | } 54 | 55 | bool dfs(vector>& graph, vector& colors, int index, int c){ 56 | 57 | colors[index] = c; 58 | for (int i = 0; i < graph[index].size(); i++){ 59 | int node = graph[index][i]; 60 | if (colors[node] != -1){ 61 | if (colors[node] == c) return false; 62 | else continue; 63 | } 64 | else 65 | if ( ! dfs(graph, colors, node, 1-c)) 66 | return false; 67 | } 68 | return true; 69 | } 70 | }; 71 | ``` 72 | 73 | * BFS 74 | 75 | ```c++ 76 | class Solution { 77 | public: 78 | bool isBipartite(vector>& graph) { 79 | int n = graph.size(); 80 | vector colors(n, -1); 81 | queue> q; 82 | for (int i = 0; i>& prerequisites) { 120 | // 不能有环, dfs 121 | vector visited(numCourses, -1); // -1表示没有访问过, 0表示被其他节点访问过,1表示被本节点访问过 122 | vector> edges(numCourses, vector()); 123 | 124 | // 构建邻接表 125 | for (int i = 0; i < prerequisites.size(); i++){ 126 | int start = prerequisites[i][1]; 127 | int end = prerequisites[i][0]; 128 | // cout<>& edges, vector& visited,int index){ 141 | 142 | if (visited[index] == 1) return false; 143 | if (visited[index] == 0) return true; 144 | visited[index] = 1; 145 | for (auto node : edges[index]){ 146 | if (! dfs(edges, visited, node)) 147 | return false; 148 | } 149 | visited[index] = 0; 150 | return true; 151 | } 152 | }; 153 | ``` 154 | 155 | 156 | * BFS(入度表) 157 | 158 | 159 | ```c++ 160 | /* 161 | 1. 转换为邻接表并生成入度表 162 | 2. 将入度为0的节点入队。 163 | 3. 遍历整个队列,如果队列不为空,将队首出列,并将邻接节点的入度减一,将入度为0的节点重新入度。 164 | */ 165 | class Solution { 166 | public: 167 | bool canFinish(int numCourses, vector>& prerequisites) { 168 | // 不能有环, dfs 169 | vector indegrees(numCourses, 0); 170 | vector> edges(numCourses, vector()); 171 | 172 | // 构建邻接表与入度表 173 | for (int i = 0; i < prerequisites.size(); i++){ 174 | int start = prerequisites[i][1]; 175 | int end = prerequisites[i][0]; 176 | edges[start].push_back(end); 177 | indegrees[end]++; 178 | } 179 | queue q; 180 | // 将入度为0的节点入队 181 | for (int i = 0; i < numCourses; i++){ 182 | if (indegrees[i] == 0) 183 | q.push(i); 184 | } 185 | while (!q.empty()){ 186 | int node = q.front(); 187 | q.pop(); 188 | // 将邻接的节点入度数减一,并将入度为0的节点入队 189 | for (auto j : edges[node]){ 190 | indegrees[j]--; 191 | if (indegrees[j] == 0) 192 | q.push(j); 193 | } 194 | } 195 | // 如果有的节点入度数不为0,那么说明存在环 196 | for (auto i: indegrees){ 197 | if (i != 0) return false; 198 | } 199 | return true; 200 | } 201 | }; 202 | ``` 203 | 204 | 205 | 206 | 207 | #### 210 课程表 II (Medium) 208 | 209 | * DFS 210 | 211 | ```c++ 212 | 213 | class Solution { 214 | public: 215 | vector findOrder(int numCourses, vector>& prerequisites) { 216 | vector> edges(numCourses, vector()); 217 | for (int i= 0; i < prerequisites.size(); i++){ 218 | int start = prerequisites[i][1]; 219 | int end = prerequisites[i][0]; 220 | edges[start].push_back(end); 221 | } 222 | 223 | // for(auto p: edges){ 224 | // for (auto v:p) 225 | // cout< visited(numCourses, -1); // -1表示没有访问过, 0表示被其他节点访问过,1表示被本节点访问过 230 | vector res; 231 | for( int i = 0; i < numCourses; i++){ 232 | if (visited[i] != -1) continue; 233 | if (! dfs(edges, visited, res, i)) 234 | return {}; 235 | } 236 | reverse(res.begin(), res.end()); 237 | return res; 238 | } 239 | 240 | bool dfs(vector>& edges, vector& visited, vector& res, int index){ 241 | 242 | if (visited[index] == 1) return false; 243 | if (visited[index] == 0) return true; 244 | visited[index] = 1; 245 | for (auto node : edges[index]){ 246 | if (!dfs(edges, visited, res, node)) 247 | return false; 248 | } 249 | res.push_back(index); 250 | visited[index] = 0; 251 | return true; 252 | } 253 | }; 254 | ``` 255 | 256 | * BFS 257 | 258 | ```c++ 259 | class Solution { 260 | public: 261 | vector findOrder(int numCourses, vector>& prerequisites) { 262 | vector> edges(numCourses, vector()); 263 | vector indegress(numCourses, 0); 264 | // 构建邻接表,入度表 265 | for (int i= 0; i < prerequisites.size(); i++){ 266 | int start = prerequisites[i][1]; 267 | int end = prerequisites[i][0]; 268 | edges[start].push_back(end); 269 | indegress[end]++; 270 | } 271 | 272 | queue q; 273 | vector res; 274 | // 将入度为0的节点入队 275 | for (int i = 0; i < numCourses; i++){ 276 | if (indegress[i] == 0) 277 | q.push(i); 278 | } 279 | // 遍历队列 280 | while (!q.empty()){ 281 | int node = q.front(); 282 | res.push_back(node); 283 | q.pop(); 284 | // 将邻接节点的入度数减一,并将入度为0的节点入队。 285 | for (auto i : edges[node]){ 286 | indegress[i]--; 287 | if (indegress[i] == 0) 288 | q.push(i); 289 | } 290 | } 291 | // 如果存在入度不为0的节点,说明存在一个环 292 | for (auto i: indegress){ 293 | if ( i != 0) 294 | return {}; 295 | } 296 | return res; 297 | } 298 | }; 299 | ``` 300 | 301 | 302 | ### 并查集 303 | 304 | #### 684 冗余连接 (Medium) 305 | 经典的并查集的套路代码。 306 | 307 | ```c++ 308 | class Solution { 309 | public: 310 | int find(vector&parent, int index){ 311 | // 查找一个给定节点的父亲节点。递归的操作。 312 | if (parent[index] != index) 313 | parent[index] = find(parent, parent[index]); 314 | return parent[index]; 315 | } 316 | 317 | void Union(vector& parent, int index1, int index2){ 318 | // index2的父亲节点转换为index1与index2共同的父亲节点。 319 | parent[find(parent,index1)] = find(parent,index2); 320 | } 321 | 322 | vector findRedundantConnection(vector>& edges) { 323 | int n = edges.size(); 324 | vector parent(n+1, 0); 325 | // 初始化的情况下,每个节点的父亲节点都是自身 326 | for (int i = 1; i< n+1; i++){ 327 | parent[i] = i; 328 | } 329 | 330 | for (auto edge: edges){ 331 | int node1 = edge[0], node2 = edge[1]; 332 | // 对于两个节点,如果其父亲节点不一样就融合这两个节点 333 | if (find(parent, node1) != find(parent, node2)) 334 | Union(parent, node1, node2); 335 | else 336 | return edge; 337 | } 338 | return {}; 339 | } 340 | }; 341 | 342 | ``` 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | ## 更多分类刷题资料 355 | 356 | * 微信公众号: 小哲AI 357 | 358 | ![wechat_QRcode](https://img-blog.csdnimg.cn/20210104185413204.jpg) 359 | 360 | * GitHub地址: [https://github.com/lxztju/leetcode-algorithm](https://github.com/lxztju/leetcode-algorithm) 361 | * csdn博客: [https://blog.csdn.net/lxztju](https://blog.csdn.net/lxztju) 362 | * 知乎专栏: [https://www.zhihu.com/column/c_1101089619118026752](https://www.zhihu.com/column/c_1101089619118026752) 363 | * AI研习社专栏:[https://www.yanxishe.com/column/109](https://www.yanxishe.com/column/109) 364 | 365 | -------------------------------------------------------------------------------- /leetcode算法之数组.md: -------------------------------------------------------------------------------- 1 | 明年就是找工作了,又要开始刷题了,把之前做过的题目再梳理整理一遍,但愿明年不要那么拉跨,祈祷明年能找到工作,千万不能毕业就失业。 2 | 3 | 分类别解析leetcode上的一些相关的例题路,代码采用**C++与python**实现。 4 | 5 | 所有的题目均来自leetcode官网. 6 | 7 | ## 数组 8 | 9 | 涉及的一些leetcode问题: 10 | 11 | * 283 Move Zeroes (Easy) 12 | * 566 Reshape the Matrix (Easy) 13 | * 485 Max Consecutive Ones (Easy) 14 | * 240 Search a 2D Matrix II (Medium) 15 | * 378 Kth Smallest Element in a Sorted Matrix ((Medium)) 16 | * 645 Set Mismatch (Easy) 17 | * 287 Find the Duplicate Number (Medium) 18 | * 697 Degree of an Array (Easy) 19 | * 766 Toeplitz Matrix (Easy) 20 | * 565 Array Nesting (Medium) 21 | 22 | 23 | #### 283 Move Zeroes (Easy) 24 | 25 | > 给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。 26 | > 27 | > 说明: 28 | > 29 | > 必须在原数组上操作,不能拷贝额外的数组。 30 | > 尽量减少操作次数。 31 | 32 | * 第一个直观的想法: 33 | 使用额外的数组,直接遍历一遍原始数组,然后将非零的复制到前半部分,在后边按照数组补零即可. 34 | 35 | ```c++ 36 | class Solution { 37 | public: 38 | void moveZeroes(vector& nums) { 39 | // 新建一个vector,保存移动后的vector 40 | vector res(nums.size(), 0); 41 | int j = 0; 42 | // 遍历原始数组,修改res中的元素 43 | for (int i=0; i < nums.size(); i++){ 44 | if (nums[i]!=0){ 45 | res[j] = nums[i]; 46 | j++; 47 | } 48 | } 49 | // 将res再赋值给原始数组 50 | nums.assign(res.begin(), res.end()); 51 | } 52 | }; 53 | ``` 54 | 55 | * 题目要求不能使用额外的数组. 56 | 就只能使用双指针在原始数组上操作,左指针指向交换完成的元素,右指针向后扫描查找交换元素 57 | 58 | ```c++ 59 | class Solution { 60 | public: 61 | void moveZeroes(vector& nums) { 62 | // 双指针 63 | int l=0, r=0; 64 | 65 | while (r < nums.size()){ 66 | if (nums[r] != 0){ 67 | swap(nums[r], nums[l]); 68 | l++; 69 | } 70 | r++; 71 | } 72 | } 73 | }; 74 | ``` 75 | 76 | 77 | #### 566 Reshape the Matrix (Easy) 78 | 题目较简单,直接附上代码,遍历即可 79 | 80 | ```c++ 81 | class Solution { 82 | public: 83 | vector> matrixReshape(vector>& nums, int r, int c) { 84 | // 直接遍历即可 85 | int m=nums.size(), n=nums[0].size(); 86 | if (m*n != r*c) return nums; 87 | vector> res(r, vector(c, 0)); //保存最终的结果 88 | for (int i = 0; i < m; i++){ 89 | for (int j = 0; j < n; j++){ 90 | int index = i*n + j; 91 | res[index/c][index%c] = nums[i][j]; 92 | } 93 | } 94 | return res; 95 | } 96 | }; 97 | ``` 98 | 99 | #### 485 Max Consecutive Ones (Easy) 100 | ```c++ 101 | class Solution { 102 | public: 103 | int findMaxConsecutiveOnes(vector& nums) { 104 | //直接遍历 105 | int res = 0; 106 | int i = 0; 107 | while (i < nums.size()){ 108 | int count = 0; 109 | while (i < nums.size() && nums[i] == 1){ 110 | count++; 111 | i++; 112 | } 113 | res = max(res, count); 114 | i++; 115 | } 116 | return res; 117 | } 118 | }; 119 | ``` 120 | 121 | #### 240 Search a 2D Matrix II (Medium) 122 | 123 | >编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性: 124 | > 125 | > 每行的元素从左到右升序排列。 126 | > 每列的元素从上到下升序排列。 127 | 128 | * 最直观的想法, 直接遍历二维数组进行对比,这就变成了一道简单题,没有用到题目当中的条件 129 | 130 | * 从右上角开始遍历,因为这样遍历,比target小的在左边,比target大的在下边 131 | 132 | ```c++ 133 | class Solution { 134 | public: 135 | bool searchMatrix(vector>& matrix, int target) { 136 | // 从右上角开始遍历 137 | int m = matrix.size(), n = matrix[0].size(); 138 | int i = 0, j = n-1; 139 | while (i >= 0 && i < m && j >= 0 && j < n){ 140 | if (matrix[i][j] == target) 141 | return true; 142 | else if (matrix[i][j] > target) 143 | j--; 144 | else 145 | i++; 146 | } 147 | return false; 148 | } 149 | }; 150 | ``` 151 | 152 | 153 | #### 378 Kth Smallest Element in a Sorted Matrix ((Medium)) 154 | > 给定一个 n x n 矩阵,其中每行和每列元素均按升序排序,找到矩阵中第 k 小的元素。 155 | 156 | * 第一个思路: 直接将其变为一维数组,然后直接进行排序,找到第k小的元素. 157 | 158 | ```c++ 159 | class Solution { 160 | public: 161 | int kthSmallest(vector>& matrix, int k) { 162 | int m = matrix.size(), n = matrix[0].size(); 163 | vector res(m*n, 0); 164 | int index = 0; 165 | for (int i = 0; i < m; i++){ 166 | for (int j = 0; j < n; j++){ 167 | res[index] = matrix[i][j]; 168 | index++; 169 | } 170 | } 171 | sort(res.begin(), res.end()); 172 | return res[k-1]; 173 | } 174 | }; 175 | ``` 176 | 177 | 178 | 179 | * 第二个思路,利用优先级队列. 180 | * 181 | 将所有的元素全部插入优先级队列中,然后找到第k个 182 | ```c++ 183 | class point{ 184 | public: 185 | int val, x, y; 186 | point()=default; 187 | point(int val, int x, int y): val(val), x(x), y(y) {} 188 | bool operator > (const point& a) const { 189 | return this->val > a.val; 190 | } 191 | }; 192 | 193 | class Solution { 194 | public: 195 | int kthSmallest(vector>& matrix, int k) { 196 | //按行进行归并排序 197 | //定义优先级队列,这个队列采用vector实现 198 | priority_queue, greater> q; 199 | int n = matrix.size(); 200 | for (int i = 0; i < n; i++) { 201 | for (int j = 0; j < n; j++) 202 | q.emplace(point(matrix[i][j], i, j)); 203 | } 204 | point res; 205 | for (int i = 0; i < k - 1; i++) { 206 | res = q.top(); 207 | q.pop(); 208 | } 209 | return q.top().val; 210 | } 211 | }; 212 | ``` 213 | 214 | 插入的时候不需要将所有的元素均插入,按照其升序的特点,只需要按行插入右侧元素即可,找到最小的k个 215 | 216 | ```c++ 217 | class point{ 218 | public: 219 | int val, x, y; 220 | point()=default; 221 | point(int val, int x, int y): val(val), x(x), y(y) {} 222 | bool operator > (const point& a) const { 223 | return this->val > a.val; 224 | } 225 | }; 226 | 227 | class Solution { 228 | public: 229 | int kthSmallest(vector>& matrix, int k) { 230 | //按行进行归并排序 231 | //定义优先级队列,这个队列采用vector实现 232 | priority_queue, greater> q; 233 | int n = matrix.size(); 234 | for (int i = 0; i < n; i++) { 235 | q.emplace(point(matrix[i][0], i, 0)); 236 | } 237 | for (int i = 0; i < k - 1; i++) { 238 | point now = q.top(); 239 | q.pop(); 240 | if (now.y != n - 1) { 241 | q.emplace(matrix[now.x][now.y + 1], now.x, now.y + 1); 242 | } 243 | } 244 | return q.top().val; 245 | } 246 | }; 247 | ``` 248 | 249 | #### 645 Set Mismatch (Easy) 250 | 251 | > 集合 S 包含从1到 n的整数。不幸的是,因为数据错误,导致集合里面某一个元素复制了成了集合里面的另外一个元素的值,导致集合丢失了一个整数并且有一个元素重复。 252 | > 253 | > 给定一个数组 nums 代表了集合 S 发生错误后的结果。你的任务是首先寻找到重复出现的整数,再找到丢失的整数,将它们以数组的形式返回。 254 | 255 | 非常简单的题目, 直接使用哈希表即可,找出哪一个数字已经出现过,然后查找那个数字没有出现过,共遍历两次. 256 | 257 | ```c++ 258 | class Solution { 259 | public: 260 | vector findErrorNums(vector& nums) { 261 | vector res; 262 | unordered_set hashset; 263 | for (auto c : nums){ 264 | if (hashset.find(c) == hashset.end()) 265 | hashset.insert(c); 266 | else 267 | res.push_back(c); 268 | } 269 | for (int i=1; i <= nums.size(); i++){ 270 | if (hashset.find(i) == hashset.end()) 271 | res.push_back(i); 272 | } 273 | return res; 274 | } 275 | }; 276 | ``` 277 | 278 | 279 | #### 287 Find the Duplicate Number (Medium) 280 | 281 | > 给定一个包含 n + 1 个整数的数组 nums,其数字都在 1 到 n 之间(包括 1 和n), 282 | > 可知至少存在一个重复的整数。假设只有一个重复的整数,找出这个重复的数。 283 | 284 | 直观的几种想法: 285 | * 直接遍历,两重循环,每次查找元素nums[i]是否在nums[i+1:]的范围内出现,时间复杂度n2,不满足题意 286 | * hash表存储访问过的元素,空间复杂度o(n),不满足题意 287 | * 先排序,然后遍历相邻的相同元素即为重复元素,不满足"不能更改原数组"的要求. 288 | 289 | 这道题的限制条件,直接堵死了常用的几种直观的思路. 290 | 291 | 这里采用**二分查找**来解决这个问题. 292 | 293 | > 题解: 294 | > 1. 这道题的数字的范围均在1~n之间,且只有一个重复的数.举个例子就知道 295 | > 例如 [1,3,4,2,2], n=4,因此设置mid = (1+4) / 2=2, 那么小于等于2的数字有3个大于选定的mid,因此重复的数字就肯定小于等于mid,否则重复的数字大于mid 296 | 297 | ```c++ 298 | class Solution { 299 | public: 300 | int findDuplicate(vector& nums) { 301 | int n = nums.size()-1; 302 | int l= 0, r = n; 303 | // 二分法查找区间 304 | while (l < r){ 305 | int mid = l + (r - l) / 2; 306 | int cnt = 0; 307 | // 统计小于等于mid的数字的数目 308 | for (auto num :nums){ 309 | if (num <= mid) 310 | cnt++; 311 | } 312 | // 如果统计的数字大于mid,那么最终重复的数字位于左侧 313 | if (cnt > mid) 314 | r = mid; 315 | else 316 | l = mid+1; 317 | } 318 | return l; 319 | } 320 | }; 321 | ``` 322 | 323 | #### 697 Degree of an Array (Easy) 324 | 325 | > 给定一个非空且只包含非负数的整数数组 nums, 数组的度的定义是指数组里任一元素出现频数的最大值。 326 | > 327 | > 你的任务是找到与 nums 拥有相同大小的度的最短连续子数组,返回其长度。 328 | 329 | 简单的题目,但是注意频数最大的数组元素不一定只有一个 330 | 331 | ```c++ 332 | #include 333 | 334 | class Solution { 335 | public: 336 | int findShortestSubArray(vector& nums) { 337 | // 使用无序映射表来存储每个元素在数组中出现的索引列表 338 | unordered_map> res; 339 | for (auto e : nums){ 340 | res.insert(make_pair(e, vector())); 341 | } 342 | 343 | for (int i=0; i index = e->second; 352 | int dis = index.size(); 353 | max_ferq = max(max_ferq, dis); 354 | } 355 | //统计最小的连续子数组 356 | for (auto e=res.cbegin(); e != res.end(); e++){ 357 | vector index = e->second; 358 | if (index.size() == max_ferq){ 359 | min_dis = min(min_dis, index[index.size()-1] - index[0] + 1); 360 | } 361 | } 362 | return min_dis; 363 | } 364 | }; 365 | ``` 366 | 367 | 368 | #### 766 Toeplitz Matrix (Easy) 369 | 370 | > 如果矩阵上每一条由左上到右下的对角线上的元素都相同,那么这个矩阵是 托普利茨矩阵 。 371 | > 372 | > 给定一个 M x N 的矩阵,当且仅当它是托普利茨矩阵时返回 True。 373 | 374 | 简单的题目, 仅仅需要发现`同一条对角线上的横纵坐标差值为常数(在做八皇后问题也会遇到)`就可以了,然后借助map可以轻松求解. 375 | 376 | ```c++ 377 | class Solution { 378 | public: 379 | bool isToeplitzMatrix(vector>& matrix) { 380 | // 利用哈希表来,键为行列坐标的差值, 值为对应的数字 381 | unordered_map indexdiff_value; 382 | for (int i = 0; i < matrix.size(); i++){ 383 | for (int j = 0; j < matrix[0].size(); j++){ 384 | if (indexdiff_value.find(i - j) == indexdiff_value.end()) 385 | indexdiff_value.insert(make_pair(i-j, matrix[i][j])); 386 | else{ 387 | if (matrix[i][j] != indexdiff_value[i-j]) 388 | return false; 389 | } 390 | } 391 | } 392 | return true; 393 | } 394 | }; 395 | ``` 396 | 397 | #### 565 Array Nesting (Medium) 398 | 399 | > 索引从0开始长度为N的数组A,包含0到N - 1的所有整数。找到最大的集合S并返回其大小,其中 S[i] = {A[i], A[A[i]], 400 | > A[A[A[i]]], ... }且遵守以下的规则。 401 | > 402 | > 假设选择索引为i的元素A[i]为S的第一个元素,S的下一个元素应该是A[A[i]],之后是A[A[A[i]]]... 403 | > 以此类推,不断添加直到S出现重复的元素。 404 | 405 | 看似复杂,其实只需要将所有的 这种嵌套的子列全部查找出来,并找到最长的满足条件的最长的长度即可. 406 | 407 | > 例如,对于[5,4,0,3,1,6,2]这个序列,其满足条件的S分别为: [5, 6, 0, 2], [4,1]与[3]这三个从中找到最短的即可. 408 | 409 | 基本办法就是遍历+ hash表去重 410 | 411 | ```c++ 412 | class Solution { 413 | public: 414 | int arrayNesting(vector& nums) { 415 | //利用哈希表记录访问过的元素,避免重复访问. 416 | unordered_set visited; 417 | 418 | int res = 0; // 最终的结果,最长的S 419 | // 遍历,找到所有的字串 420 | for (int i = 0; i < nums.size(); i++){ 421 | if( visited.find(nums[i]) != visited.end() ) continue; 422 | 423 | int length = 1; // S的长度 424 | // 开始查找一个满足条件的S. 425 | int index = i; 426 | while (nums[index] != i){ 427 | visited.insert(nums[index]); 428 | index = nums[index]; 429 | length++; 430 | } 431 | visited.insert(nums[i]); 432 | res = max(res, length); 433 | } 434 | return res; 435 | } 436 | }; 437 | ``` 438 | 439 | 440 | ## 更多笔记资料 441 | 442 | * 微信公众号: 小哲AI 443 | 444 | ![wechat_QRcode](images/wechat_QRcode.jpg) 445 | * GitHub地址: [https://github.com/lxztju/leetcode-algorithm](https://github.com/lxztju/leetcode-algorithm) 446 | * csdn博客: [小哲AI](https://blog.csdn.net/lxztju) 447 | * 知乎专栏: [小哲AI](https://www.zhihu.com/column/c_1101089619118026752) 448 | * AI研习社专栏:[小哲AI](https://www.yanxishe.com/column/109) 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | -------------------------------------------------------------------------------- /leetcode算法之栈和队列.md: -------------------------------------------------------------------------------- 1 | 今天来盘一盘 **栈以及队列 ** 这两类题目 2 | 3 | 4 | 5 | 使用**python**刷题分类整理的笔记,请参考: [https://github.com/lxztju/leetcode-algorithm/tree/v1](https://github.com/lxztju/leetcode-algorithm/tree/v1) 6 | 7 | ## 栈和队列 8 | 9 | > **栈: 先进先出 10 | > 队列: 先进后出** 11 | 12 | 利用这两类数据结构的特性解题. 13 | 14 | 其中一类非常经典的题目是: **单调栈(leetcode496题, 经典题目)**. 15 | 16 | * **单调递减栈**, 是指针对每一个待遍历的元素x, 将x与栈顶元素相比, 如果大于栈顶元素将栈顶元素出栈, 重新循环对比,直到小于栈顶元素,然后将x入栈. 17 | * **单调递增栈**: 同理分析 18 | 19 | --- 20 | 21 | * 232 用栈实现队列 (Easy) 22 | * 225 用队列实现栈 (Easy) 23 | * 155 最小栈 (Easy) 24 | * 20 有效的括号 (Easy) 25 | * 1021 删除最外层的括号 (easy) 26 | * 496 下一个更大元素 I (easy) 27 | * 503 下一个更大元素 II (Medium) 28 | * 739 每日温度 (Medium) 29 | 30 | 31 | 32 | #### 232 用栈实现队列 (Easy) 33 | * 题解: 两个栈实现 34 | * 栈的特点时先进后出, 而队列是先进先出 35 | * 使用两个栈实现先进先出 36 | * stackin控制入队, stackout控制出队 37 | * 当入队时,直接压入stackin即可 38 | * 出队时, 当stackout为空时,将stackin中的元素依次弹出压入stackout中, 然后执行stackout的出栈也就是出队操作. 39 | 40 | * 示例 41 | 先入队[1,2], 然后执行一次出队, 再入队[3,4], 然后执行两次出队 42 | 43 | 1. 入队之后stackin为[1,2], stackout为空 44 | 2. 执行出队时,将stackin中元素依次压入stackout中, 此时stackout为[2,1], 出队1, stackout为[2], stackin为空 45 | 3. 再次入队, stackin为[3,4], 此时stackout为[2] 46 | 4. 执行第一次出队时, stackout非空直接出队2, 此时stackin为[3,4], stackout为空 47 | 5. 当再次执行出队时, stackout为空,与第二步相同. 48 | 49 | ```c++ 50 | class MyQueue { 51 | public: 52 | stack stackin; 53 | stack stackout; 54 | /** Initialize your data structure here. */ 55 | MyQueue() { 56 | 57 | } 58 | 59 | /** Push element x to the back of queue. */ 60 | void push(int x) { 61 | stackin.push(x); 62 | } 63 | 64 | /** Removes the element from in front of queue and returns that element. */ 65 | int pop() { 66 | // 如果stackout为空,将stackin中的元素依次放入stackout 67 | if(stackout.empty()) 68 | while (!stackin.empty() ){ 69 | int tmp = stackin.top(); 70 | stackin.pop(); 71 | stackout.push(tmp); 72 | } 73 | // 返回stackout的栈顶元素 74 | int tmp = stackout.top(); 75 | stackout.pop(); 76 | return tmp; 77 | } 78 | 79 | /** Get the front element. */ 80 | int peek() { 81 | // 如果stackout为空,将stackin中的元素依次放入stackout 82 | if(stackout.empty()) 83 | while (!stackin.empty() ){ 84 | int tmp = stackin.top(); 85 | stackin.pop(); 86 | stackout.push(tmp); 87 | } 88 | // 返回stackout的栈顶元素 89 | return stackout.top(); 90 | } 91 | 92 | /** Returns whether the queue is empty. */ 93 | bool empty() { 94 | if (stackin.empty() && stackout.empty()) 95 | return true; 96 | else 97 | return false; 98 | } 99 | }; 100 | ``` 101 | 102 | 103 | #### 225 用队列实现栈 (Easy) 104 | 105 | * 题解: 两个队列实现 106 | * 栈是先进后出的, 队列是先进先出的. 107 | * 使用队列实现栈就是要 将入队的元素,放置在队首. 108 | * 这样出栈时, 直接使用队列出队实现. 109 | 110 | * q1存储栈中的元素, q2作为入栈时的辅助栈 111 | * 入栈时,先将元素入队到q2, 然后将q1中的元素出队并放入q2中, 此时q2队首的元素即为栈顶元素, 将q1与q2互换, q2始终作为辅助栈. 112 | 113 | ```c++ 114 | class MyStack { 115 | public: 116 | /** Initialize your data structure here. */ 117 | queue q1; 118 | queue q2; 119 | MyStack() { 120 | } 121 | 122 | /** Push element x onto stack. */ 123 | void push(int x) { 124 | q2.push(x); 125 | while(!q1.empty()){ 126 | q2.push(q1.front()); 127 | q1.pop(); 128 | } 129 | swap(q1, q2); 130 | } 131 | 132 | /** Removes the element on top of the stack and returns that element. */ 133 | int pop() { 134 | int tmp = q1.front(); 135 | q1.pop(); 136 | return tmp; 137 | } 138 | 139 | /** Get the top element. */ 140 | int top() { 141 | return q1.front(); 142 | } 143 | 144 | /** Returns whether the stack is empty. */ 145 | bool empty() { 146 | return q1.empty(); 147 | } 148 | }; 149 | ``` 150 | 151 | #### 155 最小栈 (Easy) 152 | 153 | * 题解: 辅助栈 154 | * 采用另外的一个辅助栈s2 155 | * 每次s1入栈出栈时,均在s2的对应位置存入此时对应的最小值 156 | * 对于辅助栈s2的操作, 对于一个待入栈的元素x, 如果其大于s2的栈顶,就在s2中再次压入栈顶元素, 否则压入x 157 | 158 | 159 | ```c++ 160 | class MinStack { 161 | public: 162 | stack s1; 163 | stack s2; // 辅助栈 164 | /** initialize your data structure here. */ 165 | 166 | MinStack() { 167 | } 168 | 169 | void push(int x) { 170 | s1.push(x); 171 | if (s2.empty()) 172 | s2.push(x); 173 | else{ 174 | int tmp = s2.top(); 175 | if (x <= tmp) 176 | s2.push(x); 177 | else 178 | s2.push(tmp); 179 | } 180 | } 181 | 182 | void pop() { 183 | s1.pop(); 184 | s2.pop(); 185 | } 186 | 187 | int top() { 188 | return s1.top(); 189 | } 190 | 191 | int getMin() { 192 | return s2.top(); 193 | } 194 | }; 195 | ``` 196 | 197 | #### 20 有效的括号 (Easy) 198 | * 题解 199 | * 最经典的一道使用栈的题目 200 | * 遇到左括号直接入栈, 遇到右括号,左括号出栈对比 201 | 202 | ```c++ 203 | class Solution { 204 | public: 205 | bool isValid(string s) { 206 | unordered_map parentheses = {{'(', ')'}, {'[', ']'}, {'{', '}'}}; // 字典 207 | stack stack1; 208 | for (auto s1: s){ 209 | // s1为右括号 210 | if (parentheses.find(s1) == parentheses.end()){ 211 | // 如果栈为空, 返回false 212 | if (stack1.empty()) return false; 213 | else{ 214 | char tmp = stack1.top(); 215 | stack1.pop(); 216 | // 如果栈顶元素与s1不对应, 返回false 217 | if (parentheses[tmp] != s1) return false; 218 | } 219 | } 220 | // s1为左括号, 入栈 221 | else{ 222 | stack1.push(s1); 223 | } 224 | } 225 | //最后栈为空返回true, 否则返回false. 226 | if (!stack1.empty()) return false; 227 | else return true; 228 | } 229 | }; 230 | ``` 231 | 232 | 233 | 234 | 235 | #### 1021 删除最外层的括号 (easy) 236 | 237 | * 题解 238 | * 括号的匹配, 要使用栈 239 | * 遍历字符串 240 | * 如果遇到左括号直接入栈, 如果此时栈中有超过1个左括号, 说明这个左括号需要保留 241 | * 如果遇到右括号, 左括号对应出栈, 如果此时栈中依然存在左括号, 那么这个右括号需要保留 242 | * 否则不保留. 243 | 244 | ```c++ 245 | class Solution { 246 | public: 247 | string removeOuterParentheses(string S) { 248 | string res; 249 | stack stack1; 250 | for (auto s1: S){ 251 | if (s1 == '('){ 252 | stack1.push(s1); 253 | if (stack1.size() > 1) 254 | res += s1; 255 | } 256 | else{ 257 | stack1.pop(); 258 | if (stack1.size() >= 1) 259 | res += ')'; 260 | } 261 | } 262 | return res; 263 | } 264 | }; 265 | ``` 266 | 267 | 268 | #### 496 下一个更大元素 I (easy) 269 | * 题解: 暴力法 270 | * 利用map存储nums2中每个元素与索引的位置 271 | * 对于nums1中的每个元素, 从nums2中与其对应的下一个位置开始查找 272 | 273 | ```c++ 274 | class Solution { 275 | public: 276 | vector nextGreaterElement(vector& nums1, vector& nums2) { 277 | vector res; 278 | unordered_map nums2Index; 279 | for (int i = 0; i< nums2.size(); i++){ 280 | nums2Index.insert(make_pair(nums2[i], i)); 281 | } 282 | // 暴力法 283 | for(int i = 0; i< nums1.size(); i++){ 284 | bool flag = true; 285 | for (int j = nums2Index[nums1[i]]; j< nums2.size(); j++){ 286 | if (nums2[j] > nums1[i]){ 287 | res.push_back(nums2[j]); 288 | flag = false; 289 | break; 290 | } 291 | } 292 | if (flag) res.push_back(-1); 293 | } 294 | return res; 295 | } 296 | }; 297 | ``` 298 | 299 | * 题解2: 单调栈 300 | * 依次遍历数组nums1, 如果栈为空将nums1[i]入栈 301 | * 如果nums[i+1] 大于栈顶元素, 就将栈顶出栈, 此时对应栈顶的下一个大的元素就是nums[i+1] 302 | * 如果nums[i+1] 不大于nums[i], 就将nums[i+1]入栈, 直到找到nums[j]大于栈顶就依次比较出栈. 303 | 304 | 305 | ```c++ 306 | class Solution { 307 | public: 308 | vector nextGreaterElement(vector& nums1, vector& nums2) { 309 | vector res; 310 | unordered_map nums2Index; 311 | 312 | // 单调栈 313 | stack decendStack; 314 | for (int i = 0; i < nums2.size(); i++){ 315 | if (decendStack.empty() || decendStack.top() >= nums2[i]) 316 | decendStack.push(nums2[i]); 317 | else{ 318 | while (!decendStack.empty() && decendStack.top() < nums2[i]){ 319 | nums2Index[decendStack.top()] = nums2[i]; 320 | decendStack.pop(); 321 | } 322 | decendStack.push(nums2[i]); 323 | } 324 | } 325 | 326 | // 最终如果栈非空, 那么栈中下一个最大的元素不存在 327 | while (! decendStack.empty()){ 328 | int tmp = decendStack.top(); 329 | decendStack.pop(); 330 | nums2Index[tmp] = -1; 331 | } 332 | 333 | 334 | for(int i = 0; i< nums1.size(); i++){ 335 | res.push_back(nums2Index[nums1[i]]); 336 | } 337 | return res; 338 | } 339 | }; 340 | ``` 341 | 342 | 343 | #### 503 下一个更大元素 II (Medium) 344 | 345 | 346 | * 题解: 单调栈 347 | * 循环数组, 那么直接遍历两遍就可以了 348 | * 还有一个不同点就是,这里可能会出现相同的元素, 因此使用map会导致错误(这里我就搞错了依次) 349 | * 遍历数组两次,每次pop之前都去更新一下res 350 | 351 | ```c++ 352 | class Solution { 353 | public: 354 | vector nextGreaterElements(vector& nums) { 355 | stack stk; 356 | vector res(nums.size(), -1); 357 | for(int i = 0; i < nums.size() * 2; i++) 358 | { 359 | int index = i % nums.size(); 360 | while(!stk.empty() && nums[stk.top()] < nums[index]) 361 | { 362 | // 如果已经找到了下一个最大的元素, 那么跳过 363 | if(res[stk.top()] == -1) 364 | { 365 | res[stk.top()] = nums[index]; 366 | } 367 | stk.pop(); 368 | } 369 | stk.push(index); 370 | } 371 | return res; 372 | } 373 | }; 374 | ``` 375 | 376 | #### 739 每日温度 (Medium) 377 | * 题解: 单调栈 378 | * 与496题目一样, 找寻下一个最大的元素 379 | * 不同的是这里需要保存的是索引之间的差值. 380 | 381 | ```c++ 382 | class Solution { 383 | public: 384 | vector dailyTemperatures(vector& T) { 385 | vector res(T.size(), 0); 386 | stack stk; 387 | // 遍历T 388 | for (int i = 0; i< T.size(); i++){ 389 | // 如果新的气温大于栈顶的气温, 那么保存需要等待的天数(索引值差) 390 | // 栈顶出栈 391 | while(!stk.empty() && T[stk.top()] < T[i]){ 392 | res[stk.top()] = i - stk.top(); 393 | stk.pop(); 394 | } 395 | stk.push(i); 396 | } 397 | return res; 398 | } 399 | }; 400 | ``` 401 | 402 | 403 | ## 更多分类刷题资料 404 | 405 | * 微信公众号: 小哲AI 406 | 407 | ![wechat_QRcode](https://img-blog.csdnimg.cn/20210104185413204.jpg) 408 | 409 | * GitHub地址: [https://github.com/lxztju/leetcode-algorithm](https://github.com/lxztju/leetcode-algorithm) 410 | * csdn博客: [https://blog.csdn.net/lxztju](https://blog.csdn.net/lxztju) 411 | * 知乎专栏: [小哲AI](https://www.zhihu.com/column/c_1101089619118026752) 412 | * AI研习社专栏:[小哲AI](https://www.yanxishe.com/column/109) 413 | 414 | -------------------------------------------------------------------------------- /leetcode算法之贪心.md: -------------------------------------------------------------------------------- 1 | 今天来盘一盘 **贪心算法 ** 这类题目 2 | 3 | 4 | 5 | 使用**python**刷题分类整理的笔记,请参考: [https://github.com/lxztju/leetcode-algorithm/tree/v1](https://github.com/lxztju/leetcode-algorithm/tree/v1) 6 | 7 | ## 贪心算法 8 | 贪心算法是指每步都选择最优的策略,以此达到全局的最优。**局部最优—>全局最优** 9 | 10 | 贪心策略的选择必须具备**无后效性**,也就是说某个状态以前的过程不会影响以后的状态,只与当前状态有关。 11 | 12 | * 455 分发饼干 (Easy) 13 | * 121 买卖股票的最佳时机 (Easy) 14 | * 122 买卖股票的最佳时机 II (Easy) 15 | * 605 种花问题 (Easy) 16 | * 665 非递减数列 (Easy) 17 | * 53 最大子序和 (Easy) 18 | * 435 无重叠区间 (Medium) 19 | * 452 用最少数量的箭引爆气球 (Medium) 20 | * 406 根据身高重建队列(Medium) 21 | * 763 划分字母区间 (Medium) 22 | 23 | 24 | #### 455 分发饼干 (Easy) 25 | * 二者排序之后采用双指针比较。 26 | ```c++ 27 | class Solution { 28 | public: 29 | int findContentChildren(vector& g, vector& s) { 30 | // 分别将二者排序 31 | sort(g.begin(), g.end()); 32 | sort(s.begin(), s.end()); 33 | int i = 0, j = 0; 34 | int res = 0; 35 | while (i < g.size() && j < s.size()){ 36 | if (s[j] >= g[i]){ 37 | i++; 38 | res++; 39 | } 40 | j++; 41 | } 42 | return res; 43 | } 44 | }; 45 | ``` 46 | 47 | #### 121 买卖股票的最佳时机 (Easy) 48 | 49 | ```c++ 50 | class Solution { 51 | public: 52 | int maxProfit(vector& prices) { 53 | // 暴力法 54 | // 双重循环,从第i天买入可以获得的最大利润,计算最大值 55 | int maxprofit = 0; 56 | for ( int i = 0; i < prices.size() - 1; i++){ 57 | for (int j = i + 1; j < prices.size(); j++){ 58 | int profit = prices[j] - prices[i]; 59 | maxprofit = max(profit, maxprofit); 60 | } 61 | } 62 | return maxprofit; 63 | } 64 | }; 65 | ``` 66 | 67 | ```c++ 68 | class Solution { 69 | public: 70 | int maxProfit(vector& prices) { 71 | // 使用minprice变量保存当前天之前的最低价格, 如果在此之前采用最低价格买入,那么当前卖出的利润最大 72 | int maxprofit = 0; 73 | int minprice = INT_MAX; 74 | for(auto price : prices){ 75 | minprice = min(minprice, price); 76 | maxprofit = max(maxprofit, price - minprice); 77 | } 78 | return maxprofit; 79 | } 80 | }; 81 | ``` 82 | 83 | #### 122 买卖股票的最佳时机 II (Easy) 84 | * 与上一题的主要区别就是,可以多次交易 85 | * 也就是当前只要获利为正就可以交易。 86 | 87 | 88 | ```c++ 89 | class Solution { 90 | public: 91 | int maxProfit(vector& prices) { 92 | int ans = 0; 93 | int n = prices.size(); 94 | for (int i = 1; i < n; ++i) { 95 | ans += max(0, prices[i] - prices[i - 1]); 96 | } 97 | return ans; 98 | } 99 | }; 100 | ``` 101 | 102 | #### 605 种花问题 (Easy) 103 | ```c++ 104 | class Solution { 105 | public: 106 | bool canPlaceFlowers(vector& flowerbed, int n) { 107 | // 贪心的种植, 可以种植的位置,直接众,只要左右联测均为0,就可以种植。 108 | // 需要考虑首尾的两个元素的特殊情况。将首尾各补上0 109 | int res = 0; 110 | for (int i = 0; i< flowerbed.size(); i++){ 111 | int left = (i == 0) ? 0 : flowerbed[i-1]; 112 | int right = (i == flowerbed.size()-1) ? 0 : flowerbed[i+1]; 113 | if (left == 0 && right == 0 && flowerbed[i] == 0){ 114 | // cout<= n; 120 | } 121 | }; 122 | ``` 123 | #### 665 非递减数列 (Easy) 124 | * 遇到nums[i]>nums[i+1]需要将nums[i]变小或者nums[i+1]变大 125 | * 如果nums[i + 1] < nums[i - 1],需要将nums[i+1]变大 126 | * 其他情况下nums[i]变小 127 | 128 | ```c++ 129 | class Solution { 130 | public: 131 | bool checkPossibility(vector& nums) { 132 | int cnt = 0; 133 | for (int i = 0; i < nums.size()-1 && cnt < 2; i++) { 134 | if (nums[i] > nums[i + 1]) { 135 | cnt++; 136 | if (i > 0 && nums[i + 1] < nums[i - 1]) { 137 | nums[i + 1] = nums[i]; 138 | } 139 | else { 140 | nums[i] = nums[i + 1]; 141 | } 142 | } 143 | } 144 | return cnt < 2; 145 | } 146 | }; 147 | ``` 148 | #### 53 最大子序和 (Easy) 149 | ```c++ 150 | class Solution { 151 | public: 152 | int maxSubArray(vector& nums) { 153 | // 暴力法 154 | int maxnum = nums[0]; 155 | int tmp = 0; 156 | for (int i = 0; i < nums.size(); i++){ 157 | tmp = nums[i]; 158 | maxnum = max(maxnum, tmp); 159 | for (int j = i+1; j < nums.size(); j++){ 160 | tmp += nums[j]; 161 | maxnum = max(maxnum, tmp); 162 | } 163 | } 164 | return maxnum; 165 | } 166 | }; 167 | ``` 168 | 169 | 170 | ```c++ 171 | class Solution { 172 | public: 173 | int maxSubArray(vector& nums) { 174 | // 动态规划 175 | int maxnum = nums[0]; 176 | vector dp(nums.size(), 0); // dp表示到i位置的最长子序和。 177 | dp[0] = nums[0]; 178 | for (int i = 1; i< nums.size(); i++){ 179 | dp[i] = max(nums[i], dp[i-1] + nums[i]); 180 | maxnum = max(dp[i], maxnum); 181 | } 182 | 183 | return maxnum; 184 | } 185 | }; 186 | ``` 187 | 188 | #### 435 无重叠区间 (Medium) 189 | ```c++ 190 | class Solution { 191 | public: 192 | // 按照区间右边界排序 193 | static bool cmp (const vector& a, const vector& b) { 194 | return a[1] < b[1]; 195 | } 196 | int eraseOverlapIntervals(vector>& intervals) { 197 | if (intervals.size() == 0) return 0; 198 | sort(intervals.begin(), intervals.end(), cmp); 199 | 200 | int cnt = 0; 201 | for (int i = 1; i < intervals.size(); i++) { 202 | if (intervals[i][0] < intervals[i - 1][1]) { 203 | cnt++; 204 | intervals[i][1] = min(intervals[i - 1][1], intervals[i][1]); 205 | } 206 | } 207 | return cnt; 208 | } 209 | }; 210 | 211 | 212 | ``` 213 | #### 452 用最少数量的箭引爆气球 (Medium) 214 | ```c++ 215 | class Solution { 216 | public: 217 | // 按照区间的右端点排序 218 | static bool cmp(const vector& a, const vector& b){ 219 | return a[1] < b[1]; 220 | } 221 | int findMinArrowShots(vector>& points) { 222 | // 与上一题的最大不重叠区间的问题实际是一样的 223 | if(points.size() < 2) return points.size(); 224 | sort(points.begin(), points.end(), cmp); 225 | int cnt = 1; 226 | for (int i = 1; i < points.size(); i++){ 227 | if (points[i][0] > points[i-1][1]) 228 | cnt++; 229 | else 230 | points[i][1] = min(points[i][1], points[i-1][1]); 231 | } 232 | return cnt; 233 | } 234 | }; 235 | ``` 236 | #### 406 根据身高重建队列(Medium) 237 | ```c++ 238 | class Solution { 239 | public: 240 | static bool cmp(const vector a, const vector b) { 241 | if (a[0] == b[0]) return a[1] < b[1]; 242 | return a[0] > b[0]; 243 | } 244 | vector> reconstructQueue(vector>& people) { 245 | sort (people.begin(), people.end(), cmp); 246 | vector> que; 247 | for (int i = 0; i < people.size(); i++) { 248 | int position = people[i][1]; 249 | que.insert(que.begin() + position, people[i]); 250 | } 251 | return que; 252 | } 253 | }; 254 | ``` 255 | #### 763 划分字母区间 (Medium) 256 | ```c++ 257 | class Solution { 258 | public: 259 | static bool cmp(const vector& a, const vector& b){ 260 | return a[0] < b[0]; 261 | } 262 | vector partitionLabels(string S) { 263 | // 这个题目实际上还是求最大的不重合区间的个数 264 | // 首先遍历字符串S,求出每个字符首次与最后一次出现的次数 265 | // 这样还是一个不重合区间的个数问题 266 | 267 | vector> boards(26, vector(2, -1)); 268 | for (int i = 0; i < S.size(); i++){ 269 | int index = S[i] - 'a'; 270 | if (boards[index][0] == -1){ 271 | boards[index][0] = i; 272 | boards[index][1] = i; 273 | } 274 | else 275 | boards[index][1] = i; 276 | } 277 | 278 | sort(boards.begin(), boards.end(), cmp); 279 | 280 | vector res; 281 | 282 | int i = 0; 283 | while (i < 26){ 284 | if (boards[i][1] != -1) break; 285 | i++; 286 | } 287 | 288 | int min_board = boards[i][0]; 289 | int max_board = boards[i][1]; 290 | // i++; 291 | while ( i < 26){ 292 | if ( boards[i][0] > max_board){ 293 | res.push_back(max_board - min_board + 1); 294 | min_board = boards[i][0]; 295 | } 296 | 297 | max_board = max(max_board,boards[i][1]); 298 | i++; 299 | } 300 | res.push_back(max_board - min_board + 1); 301 | return res; 302 | } 303 | }; 304 | ``` 305 | 306 | 307 | 308 | 309 | 310 | ## 更多分类刷题资料 311 | 312 | * 微信公众号: 小哲AI 313 | 314 | ![wechat_QRcode](https://img-blog.csdnimg.cn/20210104185413204.jpg) 315 | 316 | * GitHub地址: [https://github.com/lxztju/leetcode-algorithm](https://github.com/lxztju/leetcode-algorithm) 317 | * csdn博客: [https://blog.csdn.net/lxztju](https://blog.csdn.net/lxztju) 318 | * 知乎专栏: [https://www.zhihu.com/column/c_1101089619118026752](https://www.zhihu.com/column/c_1101089619118026752) 319 | * AI研习社专栏:[https://www.yanxishe.com/column/109](https://www.yanxishe.com/column/109) 320 | 321 | -------------------------------------------------------------------------------- /leetcode算法之递归.md: -------------------------------------------------------------------------------- 1 | 今天来盘一盘 **递归 ** 这类题目 2 | 3 | 这类题目是我一直很头疼的题目, 感觉有点难, 从这篇文章开始,就要开始比较难的一部分了 4 | 5 | 使用**python**刷题分类整理的笔记,请参考: [https://github.com/lxztju/leetcode-algorithm/tree/v1](https://github.com/lxztju/leetcode-algorithm/tree/v1) 6 | 7 | ## 递归 8 | * 104 二叉树的最大深度 (Easy) 9 | * 110 平衡二叉树 (Easy) 10 | * 543 二叉树的直径 (Easy) 11 | * 226 翻转二叉树 (Easy) 12 | * 617 合并二叉树 (Easy) 13 | * 112 路径总和 (Easy) 14 | * 437 路径总和 III (Easy) 15 | * 572 另一个树的子树 (Easy) 16 | * 101 对称二叉树 (Easy) 17 | * 111 二叉树的最小深度 (Easy) 18 | * 404 左叶子之和 (Easy) 19 | * 687 最长同值路径 (Easy) 20 | * 100 相同的树 (easy) 21 | * 222 完全二叉树的节点个数 (medium) 22 | * 257 二叉树的所有路径 (easy) 23 | * 113 路径总和 II (medium) 24 | * 129 求根到叶子节点数字之和 (medium) 25 | 26 | 27 | 28 | 29 | 30 | #### 104 二叉树的最大深度 (Easy) 31 | * 题解:递归 32 | * 对于一棵树,左子树的最大深度l, 左子树的最大深度为r。 33 | * 那么整个树的最大深度为l与r的较大值加上本身根节点的1. 34 | * 这里求一棵树的最大深度分别求了左右子树的最大深度,形成递归结构。 35 | 36 | ```c++ 37 | class Solution { 38 | public: 39 | int maxDepth(TreeNode* root) { 40 | if ( root == nullptr ) return 0; 41 | int l = maxDepth(root->left); 42 | int r = maxDepth(root->right); 43 | return max(l, r) + 1; 44 | } 45 | }; 46 | ``` 47 | 48 | 49 | #### 110 平衡二叉树 (Easy) 50 | 51 | * 递归 52 | * 一棵树为平衡二叉树,那么根节点是平衡二叉树, 左子节点与右右子节点均为平衡二叉树, 形成递归结构 53 | * 利用上一题的最大深度 54 | 55 | ```c++ 56 | class Solution { 57 | public: 58 | bool isBalanced(TreeNode* root) { 59 | if (root == nullptr) return true; 60 | return abs( maxDepth(root->left) - maxDepth(root->right) ) <= 1 && isBalanced(root->left) && isBalanced(root->right); 61 | } 62 | 63 | int maxDepth( TreeNode* root){ 64 | // 求一棵树的深度(也就是最大深度) 65 | if (root == nullptr) return 0; 66 | int l = maxDepth(root->left); 67 | int r = maxDepth(root->right); 68 | return max(l, r) + 1; 69 | } 70 | }; 71 | ``` 72 | 73 | 74 | #### 543 二叉树的直径 (Easy) 75 | * 对于某个节点来说其做大的直径为其左子树的最大深度与右子树最大深度之和加1. 76 | * 依然是求最大深度问题 77 | * 只是在每次计算过程中,需要替换最大的直径。 78 | 79 | 80 | ```c++ 81 | class Solution { 82 | public: 83 | int res = 1; // 保存最大值 84 | int diameterOfBinaryTree(TreeNode* root) { 85 | maxDepth(root); 86 | return res - 1; 87 | } 88 | int maxDepth(TreeNode* root){ 89 | if (root == nullptr) return 0; 90 | int l = maxDepth(root->left); 91 | int r = maxDepth(root->right); 92 | res = max(res, l + r + 1); 93 | return max(l , r) + 1; 94 | } 95 | }; 96 | ``` 97 | 98 | 99 | #### 226 翻转二叉树 (Easy) 100 | * 非常简单,遍历整个二叉树,对于一个节点将其左右子树交换即可。 101 | 102 | ```c++ 103 | class Solution { 104 | public: 105 | TreeNode* invertTree(TreeNode* root) { 106 | // 对于一个节点,交换其左右子树的节点 107 | if (root == nullptr) return nullptr; 108 | swap(root->left, root->right); 109 | invertTree(root->left); 110 | invertTree(root->right); 111 | return root; 112 | } 113 | }; 114 | ``` 115 | 116 | #### 617 合并二叉树 (Easy) 117 | * 其实就是一个遍历的过程,直接采用前序遍历。 118 | 119 | 120 | ```c++ 121 | class Solution { 122 | public: 123 | TreeNode* mergeTrees(TreeNode* t1, TreeNode* t2) { 124 | 125 | if (t1 == nullptr && t2 == nullptr) return nullptr; 126 | if (t1 == nullptr) return t2; 127 | if (t2 == nullptr) return t1; 128 | TreeNode* root = new TreeNode(t1->val + t2->val); 129 | root->left = mergeTrees(t1->left, t2->left); 130 | root->right = mergeTrees(t1->right, t2->right); 131 | return root; 132 | } 133 | }; 134 | ``` 135 | 136 | 137 | #### 112 路径总和 (Easy) 138 | * 如果刚好遍历到叶子节点,并且路径和刚好为targetSum,返回True。 139 | 140 | ```c++ 141 | class Solution { 142 | public: 143 | bool hasPathSum(TreeNode* root, int targetSum) { 144 | if (root == nullptr ) return false; 145 | if(root->left == nullptr && root->right == nullptr && targetSum - root->val == 0) return true; 146 | return hasPathSum(root->left, targetSum - root->val) || hasPathSum(root->right, targetSum - root->val); 147 | } 148 | }; 149 | ``` 150 | 151 | 152 | #### 572 另一个树的子树 (Easy) 153 | 154 | 155 | 156 | ```c++ 157 | class Solution { 158 | public: 159 | bool isSubtree(TreeNode* s, TreeNode* t) { 160 | if (s == nullptr && t == nullptr) return true; 161 | if (s == nullptr || t== nullptr) return false; 162 | 163 | // 如果两个节点均存在,如果值相等,判断以这个开始的子树是否相等, 相等返回true,不等的话继续遍历。 164 | if (s->val == t->val) 165 | if (equalTree(s, t)) return true; 166 | return isSubtree(s->left, t) || isSubtree(s->right, t); 167 | } 168 | 169 | bool equalTree(TreeNode* t1, TreeNode* t2){ 170 | // 判断两棵树是否相等 171 | if (t1 == nullptr && t2 == nullptr) return true; 172 | if (t1 == nullptr || t2 == nullptr) return false; 173 | return t1->val == t2->val && equalTree(t1->left, t2->left) && equalTree(t1->right, t2->right); 174 | } 175 | }; 176 | ``` 177 | 178 | 179 | #### 101 对称二叉树 (Easy) 180 | 181 | ```c++ 182 | class Solution { 183 | public: 184 | bool isSymmetric(TreeNode* root) { 185 | if (root == nullptr) return true; 186 | 187 | return TreeSymmetric(root->left, root->right); 188 | } 189 | bool TreeSymmetric(TreeNode* s, TreeNode* t){ 190 | // 判断两颗树是否镜面对称。 191 | if (s== nullptr && t == nullptr) return true; 192 | if (s == nullptr || t == nullptr) return false; 193 | return s->val == t->val && TreeSymmetric(s->left, t->right) && TreeSymmetric(s->right, t->left); 194 | } 195 | }; 196 | ``` 197 | 198 | #### 111 二叉树的最小深度 (Easy) 199 | * 这个题与最大深度的题目不一样,不能直接左右子树的最小值加1.因为其要求到叶子节点的距离 200 | * 如果这棵树是一条仅仅由左子树构成的一条链表式的树, 那么直接左右子树的最小值加1,结果返回就是1. 201 | * 但是此时不满足题意,题目要求到叶子节点。 202 | * 正解需要分情况讨论。见代码中分析。 203 | 204 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210130215549768.png) 205 | 206 | ```c++ 207 | class Solution { 208 | public: 209 | int minDepth(TreeNode* root) { 210 | // 这个题与最大深度的题目不一样,不能直接左右子树的最小值加1.因为其要求到叶子节点的距离 211 | // 如果这棵树是一条仅仅由左子树构成的一条链表式的树, 那么直接左右子树的最小值加1,结果返回就是1. 212 | // 但是此时不满足题意,题目要求到叶子节点。 213 | 214 | if (root == nullptr) return 0; 215 | int l = minDepth(root->left); 216 | int r = minDepth(root->right); 217 | // 如果左子树存在,右子树不存在 218 | if (root->left != nullptr && root ->right == nullptr) 219 | return l+ 1; 220 | // 如果左子树不存在,右子树存在 221 | if (root->right != nullptr && root->left == nullptr) 222 | return r + 1; 223 | // 左右子树均存在。 224 | return min(l, r) + 1; 225 | } 226 | }; 227 | 228 | ``` 229 | 230 | 231 | 232 | #### 404 左叶子之和 (Easy) 233 | * 遍历所有的节点, 将所有的左叶子节点求和即可。 234 | 235 | ```c++ 236 | 237 | class Solution { 238 | public: 239 | int res = 0; 240 | int sumOfLeftLeaves(TreeNode* root) { 241 | if (root == nullptr) return 0; 242 | if (root->left != nullptr && root->left->left == nullptr && root->left->right == nullptr){ 243 | res += root->left->val; 244 | } 245 | sumOfLeftLeaves(root->left); 246 | sumOfLeftLeaves(root->right); 247 | return res; 248 | } 249 | }; 250 | ``` 251 | 252 | 253 | 254 | 255 | 256 | #### 100 相同的树 (easy) 257 | 258 | ```c++ 259 | class Solution { 260 | public: 261 | bool isSameTree(TreeNode* p, TreeNode* q) { 262 | if (p == nullptr && q == nullptr) return true; 263 | if (p == nullptr || q == nullptr) return false; 264 | return (p->val == q->val ) && isSameTree(p->left, q->left) && isSameTree(p->right, q->right); 265 | } 266 | }; 267 | ``` 268 | 269 | #### 257 二叉树的所有路径 (easy) 270 | 271 | ```c++ 272 | class Solution { 273 | public: 274 | vector binaryTreePaths(TreeNode* root) { 275 | vector res; 276 | string path; 277 | treePaths(root, res, path); 278 | return res; 279 | } 280 | 281 | void treePaths(TreeNode* root, vector& res, string path){ 282 | // 保存所有路径 283 | if (root == nullptr) return; 284 | if(root->left == nullptr && root->right == nullptr) { 285 | path += to_string(root->val); 286 | res.push_back(path); 287 | return; 288 | } 289 | path += to_string(root->val); 290 | path += "->"; 291 | treePaths(root->left, res, path); 292 | treePaths(root->right, res, path); 293 | } 294 | }; 295 | ``` 296 | 297 | 298 | #### 222 完全二叉树的节点个数 (medium) 299 | 300 | * 方法1: 不考虑完全二叉树,直接遍历所有的节点 301 | 302 | ```c++ 303 | class Solution { 304 | public: 305 | int countNodes(TreeNode* root) { 306 | if (root == nullptr) return 0; 307 | int l = countNodes(root->left); 308 | int r = countNodes(root->right); 309 | return (l + r + 1); 310 | } 311 | }; 312 | ``` 313 | 314 | 315 | * 方法2: 利用完全二叉树的性质 316 | 317 | ```c++ 318 | 319 | class Solution { 320 | public: 321 | int countNodes(TreeNode* root) { 322 | if (root == nullptr) return 0; 323 | // 分别求左右两颗子树的高度 324 | int l = treeDepth(root->left); 325 | int r = treeDepth(root->right); 326 | // 如果两颗子树高度相等,说明左子树为满二叉树,右子树不满 327 | if (l == r){ 328 | return pow(2, l) + countNodes(root->right); 329 | } 330 | // 高度不等,说明右子树是满二叉树,左子树不满。 331 | else 332 | return pow(2, r) + countNodes(root->left); 333 | } 334 | int treeDepth(TreeNode* root){ 335 | //求一棵完全二叉树的高度 336 | if (root == nullptr) return 0; 337 | int l = treeDepth(root->left); 338 | return l + 1; 339 | } 340 | }; 341 | ``` 342 | 343 | 344 | #### 687 最长同值路径 (medium) 345 | 346 | * 最长同值路径就是过某个节点最长同值路径中的最大值。 347 | 348 | 349 | 350 | ```c++ 351 | class Solution { 352 | public: 353 | int res = 0; 354 | int longestUnivaluePath(TreeNode* root) { 355 | if (root == nullptr) return 0; 356 | arrowLength(root); 357 | return res; 358 | } 359 | int arrowLength(TreeNode* root){ 360 | // 以某个节点作为根节点的最长同值路径。 361 | if (root == nullptr) return 0; 362 | int l = arrowLength(root->left); 363 | int r = arrowLength(root->right); 364 | int left = 0, right = 0; 365 | if (root->left != nullptr && root->val == root->left->val) 366 | left = l + 1; 367 | if (root->right != nullptr && root->val == root->right->val) 368 | right = r + 1; 369 | res = max(res, left + right); 370 | return max(left, right); 371 | } 372 | }; 373 | ``` 374 | 375 | 376 | #### 113 路径总和 II (medium) 377 | * 套路代码 378 | 379 | 380 | ```c++ 381 | class Solution { 382 | public: 383 | 384 | vector> pathSum(TreeNode* root, int targetSum) { 385 | vector > res; 386 | vector path; 387 | pathsum(root, res, path, targetSum); 388 | return res; 389 | } 390 | void pathsum(TreeNode* root, vector>& res, vector path, int target){ 391 | if (root == nullptr) return; 392 | if (root->left == nullptr && root->right == nullptr && target == root->val){ 393 | path.push_back(root->val); 394 | res.push_back(path); 395 | return ; 396 | } 397 | path.push_back(root->val); 398 | target -= root->val; 399 | pathsum(root->left, res, path, target); 400 | pathsum(root->right, res, path, target); 401 | return ; 402 | } 403 | }; 404 | ``` 405 | 406 | #### 129 求根到叶子节点数字之和 (medium) 407 | * 先求出所有的路径, 然后求和。 408 | 409 | ```c++ 410 | class Solution { 411 | public: 412 | int sumNumbers(TreeNode* root) { 413 | vector > res; 414 | vector path; 415 | Path(root, res, path); 416 | int ans = 0; 417 | for (int i=0; i < res.size(); i++){ 418 | int tmp = 0; 419 | for (int j = res[i].size()-1; j >= 0; j--){ 420 | tmp += (res[i][j] * pow(10, res[i].size()-1 - j)); 421 | } 422 | ans += tmp; 423 | } 424 | return ans; 425 | } 426 | 427 | void Path(TreeNode* root, vector>& res, vector path){ 428 | // 得到所有的路径,保存至vector中 429 | if (root == nullptr) return; 430 | if (root->left == nullptr && root->right == nullptr ){ 431 | path.push_back(root->val); 432 | res.push_back(path); 433 | return ; 434 | } 435 | path.push_back(root->val); 436 | Path(root->left, res, path); 437 | Path(root->right, res, path); 438 | return ; 439 | } 440 | }; 441 | ``` 442 | 443 | 444 | #### 437 路径总和 III (medium) 445 | 446 | 447 | ```c++ 448 | class Solution { 449 | public: 450 | int res = 0; 451 | int pathSum(TreeNode* root, int sum) { 452 | if (root == nullptr) return 0; 453 | // 以根节点开始 454 | pathsum(root, sum); 455 | // 遍历整个树 456 | pathSum(root->left, sum); 457 | pathSum(root->right, sum); 458 | return res; 459 | } 460 | void pathsum(TreeNode* root, int target){ 461 | // 以某个节点开始一共有多少路径和为target的路径。 462 | if (root == nullptr) return ; 463 | target -= root->val; 464 | if (target == 0){ 465 | res += 1; 466 | } 467 | pathsum(root->left, target); 468 | pathsum(root->right, target); 469 | return ; 470 | } 471 | }; 472 | ``` 473 | 474 | 475 | 476 | 477 | 478 | ## 更多分类刷题资料 479 | 480 | * 微信公众号: 小哲AI 481 | 482 | ![wechat_QRcode](https://img-blog.csdnimg.cn/20210104185413204.jpg) 483 | 484 | * GitHub地址: [https://github.com/lxztju/leetcode-algorithm](https://github.com/lxztju/leetcode-algorithm) 485 | * csdn博客: [https://blog.csdn.net/lxztju](https://blog.csdn.net/lxztju) 486 | * 知乎专栏: [https://www.zhihu.com/column/c_1101089619118026752](https://www.zhihu.com/column/c_1101089619118026752) 487 | * AI研习社专栏:[https://www.yanxishe.com/column/109](https://www.yanxishe.com/column/109) 488 | 489 | -------------------------------------------------------------------------------- /leetcode算法之遍历(BFS与DFS).md: -------------------------------------------------------------------------------- 1 | 今天来盘一盘 **遍历 ** 这类题目 2 | 3 | 4 | 5 | 使用**python**刷题分类整理的笔记,请参考: [https://github.com/lxztju/leetcode-algorithm/tree/v1](https://github.com/lxztju/leetcode-algorithm/tree/v1) 6 | 7 | 8 | ## 遍历 9 | 层次遍历(BFS) 10 | * 102 二叉树的层序遍历(medium) 11 | * 637 二叉树的层平均值 (Easy) 12 | * 513 找树左下角的值 (medium) 13 | 14 | 15 | 深度优先遍历(DFS) 16 | * 144 二叉树的前序遍历 (meidum) 17 | * 145 二叉树的后序遍历 (hard) 18 | * 94 二叉树的中序遍历 (Medium) 19 | 20 | 21 | ### BFS 22 | 宽度优先遍历(BFS)采用**队列**的方式,依次遍历一棵树。 23 | 24 | 基本的套路代码,就是`102题目`中的代码思路。 25 | 26 | #### 102 二叉树的层序遍历(medium) 27 | 28 | * 题解 `BFS` 29 | * 使用队列存储当前节点的指针以及当前节点的层次 30 | * 循环遍历队列中的元素 31 | * 对于一个队首节点,如果其左子节点存在就将左子节点入队,如果右子节点存在,将右子节点入队列 32 | * 按照队首节点的层次数,将节点的值插入vector中对于的位置。 33 | 34 | ```c++ 35 | class Solution { 36 | public: 37 | vector> levelOrder(TreeNode* root) { 38 | if (root == nullptr) return {}; 39 | // 使用队列存储节点与节点所对应的层次 40 | queue> nodeQ; 41 | vector> res; 42 | // 将根节点以及对应的层次0,入队 43 | nodeQ.push(make_pair(root, 0)); 44 | // 如果队列不为空,就一直遍历队列元素 45 | while (!nodeQ.empty()){ 46 | // 拿出队首的节点指针以及层次 47 | auto nodeLevel = nodeQ.front(); 48 | auto nodePoint = nodeLevel.first; 49 | int level = nodeLevel.second; 50 | nodeQ.pop(); // 队首元素出队 51 | 52 | if (res.size() <= level){ 53 | res.push_back({}); 54 | } 55 | res[level].push_back(nodePoint->val); 56 | 57 | // 如果左子树存在,就将左子节点入队 58 | if (nodePoint->left){ 59 | nodeQ.push(make_pair(nodePoint->left, level+1)); 60 | } 61 | // 如果右子树存在,就将右子节点入队 62 | if (nodePoint->right){ 63 | nodeQ.push(make_pair(nodePoint->right, level+1)); 64 | } 65 | } 66 | return res; 67 | } 68 | }; 69 | ``` 70 | 71 | #### 637 二叉树的层平均值 (Easy) 72 | 与上一题一致 73 | 74 | ```c++ 75 | class Solution { 76 | public: 77 | vector averageOfLevels(TreeNode* root) { 78 | if (! root) return {}; 79 | vector nodesum; 80 | vector cnt; 81 | queue> nodeQ; 82 | nodeQ.push(make_pair(root, 0)); 83 | while (!nodeQ.empty()){ 84 | // 拿出队首的节点指针以及层次 85 | auto nodeLevel = nodeQ.front(); 86 | auto nodePoint = nodeLevel.first; 87 | int level = nodeLevel.second; 88 | nodeQ.pop(); // 队首元素出队 89 | if (nodesum.size() <= level){ 90 | nodesum.push_back(nodePoint->val); 91 | cnt.push_back(1); 92 | } 93 | else{ 94 | nodesum[level] += nodePoint->val; 95 | cnt[level]++; 96 | } 97 | 98 | // 如果左子树存在,就将左子节点入队 99 | if (nodePoint->left){ 100 | nodeQ.push(make_pair(nodePoint->left, level+1)); 101 | } 102 | // 如果右子树存在,就将右子节点入队 103 | if (nodePoint->right){ 104 | nodeQ.push(make_pair(nodePoint->right, level+1)); 105 | } 106 | } 107 | for (int i = 0; i< nodesum.size(); i++){ 108 | nodesum[i] /= cnt[i]; 109 | } 110 | return nodesum; 111 | } 112 | }; 113 | ``` 114 | 115 | 116 | 117 | #### 513 找树左下角的值 (medium) 118 | * 题解: 最左边的值,就是采用层次遍历保存每层开始的第一个值 119 | * 同样的层次遍历方法。 120 | 121 | 122 | ```c++ 123 | class Solution { 124 | public: 125 | int findBottomLeftValue(TreeNode* root) { 126 | if (root == nullptr) return {}; 127 | 128 | // 使用队列存储节点与节点所对应的层次 129 | queue> nodeQ; 130 | // pair的第一个参数为每层的第一个节点值,第二个参数为层次 131 | pair res(0, -1); 132 | // 将根节点以及对应的层次0,入队 133 | nodeQ.push(make_pair(root, 0)); 134 | // 如果队列不为空,就一直遍历队列元素 135 | while (!nodeQ.empty()){ 136 | // 拿出队首的节点指针以及层次 137 | auto nodeLevel = nodeQ.front(); 138 | auto nodePoint = nodeLevel.first; 139 | int level = nodeLevel.second; 140 | nodeQ.pop(); // 队首元素出队 141 | 142 | // 如果开始了一个新的层次,将在这一层最左侧的元素保存。 143 | if (level > res.second){ 144 | res.first = nodePoint->val; 145 | res.second = level; 146 | } 147 | 148 | // 如果左子树存在,就将左子节点入队 149 | if (nodePoint->left){ 150 | nodeQ.push(make_pair(nodePoint->left, level+1)); 151 | } 152 | // 如果右子树存在,就将右子节点入队 153 | if (nodePoint->right){ 154 | nodeQ.push(make_pair(nodePoint->right, level+1)); 155 | } 156 | } 157 | return res.first; 158 | } 159 | }; 160 | ``` 161 | 162 | 163 | 164 | 165 | ### DFS 166 | 深度优先遍历(DFS)就是**递归**访问一棵树。 167 | 168 | #### 144 二叉树的前序遍历 (meidum) 169 | 前序遍历: 根——左——右 170 | 171 | ```c++ 172 | class Solution { 173 | public: 174 | vector preorderTraversal(TreeNode* root) { 175 | vector res; 176 | preorder(root, res); 177 | return res; 178 | } 179 | void preorder(TreeNode* root, vector& res){ 180 | if (root == nullptr) return; 181 | res.push_back(root->val); 182 | preorder(root->left, res); 183 | preorder(root->right, res); 184 | } 185 | }; 186 | 187 | ``` 188 | 189 | 190 | #### 145 二叉树的后序遍历 (hard) 191 | 后序遍历: 左——右——根 192 | 193 | ```c++ 194 | class Solution { 195 | public: 196 | vector postorderTraversal(TreeNode* root) { 197 | vector res; 198 | postorder(root, res); 199 | return res; 200 | } 201 | 202 | void postorder(TreeNode* root, vector& res){ 203 | if (root == nullptr) return; 204 | postorder(root->left, res); 205 | postorder(root->right, res); 206 | res.push_back(root->val); 207 | } 208 | }; 209 | ``` 210 | #### 94 二叉树的中序遍历 (Medium) 211 | 212 | 中序遍历: 左——根——右 213 | 214 | ```c++ 215 | class Solution { 216 | public: 217 | vector inorderTraversal(TreeNode* root) { 218 | vector res; 219 | inorder(root, res); 220 | return res; 221 | } 222 | void inorder(TreeNode* root, vector & res){ 223 | if (root == nullptr) return ; 224 | inorder(root->left, res); 225 | res.push_back(root->val); 226 | inorder(root->right, res); 227 | } 228 | }; 229 | ``` 230 | 231 | 232 | ## 更多分类刷题资料 233 | 234 | * 微信公众号: 小哲AI 235 | 236 | ![wechat_QRcode](https://img-blog.csdnimg.cn/20210104185413204.jpg) 237 | 238 | * GitHub地址: [https://github.com/lxztju/leetcode-algorithm](https://github.com/lxztju/leetcode-algorithm) 239 | * csdn博客: [https://blog.csdn.net/lxztju](https://blog.csdn.net/lxztju) 240 | * 知乎专栏: [小哲AI](https://www.zhihu.com/column/c_1101089619118026752) 241 | * AI研习社专栏:[小哲AI](https://www.yanxishe.com/column/109) 242 | 243 | 244 | -------------------------------------------------------------------------------- /leetcode算法之链表.md: -------------------------------------------------------------------------------- 1 | 今天来盘一盘 **链表 ** 这类题目 2 | 3 | 4 | 5 | 使用**python**刷题分类整理的笔记,请参考: [https://github.com/lxztju/leetcode-algorithm/tree/v1](https://github.com/lxztju/leetcode-algorithm/tree/v1) 6 | 7 | ## 链表 8 | 针对链表这种数据结构,采用指针访问元素,而且指针只可向一个方向移动, 几种主要的操作: 9 | * 快慢指针,找到链表的中点 10 | * 通过pre cur与last三个指针模拟题目中的流程 11 | * 注意**虚拟头节点**(在头节点前边新建一个虚拟节点指向头节点)的使用 12 | 13 | 14 | * 160 相交链表 (Easy) 15 | * 206 反转链表 (Easy) 16 | * 21 合并两个有序链表 (Easy) 17 | * 83 删除排序链表中的重复元素 (Easy) 18 | * 234 回文链表 (Easy) 19 | * 19 删除链表的倒数第N个节点 (Medium) 20 | * 24 两两交换链表中的节点 (Medium) 21 | * 445 两数相加 II (Medium) 22 | * 725 分隔链表 (Medium) 23 | * 328 奇偶链表 (Medium) 24 | 25 | 26 | 27 | #### 160 相交链表 (Easy) 28 | 29 | * 题解: 哈希表 30 | * 一个链表的指针存储到哈希表 31 | * 遍历另一个链表,查找元素是否存在于哈希表中,如果存在返回指定的元素, 如果不存在返回nullptr 32 | 33 | ```c++ 34 | /** 35 | * Definition for singly-linked list. 36 | * struct ListNode { 37 | * int val; 38 | * ListNode *next; 39 | * ListNode(int x) : val(x), next(NULL) {} 40 | * }; 41 | */ 42 | class Solution { 43 | public: 44 | ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) { 45 | // 哈希表 46 | //将一个链表存储到哈希表中, 遍历另一个链表查找是否存在哈希表中 47 | unordered_set hashset; 48 | // 将A链表存储至哈希表中 49 | while (headA != nullptr){ 50 | hashset.insert(headA); 51 | headA = headA->next; 52 | } 53 | // 遍历查找B链表 54 | while (headB != nullptr){ 55 | if (hashset.find(headB) != hashset.end()) 56 | return headB; 57 | headB = headB->next; 58 | } 59 | return nullptr; 60 | } 61 | }; 62 | ``` 63 | 64 | * 题解: 双指针 65 | * 使用两个指针分别指向数组的两个链表的头节点 66 | * 当headA达到链表A的尾部时,将headA指向B, 当headB达到链表B的尾部时,将headB指向A 67 | * 当headA与headB相遇时的节点就是相遇节点 68 | 69 | ```c++ 70 | /** 71 | * Definition for singly-linked list. 72 | * struct ListNode { 73 | * int val; 74 | * ListNode *next; 75 | * ListNode(int x) : val(x), next(NULL) {} 76 | * }; 77 | */ 78 | class Solution { 79 | public: 80 | ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) { 81 | // 双指针 82 | ListNode* pA = headA; 83 | ListNode* pB = headB; 84 | while (pA != pB){ 85 | if (pA == nullptr) 86 | pA = headB; 87 | else 88 | pA = pA->next; 89 | 90 | if (pB == nullptr) 91 | pB = headA; 92 | else 93 | pB = pB->next; 94 | } 95 | return pA; 96 | } 97 | }; 98 | ``` 99 | 100 | 101 | #### 206 反转链表 (Easy) 102 | * 题解: 103 | * 直接模拟反转的过程, 使用三个指针, 一个前置指针pre, 一个当前指针cur, 一个后置指针next 104 | * 反转的过程: cur指向pre, cur后移, pre后移, 直到cur移动到最后一个节点. 105 | * 最后设置反转后的链表的结尾(head->next = nullptr). 106 | 107 | 108 | ```c++ 109 | class Solution { 110 | public: 111 | ListNode* reverseList(ListNode* head) { 112 | // 直接模拟反转的过程 113 | ListNode* pre = head; 114 | if (head == nullptr ||head->next == nullptr) return head; 115 | ListNode* cur = head->next; 116 | while (cur != nullptr){ 117 | ListNode* next = cur->next; 118 | cur->next = pre; 119 | pre = cur; 120 | cur = next; 121 | } 122 | head->next = nullptr; 123 | return pre; 124 | } 125 | }; 126 | ``` 127 | 128 | 129 | #### 21 合并两个有序链表 (Easy) 130 | 131 | ```c++ 132 | class Solution { 133 | public: 134 | ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) { 135 | // 设定一个虚拟的头节点, 比较大小,移动指针. 136 | ListNode* dummyHead = new ListNode(0); 137 | ListNode* dummy = dummyHead; 138 | while (l1 && l2){ 139 | if ((l1->val) >= (l2->val)){ 140 | dummyHead->next = l2; 141 | l2 = l2->next; 142 | } 143 | else{ 144 | dummyHead->next = l1; 145 | l1 = l1->next; 146 | } 147 | dummyHead = dummyHead->next; 148 | 149 | } 150 | if (l1) 151 | dummyHead->next = l1; 152 | else 153 | dummyHead->next = l2; 154 | 155 | return dummy->next; 156 | 157 | } 158 | }; 159 | ``` 160 | 161 | #### 83 删除排序链表中的重复元素 (Easy) 162 | 直接模拟题目的执行过程. 所谓删除,就是使用指针跳过相对应的重复元素. 163 | 164 | ```c++ 165 | class Solution { 166 | public: 167 | ListNode* deleteDuplicates(ListNode* head) { 168 | // 所谓删除,就是使用指针跳过相对应的重复元素. 169 | ListNode* pre = head; 170 | ListNode* cur = head; 171 | while(cur){ 172 | // 利用pre指向所有的元素 173 | // 当pre与cur的节点的值相等时, pre不动, cur后移 174 | if( pre->val == cur->val) 175 | cur = cur->next; 176 | // 当二者不等时,pre指向cur, 然后pre cur均后移 177 | else{ 178 | pre->next = cur; 179 | pre = pre->next; 180 | cur = cur->next; 181 | } 182 | } 183 | if (pre != cur) 184 | pre->next = nullptr; 185 | return head; 186 | 187 | } 188 | }; 189 | ``` 190 | 191 | #### 234 回文链表 (Easy) 192 | * 题解1: 利用数组 193 | * 直接遍历并取出链表中的元素存入vector中 194 | * 利用首尾指针遍历元素,判断是否回文 195 | 196 | ```c++ 197 | class Solution { 198 | public: 199 | bool isPalindrome(ListNode* head) { 200 | vector nums; 201 | while (head){ 202 | nums.push_back(head->val); 203 | head = head->next; 204 | } 205 | int l = 0, r = nums.size() - 1; 206 | while (l < r){ 207 | if (nums[l] != nums[r]) 208 | return false; 209 | l++; r--; 210 | } 211 | return true; 212 | } 213 | }; 214 | ``` 215 | 216 | * 题解2: 反转后半部分的链表 217 | * 利用快慢指针,找到链表中点位置 218 | * 反转后半部分的链表 219 | * 再次利用快慢指针找到反转后链表的中点位置,然后对比比较两部分的链表内容是否一致. 220 | 221 | ```c++ 222 | class Solution { 223 | public: 224 | bool isPalindrome(ListNode* head) { 225 | // 找到链表后半部分的头节点 226 | ListNode* dummy = new ListNode(0); 227 | dummy->next = head; 228 | ListNode* lastHeadpre = findLastHead(dummy); 229 | 230 | // 反转链表的后半部分 231 | ListNode* lastHead = lastHeadpre->next; 232 | if (lastHead == nullptr) return true; 233 | ListNode* pre = lastHead; 234 | ListNode* cur = lastHead->next; 235 | while (cur){ 236 | ListNode* next = cur->next; 237 | cur->next = pre; 238 | pre = cur; 239 | cur = next; 240 | } 241 | 242 | lastHead->next = nullptr; 243 | lastHeadpre->next = pre; 244 | 245 | // 找到中间节点 246 | ListNode* newlastHeadpre = findLastHead(dummy); 247 | ListNode* newlastHead = newlastHeadpre->next; 248 | // 判断是否回文 249 | while(head && newlastHead && head != newlastHead){ 250 | if (head->val != newlastHead->val) 251 | return false; 252 | head = head->next; 253 | newlastHead = newlastHead->next; 254 | } 255 | return true; 256 | } 257 | 258 | 259 | ListNode* findLastHead(ListNode* head){ 260 | // 找到链表后半部分的头节点的前置节点 261 | ListNode* slow = head; 262 | ListNode* fast = head; 263 | 264 | while (fast && fast->next){ 265 | fast = fast->next->next; 266 | slow = slow->next; 267 | } 268 | return slow; 269 | } 270 | ``` 271 | 272 | 273 | #### 19 删除链表的倒数第N个节点 (Medium) 274 | * 题解: 滑窗 双指针 275 | * 利用滑窗(双指针), 两个指针之间的距离为n 276 | * 当后指针达到nullptr(链表结尾)时, 前指针刚好指向倒数第n个节点的前一个节点 277 | * 利用虚拟头节点,解决删除头节点的问题. 278 | 279 | ```c++ 280 | class Solution { 281 | public: 282 | ListNode* removeNthFromEnd(ListNode* head, int n) { 283 | // 利用滑窗(双指针), 两个指针之间的距离为n 284 | // 当后指针达到nullptr(链表结尾)时, 前指针刚好指向倒数第n个节点的前一个节点 285 | // 利用虚拟头节点,解决删除头节点的问题. 286 | if (!head) return nullptr; 287 | ListNode* dummy = new ListNode(0); 288 | ListNode* first = dummy; 289 | ListNode* second = dummy; 290 | dummy->next = head; 291 | while (second && n >= 0){ 292 | second = second->next; 293 | n--; 294 | } 295 | while (second){ 296 | first = first->next; 297 | second = second->next; 298 | } 299 | first->next = first->next->next; 300 | return dummy->next; 301 | } 302 | }; 303 | ``` 304 | 305 | #### 24 两两交换链表中的节点 (Medium) 306 | * 题解: 模拟交换过程 307 | * 需要四个指针, pre, cur1, cur2, nex 308 | * 模拟交换的过程,交换cur1与cur2两个节点 309 | 1. pre->next = cur2 310 | 2. cur1->next = nex 311 | 3. cur2->next = cur1 312 | 4. pre = cur1; 313 | cur1 = cur1->next; 314 | 315 | ```c++ 316 | class Solution { 317 | public: 318 | ListNode* swapPairs(ListNode* head) { 319 | ListNode* dummy = new ListNode(0); 320 | dummy->next = head; 321 | ListNode* pre = dummy; 322 | 323 | while (head && head->next){ 324 | ListNode* cur1 = head; 325 | ListNode* cur2 = head->next; 326 | ListNode* nex = cur2->next; 327 | 328 | pre->next = cur2; 329 | cur2->next = cur1; 330 | cur1->next = nex; 331 | 332 | pre = cur1; 333 | head = cur1->next; 334 | } 335 | return dummy->next; 336 | 337 | } 338 | }; 339 | ``` 340 | 341 | #### 445 两数相加 II (Medium) 342 | 343 | * 题解: 反转链表,然后相加 344 | 345 | * 题解: 也可以遍历一遍,将结果存储到数组中,然后相加. 346 | 347 | ```c++ 348 | class Solution { 349 | public: 350 | ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) { 351 | // 反转两个链表 352 | ListNode* newl1 = reverseListNode(l1); 353 | ListNode* newl2 = reverseListNode(l2); 354 | // 新建结果链表 355 | ListNode* head = new ListNode(0); 356 | ListNode* res = head; 357 | int carry = 0; 358 | while (newl1 || newl2){ 359 | int l1val = newl1 ? newl1->val : 0; 360 | int l2val = newl2 ? newl2->val : 0; 361 | long sumval = l1val + l2val + carry; 362 | int nodeval = sumval % 10; 363 | carry = sumval / 10; 364 | head->next = new ListNode(nodeval); 365 | if (newl1) newl1 = newl1->next; 366 | if (newl2) newl2 = newl2->next; 367 | head = head->next; 368 | } 369 | if (carry) head->next = new ListNode(carry); 370 | return reverseListNode(res->next); 371 | } 372 | 373 | ListNode* reverseListNode(ListNode* head){ 374 | // 反转链表 375 | ListNode* pre = head; 376 | if (head == nullptr) return head; 377 | ListNode* cur = head->next; 378 | while ( cur){ 379 | ListNode* tmp = cur->next; 380 | cur->next = pre; 381 | pre = cur; 382 | cur = tmp; 383 | } 384 | head->next = nullptr; 385 | return pre; 386 | } 387 | }; 388 | ``` 389 | 390 | 391 | 392 | 393 | #### 725 分隔链表 (Medium) 394 | * 题解: 395 | * 统计链表中的节点个数 396 | * 计算每部分应该有几个节点 397 | * 分割链表 398 | 399 | ```c++ 400 | class Solution { 401 | public: 402 | vector splitListToParts(ListNode* root, int k) { 403 | // 统计链表的节点数目为cnt 404 | ListNode* head = root; 405 | int cnt = 0; 406 | while (head){ 407 | head = head->next; 408 | cnt++; 409 | } 410 | int quotient = cnt / k; 411 | int reminder = cnt % k; 412 | 413 | // 那么结果vector中前reminder的元素中存入quotient+1个节点, 后边的存入quotient个节点 414 | vector< ListNode* > res; 415 | ListNode* cur = root; 416 | int count = 0; 417 | int vectorNodeSize = quotient==0 ? reminder : k; // vector中仅仅前reminder中存储节点,后边存储null; 418 | while (count < vectorNodeSize){ 419 | // 子链表的开头 420 | ListNode* pre = cur; 421 | //vector中需要保存的节点数目 422 | int num = count < reminder ? quotient + 1 : quotient; 423 | while (num - 1){ 424 | cur = cur->next; 425 | num--; 426 | } 427 | ListNode * tmp = cur->next; 428 | cur->next = nullptr; 429 | res.push_back(pre); 430 | cur = tmp; 431 | count++; 432 | } 433 | // 链表后边补充nullptr 434 | for (int i = vectorNodeSize; i < k; i++) 435 | res.push_back(nullptr); 436 | 437 | return res; 438 | } 439 | }; 440 | ``` 441 | 442 | #### 328 奇偶链表 (Medium) 443 | 444 | * 题解: 445 | * 使用两个链表分别保存奇数节点与偶数节点 446 | * 连接两个链表 447 | 448 | ```c++ 449 | class Solution { 450 | public: 451 | ListNode* oddEvenList(ListNode* head) { 452 | //新建两个节点,分别作为奇数与偶数子链表的头节点 453 | ListNode* oddhead = new ListNode(0); 454 | ListNode* evenhead = new ListNode(0); 455 | 456 | ListNode* odd = oddhead; 457 | ListNode* even =evenhead; 458 | 459 | bool flag = true; // 当flag为true时说明此节点为奇数节点, 否则为偶节点 460 | while (head){ 461 | if(flag){ 462 | ListNode* oddpre = head; // 奇数链表的前置节点 463 | odd->next = head; 464 | flag = false; 465 | odd = odd->next; 466 | } 467 | else{ 468 | even->next = head; 469 | flag = true; 470 | even = even->next; 471 | } 472 | head = head->next; 473 | } 474 | 475 | // 连接连个链表 476 | odd->next = evenhead->next; 477 | even->next = nullptr; 478 | 479 | return oddhead->next; 480 | } 481 | }; 482 | ``` 483 | 484 | 485 | 486 | ## 更多分类刷题资料 487 | 488 | * 微信公众号: 小哲AI 489 | 490 | ![wechat_QRcode](https://img-blog.csdnimg.cn/20210104185413204.jpg) 491 | 492 | * GitHub地址: [https://github.com/lxztju/leetcode-algorithm](https://github.com/lxztju/leetcode-algorithm) 493 | * csdn博客: [https://blog.csdn.net/lxztju](https://blog.csdn.net/lxztju) 494 | * 知乎专栏: [小哲AI](https://www.zhihu.com/column/c_1101089619118026752) 495 | * AI研习社专栏:[小哲AI](https://www.yanxishe.com/column/109) 496 | 497 | --------------------------------------------------------------------------------