递归怎么写 又写错一道递归,于是乎总结一下递归的写法。
假设函数已经写好了
当前节点该怎么做
找“最小不需要处理的情况”
return 用来“兑现函数定义”1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class Solution {public : TreeNode* invertTree (TreeNode* root) { swap (root->left,root->right); return root; } TreeNode* swap (TreeNode* l,TreeNode* r) { if (l->left) l=swap (l->left,l->right); if (r->left) r=swap (r->left,r->right); TreeNode* temp=l; r=l; l=temp; return } };
直线上最多的点数 这题有两个坑。1.首先不能用double存斜率,必须是化简字符形式,并且如此做还能解决斜率为无穷大的情况。2.对于每个i,每轮要清空map矩阵。因为在遍历i时已经把能和i共线的点遍历完了,若不清空则会出现平行线问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 #include <vector> #include <unordered_map> #include <string> #include <algorithm> using namespace std;class Solution {public : int gcd (int a, int b) { return b == 0 ? a : gcd (b, a % b); } int maxPoints (vector<vector<int >>& points) { int n = points.size (); if (n <= 2 ) return n; int result = 0 ; for (int i = 0 ; i < n; i++) { unordered_map<string, int > count; int localMax = 0 ; for (int j = i + 1 ; j < n; j++) { int dx = points[i][0 ] - points[j][0 ]; int dy = points[i][1 ] - points[j][1 ]; int g = gcd (dx, dy); dx /= g; dy /= g; if (dx < 0 ) { dx = -dx; dy = -dy; } else if (dx == 0 ) { dy = abs (dy); } string key = to_string (dy) + "_" + to_string (dx); count[key]++; localMax = max (localMax, count[key]); } result = max (result, localMax + 1 ); } return result; } };
异或运算的原理 异或运算(用符号 ^ 表示)有以下几个关键性质:
任何数和 0 异或,结果仍是它本身 :
任何数和自身异或,结果为 0 :
异或运算满足交换律和结合律 :
Python 代码实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 def singleNumber (nums: list [int ] ) -> int : """ 使用异或运算找出数组中只出现一次的数字。 Args: nums: 一个非空整数数组,其中一个元素只出现一次,其余元素均出现两次。 Returns: 那个只出现了一次的元素。 """ single = 0 for num in nums: single ^= num return single nums1 = [2 , 2 , 1 ] print (f"输入: {nums1} , 输出: {singleNumber(nums1)} " ) nums2 = [4 , 1 , 2 , 1 , 2 ] print (f"输入: {nums2} , 输出: {singleNumber(nums2)} " ) nums3 = [1 ] print (f"输入: {nums3} , 输出: {singleNumber(nums3)} " )
摩尔投票算法(Boyer-Moore Voting Algorithm) 。这个算法的核心思想是“同归于尽”:如果一个数是多数元素(出现次数大于 ),那么它出现的次数比其他所有元素的出现次数总和还要多。我们可以通过互相抵消的方式来找到它。
摩尔投票算法的步骤
初始化:
candidate(候选人)变量:用于存储当前的多数元素候选。
count(计数器)变量:用于记录 candidate 的出现次数,初始值为0。
遍历数组:
遍历数组 nums 中的每个元素 num。
如果 count 为0,这意味着当前的 candidate 和之前的所有元素都抵消了。此时,我们将当前的 num 设为新的 candidate。
如果当前的 num 等于 candidate,则 count 加1。
如果当前的 num 不等于 candidate,则 count 减1。
返回结果:
遍历结束后,candidate 变量中存储的值就是多数元素。由于题目保证多数元素总是存在,所以我们不需要额外的步骤来验证。
为什么这个算法有效? 想象一下,多数元素就像是“统治者”,它拥有的票数比其他所有元素的票数总和还要多。
当遇到和 candidate 相同的元素时,count 加1,表示“统治者”的票数增加。
当遇到和 candidate 不同的元素时,count 减1,表示“统治者”的票数被“其他元素”抵消了一张。
由于多数元素的票数超过总票数的一半,它永远无法被完全抵消。最终,count 的值会大于0,而 candidate 最终存储的就是这个多数元素。
Python 代码实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 def majorityElement (nums: list [int ] ) -> int : candidate = 0 count = 0 for num in nums: if count == 0 : candidate = num if num == candidate: count += 1 else : count -= 1 return candidate nums1 = [3 , 2 , 3 ] print (f"输入: {nums1} , 输出: {majorityElement(nums1)} " ) nums2 = [2 , 2 , 1 , 1 , 1 , 2 , 2 ] print (f"输入: {nums2} , 输出: {majorityElement(nums2)} " )
这个算法的时间复杂度是线性的 ,因为它只需要遍历一次数组。空间复杂度是常数 ,因为它只使用了两个变量来存储候选人和计数器。这比先排序( )或使用哈希表( 时间,但 空间)的方法更优。
计数质数 从质数推非质数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #include <vector> class Solution {public : int countPrimes (int n) { if (n <= 2 ) return 0 ; vector<bool > isPrime (n, true ) ; int count = 0 ; for (int i = 2 ; i < n; i++) { if (isPrime[i]) { count++; if ((long long )i * i < n) { for (int j = i * i; j < n; j += i) { isPrime[j] = false ; } } } } return count; } };
关键if ((long long)i * i < n) 否则会溢出,如将long long放入for循环条件中则增加执行开销。int: 在现代系统中几乎总是 32 位(最大约 21 亿)。
long long: C++ 标准规定至少 64 位,在所有现代系统中都是 64 位。
long: 这是一个“流浪汉”。在 Windows (64位) 上,它竟然只有 32 位(和 int 一样大);而在 Linux/macOS (64位) 上,它是 64 位。 另外vector很慢可以使用
1 vector<char > isPrime (n, 1 ) ;
char c = 1;,00000001,1,SOH (标题开始,不可见控制字符) char c = ‘1’;,00110001,49,字符 ‘1’ (可见的数字符号)
快速大小写转换 利用ASCII码表中大小写字母的对应关系进行转换,大写字母和小写字母之间相差32。
1 2 3 4 5 6 char upper = 'G' ;char lower = upper + ('a' - 'A' ); char backToUpper = lower - ('a' - 'A' );
不使用+- 1 2 3 4 5 6 7 8 9 10 11 12 class Solution {public : int getSum (int a, int b) { while (b != 0 ) { unsigned carry = (unsigned )(a & b) << 1 ; a = a ^ b; b = carry; } return a; } };
加油站 贪心算法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 int canCompleteCircuit (vector<int >& gas, vector<int >& cost) { int total = 0 ; int curr = 0 ; int start = 0 ; for (int i = 0 ; i < gas.size (); i++) { int diff = gas[i] - cost[i]; total += diff; curr += diff; if (curr < 0 ) { start = i + 1 ; curr = 0 ; } } return total >= 0 ? start : -1 ; }
LRU缓存 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 class LRUCache {public : int cap; list<pair<int ,int >> cache; unordered_map<int , list<pair<int ,int >>::iterator> mp; LRUCache (int capacity) : cap (capacity) {} int get (int key) { if (mp.count (key) == 0 ) return -1 ; auto it = mp[key]; cache.splice (cache.begin (), cache, it); return it->second; } void put (int key, int value) { if (mp.count (key)) { auto it = mp[key]; it->second = value; cache.splice (cache.begin (), cache, it); } else { if (cache.size () == cap) { auto last = cache.back (); mp.erase (last.first); cache.pop_back (); } cache.emplace_front (key, value); mp[key] = cache.begin (); } } };
get(key): 如果 key 不存在 → return -1
如果存在:
拿到链表节点
把该节点移动到链表头
返回 value put(key, value) 分三种情况: 情况 1:key 已存在
更新 value
把节点移动到链表头 情况 2:key 不存在,缓存未满
新节点插入链表头
map 记录 key → 节点 情况 3:key 不存在,缓存已满
删除链表尾(LRU)
map.erase(尾节点的 key)
插入新节点到头部 注意点:list::iterator it; unordered_map<int, list::iterator> mp; list::iterator:指向 list 里某个元素的“指针”
std::list O(1)cache.splice(cache.begin(), cache, it);
参数
含义
cache.begin()
插入位置(链表头)
cache
源链表
it
要移动的节点
滑动窗口 我刚开始做的时候,字典或者哈希表都是只存当前滑动窗口的值,每次判断时,判断该字符在不在哈希表中。实际上可以用哈希表只存z字符出现的最后下标,然后判断该下标是否在当前窗口内。(即下标 >= i )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution {public : int lengthOfLongestSubstring (string s) { int n = s.size (); if (n <= 1 ) return n; unordered_map<char , int > cc; int i = 0 , result = 0 ; for (int j = 0 ; j < n; ++j) { if (cc.count (s[j]) && cc[s[j]] >= i) { i = cc[s[j]] + 1 ; } cc[s[j]] = j; result = max (result, j - i + 1 ); } return result; } };
最小覆盖子串 给定两个字符串 s 和 t,长度分别是 m 和 n,返回 s 中的 最短窗口 子串,使得该子串包含 t 中的每一个字符(包括重复字符)。如果没有这样的子串,返回空字符串 “”。
测试用例保证答案唯一。 此题思路在于,窗口不包含,右边界移动,包含时左边界移动。 不使用全局变量的几种方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 1. class Solution {public : string minWindow (string s, string t) { unordered_map<char ,int > ori, cnt; for (char c : t) ori[c]++; auto check = [&]() { for (auto &[c, need] : ori) { if (cnt[c] < need) return false ; } return true ; }; int l = 0 , r = -1 ; int len = INT_MAX, ansL = -1 ; while (r + 1 < s.size ()) { if (ori.count (s[++r])) cnt[s[r]]++; while (check ()) { if (r - l + 1 < len) { len = r - l + 1 ; ansL = l; } if (ori.count (s[l])) cnt[s[l]]--; l++; } } return ansL == -1 ? "" : s.substr (ansL, len); } }; 2. bool check (unordered_map<char ,int >& ori, unordered_map<char ,int >& cnt) { for (auto &[c, need] : ori) { if (cnt[c] < need) return false ; } return true ; } string minWindow (string s, string t) { unordered_map<char ,int > ori, cnt; for (char c : t) ori[c]++; int l = 0 , r = -1 ; int len = INT_MAX, ansL = -1 ; while (r + 1 < s.size ()) { if (ori.count (s[++r])) cnt[s[r]]++; while (check (ori, cnt)) { if (r - l + 1 < len) { len = r - l + 1 ; ansL = l; } if (ori.count (s[l])) cnt[s[l]]--; l++; } } return ansL == -1 ? "" : s.substr (ansL, len); }
环形链表II 找环的入口
解法一:字典 这个字典存的节点的地址(指针)而不是节点的值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution {public : ListNode *detectCycle (ListNode *head) { unordered_set<ListNode *> visited; while (head != nullptr ) { if (visited.count (head)) { return head; } visited.insert (head); head = head->next; } return nullptr ; } };
解法二:快慢指针+数学推理 根据题意,任意时刻,fast 指针走过的距离都为 slow 指针的 2 倍。因此,我们有
a+(n+1)b+nc=2(a+b)⟹a=c+(n−1)(b+c) 有了 a=c+(n−1)(b+c) 的等量关系,我们会发现:从相遇点到入环点的距离加上 n−1 圈的环长,恰好等于从链表头部到入环点的距离。
因此,当发现 slow 与 fast 相遇时,我们再额外使用一个指针 ptr。起始,它指向链表头部;随后,它和 slow 每次向后移动一个位置。最终,它们会在入环点相遇。 作者:力扣官方题解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution {public : ListNode *detectCycle (ListNode *head) { ListNode *slow = head, *fast = head; while (fast != nullptr ) { slow = slow->next; if (fast->next == nullptr ) { return nullptr ; } fast = fast->next->next; if (fast == slow) { ListNode *ptr = head; while (ptr != slow) { ptr = ptr->next; slow = slow->next; } return ptr; } } return nullptr ; } };
合并K个升序链表 我写的,for循环ans+list[i]合并,可以考虑归并排序思想,两两合并
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 class Solution {public : ListNode* mergeKLists (vector<ListNode*>& lists) { if (lists.size ()==0 ) return nullptr ; ListNode* head = lists[0 ]; for (int i=1 ;i<lists.size ();i++){ if (lists[i]){ head=merge (head,lists[i]); } } return head; } ListNode* merge (ListNode* node1,ListNode* node2) { ListNode* head; if (!node1) return node2; if (!node2) return node1; if (node1->val<node2->val){ head=node1; node1=node1->next; } else {head=node2; node2=node2->next;} ListNode* curr=head; while (node1&&node2){ if (node1->val<node2->val){ curr->next=node1; node1=node1->next; } else {curr->next=node2; node2=node2->next;} curr=curr->next; } if (node1){ curr->next=node1; } else {curr->next=node2;} return head; } };
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class Solution {public : ListNode* mergeTwoLists (ListNode *a, ListNode *b) { if ((!a) || (!b)) return a ? a : b; ListNode head, *tail = &head, *aPtr = a, *bPtr = b; while (aPtr && bPtr) { if (aPtr->val < bPtr->val) { tail->next = aPtr; aPtr = aPtr->next; } else { tail->next = bPtr; bPtr = bPtr->next; } tail = tail->next; } tail->next = (aPtr ? aPtr : bPtr); return head.next; } ListNode* merge (vector <ListNode*> &lists, int l, int r) { if (l == r) return lists[l]; if (l > r) return nullptr ; int mid = (l + r) >> 1 ; return mergeTwoLists (merge (lists, l, mid), merge (lists, mid + 1 , r)); } ListNode* mergeKLists (vector<ListNode*>& lists) { return merge (lists, 0 , lists.size () - 1 ); } };
K个一组翻转链表 使用哨兵节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 class Solution {public : ListNode* reverseKGroup (ListNode* head, int k) { if (!head || k == 1 ) return head; ListNode* dummy = new ListNode (0 ); dummy->next = head; ListNode* pre = dummy; ListNode* end = dummy; while (end->next != nullptr ) { for (int i = 0 ; i < k && end != nullptr ; i++) { end = end->next; } if (end == nullptr ) break ; ListNode* start = pre->next; ListNode* nextGroupHead = end->next; end->next = nullptr ; pre->next = reverseList (start); start->next = nextGroupHead; pre = start; end = pre; } return dummy->next; } ListNode* reverseList (ListNode* head) { ListNode* pre = nullptr ; ListNode* curr = head; while (curr != nullptr ) { ListNode* next = curr->next; curr->next = pre; pre = curr; curr = next; } return pre; } };
做法二
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 class Solution {public : ListNode* reverseKGroup (ListNode* head, int k) { if (!head || k <= 1 ) return head; ListNode dummy (0 ) ; dummy.next = head; ListNode* last = &dummy; ListNode* curr = head; int count = 0 ; while (curr) { count++; if (count == k) { ListNode* begin = last->next; ListNode* end = curr; ListNode* nextGroup = end->next; reverseSegment (last, begin, end); last = begin; curr = nextGroup; count = 0 ; } else { curr = curr->next; } } return dummy.next; } private : void reverseSegment (ListNode* prev, ListNode* begin, ListNode* end) { ListNode* curr = begin; ListNode* stop = end->next; ListNode* prevNode = stop; while (curr != stop) { ListNode* next = curr->next; curr->next = prevNode; prevNode = curr; curr = next; } prev->next = end; } };