From c487c28d717314eedc8f46f0839babcd57c43801 Mon Sep 17 00:00:00 2001 From: lihaibineric Date: Fri, 19 Jan 2024 21:38:54 +0800 Subject: [PATCH] Site updated: 2024-01-19 21:38:54 --- 2024/01/01/leetcode/index.html | 102 ++++++++++++++++++++++++++++++++- local-search.xml | 2 +- search.xml | 93 ++++++++++++++++++++++++++++++ 3 files changed, 193 insertions(+), 4 deletions(-) diff --git a/2024/01/01/leetcode/index.html b/2024/01/01/leetcode/index.html index 3fa585e..b6e757a 100644 --- a/2024/01/01/leetcode/index.html +++ b/2024/01/01/leetcode/index.html @@ -28,8 +28,11 @@ + + + - + @@ -215,7 +218,7 @@ - 16k words + 21k words @@ -226,7 +229,7 @@ - 137 mins + 179 mins @@ -598,8 +601,101 @@

删除链表的倒数第N个结点遍历,用两个指针分别来记录

如果要删除倒数第n个节点,让fast移动n步,然后让fast和slow同时移动,直到fast指向链表末尾。删掉slow所指向的节点就可以了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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 != NULL) {
fast = fast->next;
}
fast = fast->next; // fast再提前走一步,因为需要让slow指向删除节点的上一个节点
while (fast != NULL) {
fast = fast->next;
slow = slow->next;
}
slow->next = slow->next->next;

// ListNode *tmp = slow->next; C++释放内存的逻辑
// slow->next = tmp->next;
// delete nth;

return dummyHead->next;
}
};
+

链表相交

+

给你两个单链表的头节点 headAheadB +,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 +null

+

题目数据 保证 整个链式结构中不存在环。

+

注意,函数返回结果后,链表必须 +保持其原始结构

+

示例 1:

+

链表相交图

+
1
2
3
4
5
输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3
输出:Intersected at '8'
解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。
A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。
+

思路:

+

简单来说,就是求两个链表交点节点的指针,注意返回的是结点的指针,不是对应的数值,同时注意这里比较的是相同的指针不是数值相同,因此直接比较指针是不是相同就可以了

+

由于题目说的相交的结构如图所示,如果存在相交的指针位置,只可能出现在后面只需要考虑利用双指针从相差的数值位开始遍历

+
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
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;
}
};
+

环形链表

+

https://leetcode.cn/problems/linked-list-cycle-ii/description/

+

判断是否是有还存在,如果有那么返回开始入环的第一个节点的下标

+

环形链表

+
1
2
3
输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。
+

思路:

+

这道题用快慢指针的思路,就是慢指针每次只走一步,快指针每次走两步,如果在到达null之前出现快慢指针指向了同一个地方,说明这个链表有环存在,那么怎么判断下标的位置呢?

+

具体的证明过程:

+
+ + +
+

相遇时slow指针走过的节点数为: x + y, +fast指针走过的节点数:x + y + n (y + z),n为fast指针在环内走了n圈才遇到slow指针, +(y+z)为 一圈内节点的个数A。

+

因为fast指针是一步走两个节点,slow指针一步走一个节点, 所以 +fast指针走过的节点数 = slow指针走过的节点数 * 2:

+
1
(x + y) * 2 = x + y + n (y + z)
+

两边消掉一个(x+y): x + y = n (y + z)

+

因为要找环形的入口,那么要求的是x,因为x表示 头结点到 +环形入口节点的的距离。

+

所以要求x ,将x单独放在左面:x = n (y + z) - y ,

+

再从n(y+z)中提出一个 +(y+z)来,整理公式之后为如下公式:x = (n - 1) (y + z) + z +注意这里n一定是大于等于1的,因为 +fast指针至少要多走一圈才能相遇slow指针

+

所以可以得到的规律是:从头结点出发一个指针,从相遇节点 +也出发一个指针,这两个指针每次只走一个节点, +那么当这两个指针相遇的时候就是 环形入口的节点

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
public:
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 index2;
}
}
return NULL;
}
};

二叉树

深搜回溯

+

深度优先搜索的三部曲:

+
    +
  1. 确定搜索函数的返回值以及搜索函数的参数分别是什么
  2. +
  3. 确定每次找到叶子结点的终止条件
  4. +
  5. 确定for单层搜索的逻辑,包含push,backtracking,pop
  6. +
+

别忘了最开始的初始化步骤

+

组合问题

+

https://leetcode.cn/problems/combinations/description/

+

给定两个整数 nk,返回范围 +[1, n] 中所有可能的 k 个数的组合。

+

你可以按 任何顺序 返回答案。

+

示例 1:

+
1
2
3
4
5
6
7
8
9
10
输入:n = 4, k = 2
输出:
[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]
+

示例 2:

+
1
2
输入:n = 1, k = 1
输出:[[1]]
+

思路,使用深度优先搜索算法进行处理

+ +
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
private:
vector<vector<int>> result;
vector<int>path;
void backtracking(int n,int k, int startindex){
if(path.size()==k){
result.push_back(path);
return;
}
for(int i = startindex;i<=n;i++){
path.push_back(i);
backtracking(n,k,i+1);
path.pop_back();
}
}
public:
vector<vector<int>> combine(int n, int k){
backtracking(n,k,1);
return result;
}
};
+

组合问题III

+

https://leetcode.cn/problems/combination-sum-iii/submissions/496823507/

+

思路:简单的深度优先搜索,但需要注意的是可以适当采用减枝操作和必要的时候添加sum变量进行记录

+
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 {
private:
vector<vector<int>> result;
vector<int> path;
void backtacking(int k, int n,int startindex, int sum){
if(path.size()==k){
if(sum == n) result.push_back(path);
return;
}
for(int i= startindex;i<=9;i++){
sum+=i;
path.push_back(i);
backtacking(k,n,i+1,sum);
sum-=i;
path.pop_back();
}
}
public:
vector<vector<int>> combinationSum3(int k, int n) {
backtacking(k,n,1,0);
return result;
}
};
+

为了优化可以做一个剪枝操作

+
1
2
3
if (sum > targetSum) { // 剪枝操作
return;
}
+

电话号码组合问题

+

给定一个仅包含数字 2-9 +的字符串,返回所有它能表示的字母组合。答案可以按 +任意顺序 返回。

+

给出数字到字母的映射如下(与电话按键相同)。注意 1 +不对应任何字母。

+

电话号码的按键

+

示例 :

+
1
2
输入:digits = "23"
输出:["ad","ae","af","bd","be","bf","cd","ce","cf"]
+

这道题需要注意的地方是,首先第一步做好map字符的映射

+

第二步最关键是要写清楚回溯函数的参数可能包含index,就是第几位置的字符,同时需要区分backtracking函数的for循环的内容是相当于横向的遍历,而函数体内部的实现是纵向的遍历

+
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
class Solution {
private:
const string letterMap[10] = {
"", // 0
"", // 1
"abc", // 2
"def", // 3
"ghi", // 4
"jkl", // 5
"mno", // 6
"pqrs", // 7
"tuv", // 8
"wxyz", // 9
};
vector<string> result;
string s;
void backtracking(const string digits,int index, string s){
if(digits.size()==0){
return;
}
if(index==digits.size()){
result.push_back(s);
return;
}
int digit = digits[index]-'0';
string letters = letterMap[digit];
for(int i= 0;i<letters.size();i++){
s.push_back(letters[i]);
backtracking(digits, index+1, s);
s.pop_back();
}
}
public:
vector<string> letterCombinations(string digits) {
backtracking(digits,0,"");
return result;
}
};

贪心算法

动态规划

diff --git a/local-search.xml b/local-search.xml index 9616ae0..cf52e68 100644 --- a/local-search.xml +++ b/local-search.xml @@ -114,7 +114,7 @@ /2024/01/01/leetcode/ -

语言细节

vector的长度:

初始化数组:

构造vector:

for循环:

数组

二分查找

题目描述

链接:https://leetcode.cn/problems/binary-search/description/

给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回-1。

示例 1:

1
2
3
输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4

示例 2:

1
2
3
输入: nums = [-1,0,3,5,9,12], target = 2
输出: -1
解释: 2 不存在 nums 中因此返回 -1

思路

题目表示的是有序数组,而且题目没有重复元素。在二分查找的过程中,保持不变量,就是在while寻找中每一次边界的处理都要坚持根据区间的定义来操作,这就是循环不变量规则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution:
def search(self, nums: List[int], target: int) -> int:
left, right = 0, len(nums) - 1 # 定义target在左闭右闭的区间里,[left, right]

while left <= right:
middle = left + (right - left) // 2

if nums[middle] > target:
right = middle - 1 # target在左区间,所以[left, middle - 1]
elif nums[middle] < target:
left = middle + 1 # target在右区间,所以[middle + 1, right]
else:
return middle # 数组中找到目标值,直接返回下标
return -1 # 未找到目标值

注意这里给出的题解法:当left <= right的时候,以下的条件中全部都不取到等号nums[middle] > target nums[middle] < target

需要注意的是:right=nums.size()-1

C++版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Solution {
public:
int search(vector<int>& nums, int target) {
int left=0;
int right=nums.size()-1;
while(left<=right)
{
// int middle = (left+right)/2; 这样写会溢出
int middle = left + ((right - left) / 2);
if(nums[middle]>target)
{
right = middle-1;
}
else if(nums[middle]<target)
{
left = middle+1;
}
else{
return middle;
}
}
return -1;
}
};

Go版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func search(nums []int, target int) int {
right:=len(nums)-1
left:=0
for left<=right{
middle:= left+(right-left)/2
if nums[middle]<target{
left = middle+1
}else if nums[middle]>target{
right = middle-1
}else{
return middle
}
}
return -1
}

移除元素

https://leetcode.cn/problems/remove-element/description/

题目描述

示例 1:

1
2
3
输入:nums = [3,2,2,3], val = 3
输出:2, nums = [2,2]
解释:函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。你不需要考虑数组中超出新长度后面的元素。例如,函数返回的新长度为 2 ,而 nums = [2,2,3,3] 或 nums = [2,2,0,0],也会被视作正确答案。

示例 2:

1
2
3
输入:nums = [0,1,2,2,3,0,4,2], val = 2
输出:5, nums = [0,1,3,0,4]
解释:函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。注意这五个元素可为任意顺序。你不需要考虑数组中超出新长度后面的元素。

思路

双指针法(快慢指针法):通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。

定义快慢指针

双指针题解

C++版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int slowindex=0;
for(int fastindex = 0; fastindex<nums.size();fastindex++)
{
if(val!=nums[fastindex]){
nums[slowindex] = nums[fastindex];
slowindex++;
}
}
return slowindex;
}
};

python版本

1
2
3
4
5
6
7
8
9
10
class Solution(object):
def removeElement(self, nums, val):
slowindex=0
fastindex=0
while fastindex<len(nums):
if val!=nums[fastindex]:
nums[slowindex]=nums[fastindex]
slowindex = slowindex+1
fastindex+=1
return slowindex

GO版本:

1
2
3
4
5
6
7
8
9
10
func removeElement(nums []int, val int) int {
slow:=0
for i:=0;i<len(nums);i++{
if nums[i]!=val{
nums[slow]=nums[i]
slow++
}
}
return slow
}

有序数组的平方

https://leetcode.cn/problems/squares-of-a-sorted-array/

题目描述

示例 1:

1
2
3
4
输入:nums = [-4,-1,0,3,10]
输出:[0,1,9,16,100]
解释:平方后,数组变为 [16,1,0,9,100]
排序后,数组变为 [0,1,9,16,100]

示例 2:

1
2
输入:nums = [-7,-3,2,3,11]
输出:[4,9,9,49,121]

思路

双指针法,首尾遍历比较并存储

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
vector<int> result(nums.size(),0);
int j = nums.size()-1;
int k =j;
for(int i = 0 ;i<=j;)
{
if(nums[i]*nums[i]>nums[j]*nums[j]){
result[k--]= nums[i]*nums[i];
i++;
}else{
result[k--]= nums[j]*nums[j];
j--;
}
}
return result;
}
};

Python:

1
2
3
4
5
6
7
8
9
10
11
12
class Solution(object):
def sortedSquares(self, nums):
l, r, i = 0, len(nums)-1, len(nums)-1
res = [float('inf')] * len(nums) # 需要提前定义列表,存放结果
while l<=r :
if nums[l]*nums[l] < nums[r]*nums[r] :
res[i--]=nums[r]*nums[r]
r--
else:
res[i--]=nums[l]*nums[l]
l++
return

GO:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func sortedSquares(nums []int) []int {
n := len(nums)
i, j, k := 0, n-1, n-1
ans := make([]int, n)
for i <= j {
lm, rm := nums[i]*nums[i], nums[j]*nums[j]
if lm > rm {
ans[k] = lm
i++
} else {
ans[k] = rm
j--
}
k--
}
return ans
}

长度最小的子数组

https://leetcode.cn/problems/minimum-size-subarray-sum/description/

题目描述

给定一个含有 n 个正整数的数组和一个正整数target

找出该数组中满足其总和大于等于 target 的长度最小的连续子数组[numsl, numsl+1, ..., numsr-1, numsr],并返回其长度如果不存在符合条件的子数组,返回0

示例 1:

1
2
3
输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。

示例 2:

1
2
输入:target = 4, nums = [1,4,4]
输出:1

示例 3:

1
2
输入:target = 11, nums = [1,1,1,1,1,1,1,1]
输出:0

思路

滑动窗口法

滑动窗口法

滑动窗口也可以理解为双指针法的一种!只不过这种解法更像是一个窗口的移动本题中实现滑动窗口,主要确定如下三点:

窗口就是 满足其和 ≥ s 的长度最小的 连续 子数组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public:
int minSubArrayLen(int s, vector<int>& nums) {
int result = INT32_MAX;
int sum = 0; // 滑动窗口数值之和
int i = 0; // 滑动窗口起始位置
int subLength = 0; // 滑动窗口的长度
for (int j = 0; j < nums.size(); j++) {
sum += nums[j];
// 注意这里使用while,每次更新 i(起始位置),并不断比较子序列是否符合条件
while (sum >= s) {
subLength = (j - i + 1); // 取子序列的长度
result = result < subLength ? result : subLength;
sum -= nums[i++]; // 这里体现出滑动窗口的精髓之处,不断变更i(子序列的起始位置)
}
}
// 如果result没有被赋值的话,就返回0,说明没有符合条件的子序列
return result == INT32_MAX ? 0 : result;
}
};

螺旋矩阵

https://leetcode.cn/problems/spiral-matrix-ii/

题目描述

螺旋矩阵

给你一个正整数 n ,生成一个包含 1n2 所有元素,且元素按顺时针顺序螺旋排列的n x n 正方形矩阵 matrix

1
2
输入:n = 3
输出:[[1,2,3],[8,9,4],[7,6,5]]

示例 2:

1
2
输入:n = 1
输出:[[1]]

思路:大模拟循环遍历

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
class Solution {
public:
vector<vector<int>> generateMatrix(int n) {
vector<vector<int>> result(n, vector<int>(n,0));
int is=0,ie=n-1,js=0,je=n-1;
int k = 1;
while(is<=ie&&js<=je){
for(int j=js;j<=je;j++)
{
result[is][j] = k++;
}
is++;
for(int i =is;i<=ie;i++)
{
result[i][je] = k++;
}
je--;
for(int j=je;j>=js;j--)
{
result[ie][j] = k++;
}
ie--;
for(int i=ie;i>=is;i--)
{
result[i][js] = k++;
}
js++;
}
return result;
}
};

哈希表

一般哈希表都是用来快速判断一个元素是否出现集合里

只需要初始化把所有元素都存在哈希表里,在查询的时候通过索引直接就可以知道元素在不在这哈希表里了

建立索引:哈希函数

有效的字母异位词

https://leetcode.cn/problems/valid-anagram/description/

题目描述

给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s的字母异位词。

示例 1: 输入: s = "anagram", t = "nagaram" 输出: true

示例 2: 输入: s = "rat", t = "car" 输出: false

思路

暴力的方法可能时间复杂度会很高

判断有没有异位词的本质就是查看当前的字母是不是有出现过,那么思路就是选择哈希表

定义一个数组叫做record用来上记录字符串s里字符出现的次数。

需要把字符映射到数组也就是哈希表的索引下标上,因为字符a到字符z的ASCII是26个连续的数值,所以字符a映射为下标0,相应的字符z映射为下标25。

再遍历 字符串s的时候,只需要将 s[i] - ‘a’ 所在的元素做+1操作即可,并不需要记住字符a的ASCII,只要求出一个相对数值就可以了。这样就将字符串s中字符出现的次数,统计出来了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
public:
bool isAnagram(string s, string t) {
int record[26] = {0};
for (int i = 0; i < s.size(); i++) {
// 并不需要记住字符a的ASCII,只要求出一个相对数值就可以了
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) {
// record数组如果有的元素不为零0,说明字符串s和t 一定是谁多了字符或者谁少了字符。
return false;
}
}
// record数组所有元素都为零0,说明字符串s和t是字母异位词
return true;
}
};

两个数组的交集

https://leetcode.cn/problems/intersection-of-two-arrays/description/

题目描述

示例 1:

1
2
输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2]

示例 2:

1
2
3
输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[9,4]
解释:[4,9] 也是可通过的

思路

使用哈希表存储,但是用set(unordered_set)

std::set和std::multiset底层实现都是红黑树,std::unordered_set的底层实现是哈希表,使用unordered_set读写效率是最高的,并不需要对数据进行排序,而且还不要让数据重复,所以选择unordered_set

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
unordered_set<int> result_set; // 存放结果,之所以用set是为了给结果集去重
unordered_set<int> nums_set(nums1.begin(), nums1.end());
for (int num : nums2) {
// 发现nums2的元素 在nums_set里又出现过
if (nums_set.find(num) != nums_set.end()) {
result_set.insert(num);
}
}
return vector<int>(result_set.begin(), result_set.end());
}
};

快乐数

https://leetcode.cn/problems/happy-number/description/

题目描述

编写一个算法来判断一个数 n 是不是快乐数。

「快乐数」 定义为:

如果 n快乐数 就返回 true;不是,则返回 false

示例 1:

1
2
3
4
5
6
7
输入:n = 19
输出:true
解释:
1**2 + 9**2 = 82
8**2 + 2**2 = 68
6**2 + 8**2 = 100
1**2 + 0**2 + 0**2 = 1

思路:

注意,题目中提到一个点是无限循环,说明计算的结果sum是有限的只需要在哈希表中将这部分的结果存储进去,并每次比较是不是出现1如果是那么就是快乐数,否则就不是快乐数

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
class Solution {
public:
int getSum(int n){
int sum=0;
while(n){
sum+=(n%10)*(n%10);
n/=10;
}
return sum;
}
bool isHappy(int n) {
//首先建立哈希表来存储是不是出现了无限循环的结果
unordered_set<int>sum_set;
//无限循环 直到出现1或者无限循环且不是快乐数
while(1){
n=getSum(n);
if(sum_set.find(n)!=sum_set.end()){
return false;
}else{
sum_set.insert(n);
}
if(n==1){
return true;
}
}
}
};

两数之和

题目描述

https://leetcode.cn/problems/two-sum/submissions/495021134/

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

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现

你可以按任意顺序返回答案。

示例 1:

1
2
3
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1]

示例 2:

1
2
输入:nums = [3,2,4], target = 6
输出:[1,2]

示例 3:

1
2
输入:nums = [3,3], target = 6
输出:[0,1]

思路:

构建一个哈希表,然后遍历一遍就行了在哈希表中找n-a的值是否存在,但是最大的问题是数组中同一个元素在答案里不能重复出现,所以不能简单考虑unordered_set

这里提供一种新的思路,就是用unordered_map来存储数组中的数据内容和下标的数值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
std::unordered_map <int,int> map;
for(int i = 0; i < nums.size(); i++) {
// 遍历当前元素,并在map中寻找是否有匹配的key
auto iter = map.find(target - nums[i]);
if(iter != map.end()) {
return {iter->second, i};
}
// 如果没找到匹配对,就把访问过的元素和下标加入到map中
map.insert(pair<int, int>(nums[i], i));
}
return {};
}
};

四数相加

https://leetcode.cn/problems/4sum-ii/description/

给你四个整数数组nums1nums2nums3nums4 ,数组长度都是 n ,请你计算有多少个元组(i, j, k, l) 能满足:

示例 1:

1
2
3
4
5
6
输入:nums1 = [1,2], nums2 = [-2,-1], nums3 = [-1,2], nums4 = [0,2]
输出:2
解释:
两个元组如下:
1. (0, 0, 0, 1) -> nums1[0] + nums2[0] + nums3[0] + nums4[1] = 1 + (-2) + (-1) + 2 = 0
2. (1, 1, 0, 0) -> nums1[1] + nums2[1] + nums3[0] + nums4[0] = 2 + (-1) + (-1) + 0 = 0

示例 2:

1
2
输入:nums1 = [0], nums2 = [0], nums3 = [0], nums4 = [0]
输出:1

思路

  1. 首先定义 一个unordered_map,key放a和b两数之和,value放a和b两数之和出现的次数
  2. 遍历大A和大B数组,统计两个数组元素之和,和出现的次数,放到map中。
  3. 定义int变量count,用来统计 a+b+c+d = 0 出现的次数。
  4. 在遍历大C和大D数组,找到如果 0-(c+d)在map中出现过的话,就用count把map中key对应的value也就是出现次数统计出来。
  5. 最后返回统计值 count 就可以了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
public:
int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
std::unordered_map<int,int>nm;
int res=0;

for(int i=0;i<nums1.size();i++){
for(int j=0;j<nums2.size();j++){
int s = nums1[i]+nums2[j];
nm[s]++;
}
}
for(int i=0;i<nums3.size();i++){
for(int j=0;j<nums4.size();j++){
if(nm.find(0-nums3[i]-nums4[j])!=nm.end()){
res+=nm[0-(nums3[i]+nums4[j])];
}
}
}
return res;
}
};

赎金信

https://leetcode.cn/problems/ransom-note/description/

给你两个字符串:ransomNotemagazine,判断 ransomNote 能不能由 magazine里面的字符构成。

如果可以,返回 true ;否则返回 false

magazine 中的每个字符只能在 ransomNote中使用一次。

示例 1:

1
2
输入:ransomNote = "a", magazine = "b"
输出:false

示例 2:

1
2
输入:ransomNote = "aa", magazine = "ab"
输出:false

思路:

用哈希表unordered_map来存储次数,对于ransomNote来减去次数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public:
bool canConstruct(string ransomNote, string magazine) {
unordered_map<int,int>umap;
if(ransomNote.size()>magazine.size()){return false;}
for(int i=0;i<magazine.size();i++){
umap[magazine[i]-'a']++;
}
for(int i=0;i<ransomNote.size();i++){
if(umap.find(ransomNote[i]-'a')!=umap.end()){
umap[ransomNote[i]-'a']--;
if(umap[ransomNote[i]-'a']<0) {return false;}
}else{
return false;
}
}
return true;
}
};

三数之和

https://leetcode.cn/problems/3sum/description/

给你一个整数数组 nums ,判断是否存在三元组[nums[i], nums[j], nums[k]] 满足i != ji != kj != k,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请

你返回所有和为 0 且不重复的三元组。

注意:答案中不可以包含重复的三元组。

示例 1:

1
2
3
4
5
6
7
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
解释:
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
不同的三元组是 [-1,0,1][-1,-1,2]

思路

其实这道题目使用哈希法并不十分合适,因为在去重的操作中有很多细节需要注意,在面试中很难直接写出没有bug的代码,而且使用哈希法在使用两层for循环的时候,能做的剪枝操作很有限,虽然时间复杂度是O(n^2)

这道题可以用双指针法求解

拿这个nums数组来举例,首先将数组排序,然后有一层for循环,i从下标0的地方开始,同时定一个下标left定义在i+1的位置上,定义下标right 在数组结尾的位置上

依然还是在数组中找到 abc 使得a + b +c =0,我们这里相当于 a =nums[i],b = nums[left],c = nums[right]。

接下来如何移动left 和right呢, 如果nums[i] + nums[left] + nums[right]> 0 就说明此时三数之和大了,因为数组是排序后了,所以right下标就应该向左移动,这样才能让三数之和小一些。

如果 nums[i] + nums[left] + nums[right] < 0 说明 此时三数之和小了,left就向右移动,才能让三数之和大一些,直到left与right相遇为止

还有一个难度就是不能有重复的结果,需要做一次去重的操作

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 Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> result;
sort(nums.begin(), nums.end());
// 找出a + b + c = 0
// a = nums[i], b = nums[left], c = nums[right]
for (int i = 0; i < nums.size(); i++) {
// 排序之后如果第一个元素已经大于零,那么无论如何组合都不可能凑成三元组,直接返回结果就可以了
if (nums[i] > 0) {
return result;
}
// 正确去重a方法
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
int left = i + 1;
int right = nums.size() - 1;
while (right > left) {
if (nums[i] + nums[left] + nums[right] > 0) right--;
else if (nums[i] + nums[left] + nums[right] < 0) left++;
else {
result.push_back(vector<int>{nums[i], nums[left], nums[right]});
// 去重逻辑应该放在找到一个三元组之后,对b 和 c去重
while (right > left && nums[right] == nums[right - 1]) right--;
while (right > left && nums[left] == nums[left + 1]) left++;
// 找到答案时,双指针同时收缩
right--;
left++;
}
}

}
return result;
}
};

双指针

移除元素

https://leetcode.cn/problems/remove-element/description/

示例 1:

1
2
3
输入:nums = [3,2,2,3], val = 3
输出:2, nums = [2,2]
解释:函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。你不需要考虑数组中超出新长度后面的元素。例如,函数返回的新长度为 2 ,而 nums = [2,2,3,3] 或 nums = [2,2,0,0],也会被视作正确答案。

示例 2:

1
2
3
输入:nums = [0,1,2,2,3,0,4,2], val = 2
输出:5, nums = [0,1,3,0,4]
解释:函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。注意这五个元素可为任意顺序。你不需要考虑数组中超出新长度后面的元素。

思路:

使用快慢指针来实现两个指针之间的移动,对于找到了和val数值一样的就进行替换

反转字符串

https://leetcode.cn/problems/reverse-string/description/

示例 1:

1
2
输入:s = ["h","e","l","l","o"]
输出:["o","l","l","e","h"]

示例 2:

1
2
输入:s = ["H","a","n","n","a","h"]
输出:["h","a","n","n","a","H"]

思路:

采用两个指针之间互相交换,首尾交换

1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
public:
void reverseString(vector<char>& s) {
for(int a=0, b = s.size()-1;a<b;){
char tmp;
tmp=s[a];
s[a]=s[b];
s[b]=tmp;
a++;
b--;
}
}
};

反转字符串中的单词

https://leetcode.cn/problems/reverse-words-in-a-string/description/

示例 1:

1
2
输入:s = "the sky is blue"
输出:"blue is sky the"

示例 2:

1
2
3
输入:s = "  hello world  "
输出:"world hello"
解释:反转后的字符串中不能存在前导空格和尾随空格。

示例 3:

1
2
3
输入:s = "a good   example"
输出:"example good a"
解释:如果两个单词间有多余的空格,反转后的字符串需要将单词间的空格减少到仅有一个。

思路:

首先对字符串中额外的空格进行删除

字符串进行全局的逆序

再根据空格作为一个单独字母的节点进行分格分别进行逆序

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
class Solution {
public:
string reverseWords(string s) {
for(int i = s.size()-1;i>0;i--){
if(s[i]==s[i-1]&&s[i]==' '){
s.erase(s.begin()+i);
}
}
if(s.size()>0&&s[s.size()-1]==' '){
s.erase(s.begin()+s.size()-1);
}
if(s.size()>0&&s[0]==' '){
s.erase(s.begin());
}

//完成全局的交换
for(int i = 0, j=s.size()-1;i<j;i++,j--){
char tmp;
tmp = s[i];
s[i] = s[j];
s[j] = tmp;
}
cout<<s;
//进行局部的交换
int i=0;
int j=1;
while(j<=s.size()){
if(s[j]==' '||j==s.size()){
for(int k =i, q =j-1;k<q;k++,q--){
char tmp;
tmp = s[k];
s[k] = s[q];
s[q] = tmp;
}
i=j+1;
j=i+1;
}else{
j++;
}
}
return s;
}
};

反转链表

https://leetcode.cn/problems/reverse-linked-list/description/

image-20240118151406885

1
2
输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]

思路:本质上就是利用了两个链表指针实现对元素的转向

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* temp;
ListNode* cur = head;
ListNode* pre = nullptr;
while(cur){
temp = cur->next;
cur->next = pre;
pre = cur;
cur = temp;
}
return pre;
}
};

删除链表的倒数第N个结点

https://leetcode.cn/problems/remove-nth-node-from-end-of-list/description/

image-20240118152000575

1
2
输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]

示例 2:

1
2
输入:head = [1], n = 1
输出:[]

思路:

遍历,用两个指针分别来记录

如果要删除倒数第n个节点,让fast移动n步,然后让fast和slow同时移动,直到fast指向链表末尾。删掉slow所指向的节点就可以了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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 != NULL) {
fast = fast->next;
}
fast = fast->next; // fast再提前走一步,因为需要让slow指向删除节点的上一个节点
while (fast != NULL) {
fast = fast->next;
slow = slow->next;
}
slow->next = slow->next->next;

// ListNode *tmp = slow->next; C++释放内存的逻辑
// slow->next = tmp->next;
// delete nth;

return dummyHead->next;
}
};

二叉树

深搜回溯

贪心算法

动态规划

]]>
+

语言细节

vector的长度:

初始化数组:

构造vector:

for循环:

数组

二分查找

题目描述

链接:https://leetcode.cn/problems/binary-search/description/

给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回-1。

示例 1:

1
2
3
输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4

示例 2:

1
2
3
输入: nums = [-1,0,3,5,9,12], target = 2
输出: -1
解释: 2 不存在 nums 中因此返回 -1

思路

题目表示的是有序数组,而且题目没有重复元素。在二分查找的过程中,保持不变量,就是在while寻找中每一次边界的处理都要坚持根据区间的定义来操作,这就是循环不变量规则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution:
def search(self, nums: List[int], target: int) -> int:
left, right = 0, len(nums) - 1 # 定义target在左闭右闭的区间里,[left, right]

while left <= right:
middle = left + (right - left) // 2

if nums[middle] > target:
right = middle - 1 # target在左区间,所以[left, middle - 1]
elif nums[middle] < target:
left = middle + 1 # target在右区间,所以[middle + 1, right]
else:
return middle # 数组中找到目标值,直接返回下标
return -1 # 未找到目标值

注意这里给出的题解法:当left <= right的时候,以下的条件中全部都不取到等号nums[middle] > target nums[middle] < target

需要注意的是:right=nums.size()-1

C++版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Solution {
public:
int search(vector<int>& nums, int target) {
int left=0;
int right=nums.size()-1;
while(left<=right)
{
// int middle = (left+right)/2; 这样写会溢出
int middle = left + ((right - left) / 2);
if(nums[middle]>target)
{
right = middle-1;
}
else if(nums[middle]<target)
{
left = middle+1;
}
else{
return middle;
}
}
return -1;
}
};

Go版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func search(nums []int, target int) int {
right:=len(nums)-1
left:=0
for left<=right{
middle:= left+(right-left)/2
if nums[middle]<target{
left = middle+1
}else if nums[middle]>target{
right = middle-1
}else{
return middle
}
}
return -1
}

移除元素

https://leetcode.cn/problems/remove-element/description/

题目描述

示例 1:

1
2
3
输入:nums = [3,2,2,3], val = 3
输出:2, nums = [2,2]
解释:函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。你不需要考虑数组中超出新长度后面的元素。例如,函数返回的新长度为 2 ,而 nums = [2,2,3,3] 或 nums = [2,2,0,0],也会被视作正确答案。

示例 2:

1
2
3
输入:nums = [0,1,2,2,3,0,4,2], val = 2
输出:5, nums = [0,1,3,0,4]
解释:函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。注意这五个元素可为任意顺序。你不需要考虑数组中超出新长度后面的元素。

思路

双指针法(快慢指针法):通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。

定义快慢指针

双指针题解

C++版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int slowindex=0;
for(int fastindex = 0; fastindex<nums.size();fastindex++)
{
if(val!=nums[fastindex]){
nums[slowindex] = nums[fastindex];
slowindex++;
}
}
return slowindex;
}
};

python版本

1
2
3
4
5
6
7
8
9
10
class Solution(object):
def removeElement(self, nums, val):
slowindex=0
fastindex=0
while fastindex<len(nums):
if val!=nums[fastindex]:
nums[slowindex]=nums[fastindex]
slowindex = slowindex+1
fastindex+=1
return slowindex

GO版本:

1
2
3
4
5
6
7
8
9
10
func removeElement(nums []int, val int) int {
slow:=0
for i:=0;i<len(nums);i++{
if nums[i]!=val{
nums[slow]=nums[i]
slow++
}
}
return slow
}

有序数组的平方

https://leetcode.cn/problems/squares-of-a-sorted-array/

题目描述

示例 1:

1
2
3
4
输入:nums = [-4,-1,0,3,10]
输出:[0,1,9,16,100]
解释:平方后,数组变为 [16,1,0,9,100]
排序后,数组变为 [0,1,9,16,100]

示例 2:

1
2
输入:nums = [-7,-3,2,3,11]
输出:[4,9,9,49,121]

思路

双指针法,首尾遍历比较并存储

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
vector<int> result(nums.size(),0);
int j = nums.size()-1;
int k =j;
for(int i = 0 ;i<=j;)
{
if(nums[i]*nums[i]>nums[j]*nums[j]){
result[k--]= nums[i]*nums[i];
i++;
}else{
result[k--]= nums[j]*nums[j];
j--;
}
}
return result;
}
};

Python:

1
2
3
4
5
6
7
8
9
10
11
12
class Solution(object):
def sortedSquares(self, nums):
l, r, i = 0, len(nums)-1, len(nums)-1
res = [float('inf')] * len(nums) # 需要提前定义列表,存放结果
while l<=r :
if nums[l]*nums[l] < nums[r]*nums[r] :
res[i--]=nums[r]*nums[r]
r--
else:
res[i--]=nums[l]*nums[l]
l++
return

GO:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func sortedSquares(nums []int) []int {
n := len(nums)
i, j, k := 0, n-1, n-1
ans := make([]int, n)
for i <= j {
lm, rm := nums[i]*nums[i], nums[j]*nums[j]
if lm > rm {
ans[k] = lm
i++
} else {
ans[k] = rm
j--
}
k--
}
return ans
}

长度最小的子数组

https://leetcode.cn/problems/minimum-size-subarray-sum/description/

题目描述

给定一个含有 n 个正整数的数组和一个正整数target

找出该数组中满足其总和大于等于 target 的长度最小的连续子数组[numsl, numsl+1, ..., numsr-1, numsr],并返回其长度如果不存在符合条件的子数组,返回0

示例 1:

1
2
3
输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。

示例 2:

1
2
输入:target = 4, nums = [1,4,4]
输出:1

示例 3:

1
2
输入:target = 11, nums = [1,1,1,1,1,1,1,1]
输出:0

思路

滑动窗口法

滑动窗口法

滑动窗口也可以理解为双指针法的一种!只不过这种解法更像是一个窗口的移动本题中实现滑动窗口,主要确定如下三点:

窗口就是 满足其和 ≥ s 的长度最小的 连续 子数组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public:
int minSubArrayLen(int s, vector<int>& nums) {
int result = INT32_MAX;
int sum = 0; // 滑动窗口数值之和
int i = 0; // 滑动窗口起始位置
int subLength = 0; // 滑动窗口的长度
for (int j = 0; j < nums.size(); j++) {
sum += nums[j];
// 注意这里使用while,每次更新 i(起始位置),并不断比较子序列是否符合条件
while (sum >= s) {
subLength = (j - i + 1); // 取子序列的长度
result = result < subLength ? result : subLength;
sum -= nums[i++]; // 这里体现出滑动窗口的精髓之处,不断变更i(子序列的起始位置)
}
}
// 如果result没有被赋值的话,就返回0,说明没有符合条件的子序列
return result == INT32_MAX ? 0 : result;
}
};

螺旋矩阵

https://leetcode.cn/problems/spiral-matrix-ii/

题目描述

螺旋矩阵

给你一个正整数 n ,生成一个包含 1n2 所有元素,且元素按顺时针顺序螺旋排列的n x n 正方形矩阵 matrix

1
2
输入:n = 3
输出:[[1,2,3],[8,9,4],[7,6,5]]

示例 2:

1
2
输入:n = 1
输出:[[1]]

思路:大模拟循环遍历

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
class Solution {
public:
vector<vector<int>> generateMatrix(int n) {
vector<vector<int>> result(n, vector<int>(n,0));
int is=0,ie=n-1,js=0,je=n-1;
int k = 1;
while(is<=ie&&js<=je){
for(int j=js;j<=je;j++)
{
result[is][j] = k++;
}
is++;
for(int i =is;i<=ie;i++)
{
result[i][je] = k++;
}
je--;
for(int j=je;j>=js;j--)
{
result[ie][j] = k++;
}
ie--;
for(int i=ie;i>=is;i--)
{
result[i][js] = k++;
}
js++;
}
return result;
}
};

哈希表

一般哈希表都是用来快速判断一个元素是否出现集合里

只需要初始化把所有元素都存在哈希表里,在查询的时候通过索引直接就可以知道元素在不在这哈希表里了

建立索引:哈希函数

有效的字母异位词

https://leetcode.cn/problems/valid-anagram/description/

题目描述

给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s的字母异位词。

示例 1: 输入: s = "anagram", t = "nagaram" 输出: true

示例 2: 输入: s = "rat", t = "car" 输出: false

思路

暴力的方法可能时间复杂度会很高

判断有没有异位词的本质就是查看当前的字母是不是有出现过,那么思路就是选择哈希表

定义一个数组叫做record用来上记录字符串s里字符出现的次数。

需要把字符映射到数组也就是哈希表的索引下标上,因为字符a到字符z的ASCII是26个连续的数值,所以字符a映射为下标0,相应的字符z映射为下标25。

再遍历 字符串s的时候,只需要将 s[i] - ‘a’ 所在的元素做+1操作即可,并不需要记住字符a的ASCII,只要求出一个相对数值就可以了。这样就将字符串s中字符出现的次数,统计出来了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
public:
bool isAnagram(string s, string t) {
int record[26] = {0};
for (int i = 0; i < s.size(); i++) {
// 并不需要记住字符a的ASCII,只要求出一个相对数值就可以了
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) {
// record数组如果有的元素不为零0,说明字符串s和t 一定是谁多了字符或者谁少了字符。
return false;
}
}
// record数组所有元素都为零0,说明字符串s和t是字母异位词
return true;
}
};

两个数组的交集

https://leetcode.cn/problems/intersection-of-two-arrays/description/

题目描述

示例 1:

1
2
输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2]

示例 2:

1
2
3
输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[9,4]
解释:[4,9] 也是可通过的

思路

使用哈希表存储,但是用set(unordered_set)

std::set和std::multiset底层实现都是红黑树,std::unordered_set的底层实现是哈希表,使用unordered_set读写效率是最高的,并不需要对数据进行排序,而且还不要让数据重复,所以选择unordered_set

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
unordered_set<int> result_set; // 存放结果,之所以用set是为了给结果集去重
unordered_set<int> nums_set(nums1.begin(), nums1.end());
for (int num : nums2) {
// 发现nums2的元素 在nums_set里又出现过
if (nums_set.find(num) != nums_set.end()) {
result_set.insert(num);
}
}
return vector<int>(result_set.begin(), result_set.end());
}
};

快乐数

https://leetcode.cn/problems/happy-number/description/

题目描述

编写一个算法来判断一个数 n 是不是快乐数。

「快乐数」 定义为:

如果 n快乐数 就返回 true;不是,则返回 false

示例 1:

1
2
3
4
5
6
7
输入:n = 19
输出:true
解释:
1**2 + 9**2 = 82
8**2 + 2**2 = 68
6**2 + 8**2 = 100
1**2 + 0**2 + 0**2 = 1

思路:

注意,题目中提到一个点是无限循环,说明计算的结果sum是有限的只需要在哈希表中将这部分的结果存储进去,并每次比较是不是出现1如果是那么就是快乐数,否则就不是快乐数

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
class Solution {
public:
int getSum(int n){
int sum=0;
while(n){
sum+=(n%10)*(n%10);
n/=10;
}
return sum;
}
bool isHappy(int n) {
//首先建立哈希表来存储是不是出现了无限循环的结果
unordered_set<int>sum_set;
//无限循环 直到出现1或者无限循环且不是快乐数
while(1){
n=getSum(n);
if(sum_set.find(n)!=sum_set.end()){
return false;
}else{
sum_set.insert(n);
}
if(n==1){
return true;
}
}
}
};

两数之和

题目描述

https://leetcode.cn/problems/two-sum/submissions/495021134/

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

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现

你可以按任意顺序返回答案。

示例 1:

1
2
3
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1]

示例 2:

1
2
输入:nums = [3,2,4], target = 6
输出:[1,2]

示例 3:

1
2
输入:nums = [3,3], target = 6
输出:[0,1]

思路:

构建一个哈希表,然后遍历一遍就行了在哈希表中找n-a的值是否存在,但是最大的问题是数组中同一个元素在答案里不能重复出现,所以不能简单考虑unordered_set

这里提供一种新的思路,就是用unordered_map来存储数组中的数据内容和下标的数值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
std::unordered_map <int,int> map;
for(int i = 0; i < nums.size(); i++) {
// 遍历当前元素,并在map中寻找是否有匹配的key
auto iter = map.find(target - nums[i]);
if(iter != map.end()) {
return {iter->second, i};
}
// 如果没找到匹配对,就把访问过的元素和下标加入到map中
map.insert(pair<int, int>(nums[i], i));
}
return {};
}
};

四数相加

https://leetcode.cn/problems/4sum-ii/description/

给你四个整数数组nums1nums2nums3nums4 ,数组长度都是 n ,请你计算有多少个元组(i, j, k, l) 能满足:

示例 1:

1
2
3
4
5
6
输入:nums1 = [1,2], nums2 = [-2,-1], nums3 = [-1,2], nums4 = [0,2]
输出:2
解释:
两个元组如下:
1. (0, 0, 0, 1) -> nums1[0] + nums2[0] + nums3[0] + nums4[1] = 1 + (-2) + (-1) + 2 = 0
2. (1, 1, 0, 0) -> nums1[1] + nums2[1] + nums3[0] + nums4[0] = 2 + (-1) + (-1) + 0 = 0

示例 2:

1
2
输入:nums1 = [0], nums2 = [0], nums3 = [0], nums4 = [0]
输出:1

思路

  1. 首先定义 一个unordered_map,key放a和b两数之和,value放a和b两数之和出现的次数
  2. 遍历大A和大B数组,统计两个数组元素之和,和出现的次数,放到map中。
  3. 定义int变量count,用来统计 a+b+c+d = 0 出现的次数。
  4. 在遍历大C和大D数组,找到如果 0-(c+d)在map中出现过的话,就用count把map中key对应的value也就是出现次数统计出来。
  5. 最后返回统计值 count 就可以了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
public:
int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
std::unordered_map<int,int>nm;
int res=0;

for(int i=0;i<nums1.size();i++){
for(int j=0;j<nums2.size();j++){
int s = nums1[i]+nums2[j];
nm[s]++;
}
}
for(int i=0;i<nums3.size();i++){
for(int j=0;j<nums4.size();j++){
if(nm.find(0-nums3[i]-nums4[j])!=nm.end()){
res+=nm[0-(nums3[i]+nums4[j])];
}
}
}
return res;
}
};

赎金信

https://leetcode.cn/problems/ransom-note/description/

给你两个字符串:ransomNotemagazine,判断 ransomNote 能不能由 magazine里面的字符构成。

如果可以,返回 true ;否则返回 false

magazine 中的每个字符只能在 ransomNote中使用一次。

示例 1:

1
2
输入:ransomNote = "a", magazine = "b"
输出:false

示例 2:

1
2
输入:ransomNote = "aa", magazine = "ab"
输出:false

思路:

用哈希表unordered_map来存储次数,对于ransomNote来减去次数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public:
bool canConstruct(string ransomNote, string magazine) {
unordered_map<int,int>umap;
if(ransomNote.size()>magazine.size()){return false;}
for(int i=0;i<magazine.size();i++){
umap[magazine[i]-'a']++;
}
for(int i=0;i<ransomNote.size();i++){
if(umap.find(ransomNote[i]-'a')!=umap.end()){
umap[ransomNote[i]-'a']--;
if(umap[ransomNote[i]-'a']<0) {return false;}
}else{
return false;
}
}
return true;
}
};

三数之和

https://leetcode.cn/problems/3sum/description/

给你一个整数数组 nums ,判断是否存在三元组[nums[i], nums[j], nums[k]] 满足i != ji != kj != k,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请

你返回所有和为 0 且不重复的三元组。

注意:答案中不可以包含重复的三元组。

示例 1:

1
2
3
4
5
6
7
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
解释:
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
不同的三元组是 [-1,0,1][-1,-1,2]

思路

其实这道题目使用哈希法并不十分合适,因为在去重的操作中有很多细节需要注意,在面试中很难直接写出没有bug的代码,而且使用哈希法在使用两层for循环的时候,能做的剪枝操作很有限,虽然时间复杂度是O(n^2)

这道题可以用双指针法求解

拿这个nums数组来举例,首先将数组排序,然后有一层for循环,i从下标0的地方开始,同时定一个下标left定义在i+1的位置上,定义下标right 在数组结尾的位置上

依然还是在数组中找到 abc 使得a + b +c =0,我们这里相当于 a =nums[i],b = nums[left],c = nums[right]。

接下来如何移动left 和right呢, 如果nums[i] + nums[left] + nums[right]> 0 就说明此时三数之和大了,因为数组是排序后了,所以right下标就应该向左移动,这样才能让三数之和小一些。

如果 nums[i] + nums[left] + nums[right] < 0 说明 此时三数之和小了,left就向右移动,才能让三数之和大一些,直到left与right相遇为止

还有一个难度就是不能有重复的结果,需要做一次去重的操作

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 Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> result;
sort(nums.begin(), nums.end());
// 找出a + b + c = 0
// a = nums[i], b = nums[left], c = nums[right]
for (int i = 0; i < nums.size(); i++) {
// 排序之后如果第一个元素已经大于零,那么无论如何组合都不可能凑成三元组,直接返回结果就可以了
if (nums[i] > 0) {
return result;
}
// 正确去重a方法
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
int left = i + 1;
int right = nums.size() - 1;
while (right > left) {
if (nums[i] + nums[left] + nums[right] > 0) right--;
else if (nums[i] + nums[left] + nums[right] < 0) left++;
else {
result.push_back(vector<int>{nums[i], nums[left], nums[right]});
// 去重逻辑应该放在找到一个三元组之后,对b 和 c去重
while (right > left && nums[right] == nums[right - 1]) right--;
while (right > left && nums[left] == nums[left + 1]) left++;
// 找到答案时,双指针同时收缩
right--;
left++;
}
}

}
return result;
}
};

双指针

移除元素

https://leetcode.cn/problems/remove-element/description/

示例 1:

1
2
3
输入:nums = [3,2,2,3], val = 3
输出:2, nums = [2,2]
解释:函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。你不需要考虑数组中超出新长度后面的元素。例如,函数返回的新长度为 2 ,而 nums = [2,2,3,3] 或 nums = [2,2,0,0],也会被视作正确答案。

示例 2:

1
2
3
输入:nums = [0,1,2,2,3,0,4,2], val = 2
输出:5, nums = [0,1,3,0,4]
解释:函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。注意这五个元素可为任意顺序。你不需要考虑数组中超出新长度后面的元素。

思路:

使用快慢指针来实现两个指针之间的移动,对于找到了和val数值一样的就进行替换

反转字符串

https://leetcode.cn/problems/reverse-string/description/

示例 1:

1
2
输入:s = ["h","e","l","l","o"]
输出:["o","l","l","e","h"]

示例 2:

1
2
输入:s = ["H","a","n","n","a","h"]
输出:["h","a","n","n","a","H"]

思路:

采用两个指针之间互相交换,首尾交换

1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
public:
void reverseString(vector<char>& s) {
for(int a=0, b = s.size()-1;a<b;){
char tmp;
tmp=s[a];
s[a]=s[b];
s[b]=tmp;
a++;
b--;
}
}
};

反转字符串中的单词

https://leetcode.cn/problems/reverse-words-in-a-string/description/

示例 1:

1
2
输入:s = "the sky is blue"
输出:"blue is sky the"

示例 2:

1
2
3
输入:s = "  hello world  "
输出:"world hello"
解释:反转后的字符串中不能存在前导空格和尾随空格。

示例 3:

1
2
3
输入:s = "a good   example"
输出:"example good a"
解释:如果两个单词间有多余的空格,反转后的字符串需要将单词间的空格减少到仅有一个。

思路:

首先对字符串中额外的空格进行删除

字符串进行全局的逆序

再根据空格作为一个单独字母的节点进行分格分别进行逆序

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
class Solution {
public:
string reverseWords(string s) {
for(int i = s.size()-1;i>0;i--){
if(s[i]==s[i-1]&&s[i]==' '){
s.erase(s.begin()+i);
}
}
if(s.size()>0&&s[s.size()-1]==' '){
s.erase(s.begin()+s.size()-1);
}
if(s.size()>0&&s[0]==' '){
s.erase(s.begin());
}

//完成全局的交换
for(int i = 0, j=s.size()-1;i<j;i++,j--){
char tmp;
tmp = s[i];
s[i] = s[j];
s[j] = tmp;
}
cout<<s;
//进行局部的交换
int i=0;
int j=1;
while(j<=s.size()){
if(s[j]==' '||j==s.size()){
for(int k =i, q =j-1;k<q;k++,q--){
char tmp;
tmp = s[k];
s[k] = s[q];
s[q] = tmp;
}
i=j+1;
j=i+1;
}else{
j++;
}
}
return s;
}
};

反转链表

https://leetcode.cn/problems/reverse-linked-list/description/

image-20240118151406885

1
2
输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]

思路:本质上就是利用了两个链表指针实现对元素的转向

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* temp;
ListNode* cur = head;
ListNode* pre = nullptr;
while(cur){
temp = cur->next;
cur->next = pre;
pre = cur;
cur = temp;
}
return pre;
}
};

删除链表的倒数第N个结点

https://leetcode.cn/problems/remove-nth-node-from-end-of-list/description/

image-20240118152000575

1
2
输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]

示例 2:

1
2
输入:head = [1], n = 1
输出:[]

思路:

遍历,用两个指针分别来记录

如果要删除倒数第n个节点,让fast移动n步,然后让fast和slow同时移动,直到fast指向链表末尾。删掉slow所指向的节点就可以了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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 != NULL) {
fast = fast->next;
}
fast = fast->next; // fast再提前走一步,因为需要让slow指向删除节点的上一个节点
while (fast != NULL) {
fast = fast->next;
slow = slow->next;
}
slow->next = slow->next->next;

// ListNode *tmp = slow->next; C++释放内存的逻辑
// slow->next = tmp->next;
// delete nth;

return dummyHead->next;
}
};

链表相交

给你两个单链表的头节点 headAheadB,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回null

题目数据 保证 整个链式结构中不存在环。

注意,函数返回结果后,链表必须保持其原始结构

示例 1:

链表相交图

1
2
3
4
5
输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3
输出:Intersected at '8'
解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。
A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。

思路:

简单来说,就是求两个链表交点节点的指针,注意返回的是结点的指针,不是对应的数值,同时注意这里比较的是相同的指针不是数值相同,因此直接比较指针是不是相同就可以了

由于题目说的相交的结构如图所示,如果存在相交的指针位置,只可能出现在后面只需要考虑利用双指针从相差的数值位开始遍历

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
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;
}
};

环形链表

https://leetcode.cn/problems/linked-list-cycle-ii/description/

判断是否是有还存在,如果有那么返回开始入环的第一个节点的下标

环形链表

1
2
3
输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。

思路:

这道题用快慢指针的思路,就是慢指针每次只走一步,快指针每次走两步,如果在到达null之前出现快慢指针指向了同一个地方,说明这个链表有环存在,那么怎么判断下标的位置呢?

具体的证明过程:

相遇时slow指针走过的节点数为: x + y,fast指针走过的节点数:x + y + n (y + z),n为fast指针在环内走了n圈才遇到slow指针,(y+z)为 一圈内节点的个数A。

因为fast指针是一步走两个节点,slow指针一步走一个节点, 所以fast指针走过的节点数 = slow指针走过的节点数 * 2:

1
(x + y) * 2 = x + y + n (y + z)

两边消掉一个(x+y): x + y = n (y + z)

因为要找环形的入口,那么要求的是x,因为x表示 头结点到环形入口节点的的距离。

所以要求x ,将x单独放在左面:x = n (y + z) - y ,

再从n(y+z)中提出一个(y+z)来,整理公式之后为如下公式:x = (n - 1) (y + z) + z注意这里n一定是大于等于1的,因为fast指针至少要多走一圈才能相遇slow指针

所以可以得到的规律是:从头结点出发一个指针,从相遇节点也出发一个指针,这两个指针每次只走一个节点,那么当这两个指针相遇的时候就是 环形入口的节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
public:
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 index2;
}
}
return NULL;
}
};

二叉树

深搜回溯

深度优先搜索的三部曲:

  1. 确定搜索函数的返回值以及搜索函数的参数分别是什么
  2. 确定每次找到叶子结点的终止条件
  3. 确定for单层搜索的逻辑,包含push,backtracking,pop

别忘了最开始的初始化步骤

组合问题

https://leetcode.cn/problems/combinations/description/

给定两个整数 nk,返回范围[1, n] 中所有可能的 k 个数的组合。

你可以按 任何顺序 返回答案。

示例 1:

1
2
3
4
5
6
7
8
9
10
输入:n = 4, k = 2
输出:
[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]

示例 2:

1
2
输入:n = 1, k = 1
输出:[[1]]

思路,使用深度优先搜索算法进行处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
private:
vector<vector<int>> result;
vector<int>path;
void backtracking(int n,int k, int startindex){
if(path.size()==k){
result.push_back(path);
return;
}
for(int i = startindex;i<=n;i++){
path.push_back(i);
backtracking(n,k,i+1);
path.pop_back();
}
}
public:
vector<vector<int>> combine(int n, int k){
backtracking(n,k,1);
return result;
}
};

组合问题III

https://leetcode.cn/problems/combination-sum-iii/submissions/496823507/

思路:简单的深度优先搜索,但需要注意的是可以适当采用减枝操作和必要的时候添加sum变量进行记录

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 {
private:
vector<vector<int>> result;
vector<int> path;
void backtacking(int k, int n,int startindex, int sum){
if(path.size()==k){
if(sum == n) result.push_back(path);
return;
}
for(int i= startindex;i<=9;i++){
sum+=i;
path.push_back(i);
backtacking(k,n,i+1,sum);
sum-=i;
path.pop_back();
}
}
public:
vector<vector<int>> combinationSum3(int k, int n) {
backtacking(k,n,1,0);
return result;
}
};

为了优化可以做一个剪枝操作

1
2
3
if (sum > targetSum) { // 剪枝操作
return;
}

电话号码组合问题

给定一个仅包含数字 2-9的字符串,返回所有它能表示的字母组合。答案可以按任意顺序 返回。

给出数字到字母的映射如下(与电话按键相同)。注意 1不对应任何字母。

电话号码的按键

示例 :

1
2
输入:digits = "23"
输出:["ad","ae","af","bd","be","bf","cd","ce","cf"]

这道题需要注意的地方是,首先第一步做好map字符的映射

第二步最关键是要写清楚回溯函数的参数可能包含index,就是第几位置的字符,同时需要区分backtracking函数的for循环的内容是相当于横向的遍历,而函数体内部的实现是纵向的遍历

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
class Solution {
private:
const string letterMap[10] = {
"", // 0
"", // 1
"abc", // 2
"def", // 3
"ghi", // 4
"jkl", // 5
"mno", // 6
"pqrs", // 7
"tuv", // 8
"wxyz", // 9
};
vector<string> result;
string s;
void backtracking(const string digits,int index, string s){
if(digits.size()==0){
return;
}
if(index==digits.size()){
result.push_back(s);
return;
}
int digit = digits[index]-'0';
string letters = letterMap[digit];
for(int i= 0;i<letters.size();i++){
s.push_back(letters[i]);
backtracking(digits, index+1, s);
s.pop_back();
}
}
public:
vector<string> letterCombinations(string digits) {
backtracking(digits,0,"");
return result;
}
};

贪心算法

动态规划

]]>
diff --git a/search.xml b/search.xml index b1a6815..6bb7df4 100644 --- a/search.xml +++ b/search.xml @@ -2187,8 +2187,101 @@ href="https://leetcode.cn/problems/remove-nth-node-from-end-of-list/description/

遍历,用两个指针分别来记录

如果要删除倒数第n个节点,让fast移动n步,然后让fast和slow同时移动,直到fast指向链表末尾。删掉slow所指向的节点就可以了

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 != NULL) {
fast = fast->next;
}
fast = fast->next; // fast再提前走一步,因为需要让slow指向删除节点的上一个节点
while (fast != NULL) {
fast = fast->next;
slow = slow->next;
}
slow->next = slow->next->next;

// ListNode *tmp = slow->next; C++释放内存的逻辑
// slow->next = tmp->next;
// delete nth;

return dummyHead->next;
}
};
+

链表相交

+

给你两个单链表的头节点 headAheadB +,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 +null

+

题目数据 保证 整个链式结构中不存在环。

+

注意,函数返回结果后,链表必须 +保持其原始结构

+

示例 1:

+

链表相交图

+
输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3
输出:Intersected at '8'
解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。
A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。
+

思路:

+

简单来说,就是求两个链表交点节点的指针,注意返回的是结点的指针,不是对应的数值,同时注意这里比较的是相同的指针不是数值相同,因此直接比较指针是不是相同就可以了

+

由于题目说的相交的结构如图所示,如果存在相交的指针位置,只可能出现在后面只需要考虑利用双指针从相差的数值位开始遍历

+
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;
}
};
+

环形链表

+

https://leetcode.cn/problems/linked-list-cycle-ii/description/

+

判断是否是有还存在,如果有那么返回开始入环的第一个节点的下标

+

环形链表

+
输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。
+

思路:

+

这道题用快慢指针的思路,就是慢指针每次只走一步,快指针每次走两步,如果在到达null之前出现快慢指针指向了同一个地方,说明这个链表有环存在,那么怎么判断下标的位置呢?

+

具体的证明过程:

+
+ + +
+

相遇时slow指针走过的节点数为: x + y, +fast指针走过的节点数:x + y + n (y + z),n为fast指针在环内走了n圈才遇到slow指针, +(y+z)为 一圈内节点的个数A。

+

因为fast指针是一步走两个节点,slow指针一步走一个节点, 所以 +fast指针走过的节点数 = slow指针走过的节点数 * 2:

+
(x + y) * 2 = x + y + n (y + z)
+

两边消掉一个(x+y): x + y = n (y + z)

+

因为要找环形的入口,那么要求的是x,因为x表示 头结点到 +环形入口节点的的距离。

+

所以要求x ,将x单独放在左面:x = n (y + z) - y ,

+

再从n(y+z)中提出一个 +(y+z)来,整理公式之后为如下公式:x = (n - 1) (y + z) + z +注意这里n一定是大于等于1的,因为 +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 index2;
}
}
return NULL;
}
};

二叉树

深搜回溯

+

深度优先搜索的三部曲:

+
    +
  1. 确定搜索函数的返回值以及搜索函数的参数分别是什么
  2. +
  3. 确定每次找到叶子结点的终止条件
  4. +
  5. 确定for单层搜索的逻辑,包含push,backtracking,pop
  6. +
+

别忘了最开始的初始化步骤

+

组合问题

+

https://leetcode.cn/problems/combinations/description/

+

给定两个整数 nk,返回范围 +[1, n] 中所有可能的 k 个数的组合。

+

你可以按 任何顺序 返回答案。

+

示例 1:

+
输入:n = 4, k = 2
输出:
[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]
+

示例 2:

+
输入:n = 1, k = 1
输出:[[1]]
+

思路,使用深度优先搜索算法进行处理

+ +
class Solution {
private:
vector<vector<int>> result;
vector<int>path;
void backtracking(int n,int k, int startindex){
if(path.size()==k){
result.push_back(path);
return;
}
for(int i = startindex;i<=n;i++){
path.push_back(i);
backtracking(n,k,i+1);
path.pop_back();
}
}
public:
vector<vector<int>> combine(int n, int k){
backtracking(n,k,1);
return result;
}
};
+

组合问题III

+

https://leetcode.cn/problems/combination-sum-iii/submissions/496823507/

+

思路:简单的深度优先搜索,但需要注意的是可以适当采用减枝操作和必要的时候添加sum变量进行记录

+
class Solution {
private:
vector<vector<int>> result;
vector<int> path;
void backtacking(int k, int n,int startindex, int sum){
if(path.size()==k){
if(sum == n) result.push_back(path);
return;
}
for(int i= startindex;i<=9;i++){
sum+=i;
path.push_back(i);
backtacking(k,n,i+1,sum);
sum-=i;
path.pop_back();
}
}
public:
vector<vector<int>> combinationSum3(int k, int n) {
backtacking(k,n,1,0);
return result;
}
};
+

为了优化可以做一个剪枝操作

+
if (sum > targetSum) { // 剪枝操作
return;
}
+

电话号码组合问题

+

给定一个仅包含数字 2-9 +的字符串,返回所有它能表示的字母组合。答案可以按 +任意顺序 返回。

+

给出数字到字母的映射如下(与电话按键相同)。注意 1 +不对应任何字母。

+

电话号码的按键

+

示例 :

+
输入:digits = "23"
输出:["ad","ae","af","bd","be","bf","cd","ce","cf"]
+

这道题需要注意的地方是,首先第一步做好map字符的映射

+

第二步最关键是要写清楚回溯函数的参数可能包含index,就是第几位置的字符,同时需要区分backtracking函数的for循环的内容是相当于横向的遍历,而函数体内部的实现是纵向的遍历

+
class Solution {
private:
const string letterMap[10] = {
"", // 0
"", // 1
"abc", // 2
"def", // 3
"ghi", // 4
"jkl", // 5
"mno", // 6
"pqrs", // 7
"tuv", // 8
"wxyz", // 9
};
vector<string> result;
string s;
void backtracking(const string digits,int index, string s){
if(digits.size()==0){
return;
}
if(index==digits.size()){
result.push_back(s);
return;
}
int digit = digits[index]-'0';
string letters = letterMap[digit];
for(int i= 0;i<letters.size();i++){
s.push_back(letters[i]);
backtracking(digits, index+1, s);
s.pop_back();
}
}
public:
vector<string> letterCombinations(string digits) {
backtracking(digits,0,"");
return result;
}
};

贪心算法

动态规划

]]>