LeetCode解题思路(每日更新)
- 标准函数
- 一、数组
- 二、链表
- 三、哈希表
- [LeetCode242 | 有效的字母异位词 ](https://leetcode.cn/problems/valid-anagram/description/)
- [LeetCode349 | 两个数组的交集](https://leetcode.cn/problems/intersection-of-two-arrays/description/)
- [LeetCode202 | 快乐数](https://leetcode.cn/problems/happy-number/description/)
- [LeetCode1 | 两数之和](https://leetcode.cn/problems/two-sum/)
- [LeetCode454 | 四数之和](https://leetcode.cn/problems/4sum-ii/submissions/549173012/)
- [LeetCode383 | 赎金信](https://leetcode.cn/problems/ransom-note/submissions/549174331/)
- [LeetCode15 | 三数之和](https://leetcode.cn/problems/3sum/)
- [LeetCode18 | 四数之和](https://leetcode.cn/problems/4sum/)
- 四、字符串
标准函数
0.1 algorithm
0.2 cmath
一、数组
- 存放在连续内存空间上的相同类型数据的集合
- 因为数组在内存空间的地址是连续的,所以我们在删除或者增添元素的时候,就难免要移动其他元素的地址
- 数组的元素是不能删的,只能覆盖。
1.1 二分法
- 需要是有序数组
LeetCode704 | 二分查找
1) 左闭右开
- 在确定候选区间时,右区间需要多给一位,因此,初始化时,right给到nums.size();
- while条件中,left==right时,[left,right)为空集,无意义;
- 二分缩小区间时,nums[middle]不符合条件时(nums[middle] > target),right取不到,所以right=middle;
class Solution {
public:
int search(vector<int>& nums, int target) {
int left = 0, right = nums.size();
while(left < right) {
int middle = left + ((right-left) >> 1);
if(target > nums[middle]) {
% target落在右半边,更新左边界
left = middle + 1;
} else if(target < nums[middle]) {
% target落在左半边,更新右边界
right = middle;
} else {
return middle;
}
}
return -1;
}
};
2) 左闭右闭
相反的,
class Solution {
public:
int search(vector<int>& nums, int target) {
int left = 0, right = nums.size() - 1;
while (left <= right) {
int middle = left + ((right - left) >> 1);
if (nums[middle] < target) {
left = middle + 1;
} else if (nums[middle] > target) {
right = middle - 1;
} else {
return middle;
}
}
return -1;
}
};
LeetCode35 | 搜索插入位置
二分法解法(左闭右开)
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int left = 0, right = nums.size();
while (left < right) {
int middle = left + ((right-left) >> 1);
if (nums[middle] < target) {
left = middle + 1;
} else if (nums[middle] > target) {
right = middle;
} else {
return middle;
}
}
return right;
}
};
暴力解法
- 依次查询
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
for (int i = 0; i < nums.size(); i++) {
if (nums[i] >= target) {
return i;
}
}
return nums.size();
// 错误返回:numshttps://www.yuque.com/chengxuyuancarl/wnx1np/ktwax2.size()-1
}
};
1.2 双指针
LeetCode27 | 移除元素
双指针解法
- 通过快指针判断条件,慢指针记录数组
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int slow = 0;
for (int fast = 0; fast < nums.size(); fast++) {
if(nums[fast] != val) {
nums[slow] = nums[fast];
slow ++;
// nums[slow++] = nums[fast];
}
}
return slow;
}
};
暴力解法
- 因为整体前移,所以下标需要前移一位
- 数组移除元素等价于将后面的元素覆盖当前位置元素(下标前移、数组个数缩小)
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int size = nums.size();
for (int i = 0; i < size; i++) {
if (nums[i] == val) {
for (int j = i+1; j < nums.size(); j++) {
nums[j-1] = nums[j];
}
i--; // 因为整体前移,所以下标需要前移一位
size--; // 数组大小手动缩小(类似ector的计数器)
}
}
return size;
}
};
------------------------------------------------------------
错误解法(没有移动下标):
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int res = 0;
for (int i = 0; i < nums.size(); i++) {
if (nums[i] == val) {
for (int j = i+1; j < nums.size(); j++) {
nums[j-1] = nums[j];
}
} else {
res ++;
}
}
return res;
}
};
LeetCode977 | 有序数组的平方
双指针
- 通过判断首尾指针对应数的大小确定填入哪个
- for循环终止条件是首位指针相遇
#include <cmath>
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
vector<int> res(nums.size(), 0);
int n = nums.size() - 1;
for (int i = 0, j = nums.size()-1; i <= j;) {
if (pow(nums[i],2) > pow(nums[j],2)) {
res[n--] = pow(nums[i],2);
i++;
} else {
res[n--] = pow(nums[j],2);
j--;
}
}
return res;
}
};
暴力解法
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
for(int i = 0; i < nums.size(); i++) {
nums[i] *= nums[i];
}
sort(nums.begin(), nums.end());
return nums;
}
};
1.3 滑动窗口
- 关注窗口左右边界更新条件及窗口内容
- 核心思路:不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果(窗口内容相关)
LeetCode209 | 长度最小的子数组
在这道题里,窗口内求和,右边界为for循环下标(可以遍历到所有情况),通过判断窗口内的和与目标值的关系确定是否需要更新左边界
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int res = INT32_MAX;
int sum = 0; // 滑动窗口数值之和
int i = 0; // 滑动窗口左边界
int subLength = 0; // 当前滑动窗口大小
for (int j = 0; j < nums.size(); j++) {
sum += nums[j];
// 如果总和超过target,则缩小滑动窗口
while (sum >= target) {
subLength = j - i + 1;
res = subLength < res ? subLength : res;
sum -= nums[i++];
}
}
return res < INT32_MAX ? res : 0;
}
};
LeetCode59 | 螺旋矩阵II
以左闭右闭的思路写,以填入数的上限为while循环条件,因为不确定是从哪个方向闭合最后一段,所以在每个方向for循环后都设置判断条件,如果上下或左右相接说明结束填写
- 关键点:遍历和更新矩阵的四个顶点,类似四个滑动窗口,边界点变为四个顶点,通过窗口内容填写矩阵
class Solution {
public:
vector<vector<int>> generateMatrix(int n) {
vector<vector<int>> res(n, vector<int>(n, 0));
int l = 0, r = n - 1;
int u = 0, d = n - 1;
int num = 1;
while (num < pow(n,2)+1) {
for (int i = l; i <= r; ++i) {
res[u][i] = num;
++ num;
}
if (++u > d) break;
for (int i = u; i <= d; ++i) {
res[i][r] = num;
++num;
}
if (--r < l) break;
for (int i = r; i >= l; --i) {
res[d][i] = num;
++num;
}
if (--d < u) break;
for (int i = d; i >= u; --i) {
res[i][l] = num;
++num;
}
if (++l > r) break;
}
return res;
}
};
二、链表
-
分类:单链表、双链表、循环链表
-
节点(数据域+指针域)
-
通过指针域的指针链接在内存中各个节点,非连续
-
while中条件为作用域内需要查找下一个节点的节点不为空指针
-
链表定义:
//单链表
struct ListNode {
int val;
ListNode *next;
ListNode(int x) : val(x), next(NULL) {}
};
2.1 移除链表
LeetCode203 | 移除链表元素
- 通过设置虚节点的方式统一头节点和非头节点的操作
- cur从dummyHead开始(创建、next指向head、cur设为dummyHead)
- 通过判断下一个节点是否为空指针,判断是否到达链表尾部
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
ListNode* dummyHead = new ListNode(0); //设置虚拟头节点
dummyHead->next = head;
ListNode* cur = dummyHead;
while (cur->next != NULL) {
if (cur->next->val == val) {
ListNode* temp = cur->next;
cur->next = cur->next->next;
delete temp;
} else {
cur = cur->next;
}
}
head = dummyHead->next;
delete dummyHead;
return head;
}
};
2.2 设计链表
LeetCode | 模板
- get是找到需要操作的节点,addAtIndex与deleteAtIndex都是找到需要操作的前一个节点
class MyLinkedList {
public:
// 定义链表节点结构体
// struct ListNode {
// int val;
// ListNode *next;
// ListNode(int val) : val(val), next(NULL) {};
// }
MyLinkedList() {
_dummyHead = new ListNode(0);
_size = 0; // 链表中节点个数,非下标,下标-1
}
int get(int index) {
if (index < 0 || index > _size - 1) {
return -1;
}
ListNode* cur = _dummyHead->next;
while (index--) {
cur = cur->next;
}
return cur->val;
}
void addAtHead(int val) {
ListNode* newNode = new ListNode(val);
newNode->next = _dummyHead->next;
_dummyHead->next = newNode;
_size++;
}
void addAtTail(int val) {
ListNode* newNode = new ListNode(val);
ListNode* cur = _dummyHead;
while (cur->next != NULL) {
cur = cur->next;
}
cur->next = newNode;
_size++;
}
void addAtIndex(int index, int val) {
if (index > _size) return; // 如果index大于链表的长度,则返回空;等于链表长度时,添加在尾部
if (index < 0) index = 0; // 如果index小于0,则在头部插入节点
ListNode* newNode = new ListNode(val);
ListNode* cur = _dummyHead;
while(index--) {
cur = cur->next;
}
newNode->next = cur->next;
cur->next = newNode;
_size++;
}
void deleteAtIndex(int index) {
if (index > _size-1 || index < 0) {
return; // 节点下标最大为_size-1
}
ListNode* cur = _dummyHead;
while(index--) {
cur = cur->next;
}
ListNode* temp = cur->next;
cur->next = cur->next->next;
delete temp;
temp = nullptr;
_size--;
}
private:
int _size;
ListNode* _dummyHead;
};
2.3 反转链表
LeetCode206 | 反转链表
双指针法:
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* temp; //保存cur的下一个节点,因为接下来要改变cur->next
ListNode* cur = head;
ListNode* pre = NULL;
while(cur) {
temp = cur->next;
cur->next = pre;
// 更新
pre = cur;
cur = temp;
}
return pre;
}
};
2.4 两两操作链表
- cur设置为要操作的链表节点前一个节点(操作1、2,cur=dummyHead;操作3、4,cur=新的1)
- 节点修改指向后,原指向的节点索引不到,因此需要先创建一个临时节点指向此节点(1、3)
LeetCode24 | 两两交换链表中的节点
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
ListNode* dummyHead = new ListNode(0);
dummyHead->next = head;
ListNode* cur = dummyHead;
while(cur->next != nullptr && cur->next->next != nullptr) {
ListNode* temp = cur->next; // 1
ListNode* temp1 = cur->next->next->next; // 3
cur->next = cur->next->next; // cur->2
cur->next->next = temp; // 2->1
cur->next->next->next = temp1; // 1->3
cur = cur->next->next; // cur=1
}
ListNode* result = dummyHead->next;
delete dummyHead;
return result;
}
};
2.5 操作链表中倒数第n个节点
- 通过快慢指针间隔n+1进行移动,直至快指针移动到队尾
LeetCode19 | 删除链表的倒数第N个节点
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* dummyHead = new ListNode(0);
dummyHead->next = head;
ListNode* slow = dummyHead;
ListNode* fast = dummyHead;
while(n-- && fast != nullptr) {
fast = fast->next;
}
fast = fast->next; //fast多走一步,使最后slow指向要删除节点的前一个节点
while(fast != nullptr) {
fast = fast->next;
slow = slow->next;
}
ListNode* temp = slow->next;
slow->next = slow->next->next;
delete temp;
return dummyHead->next;
}
};
2.6 找链表中相交的节点
LeetCode 面试题0207| 链表相交
- 交点不是数值相等,而是指针相等。求两个链表交点节点的指针
- 求出两个链表的长度,并求出两个链表长度的差值,然后让curA移动到,和curB 末尾对齐的位置
- 比较curA和curB是否相同,如果不相同,同时向后移动curA和curB,如果遇到curA == curB,则找到交点
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
ListNode* curA = headA;
ListNode* curB = headB;
int lenA = 0, lenB = 0;
while (curA != NULL) {
lenA++;
curA = curA->next;
}
while (curB != NULL) {
lenB++;
curB = curB->next;
}
curA = headA;
curB = headB;
if (lenB > lenA) {
swap(lenA, lenB);
swap(curA, curB);
}
int gap = lenA - lenB;
while(gap--) {
curA = curA->next;
}
while (curA != NULL) {
if (curA == curB) {
return curA;
}
curA = curA->next;
curB = curB->next;
}
return NULL;
}
};
2.7 环形列表
LeetCode142 | 环形链表II
- 判断是否有环:快慢指针,从头结点出发,fast指针每次移动两个节点,slow指针每次移动一个节点,如果 fast 和 slow指针在途中相遇 ,说明这个链表有环。
- 寻找环的入口:从头结点出发一个指针,从相遇节点也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是环形入口的节点。
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode* fast = head;
ListNode* slow = head;
while(fast != NULL && fast->next != NULL) {
slow = slow->next;
fast = fast->next->next;
if (slow == fast) {
ListNode* index1 = fast;
ListNode* index2 = head;
while (index1 != index2) {
index1 = index1->next;
index2 = index2->next;
}
return index1;
}
}
return NULL;
}
};
三、哈希表
- 快速判断一个元素是否出现集合里的时候,就要考虑哈希法
LeetCode242 | 有效的字母异位词
- 数组即为哈希表
class Solution {
public:
bool isAnagram(string s, string t) {
int record[26] = {0};
for (int i = 0; i < s.size(); i++) {
record[s[i]-'a']++;
}
for (int i = 0; i < t.size(); i++) {
record[t[i]-'a']--;
}
for (int i = 0; i < 26; i++) {
if (record[i] != 0) {
return false;
}
}
return true;
}
};
LeetCode349 | 两个数组的交集
- unordered_set.find() 结果不为unordered_set.end(),即为找到对应值
- 去重,查找,vector、unordered_set相互转换
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
unordered_set<int> res;
unordered_set<int> nums1_set(nums1.begin(), nums1.end());
for (int num : nums2) {
if (nums1_set.find(num) != nums1_set.end()) {
res.insert(num);
}
}
return vector<int>(res.begin(), res.end());
}
};
LeetCode202 | 快乐数
- 使用unordered_set判断结果是否进入循环
- 分离整数的各位数
class Solution {
public:
bool isHappy(int n) {
unordered_set<int> set;
while (1) {
int sum = 0;
while (n) {
sum += pow(n%10, 2);
n /= 10;
}
if (sum == 1) {
return true;
}
// 使用unordered_set判断是否循环
if (set.find(sum) != set.end()) {
return false;
} else {
set.insert(sum);
}
n = sum;
}
}
};
LeetCode1 | 两数之和
- 查找给出元素(target-nums[i])对应的下标i
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
unordered_map<int, int> map;
for (int i = 0; i < nums.size(); i++) {
auto iter = map.find(target-nums[i]);
if (iter != map.end()) {
return {i, iter->second};
}
map[nums[i]] = i;
}
return {};
}
};
LeetCode454 | 四数之和
- 两两分组,使用unordered_map记录第一组数求和出现次数,对第二组求和依次查找是否存在满足要求的数,并对出现的次数求和输出
class Solution {
public:
int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
unordered_map<int, int> map; // key: a+b value:出现的次数
for (int a : nums1) {
for (int b : nums2) {
map[a + b]++;
}
}
int count = 0;
for (int c : nums3) {
for (int d : nums4) {
if (map.find(-(c+d)) != map.end()) {
count += map[-(c+d)];
}
}
}
return count;
}
};
LeetCode383 | 赎金信
- 与242.有效的字母异位词思路一致
class Solution {
public:
bool canConstruct(string ransomNote, string magazine) {
int record[26] = {0};
for (int i = 0; i < magazine.size(); i++) {
record[magazine[i]-'a']++;
}
for (int i = 0; i < ransomNote.size(); i++) {
record[ransomNote[i]-'a']--;
if (record[ransomNote[i]-'a'] < 0) {
return false;
}
}
return true;
}
};
LeetCode15 | 三数之和
- for循环遍历第一数,重复则跳过
- 通过排序后数组头尾双指针收缩进行剩下两数的选择
- 找到三元组后,需进行后两数的去重
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> res;
sort(nums.begin(), nums.end());
for (int i = 0; i < nums.size(); i++){
if (nums[i] > 0) return res;
// 第一个数去重
if (i > 0 && nums[i] == nums[i-1]) continue;
// 错误去重a方法,将会漏掉-1,-1,2 这种情况
/*
if (nums[i] == nums[i + 1]) {
continue;
}
*/
int left = i+1, right = nums.size()-1;
while (left < right) {
if (nums[left] + nums[right] > -nums[i]) right--;
else if (nums[left] + nums[right] < -nums[i]) left++;
else {
res.push_back(vector<int>({nums[i], nums[left], nums[right]}));
// 找到一个三元组后,进行去重收缩
while (right > left && nums[right] == nums[right - 1]) right--;
while (right > left && nums[left] == nums[left + 1]) left++;
// 找到答案时,双指针同时收缩
right--;
left++;
}
}
}
return res;
}
};
LeetCode18 | 四数之和
-
在C++中,不同的数据类型表示不同范围的整数值。以下是各种整数数据类型的位数和范围:
-
int: 通常为32位,表示带符号的整数,范围约为 -2,147,483,648 到 2,147,483,647。
-
short: 通常为16位,表示带符号的短整数,范围约为 -32,768 到 32,767。
-
long: 通常为32位,表示带符号的长整数,范围约为 -2,147,483,648 到 2,147,483,647。
-
long long: 通常为64位,表示带符号的长长整数,范围约为 -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807。
-
-
与三数之和类似,通过两层for循环遍历前两个数,后两个数通过双指针收缩获得
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
vector<vector<int>> res;
sort(nums.begin(), nums.end());
for (int i = 0; i < nums.size(); i++) {
// 去重
if (i > 0 && nums[i] == nums[i-1]) continue;
for (int j = i+1; j < nums.size(); j++) {
// 去重
if (j > i+1 && nums[j] == nums[j-1]) continue;
int left = j + 1, right = nums.size() - 1;
while (left < right) {
if ((long)nums[left] + nums[right] < (long)target - nums[i] - nums[j]) left++;
else if ((long)nums[left] + nums[right] > (long)target - nums[i] - nums[j]) right--;
else {
res.push_back({nums[i], nums[j], nums[left], nums[right]});
// 去重
while(left < right && nums[right] == nums[right-1]) right--;
while(left < right && nums[left] == nums[left+1]) left++;
// 找到答案时收缩
right--;
left++;
}
}
}
}
return res;
}
};
四、字符串
- s.insert(startIdx, s)
- s.replace(startIdx, s)
- reverse(s.begin(), s.end())
- s.erase(iterator pos)
- s.erase(iterator fisrt, iterator last)
LeetCode151 | 反转字符串中的单词
stringstream解法:
class Solution {
public:
string reverseWords(string s) {
stringstream ss(s);
string word;
vector<string> words;
while (ss >> word) {
words.push_back(word);
}
reverse(words.begin(), words.end());
ostringstream oss;
for (int i = 0; i < words.size(); i++) {
if (i != 0) oss << ' ';
oss << words[i];
}
return oss.str();
}
};
LeetCode239 | 滑动窗口最大值
class Solution {
//维护有可能成为窗口里最大值的元素,同时保证队列里的元素数值是由大到小的。
// pop(value):如果窗口移除的元素value等于单调队列的出口元素,那么队列弹出元素,否则不用任何操作
// push(value):如果push的元素value大于入口元素的数值,那么就将队列入口的元素弹出,直到push元素的数值小于等于队列入口元素的数值为止
class MyQueue {
public:
deque<int> que;
void pop(int value) {
if (!que.empty() && value == que.front()) {
que.pop_front();
}
}
void push(int value) {
while (!que.emptya() && value > que.back()) {
que.pop_back();
}
que.push_back(value);
}
int front() {
return que.front();
}
};
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
MyQueue que;
vector<int> res;
for (int i = 0; i < k; i++) {
que.push(nums[i]);
}
res.push_back(que.front());
for (int i = k; i < nums.size(); i++) {
que.pop(nums[i-k]);
que.push(nums[i]);
res.push_back(que.front());
}
return res;
}
};
LeetCode347 | 前 K 个高频元素
- https://en.cppreference.com/w/cpp/container/priority_queue
- 命名结构体实现仿函数比较,用于实现定制化priority_queque小顶堆
class Solution {
public:
struct mycomparison{
bool operator()(const pair<int, int> &lhs, const pair<int, int> &rhs) {
return lhs.second > rhs.second;
}
};
vector<int> topKFrequent(vector<int>& nums, int k) {
unordered_map<int, int> mp;
for (int i = 0; i < nums.size(); i++) {
mp[nums[i]]++;
}
// 小顶堆
priority_queue<pair<int,int>, vector<pair<int,int>>, mycomparison> pri_que;
for (unordered_map<int, int>::iterator it = mp.begin(); it != mp.end(); it++) {
pri_que.push(*it);
if (pri_que.size() > k){
pri_que.pop();
}
}
vector<int> res(k);
for(int i = k-1; i >= 0; i--) {
res[i] = pri_que.top().first;
pri_que.pop();
}
return res;
}
};
LeetCode | 模板