From b21095e3235c5a11aa696d6c238975176564c3da Mon Sep 17 00:00:00 2001
From: lihaibineric
Date: Tue, 30 Jan 2024 20:49:09 +0800
Subject: [PATCH] Site updated: 2024-01-30 20:49:09
---
2024/01/01/leetcode/index.html | 137 +++++++++++++++++++++++++++++++--
local-search.xml | 2 +-
search.xml | 125 ++++++++++++++++++++++++++++++
3 files changed, 258 insertions(+), 6 deletions(-)
diff --git a/2024/01/01/leetcode/index.html b/2024/01/01/leetcode/index.html
index 95d94a3..f7fa417 100644
--- a/2024/01/01/leetcode/index.html
+++ b/2024/01/01/leetcode/index.html
@@ -30,12 +30,14 @@
+
+
-
+
@@ -221,7 +223,7 @@
- 44k words
+ 52k words
@@ -232,7 +234,7 @@
- 370 mins
+ 432 mins
@@ -274,7 +276,7 @@
- Last updated on January 28, 2024 pm
+ Last updated on January 30, 2024 pm
@@ -659,6 +661,28 @@ 环形链表
也出发一个指针,这两个指针每次只走一个节点,
那么当这两个指针相遇的时候就是 环形入口的节点
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; } };
|
+接雨水
+给定 n
个非负整数表示每个宽度为 1
+的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
+示例 1:
+
+1 2 3
| 输入:height = [0,1,0,2,1,0,1,3,2,1,2,1] 输出:6 解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。
|
+示例 2:
+1 2
| 输入:height = [4,2,0,3,2,5] 输出:9
|
+思路:
+找到最大的左边和最大的右边并相减
+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
| class Solution { public: int trap(vector<int>& height) { if(height.size()<=2) return 0; vector<int> maxLeft(height.size(), 0); vector<int> maxRight(height.size(), 0); int size = maxRight.size(); maxLeft[0] = height[0]; for(int i=1;i<size;i++){ maxLeft[i] = max(height[i],maxLeft[i-1]); } maxRight[size-1] = height[size-1]; for(int i=size-2;i>=0;i--){ maxRight[i] = max(height[i],maxRight[i+1]); } int sum=0; for(int i=0;i<size;i++){ int count = min(maxLeft[i], maxRight[i])-height[i]; if(count > 0) sum+=count; } return sum; } };
|
+柱形图中的最大矩形
+https://leetcode.cn/problems/largest-rectangle-in-histogram/description/
+
+1 2 3
| 输入:heights = [2,1,5,6,2,3] 输出:10 解释:最大的矩形为图中红色区域,面积为 10
|
+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: int largestRectangleArea(vector<int>& heights) { vector<int> minLeft(heights.size()); vector<int> minRight(heights.size()); int size = heights.size();
minLeft[0] = -1; for(int i=1;i<size;i++){ int t= i-1; while(t>=0&&heights[t]>=heights[i]) t=minLeft[t]; minLeft[i]=t; } minRight[size-1]=size; for(int i=size -2;i>=0;i--){ int t=i+1; while(t<size&&heights[t]>=heights[i]) t=minRight[t]; minRight[i]=t; }
int res=0; for(int i=0;i<size;i++){ int sum=heights[i]*(minRight[i]-minLeft[i]-1); res = max(sum,res); }
return res; } };
|
二叉树
深搜回溯
深度优先搜索的三部曲:
@@ -691,6 +715,16 @@ 组合问题
组合问题III
https://leetcode.cn/problems/combination-sum-iii/submissions/496823507/
+找出所有相加之和为 n
的 k
+个数的组合,且满足下列条件:
+
+返回 所有可能的有效组合的列表
+。该列表不能包含相同的组合两次,组合可以以任何顺序返回。
+示例:
+1 2 3 4 5
| 输入: k = 3, n = 7 输出: [[1,2,4]] 解释: 1 + 2 + 4 = 7 没有其他符合的组合了。
|
思路:简单的深度优先搜索,但需要注意的是可以适当采用减枝操作和必要的时候添加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; } };
|
为了优化可以做一个剪枝操作
@@ -1089,6 +1123,99 @@ 不同路径II
&&obstacleGrid[i][0]==0
的信息,同时遇到障碍物就不改变对应的值,直接
continue
就好
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class Solution { public: int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) { vector<vector<int>>dp(obstacleGrid.size(), vector<int>(obstacleGrid[0].size(), 0)); for(int i=0;i<obstacleGrid.size()&&obstacleGrid[i][0]==0;i++) dp[i][0]=1; for(int i=0;i<obstacleGrid[0].size()&&obstacleGrid[0][i]==0;i++) dp[0][i]=1; for(int i=1;i<obstacleGrid.size();i++){ for(int j=1;j<obstacleGrid[0].size();j++){ if(obstacleGrid[i][j]==1) continue; dp[i][j]=dp[i-1][j]+dp[i][j-1]; } } return dp[obstacleGrid.size()-1][obstacleGrid[0].size()-1]; } };
|
+整数拆分
+https://leetcode.cn/problems/integer-break/description/
+给定一个正整数 n
,将其拆分为 k
个
+正整数 的和( k >= 2
+),并使这些整数的乘积最大化。返回 你可以获得的最大乘积 。
+示例 :
+1 2 3
| 输入: n = 2 输出: 1 解释: 2 = 1 + 1, 1 × 1 = 1。
|
+思路:
+给出递推公式一个是j * (i - j)
+直接相乘。一个是j * dp[i - j]
,相当于是拆分(i - j)
,在遍历j的过程中其实都计算过了。那么从1遍历j,比较(i - j) * j和dp[i - j] * j
+取最大的。递推公式:dp[i] = max(dp[i], max((i - j) * j, dp[i - j] * j))
;
+1 2 3 4 5 6 7 8 9 10 11 12 13 14
| class Solution { public: int integerBreak(int n) { vector<int> dp(n + 1); dp[2] = 1; for (int i = 3; i <= n ; i++) { for (int j = 1; j <= i / 2; j++) { dp[i] = max(dp[i], max((i - j) * j, dp[i - j] * j)); } } return dp[n]; } };
|
+背包问题解题框架
+
+0-1背包
+dp[j]
为
+容量为j
的背包所背的最大价值,那么如何推导dp[j]
呢?dp[j]
可以通过dp[j - weight[i]]
推导出来,dp[j - weight[i]]
表示容量为j - weight[i]
的背包所背的最大价值。
+dp[j - weight[i]] + value[i]
表示 容量为 j
+- 物品i重量 的背包 加上
+物品i的价值。(也就是容量为j的背包,放入物品i了之后的价值即:dp[j])
+此时dp[j]有两个选择,一个是取自己dp[j]
相当于
+二维dp数组中的dp[i-1][j]
,即不放物品i,一个是取dp[j - weight[i]] + value[i]
,即放物品i,指定是取最大的,毕竟是求最大价值,
+递推公式:
+1
| dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
|
+初始化:
+全部初始化为0
+遍历顺序:
+1 2 3 4 5
| for(int i = 0; i < weight.size(); i++) { for(int j = bagWeight; j >= weight[i]; j--) { dp[j] = max(dp[j], dp[j - weight[i]] + value[i]); } }
|
+整体的代码结构是:
+1 2 3 4 5 6 7 8 9 10 11 12 13
| void test_1_wei_bag_problem() { vector<int> weight = {1, 3, 4}; vector<int> value = {15, 20, 30}; int bagWeight = 4; vector<int> dp(bagWeight + 1, 0); for(int i = 0; i < weight.size(); i++) { for(int j = bagWeight; j >= weight[i]; j--) { dp[j] = max(dp[j], dp[j - weight[i]] + value[i]); } } cout << dp[bagWeight] << endl; }
|
+完全背包问题
+有N件物品和一个最多能背重量为W的背包。第i件物品的重量是weight[i],得到的价值是value[i]
+。每件物品都有无限个(也就是可以放入背包多次),求解将哪些物品装入背包里物品价值总和最大。
+完全背包和01背包问题唯一不同的地方就是,每种物品有无限件。
+在代码层面的区别在于背包遍历的时候是从头开始到尾遍历,int j = weight[i]; j <= bagWeight; j++
,因为所有的背包内部都是无限的
+1、先遍历物品再遍历背包
+1 2 3 4 5 6 7 8 9 10 11 12
| void test_CompletePack() { vector<int> weight = {1, 3, 4}; vector<int> value = {15, 20, 30}; int bagWeight = 4; vector<int> dp(bagWeight + 1, 0); for(int i = 0; i < weight.size(); i++) { for(int j = weight[i]; j <= bagWeight; j++) { dp[j] = max(dp[j], dp[j - weight[i]] + value[i]); } } cout << dp[bagWeight] << endl; }
|
+2、先遍历背包再遍历物品
+1 2 3 4 5 6
| for(int j = 0; j <= bagWeight; j++) { for(int i = 0; i < weight.size(); i++) { if (j - weight[i] >= 0) dp[j] = max(dp[j], dp[j - weight[i]] + value[i]); } cout << endl; }
|
+分割等和子集
+https://leetcode.cn/problems/partition-equal-subset-sum/description/
+给你一个只包含正整数的非空 数组
+nums
+。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等示例:
+1 2 3
| 输入:nums = 输出:true 解释:数组可以分割成 和
|
+单调栈
+每日温度
+给定一个整数数组 temperatures
+,表示每天的温度,返回一个数组 answer
,其中
+answer[i]
是指对于第 i
+天,下一个更高温度出现在几天后。如果气温在这之后都不会升高,请在该位置用
+0
来代替。
+示例 1:
+1 2
| 输入: temperatures = [73,74,75,71,69,72,76,73] 输出: [1,1,4,2,1,1,0,0]
|
+思路:
+可以选择使用单调栈的方法来求解,具体的思路是设置一个栈,遍历数组的时候和栈顶元素进行比较,小于栈顶元素的时候就需要将当前元素放入栈中
+如果大于当前的栈顶元素的值,那么就要进行比较while
循环,只要还是大于当前栈顶的元素都需要对栈顶的元素进行pop()
+1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| class Solution { public: vector<int> dailyTemperatures(vector<int>& temperatures) { stack<int> st; vector<int> res(temperatures.size(),0); st.push(0); for(int i=1;i<temperatures.size();i++){ if(temperatures[i]<=temperatures[st.top()]){ st.push(i); }else{ while (!st.empty()&& temperatures[i]>temperatures[st.top()]){ res[st.top()]=i-st.top(); st.pop(); } st.push(i); } } return res; } };
|
+图论
+深度优先搜索理论
+广度优先搜索理论
+所有可能的路径
+https://leetcode.cn/problems/all-paths-from-source-to-target/description/
+
+给你一个有 n
个节点的
+有向无环图(DAG),请你找出所有从节点 0
+到节点 n-1
+的路径并输出(不要求按特定顺序) graph[i]
+是一个从节点 i
可以访问的所有节点的列表(即从节点
+i
到节点 graph[i][j]
存在一条有向边)。
+1 2 3
| 输入:graph = [[1,2],[3],[3],[]] 输出:[[0,1,3],[0,2,3]] 解释:有两条路径 0 -> 1 -> 3 和 0 -> 2 -> 3
|
+思路:
+深度优先搜索
+注意在用dfs做题的时候需要初始化path.push_back(0)
每一次都需要初始化输入这个数值起点
+1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| class Solution { public: vector<vector<int>> res; vector<int> path; void dfs(vector<vector<int>>& graph, int x){ if(x == graph.size()-1){ res.push_back(path); return; } for(int i=0;i<graph[x].size();i++){ path.push_back(graph[x][i]); dfs(graph,graph[x][i]); path.pop_back(); } } vector<vector<int>> allPathsSourceTarget(vector<vector<int>>& graph) { path.push_back(0); dfs(graph, 0); return res; } };
|
+岛屿数量
+https://leetcode.cn/problems/number-of-islands/description/
+给你一个由 '1'
(陆地)和
+'0'
(水)组成的的二维网格,请你计算网格中岛屿的数量。岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
+示例 :
+1 2 3 4 5 6 7
| 输入:grid = [ ["1","1","1","1","0"], ["1","1","0","1","0"], ["1","1","0","0","0"], ["0","0","0","0","0"] ] 输出:1
|
+深度优先搜索版本:
+思路在于利用dfs来对岛屿中的数量进行标记是否能visited,必须是联通的才能继续标记为res++
+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
| class Solution { public: int dir[4][2] ={0,1,1,0,-1,0,0,-1}; void dfs(vector<vector<char>>& grid, vector<vector<bool>>& visited, int x, int y){ for(int i=0;i<4;i++){ int nextx = x+dir[i][0]; int nexty = y+dir[i][1]; if(nextx < 0||nextx>=grid.size()||nexty<0 ||nexty>=grid[0].size()) continue; if(!visited[nextx][nexty]&&grid[nextx][nexty]=='1'){ visited[nextx][nexty]=true; dfs(grid, visited , nextx, nexty); } } } int numIslands(vector<vector<char>>& grid) { int n = grid.size(), m = grid[0].size(); vector<vector<bool>> visited = vector<vector<bool>>(n,vector<bool>(m,false)); int res = 0; for(int i=0;i<n;i++){ for(int j = 0;j<m;j++){ if(!visited[i][j] && grid[i][j]=='1'){ visited[i][j]=true; res++; dfs(grid, visited, i,j); } } } return res; } };
|
@@ -1155,7 +1282,7 @@ 不同路径II
diff --git a/local-search.xml b/local-search.xml
index ad24a35..159421c 100644
--- a/local-search.xml
+++ b/local-search.xml
@@ -139,7 +139,7 @@
/2024/01/01/leetcode/
- 语言细节
vector的长度:
- C++:nums.size()
- Python:len(nums)
- GO:len(nums)
初始化数组:
- C++:array[n]={0} 让所有元素都是0
构造vector:
- C++:vector result(长度,元素)
- Python:res = [float('inf')] * len(nums)
- GO:=make([]int,n)
for循环:
- C++:条件小括号+循环体中括号
- Python:冒号且不需要小括号包条件
- GO:循环体中括号,条件按照C++写但是不需要小括号
数组
二分查找
题目描述
链接: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
while left <= right: middle = left + (right - left) // 2 if nums[middle] > target: right = middle - 1 elif nums[middle] < target: left = middle + 1 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 - 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 = , val = 3 输出:2, nums = 解释:函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。你不需要考虑数组中超出新长度后面的元素。例如,函数返回的新长度为 2 ,而 nums = 或 nums = ,也会被视作正确答案。
|
示例 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 解释:子数组 是该条件下的长度最小的子数组。
|
示例 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 (sum >= s) { subLength = (j - i + 1); result = result < subLength ? result : subLength; sum -= nums[i++]; } } return result == INT32_MAX ? 0 : result; } };
|
螺旋矩阵
https://leetcode.cn/problems/spiral-matrix-ii/
题目描述
给你一个正整数 n
,生成一个包含 1
到n2
所有元素,且元素按顺时针顺序螺旋排列的n x n
正方形矩阵 matrix
1 2
| 输入:n = 3 输出:[[1,2,3],[8,9,4],[7,6,5]]
|
示例 2:
思路:大模拟循环遍历
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++) { 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; } };
|
两个数组的交集
https://leetcode.cn/problems/intersection-of-two-arrays/description/
题目描述
示例 1:
1 2
| 输入:nums1 = , nums2 = 输出:
|
示例 2:
1 2 3
| 输入:nums1 = , nums2 = 输出: 解释: 也是可通过的
|
思路
使用哈希表存储,但是用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; unordered_set<int> nums_set(nums1.begin(), nums1.end()); for (int num : nums2) { 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
是不是快乐数。
「快乐数」 定义为:
- 对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
- 然后重复这个过程直到这个数变为 1,也可能是 无限循环但始终变不到 1。
- 如果这个过程 结果为 1,那么这个数就是快乐数。
如果 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; 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 = , target = 9 输出: 解释:因为 nums + nums == 9 ,返回 。
|
示例 2:
1 2
| 输入:nums = , target = 6 输出:
|
示例 3:
1 2
| 输入:nums = , target = 6 输出:
|
思路:
构建一个哈希表,然后遍历一遍就行了在哈希表中找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++) { auto iter = map.find(target - nums[i]); if(iter != map.end()) { return {iter->second, i}; } map.insert(pair<int, int>(nums[i], i)); } return {}; } };
|
四数相加
https://leetcode.cn/problems/4sum-ii/description/
给你四个整数数组nums1
、nums2
、nums3
和nums4
,数组长度都是 n
,请你计算有多少个元组(i, j, k, l)
能满足:
0 <= i, j, k, l < n
nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0
示例 1:
1 2 3 4 5 6
| 输入:nums1 = [1,2], nums2 = [-2,-1], nums3 = [-1,2], nums4 = [0,2] 输出:2 解释: 两个元组如下: 1. -> nums1[0] + nums2[0] + nums3[0] + nums4[1] = 1 + + + 2 = 0 2. -> nums1[1] + nums2[1] + nums3[0] + nums4[0] = 2 + + + 0 = 0
|
示例 2:
1 2
| 输入:nums1 = [0], nums2 = [0], nums3 = [0], nums4 = [0] 输出:1
|
思路
- 首先定义 一个unordered_map,key放a和b两数之和,value放a和b两数之和出现的次数。
- 遍历大A和大B数组,统计两个数组元素之和,和出现的次数,放到map中。
- 定义int变量count,用来统计 a+b+c+d = 0 出现的次数。
- 在遍历大C和大D数组,找到如果 0-(c+d)在map中出现过的话,就用count把map中key对应的value也就是出现次数统计出来。
- 最后返回统计值 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/
给你两个字符串:ransomNote
和 magazine
,判断 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 != j
、i != k
且 j != k
,同时还满足 nums[i] + nums[j] + nums[k] == 0
。请
你返回所有和为 0
且不重复的三元组。
注意:答案中不可以包含重复的三元组。
示例 1:
1 2 3 4 5 6 7
| 输入:nums = 输出: 解释: nums + nums + nums = (-1) + 0 + 1 = 0 。 nums + nums + nums = 0 + 1 + (-1) = 0 。 nums + nums + nums = (-1) + 2 + (-1) = 0 。 不同的三元组是 和 。
|
思路
其实这道题目使用哈希法并不十分合适,因为在去重的操作中有很多细节需要注意,在面试中很难直接写出没有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()); for (int i = 0; i < nums.size(); i++) { if (nums[i] > 0) { return result; } 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]}); 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 = , val = 3 输出:2, nums = 解释:函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。你不需要考虑数组中超出新长度后面的元素。例如,函数返回的新长度为 2 ,而 nums = 或 nums = ,也会被视作正确答案。
|
示例 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/
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/
1 2
| 输入:head = [1,2,3,4,5], n = 2 输出:[1,2,3,5]
|
示例 2:
思路:
遍历,用两个指针分别来记录
如果要删除倒数第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; while (fast != NULL) { fast = fast->next; slow = slow->next; } slow->next = slow->next->next; return dummyHead->next; } };
|
链表相交
给你两个单链表的头节点 headA
和 headB
,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回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; } };
|
二叉树
深搜回溯
深度优先搜索的三部曲:
- 确定搜索函数的返回值以及搜索函数的参数分别是什么
- 确定每次找到叶子结点的终止条件
- 确定for单层搜索的逻辑,包含push,backtracking,pop
别忘了最开始的初始化步骤
组合问题
https://leetcode.cn/problems/combinations/description/
给定两个整数 n
和 k
,返回范围[1, n]
中所有可能的 k
个数的组合。
你可以按 任何顺序 返回答案。
示例 1:
1 2 3 4 5 6 7 8 9 10
| 输入:n = 4, k = 2 输出:
|
示例 2:
1 2
| 输入:n = 1, k = 1 输出:[[1]]
|
思路,使用深度优先搜索算法进行处理
- 首先要区分private和public这两个部分分别做的内容,private主要就是写出回溯的函数主体并且可能需要的数据结构
- public中就对函数进行跳用以及数据结构的使用
- 第一步就是确定函数的类型和返回,这里用了一个
startindex
用来存储下一次进行选择的位置点这样能够避免重复 - 同时函数的返回类型是二位的向量结构
- 同时定义终止条件和单层循环的内容
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] = { "", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz", }; 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; } };
|
组合总和
https://leetcode.cn/problems/combination-sum/
给你一个 无重复元素 的整数数组candidates
和一个目标整数 target
,找出candidates
中可以使数字和为目标数 target
的所有 不同组合 ,并以列表形式返回。你可以按任意顺序 返回这些组合。
candidates
中的 同一个 数字可以无限制重复被选取。如果至少一个数字的被选数量不同,则两种组合是不同的。
对于给定的输入,保证和为 target
的不同组合数少于150
个。
示例 :
1 2 3 4 5 6
| 输入:candidates = [2,3,6,7], target = 7 输出:[[2,2,3],[7]] 解释: 2 和 3 可以形成一组候选,2 + 2 + 3 = 7 。注意 2 可以使用多次。 7 也是一个候选, 7 = 7 。 仅有这两种组合。
|
思路:
题目最关键的点在于能重复使用元素但是不能重复元素的组合不能被重复输出
因此需要调整startindex的开始的位置是在backtracking(candidates,target,sum,i);
注意,这个时候从i开始保证还能用到自己的元素重复使用,还有最重要的sort(candidates.begin(), candidates.end()); // 需要排序
排序之后能够很好的进行剪枝,将一些加了之后元素大于目标的删掉直接跳过
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
| class Solution { public: vector<vector<int>> res; vector<int> path; void backtracking(vector<int>&candidates, int target, int sum, int startindex){ if(sum==target){ res.push_back(path); return; } for(int i=startindex;i<candidates.size();i++){ if(sum>target){ return; } sum+=candidates[i]; path.push_back(candidates[i]); backtracking(candidates,target,sum,i); sum-=candidates[i]; path.pop_back(); } } vector<vector<int>> combinationSum(vector<int>& candidates, int target) { sort(candidates.begin(), candidates.end()); backtracking(candidates, target, 0,0); return res; } };
|
组合总和II
https://leetcode.cn/problems/combination-sum-ii/description/
给定一个候选人编号的集合 candidates
和一个目标数target
,找出 candidates
中所有可以使数字和为target
的组合。
candidates
中的每个数字在每个组合中只能使用 一次。
注意:解集不能包含重复的组合。
示例 :
1 2 3 4 5 6 7 8
| 输入: candidates = , target = 8, 输出:
|
思路:
这个地方最大的困难在于每个数字在每个组合中只能使用一次,同时集合中的元素存在重复的元素,那么这个时候有一个问题是如何才能对元素进行去重处理呢,就是让每个元素只能被使用一次
去重的操作就在于vector<bool> used(candidates.size(),false); sort(candidates.begin(), candidates.end());
首先需要在backtracking
中定一个continue,这个地方是为了筛选不是重复的部分,那么如何区分开是否是同一个数组中重复的元素而不是重复利用的元素呢?
i>0&&candidates[i]==candidates[i-1]
这个地方表明了对元素相邻之间进行比较used[i-1]==false
如果这个地方是false,那么说明这个元素是同一层的元素(同一个数组中的元素)- 注意
i<candidates.size()&&sum + candidates[i] <= target
为了避免出现超出时间限制的情况
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
| class Solution { public: vector<vector<int>> res; vector<int> path; void backtracking(vector<int>& candidates, int target, int sum, int startindex, vector<bool>used){ if(sum==target){ res.push_back(path); return; } for(int i= startindex;i<candidates.size();i++){ if(i>0&&candidates[i]==candidates[i-1]&&used[i-1]==false){ continue; } sum+=candidates[i]; used[i]=true; path.push_back(candidates[i]); backtracking(candidates, target, sum, i+1, used); used[i]=false; path.pop_back(); sum-=candidates[i]; } } vector<vector<int>> combinationSum2(vector<int>& candidates, int target) { vector<bool> used(candidates.size(),false); sort(candidates.begin(), candidates.end()); backtracking(candidates, target, 0, 0, used); return res; } };
|
分割回文串
https://leetcode.cn/problems/palindrome-partitioning/description/
给你一个字符串 s
,请你将 s
分割成一些子串,使每个子串都是 回文串 。返回s
所有可能的分割方案。
回文串 是正着读和反着读都一样的字符串。
示例:
1 2
| 输入:s = "aab" 输出:[["a","a","b"],["aa","b"]]
|
思路:
- 首先给出回文字符的判断方法:首尾指针来回比较
- 回溯算法中最关键的点在于
startindex
的使用,利用这个来移动s.substr(startindex, i-startindex+1)
来截取并筛选出相应的字符串的值进行回文比较 - 回溯的终止条件是
startindex>=s.size()
如果超出了范围那么久说明到终点了
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
| class Solution { public: vector<vector<string>> res; vector<string> path; bool ishuiwen(string s, int start, int end){ for(int i = start,j = end;i<j;i++,j--){ if(s[i]!=s[j]){ return false; } } return true; } void backtracking(string s, int startindex){ if(startindex>=s.size()){ res.push_back(path); return; } for(int i=startindex;i<s.size();i++){ if(ishuiwen(s,startindex,i)){ string str = s.substr(startindex, i-startindex+1); path.push_back(str); }else{ continue; } backtracking(s,i+1); path.pop_back(); } } vector<vector<string>> partition(string s) { backtracking(s,0); return res; } };
|
复原IP地址
https://leetcode.cn/problems/restore-ip-addresses/description/
有效 IP 地址 正好由四个整数(每个整数位于0
到 255
之间组成,且不能含有前导0
),整数之间用 '.'
分隔。
- 例如:
"0.1.2.201"
和"192.168.1.1"
是有效 IP 地址,但是"0.011.255.245"
、"192.168.1.312"
和"192.168@1.1"
是 无效 IP 地址。
给定一个只包含数字的字符串 s
,用以表示一个 IP地址,返回所有可能的有效 IP 地址,这些地址可以通过在s
中插入 '.'
来形成。你 不能重新排序或删除 s
中的任何数字。你可以按任何 顺序返回答案。
示例 :
1 2
| 输入:s = "25525511135" 输出:["255.255.11.135","255.255.111.35"]
|
思路:
- 回溯三部曲第一步:如何确定函数的类型以及参数呢?首先参数肯定包含了s、开始的位置(因为要一直往后移动并选择)、以及一个标记用于是否已经有三个点
- 写好判断是否合法的函数,这里比较多的陷阱需要注意
- 注意,当放进去3个点之后别忘了判断最后一位是否满足合法性的要求,容易忽略最后一位的情况
- 注意字符串的插入
s.inset(n,'.')
和删除s.erase(n)
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
| class Solution { public: vector<string> res; bool isvalid(string s, int start, int end){ if(start>end){ return false; } if(s[start]=='0'&&start!=end){ return false; } int num = 0; for(int i=start;i<=end;i++){ if (s[i] > '9' || s[i] < '0') { return false; } num=num*10+(s[i]-'0'); if(num>255){ return false; } } return true; } void backtracking(string s, int startindex, int pointnum){ if(pointnum==3){ if (isvalid(s, startindex, s.size() - 1)) { res.push_back(s); } return; } for(int i=startindex;i<s.size();i++){ if(isvalid(s,startindex,i)){ s.insert(s.begin()+i+1,'.'); pointnum++; backtracking(s,i+2,pointnum); pointnum--; s.erase(s.begin()+i+1); }else break; } } vector<string> restoreIpAddresses(string s) { backtracking(s,0,0); return res; } };
|
子集
https://leetcode.cn/problems/subsets/description/
给你一个整数数组 nums
,数组中的元素互不相同 。返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。你可以按任意顺序 返回解集。
示例:
思路:
这道题比较简单,就是简单的遍历就可以了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| class Solution { public: vector<vector<int>> res; vector<int> path; void backtracking(vector<int>& nums,int startindex){ res.push_back(path); for(int i=startindex;i<nums.size();i++){ path.push_back(nums[i]); backtracking(nums,i+1); path.pop_back(); } } vector<vector<int>> subsets(vector<int>& nums) { backtracking(nums,0); return res; } };
|
子集II
https://leetcode.cn/problems/subsets-ii/description/
给你一个整数数组 nums
,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。返回的解集中,子集可以按任意顺序 排列。
示例:
注意:
凡是涉及到去重的操作,都需要优先进行排序操作
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
| class Solution { public: vector<vector<int>> res; vector<int> path; void backtracking(vector<int> nums, int startindex, vector<bool> used){ res.push_back(path);
for(int i=startindex;i<nums.size();i++){ if(i>0&&nums[i]==nums[i-1]&&used[i-1]==false){ continue; } path.push_back(nums[i]); used[i]=true; backtracking(nums,i+1,used); used[i]=false; path.pop_back(); } } vector<vector<int>> subsetsWithDup(vector<int>& nums) { vector<bool> used(nums.size(),false); sort(nums.begin(), nums.end()); backtracking(nums,0,used); return res; } };
|
非递减子序列
https://leetcode.cn/problems/non-decreasing-subsequences/description/
给你一个整数数组 nums
,找出并返回所有该数组中不同的递增子序列,递增子序列中至少有两个元素 。你可以按 任意顺序返回答案。数组中可能含有重复元素,如出现两个整数相等,也可以视作递增序列的一种特殊情况。
示例 1:
示例 2:
1 2
| 输入:nums = [4,4,3,2,1] 输出:[[4,4]]
|
思路:
首先这道题不需要去重同时也不需要提前进行排序
但是需要对同一层的元素进行去重操作
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
| class Solution { public: vector<vector<int>> res; vector<int> path; void backtracking(vector<int>& nums, int startindex){ if(path.size()>1){ res.push_back(path); } unordered_set<int> uset; for(int i =startindex;i<nums.size();i++){ if ((!path.empty() && nums[i] < path.back()) || uset.find(nums[i]) != uset.end()) { continue; } uset.insert(nums[i]); path.push_back(nums[i]); backtracking(nums,i+1); path.pop_back(); } } vector<vector<int>> findSubsequences(vector<int>& nums) { backtracking(nums,0); return res; } };
|
全排列
https://leetcode.cn/problems/permutations/description/
给定一个不含重复数字的数组 nums
,返回其所有可能的全排列 。你可以 按任意顺序返回答案。
示例 1:
示例 2:
1 2
| 输入:nums = [0,1] 输出:[[0,1],[1,0]]
|
思路:
要求解全排列,因此回溯退出的条件是当path的长度和nums的长度一样的时候就达到了退出的条件
因为这道题没有重复的元素,求解全排列需要每次都从0开始选择,因此难点在于如何标记出已经选择过的元素
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
| class Solution { public: vector<vector<int>> res; vector<int> path; void backtracking(vector<int>& nums, vector<bool>& used){ if(path.size()==nums.size()){ res.push_back(path); } for(int i=0;i<nums.size();i++){ if(used[i]==true){ continue; } path.push_back(nums[i]); used[i]=true; backtracking(nums,used); used[i]=false; path.pop_back(); } } vector<vector<int>> permute(vector<int>& nums) { vector<bool> used(nums.size(),false); backtracking(nums,used); return res; } };
|
全排列II
https://leetcode.cn/problems/permutations-ii/
给定一个可包含重复数字的序列 nums
,按任意顺序 返回所有不重复的全排列。
示例 1:
示例 2:
思路:
首先这个全排列有重复的元素,因此需要有去重的操作,既然涉及到去重那需要重新排序,同时需要跳过重复的元素
第二步,既然是全排列,那么需要标记重复选择的元素并选择跳过
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
| class Solution { public: vector<vector<int>> res; vector<int> path; void backtracking(vector<int>& nums, vector<bool>& used){ if(path.size()==nums.size()){ res.push_back(path); return; } for(int i=0;i<nums.size();i++){ if(i>0&&nums[i]==nums[i-1]&&used[i-1]==false){ continue; } if(used[i]==false){ used[i]=true; path.push_back(nums[i]); backtracking(nums, used); path.pop_back(); used[i]=false; } } } vector<vector<int>> permuteUnique(vector<int>& nums) { sort(nums.begin(),nums.end()); vector<bool> used(nums.size(),false); backtracking(nums,used); return res; } };
|
重新安排行程
https://leetcode.cn/problems/reconstruct-itinerary/description/
给你一份航线列表 tickets
,其中tickets[i] = [fromi, toi]
表示飞机出发和降落的机场地点。请你对该行程进行重新规划排序。所有这些机票都属于一个从JFK
(肯尼迪国际机场)出发的先生,所以该行程必须从JFK
开始。如果存在多种有效的行程,请你按字典排序返回最小的行程组合。
- 例如,行程
["JFK", "LGA"]
与["JFK", "LGB"]
相比就更小,排序更靠前。
思路:
【困难】
首先第一步:确定终止条件,遇到的机场个数,如果达到了(航班数量+1)
记录航班的数量,使用unordered_map<string, map<string, int>> targets;
来记录航班的映射关系,我定义为全局变量。
当然把参数放进函数里传进去也是可以的,我是尽量控制函数里参数的长度。参数里还需要ticketNum,表示有多少个航班
回溯的过程中,如何遍历一个机场所对应的所有机场呢?
这里刚刚说过,在选择映射函数的时候,不能选择unordered_map<string, multiset<string>> targets
,因为一旦有元素增删multiset的迭代器就会失效,当然可能有牛逼的容器删除元素迭代器不会失效,这里就不在讨论了。
可以说本题既要找到一个对数据进行排序的容器,而且还要容易增删元素,迭代器还不能失效。
所以我选择了unordered_map<string, map<string, int>> targets
来做机场之间的映射
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
| class Solution { public: vector<string> res; unordered_map<string, map<string, int>> targets; bool backtracking(int ticketnum, vector<string>& res){ if(res.size()==ticketnum+1){ return true; } for(pair<const string, int>& target: targets[res[res.size()-1]]){ if(target.second>0){ res.push_back(target.first); target.second--; if(backtracking(ticketnum, res)) return true; res.pop_back(); target.second++; } } return false; } vector<string> findItinerary(vector<vector<string>>& tickets) {
for(const vector<string>& vec: tickets){ targets[vec[0]][vec[1]]++; } res.push_back("JFK"); backtracking(tickets.size(), res); return res; } };
|
N皇后
https://leetcode.cn/problems/n-queens/description/
n 皇后问题 研究的是如何将 n
个皇后放置在 n×n
的棋盘上,并且使皇后彼此之间不能相互攻击。
给你一个整数 n
,返回所有不同的 n皇后问题 的解决方案。
每一种解法包含一个不同的 n 皇后问题的棋子放置方案,该方案中 'Q'
和 '.'
分别代表了皇后和空位。
1 2 3
| 输入:n = 4 输出:[[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]] 解释:如上图所示,4 皇后问题存在两个不同的解法。
|
思路:
这道题关键在于用好数据结构和写好合法性的判断
关键在于定义好chessboard
第二步是把合法性位置判断写好
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| class Solution { public: vector<vector<string>> res; bool isvalid(int row, int col, vector<string>& chessboard,int n){ for(int j=0;j<n;j++){ if(chessboard[row][j]=='Q') return false; } for(int i=0;i<n;i++){ if(chessboard[i][col]=='Q') return false; } for (int i = row - 1, j = col - 1; i >=0 && j >= 0; i--, j--) { if (chessboard[i][j] == 'Q') { return false; } } for(int i = row-1,j=col+1;i>=0&&j<n;i--,j++){ if(chessboard[i][j]=='Q'){ return false; } } return true; } void backtracking(vector<string>& chessboard, int row, int n){ if(row==n) { res.push_back(chessboard); return; } for(int col = 0;col<n;col++){ if(isvalid(row,col,chessboard,n)){ chessboard[row][col]='Q'; backtracking(chessboard,row+1,n); chessboard[row][col]='.'; } } }
vector<vector<string>> solveNQueens(int n) { std::vector<std::string> chessboard(n, std::string(n, '.')); backtracking(chessboard,0,n); return res; } };
|
解数独
编写一个程序,通过填充空格来解决数独问题。
数独的解法需 遵循如下规则:
- 数字
1-9
在每一行只能出现一次。 - 数字
1-9
在每一列只能出现一次。 - 数字
1-9
在每一个以粗实线分隔的 3x3
宫内只能出现一次。(请参考示例图)
数独部分空格内已填入了数字,空白格用 '.'
表示。
1 2
| 输入:board = [["5","3",".",".","7",".",".",".","."],["6",".",".","1","9","5",".",".","."],[".","9","8",".",".",".",".","6","."],["8",".",".",".","6",".",".",".","3"],["4",".",".","8",".","3",".",".","1"],["7",".",".",".","2",".",".",".","6"],[".","6",".",".",".",".","2","8","."],[".",".",".","4","1","9",".",".","5"],[".",".",".",".","8",".",".","7","9"]] 输出:[["5","3","4","6","7","8","9","1","2"],["6","7","2","1","9","5","3","4","8"],["1","9","8","3","4","2","5","6","7"],["8","5","9","7","6","1","4","2","3"],["4","2","6","8","5","3","7","9","1"],["7","1","3","9","2","4","8","5","6"],["9","6","1","5","3","7","2","8","4"],["2","8","7","4","1","9","6","3","5"],["3","4","5","2","8","6","1","7","9"]]
|
思路:
深度优先搜索,加上合法性判断
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
| class Solution { public: bool isvalid(vector<vector<char>>& board, int row, int col, char a){ for(int j=0;j<9;j++){ if(board[row][j]==a) return false; } for(int i=0;i<9;i++){ if(board[i][col]==a) return false; } for(int i= (row/3)*3;i<(row/3)*3+3;i++){ for(int j=(col/3)*3; j<(col/3)*3+3;j++){ if(board[i][j]==a) return false; } } return true; } bool backtracking(vector<vector<char>>& board){ for(int i=0;i<board.size();i++){ for(int j=0;j<board[0].size();j++){ if(board[i][j]=='.'){ for(char a='1';a<='9';a++){ if(isvalid(board,i,j,a)){ board[i][j]=a; if(backtracking(board)) return true; board[i][j]='.'; } } return false; } } } return true; } void solveSudoku(vector<vector<char>>& board) { backtracking(board); } };
|
贪心算法
分发饼干
https://leetcode.cn/problems/assign-cookies/description/
假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。对每个孩子i
,都有一个胃口值g[i]
,这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干j
,都有一个尺寸 s[j]
。如果s[j] >= g[i]
,我们可以将这个饼干 j
分配给孩子 i
,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。
示例 :
1 2 3 4 5 6
| 输入: g = [1,2,3], s = [1,1] 输出: 1 解释: 你有三个孩子和两块小饼干,3个孩子的胃口值分别是:1,2,3。 虽然你有两块小饼干,由于他们的尺寸都是1,你只能让胃口值是1的孩子满足。 所以你应该输出1。
|
示例 :
1 2 3 4 5 6
| 输入: g = [1,2], s = [1,2,3] 输出: 2 解释: 你有两个孩子和三块小饼干,2个孩子的胃口值分别是1,2。 你拥有的饼干数量和尺寸都足以让所有孩子满足。 所以你应该输出2.
|
思路:
这里的局部最优就是大饼干喂给胃口大的,充分利用饼干尺寸喂饱一个,全局最优就是喂饱尽可能多的小孩。
可以尝试使用贪心策略,先将饼干数组和小孩数组排序。
然后从后向前遍历小孩数组,用大饼干优先满足胃口大的,并统计满足小孩数量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| class Solution { public: int findContentChildren(vector<int>& g, vector<int>& s) { sort(s.begin(),s.end()); sort(g.begin(),g.end()); int index=s.size()-1; int num= 0; for(int i=g.size()-1; i>=0;i--){ if(index>=0&&s[index]>=g[i]){ num++; index--; } } return num; } };
|
摆动序列
https://leetcode.cn/problems/wiggle-subsequence/description/
如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为摆动序列。第一个差(如果存在的话)可能是正数或负数。仅有一个元素或者含两个不等元素的序列也视作摆动序列。
- 例如,
[1, 7, 4, 9, 2, 5]
是一个摆动序列 ,因为差值 (6, -3, 5, -7, 3)
是正负交替出现的。
给你一个整数数组 nums
,返回 nums
中作为摆动序列 的 最长子序列的长度
示例 :
1 2 3
| 输入:nums = [1,7,4,9,2,5] 输出:6 解释:整个序列均为摆动序列,各元素之间的差值为 (6, -3, 5, -7, 3)
|
1 2 3 4
| 输入:nums = [1,17,5,10,13,15,10,5,16,8] 输出:7 解释:这个序列包含几个长度为 7 摆动序列。 其中一个是 [1, 17, 10, 13, 10, 16, 8] ,各元素之间的差值为 (16, -7, 3, -3, 6, -8)
|
思路:
本题异常情况的本质,就是要考虑平坡,平坡分两种,一个是 上下中间有平坡,一个是单调有平坡,如图
同时需要注意的是在判断条件语句的时候,不能简单的用判断相乘法小于0作为判断,因为存在平坡的情况
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| class Solution { public: int wiggleMaxLength(vector<int>& nums) { if(nums.size()<=1){ return nums.size(); } int num=1; vector<int> differ; for(int i=1;i<nums.size();i++){ differ.push_back(nums[i]-nums[i-1]); } int preDiff=0; for(int i=0;i<differ.size();i++){ if((preDiff<=0&& differ[i]>0)||(differ[i]<0&&preDiff>=0)){ num++; preDiff = differ[i]; } } return num; } };
|
最大子数组和
https://leetcode.cn/problems/maximum-subarray/description/
给你一个整数数组 nums
,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
子数组 是数组中的一个连续部分。
示例:
1 2 3
| 输入:nums = [-2,1,-3,4,-1,2,1,-5,4] 输出:6 解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。
|
思路:
这道题使用的是局部的最优贪心的思路,如果遇到让总的值小于0,那么久立刻让总的值变成0,那么下一轮就从头开始记了,同时max会每一轮进行判断是否有比当前的最大值大,如果有那么就进行替换
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class Solution { public: int maxSubArray(vector<int>& nums) { int res = INT32_MIN; int count = 0; for(int i=0;i<nums.size();i++){ count+=nums[i]; if(count>res){ res = count; } if(count<=0) count = 0; } return res; } };
|
买卖股票的最佳时机
https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-ii/description/
给你一个整数数组 prices
,其中 prices[i]
表示某支股票第 i
天的价格。在每一天,你可以决定是否购买和/或出售股票。你在任何时候最多 只能持有 一股股票。你也可以先购买,然后在 同一天 出售。
返回 你能获得的 最大 利润 。
示例 1:
1 2 3 4 5
| 输入:prices = [7,1,5,3,6,4] 输出:7 解释:在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5 - 1 = 4 。 随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6 - 3 = 3 。 总利润为 4 + 3 = 7 。
|
思路:
把利润分解为每天为单位的维度,而不是从 0 天到第 3天整体去考虑!
那么根据 prices可以得到每天的利润序列:(prices[i] - prices[i - 1]).....(prices[1] - prices[0])
相当于是每天的利润之差和0的比较,只选择为正的值,负数的情况直接忽略
1 2 3 4 5 6 7 8 9 10
| class Solution { public: int maxProfit(vector<int>& prices) { int res = 0; for(int i=1;i<prices.size();i++){ res+=max(prices[i]-prices[i-1],0); } return res; } };
|
动态规划
状态转移公式(递推公式)是很重要,但动规不仅仅只有递推公式。
对于动态规划问题,我将拆解为如下五步曲,这五步都搞清楚了,才能说把动态规划真的掌握了!
- 确定dp数组(dp table)以及下标的含义
- 确定递推公式
- dp数组如何初始化
- 确定遍历顺序
- 举例推导dp数组
注意:动态规划的问题一般只会输出最后的一个结果,不会输出比如中间的路径等相关的值
斐波那契数列
https://leetcode.cn/problems/fibonacci-number/
斐波那契数 (通常用 F(n)
表示)形成的序列称为 斐波那契数列 。该数列由0
和 1
开始,后面的每一项数字都是前面两项数字的和。也就是:
1 2
| F(0) = 0,F(1) = 1 F(n) = F(n - 1) + F(n - 2),其中 n > 1
|
给定 n
,请计算 F(n)
。
示例:
1 2 3
| 输入:n = 2 输出:1 解释:F(2) = F(1) + F(0) = 1 + 0 = 1
|
思路:
因为这道题给出了递推公式:F(n) = F(n - 1) + F(n - 2)
动规五部曲:
这里我们要用一个一维dp数组来保存递归的结果
确定dp数组以及下标的含义:dp[i]的定义为:第i个数的斐波那契数值是dp[i]
确定递推公式F(n) = F(n - 1) + F(n - 2)
dp数组如何初始化
确定遍历顺序
从递归公式dp[i] = dp[i - 1] + dp[i - 2];中可以看出,dp[i]是依赖 dp[i- 1] 和 dp[i - 2],那么遍历的顺序一定是从前到后遍历的
举例推导dp数组
1 2 3 4 5 6 7 8 9 10 11 12 13
| class Solution { public: int fib(int n) { if(n<=1) return n; vector<int>dp(n+1); dp[0]=0; dp[1]=1; for(int i=2;i<=n;i++){ dp[i]=dp[i-1]+dp[i-2]; } return dp[n]; } };
|
爬楼梯
假设你正在爬楼梯。需要 n
阶你才能到达楼顶。每次你可以爬1
或 2
个台阶。你有多少种不同的方法可以爬到楼顶呢?
示例 1:
1 2 3 4 5
| 输入:n = 2 输出:2 解释:有两种方法可以爬到楼顶。 1. 1 阶 + 1 阶 2. 2 阶
|
思路:
动态规划简单题,递推公式:dp[i] = dp[i-2]+dp[i-1];
1 2 3 4 5 6 7 8 9 10 11 12 13
| class Solution { public: int climbStairs(int n) { if(n<=2) return n; vector<int> dp(n+1); dp[1]=1; dp[2]=2; for(int i=3;i<=n;i++){ dp[i] = dp[i-2]+dp[i-1]; } return dp[n]; } };
|
最小费用爬楼梯
https://leetcode.cn/problems/min-cost-climbing-stairs/description/
给你一个整数数组 cost
,其中 cost[i]
是从楼梯第 i
个台阶向上爬需要支付的费用。一旦你支付此费用,即可选择向上爬一个或者两个台阶。你可以选择从下标为0
或下标为 1
的台阶开始爬楼梯。请你计算并返回达到楼梯顶部的最低花费。
示例 1:
1 2 3 4 5
| 输入:cost = [10,15,20] 输出:15 解释:你将从下标为 1 的台阶开始。 - 支付 15 ,向上爬两个台阶,到达楼梯顶部。 总花费为 15 。
|
思路:
动态规划可以有两个途径得到dp[i],一个是dp[i-1]一个是dp[i-2]。
dp[i - 1] 跳到 dp[i] 需要花费dp[i - 1] + cost[i - 1]
。
dp[i - 2] 跳到 dp[i] 需要花费dp[i - 2] + cost[i - 2]
。
那么究竟是选从dp[i - 1]跳还是从dp[i - 2]跳呢?
一定是选最小的,所以dp[i] = min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2])
;
1 2 3 4 5 6 7 8 9 10 11 12 13
| class Solution { public: int minCostClimbingStairs(vector<int>& cost) { int n = cost.size(); vector<int> dp(n+1); dp[0]= 0; dp[1] = 0; for(int i=2;i<=n;i++){ dp[i]=min(dp[i-1]+cost[i-1], dp[i-2]+cost[i-2]); } return dp[n]; } };
|
不同路径
一个机器人位于一个 m x n
网格的左上角(起始点在下图中标记为 “Start”)。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish” )。问总共有多少条不同的路径?
思路:
简单的动态规划问题,只需要保证每次迭代都从上面和左边进行叠加
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| class Solution { public: int uniquePaths(int m, int n) { vector<vector<int>>dp(m, vector<int>(n, 0)); for(int i=0;i<m;i++){ dp[i][0]=1; } for(int i=0;i<n;i++){ dp[0][i]=1; } for(int i=1;i<m;i++){ for(int j=1;j<n;j++){ dp[i][j]=dp[i-1][j]+dp[i][j-1]; } } return dp[m-1][n-1]; } };
|
不同路径II
https://leetcode.cn/problems/unique-paths-ii/description/
一个机器人位于一个 m x n
网格的左上角(起始点在下图中标记为 “Start”)。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?网格中的障碍物和空位置分别用1
和 0
来表示。
思路:
和上一题的思路一样,都是需要遍历路径就行,但是这里加入了一个新的数组用来存储有障碍物的位置,因此需要额外进行标记&&obstacleGrid[i][0]==0
的信息,同时遇到障碍物就不改变对应的值,直接continue
就好
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class Solution { public: int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) { vector<vector<int>>dp(obstacleGrid.size(), vector<int>(obstacleGrid[0].size(), 0)); for(int i=0;i<obstacleGrid.size()&&obstacleGrid[i][0]==0;i++) dp[i][0]=1; for(int i=0;i<obstacleGrid[0].size()&&obstacleGrid[0][i]==0;i++) dp[0][i]=1; for(int i=1;i<obstacleGrid.size();i++){ for(int j=1;j<obstacleGrid[0].size();j++){ if(obstacleGrid[i][j]==1) continue; dp[i][j]=dp[i-1][j]+dp[i][j-1]; } } return dp[obstacleGrid.size()-1][obstacleGrid[0].size()-1]; } };
|
]]>
+ 语言细节
vector的长度:
- C++:nums.size()
- Python:len(nums)
- GO:len(nums)
初始化数组:
- C++:array[n]={0} 让所有元素都是0
构造vector:
- C++:vector result(长度,元素)
- Python:res = [float('inf')] * len(nums)
- GO:=make([]int,n)
for循环:
- C++:条件小括号+循环体中括号
- Python:冒号且不需要小括号包条件
- GO:循环体中括号,条件按照C++写但是不需要小括号
数组
二分查找
题目描述
链接: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
while left <= right: middle = left + (right - left) // 2 if nums[middle] > target: right = middle - 1 elif nums[middle] < target: left = middle + 1 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 - 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 = , val = 3 输出:2, nums = 解释:函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。你不需要考虑数组中超出新长度后面的元素。例如,函数返回的新长度为 2 ,而 nums = 或 nums = ,也会被视作正确答案。
|
示例 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 解释:子数组 是该条件下的长度最小的子数组。
|
示例 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 (sum >= s) { subLength = (j - i + 1); result = result < subLength ? result : subLength; sum -= nums[i++]; } } return result == INT32_MAX ? 0 : result; } };
|
螺旋矩阵
https://leetcode.cn/problems/spiral-matrix-ii/
题目描述
给你一个正整数 n
,生成一个包含 1
到n2
所有元素,且元素按顺时针顺序螺旋排列的n x n
正方形矩阵 matrix
1 2
| 输入:n = 3 输出:[[1,2,3],[8,9,4],[7,6,5]]
|
示例 2:
思路:大模拟循环遍历
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++) { 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; } };
|
两个数组的交集
https://leetcode.cn/problems/intersection-of-two-arrays/description/
题目描述
示例 1:
1 2
| 输入:nums1 = , nums2 = 输出:
|
示例 2:
1 2 3
| 输入:nums1 = , nums2 = 输出: 解释: 也是可通过的
|
思路
使用哈希表存储,但是用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; unordered_set<int> nums_set(nums1.begin(), nums1.end()); for (int num : nums2) { 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
是不是快乐数。
「快乐数」 定义为:
- 对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
- 然后重复这个过程直到这个数变为 1,也可能是 无限循环但始终变不到 1。
- 如果这个过程 结果为 1,那么这个数就是快乐数。
如果 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; 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 = , target = 9 输出: 解释:因为 nums + nums == 9 ,返回 。
|
示例 2:
1 2
| 输入:nums = , target = 6 输出:
|
示例 3:
1 2
| 输入:nums = , target = 6 输出:
|
思路:
构建一个哈希表,然后遍历一遍就行了在哈希表中找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++) { auto iter = map.find(target - nums[i]); if(iter != map.end()) { return {iter->second, i}; } map.insert(pair<int, int>(nums[i], i)); } return {}; } };
|
四数相加
https://leetcode.cn/problems/4sum-ii/description/
给你四个整数数组nums1
、nums2
、nums3
和nums4
,数组长度都是 n
,请你计算有多少个元组(i, j, k, l)
能满足:
0 <= i, j, k, l < n
nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0
示例 1:
1 2 3 4 5 6
| 输入:nums1 = [1,2], nums2 = [-2,-1], nums3 = [-1,2], nums4 = [0,2] 输出:2 解释: 两个元组如下: 1. -> nums1[0] + nums2[0] + nums3[0] + nums4[1] = 1 + + + 2 = 0 2. -> nums1[1] + nums2[1] + nums3[0] + nums4[0] = 2 + + + 0 = 0
|
示例 2:
1 2
| 输入:nums1 = [0], nums2 = [0], nums3 = [0], nums4 = [0] 输出:1
|
思路
- 首先定义 一个unordered_map,key放a和b两数之和,value放a和b两数之和出现的次数。
- 遍历大A和大B数组,统计两个数组元素之和,和出现的次数,放到map中。
- 定义int变量count,用来统计 a+b+c+d = 0 出现的次数。
- 在遍历大C和大D数组,找到如果 0-(c+d)在map中出现过的话,就用count把map中key对应的value也就是出现次数统计出来。
- 最后返回统计值 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/
给你两个字符串:ransomNote
和 magazine
,判断 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 != j
、i != k
且 j != k
,同时还满足 nums[i] + nums[j] + nums[k] == 0
。请
你返回所有和为 0
且不重复的三元组。
注意:答案中不可以包含重复的三元组。
示例 1:
1 2 3 4 5 6 7
| 输入:nums = 输出: 解释: nums + nums + nums = (-1) + 0 + 1 = 0 。 nums + nums + nums = 0 + 1 + (-1) = 0 。 nums + nums + nums = (-1) + 2 + (-1) = 0 。 不同的三元组是 和 。
|
思路
其实这道题目使用哈希法并不十分合适,因为在去重的操作中有很多细节需要注意,在面试中很难直接写出没有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()); for (int i = 0; i < nums.size(); i++) { if (nums[i] > 0) { return result; } 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]}); 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 = , val = 3 输出:2, nums = 解释:函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。你不需要考虑数组中超出新长度后面的元素。例如,函数返回的新长度为 2 ,而 nums = 或 nums = ,也会被视作正确答案。
|
示例 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/
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/
1 2
| 输入:head = [1,2,3,4,5], n = 2 输出:[1,2,3,5]
|
示例 2:
思路:
遍历,用两个指针分别来记录
如果要删除倒数第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; while (fast != NULL) { fast = fast->next; slow = slow->next; } slow->next = slow->next->next; return dummyHead->next; } };
|
链表相交
给你两个单链表的头节点 headA
和 headB
,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回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; } };
|
接雨水
给定 n
个非负整数表示每个宽度为 1
的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
示例 1:
1 2 3
| 输入:height = [0,1,0,2,1,0,1,3,2,1,2,1] 输出:6 解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。
|
示例 2:
1 2
| 输入:height = [4,2,0,3,2,5] 输出:9
|
思路:
找到最大的左边和最大的右边并相减
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
| class Solution { public: int trap(vector<int>& height) { if(height.size()<=2) return 0; vector<int> maxLeft(height.size(), 0); vector<int> maxRight(height.size(), 0); int size = maxRight.size(); maxLeft[0] = height[0]; for(int i=1;i<size;i++){ maxLeft[i] = max(height[i],maxLeft[i-1]); } maxRight[size-1] = height[size-1]; for(int i=size-2;i>=0;i--){ maxRight[i] = max(height[i],maxRight[i+1]); } int sum=0; for(int i=0;i<size;i++){ int count = min(maxLeft[i], maxRight[i])-height[i]; if(count > 0) sum+=count; } return sum; } };
|
柱形图中的最大矩形
https://leetcode.cn/problems/largest-rectangle-in-histogram/description/
1 2 3
| 输入:heights = [2,1,5,6,2,3] 输出:10 解释:最大的矩形为图中红色区域,面积为 10
|
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: int largestRectangleArea(vector<int>& heights) { vector<int> minLeft(heights.size()); vector<int> minRight(heights.size()); int size = heights.size();
minLeft[0] = -1; for(int i=1;i<size;i++){ int t= i-1; while(t>=0&&heights[t]>=heights[i]) t=minLeft[t]; minLeft[i]=t; } minRight[size-1]=size; for(int i=size -2;i>=0;i--){ int t=i+1; while(t<size&&heights[t]>=heights[i]) t=minRight[t]; minRight[i]=t; }
int res=0; for(int i=0;i<size;i++){ int sum=heights[i]*(minRight[i]-minLeft[i]-1); res = max(sum,res); }
return res; } };
|
二叉树
深搜回溯
深度优先搜索的三部曲:
- 确定搜索函数的返回值以及搜索函数的参数分别是什么
- 确定每次找到叶子结点的终止条件
- 确定for单层搜索的逻辑,包含push,backtracking,pop
别忘了最开始的初始化步骤
组合问题
https://leetcode.cn/problems/combinations/description/
给定两个整数 n
和 k
,返回范围[1, n]
中所有可能的 k
个数的组合。
你可以按 任何顺序 返回答案。
示例 1:
1 2 3 4 5 6 7 8 9 10
| 输入:n = 4, k = 2 输出:
|
示例 2:
1 2
| 输入:n = 1, k = 1 输出:[[1]]
|
思路,使用深度优先搜索算法进行处理
- 首先要区分private和public这两个部分分别做的内容,private主要就是写出回溯的函数主体并且可能需要的数据结构
- public中就对函数进行跳用以及数据结构的使用
- 第一步就是确定函数的类型和返回,这里用了一个
startindex
用来存储下一次进行选择的位置点这样能够避免重复 - 同时函数的返回类型是二位的向量结构
- 同时定义终止条件和单层循环的内容
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/
找出所有相加之和为 n
的 k
个数的组合,且满足下列条件:
返回 所有可能的有效组合的列表。该列表不能包含相同的组合两次,组合可以以任何顺序返回。
示例:
1 2 3 4 5
| 输入: k = 3, n = 7 输出: [[1,2,4]] 解释: 1 + 2 + 4 = 7 没有其他符合的组合了。
|
思路:简单的深度优先搜索,但需要注意的是可以适当采用减枝操作和必要的时候添加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] = { "", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz", }; 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; } };
|
组合总和
https://leetcode.cn/problems/combination-sum/
给你一个 无重复元素 的整数数组candidates
和一个目标整数 target
,找出candidates
中可以使数字和为目标数 target
的所有 不同组合 ,并以列表形式返回。你可以按任意顺序 返回这些组合。
candidates
中的 同一个 数字可以无限制重复被选取。如果至少一个数字的被选数量不同,则两种组合是不同的。
对于给定的输入,保证和为 target
的不同组合数少于150
个。
示例 :
1 2 3 4 5 6
| 输入:candidates = [2,3,6,7], target = 7 输出:[[2,2,3],[7]] 解释: 2 和 3 可以形成一组候选,2 + 2 + 3 = 7 。注意 2 可以使用多次。 7 也是一个候选, 7 = 7 。 仅有这两种组合。
|
思路:
题目最关键的点在于能重复使用元素但是不能重复元素的组合不能被重复输出
因此需要调整startindex的开始的位置是在backtracking(candidates,target,sum,i);
注意,这个时候从i开始保证还能用到自己的元素重复使用,还有最重要的sort(candidates.begin(), candidates.end()); // 需要排序
排序之后能够很好的进行剪枝,将一些加了之后元素大于目标的删掉直接跳过
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
| class Solution { public: vector<vector<int>> res; vector<int> path; void backtracking(vector<int>&candidates, int target, int sum, int startindex){ if(sum==target){ res.push_back(path); return; } for(int i=startindex;i<candidates.size();i++){ if(sum>target){ return; } sum+=candidates[i]; path.push_back(candidates[i]); backtracking(candidates,target,sum,i); sum-=candidates[i]; path.pop_back(); } } vector<vector<int>> combinationSum(vector<int>& candidates, int target) { sort(candidates.begin(), candidates.end()); backtracking(candidates, target, 0,0); return res; } };
|
组合总和II
https://leetcode.cn/problems/combination-sum-ii/description/
给定一个候选人编号的集合 candidates
和一个目标数target
,找出 candidates
中所有可以使数字和为target
的组合。
candidates
中的每个数字在每个组合中只能使用 一次。
注意:解集不能包含重复的组合。
示例 :
1 2 3 4 5 6 7 8
| 输入: candidates = , target = 8, 输出:
|
思路:
这个地方最大的困难在于每个数字在每个组合中只能使用一次,同时集合中的元素存在重复的元素,那么这个时候有一个问题是如何才能对元素进行去重处理呢,就是让每个元素只能被使用一次
去重的操作就在于vector<bool> used(candidates.size(),false); sort(candidates.begin(), candidates.end());
首先需要在backtracking
中定一个continue,这个地方是为了筛选不是重复的部分,那么如何区分开是否是同一个数组中重复的元素而不是重复利用的元素呢?
i>0&&candidates[i]==candidates[i-1]
这个地方表明了对元素相邻之间进行比较used[i-1]==false
如果这个地方是false,那么说明这个元素是同一层的元素(同一个数组中的元素)- 注意
i<candidates.size()&&sum + candidates[i] <= target
为了避免出现超出时间限制的情况
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
| class Solution { public: vector<vector<int>> res; vector<int> path; void backtracking(vector<int>& candidates, int target, int sum, int startindex, vector<bool>used){ if(sum==target){ res.push_back(path); return; } for(int i= startindex;i<candidates.size();i++){ if(i>0&&candidates[i]==candidates[i-1]&&used[i-1]==false){ continue; } sum+=candidates[i]; used[i]=true; path.push_back(candidates[i]); backtracking(candidates, target, sum, i+1, used); used[i]=false; path.pop_back(); sum-=candidates[i]; } } vector<vector<int>> combinationSum2(vector<int>& candidates, int target) { vector<bool> used(candidates.size(),false); sort(candidates.begin(), candidates.end()); backtracking(candidates, target, 0, 0, used); return res; } };
|
分割回文串
https://leetcode.cn/problems/palindrome-partitioning/description/
给你一个字符串 s
,请你将 s
分割成一些子串,使每个子串都是 回文串 。返回s
所有可能的分割方案。
回文串 是正着读和反着读都一样的字符串。
示例:
1 2
| 输入:s = "aab" 输出:[["a","a","b"],["aa","b"]]
|
思路:
- 首先给出回文字符的判断方法:首尾指针来回比较
- 回溯算法中最关键的点在于
startindex
的使用,利用这个来移动s.substr(startindex, i-startindex+1)
来截取并筛选出相应的字符串的值进行回文比较 - 回溯的终止条件是
startindex>=s.size()
如果超出了范围那么久说明到终点了
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
| class Solution { public: vector<vector<string>> res; vector<string> path; bool ishuiwen(string s, int start, int end){ for(int i = start,j = end;i<j;i++,j--){ if(s[i]!=s[j]){ return false; } } return true; } void backtracking(string s, int startindex){ if(startindex>=s.size()){ res.push_back(path); return; } for(int i=startindex;i<s.size();i++){ if(ishuiwen(s,startindex,i)){ string str = s.substr(startindex, i-startindex+1); path.push_back(str); }else{ continue; } backtracking(s,i+1); path.pop_back(); } } vector<vector<string>> partition(string s) { backtracking(s,0); return res; } };
|
复原IP地址
https://leetcode.cn/problems/restore-ip-addresses/description/
有效 IP 地址 正好由四个整数(每个整数位于0
到 255
之间组成,且不能含有前导0
),整数之间用 '.'
分隔。
- 例如:
"0.1.2.201"
和"192.168.1.1"
是有效 IP 地址,但是"0.011.255.245"
、"192.168.1.312"
和"192.168@1.1"
是 无效 IP 地址。
给定一个只包含数字的字符串 s
,用以表示一个 IP地址,返回所有可能的有效 IP 地址,这些地址可以通过在s
中插入 '.'
来形成。你 不能重新排序或删除 s
中的任何数字。你可以按任何 顺序返回答案。
示例 :
1 2
| 输入:s = "25525511135" 输出:["255.255.11.135","255.255.111.35"]
|
思路:
- 回溯三部曲第一步:如何确定函数的类型以及参数呢?首先参数肯定包含了s、开始的位置(因为要一直往后移动并选择)、以及一个标记用于是否已经有三个点
- 写好判断是否合法的函数,这里比较多的陷阱需要注意
- 注意,当放进去3个点之后别忘了判断最后一位是否满足合法性的要求,容易忽略最后一位的情况
- 注意字符串的插入
s.inset(n,'.')
和删除s.erase(n)
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
| class Solution { public: vector<string> res; bool isvalid(string s, int start, int end){ if(start>end){ return false; } if(s[start]=='0'&&start!=end){ return false; } int num = 0; for(int i=start;i<=end;i++){ if (s[i] > '9' || s[i] < '0') { return false; } num=num*10+(s[i]-'0'); if(num>255){ return false; } } return true; } void backtracking(string s, int startindex, int pointnum){ if(pointnum==3){ if (isvalid(s, startindex, s.size() - 1)) { res.push_back(s); } return; } for(int i=startindex;i<s.size();i++){ if(isvalid(s,startindex,i)){ s.insert(s.begin()+i+1,'.'); pointnum++; backtracking(s,i+2,pointnum); pointnum--; s.erase(s.begin()+i+1); }else break; } } vector<string> restoreIpAddresses(string s) { backtracking(s,0,0); return res; } };
|
子集
https://leetcode.cn/problems/subsets/description/
给你一个整数数组 nums
,数组中的元素互不相同 。返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。你可以按任意顺序 返回解集。
示例:
思路:
这道题比较简单,就是简单的遍历就可以了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| class Solution { public: vector<vector<int>> res; vector<int> path; void backtracking(vector<int>& nums,int startindex){ res.push_back(path); for(int i=startindex;i<nums.size();i++){ path.push_back(nums[i]); backtracking(nums,i+1); path.pop_back(); } } vector<vector<int>> subsets(vector<int>& nums) { backtracking(nums,0); return res; } };
|
子集II
https://leetcode.cn/problems/subsets-ii/description/
给你一个整数数组 nums
,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。返回的解集中,子集可以按任意顺序 排列。
示例:
注意:
凡是涉及到去重的操作,都需要优先进行排序操作
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
| class Solution { public: vector<vector<int>> res; vector<int> path; void backtracking(vector<int> nums, int startindex, vector<bool> used){ res.push_back(path);
for(int i=startindex;i<nums.size();i++){ if(i>0&&nums[i]==nums[i-1]&&used[i-1]==false){ continue; } path.push_back(nums[i]); used[i]=true; backtracking(nums,i+1,used); used[i]=false; path.pop_back(); } } vector<vector<int>> subsetsWithDup(vector<int>& nums) { vector<bool> used(nums.size(),false); sort(nums.begin(), nums.end()); backtracking(nums,0,used); return res; } };
|
非递减子序列
https://leetcode.cn/problems/non-decreasing-subsequences/description/
给你一个整数数组 nums
,找出并返回所有该数组中不同的递增子序列,递增子序列中至少有两个元素 。你可以按 任意顺序返回答案。数组中可能含有重复元素,如出现两个整数相等,也可以视作递增序列的一种特殊情况。
示例 1:
示例 2:
1 2
| 输入:nums = [4,4,3,2,1] 输出:[[4,4]]
|
思路:
首先这道题不需要去重同时也不需要提前进行排序
但是需要对同一层的元素进行去重操作
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
| class Solution { public: vector<vector<int>> res; vector<int> path; void backtracking(vector<int>& nums, int startindex){ if(path.size()>1){ res.push_back(path); } unordered_set<int> uset; for(int i =startindex;i<nums.size();i++){ if ((!path.empty() && nums[i] < path.back()) || uset.find(nums[i]) != uset.end()) { continue; } uset.insert(nums[i]); path.push_back(nums[i]); backtracking(nums,i+1); path.pop_back(); } } vector<vector<int>> findSubsequences(vector<int>& nums) { backtracking(nums,0); return res; } };
|
全排列
https://leetcode.cn/problems/permutations/description/
给定一个不含重复数字的数组 nums
,返回其所有可能的全排列 。你可以 按任意顺序返回答案。
示例 1:
示例 2:
1 2
| 输入:nums = [0,1] 输出:[[0,1],[1,0]]
|
思路:
要求解全排列,因此回溯退出的条件是当path的长度和nums的长度一样的时候就达到了退出的条件
因为这道题没有重复的元素,求解全排列需要每次都从0开始选择,因此难点在于如何标记出已经选择过的元素
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
| class Solution { public: vector<vector<int>> res; vector<int> path; void backtracking(vector<int>& nums, vector<bool>& used){ if(path.size()==nums.size()){ res.push_back(path); } for(int i=0;i<nums.size();i++){ if(used[i]==true){ continue; } path.push_back(nums[i]); used[i]=true; backtracking(nums,used); used[i]=false; path.pop_back(); } } vector<vector<int>> permute(vector<int>& nums) { vector<bool> used(nums.size(),false); backtracking(nums,used); return res; } };
|
全排列II
https://leetcode.cn/problems/permutations-ii/
给定一个可包含重复数字的序列 nums
,按任意顺序 返回所有不重复的全排列。
示例 1:
示例 2:
思路:
首先这个全排列有重复的元素,因此需要有去重的操作,既然涉及到去重那需要重新排序,同时需要跳过重复的元素
第二步,既然是全排列,那么需要标记重复选择的元素并选择跳过
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
| class Solution { public: vector<vector<int>> res; vector<int> path; void backtracking(vector<int>& nums, vector<bool>& used){ if(path.size()==nums.size()){ res.push_back(path); return; } for(int i=0;i<nums.size();i++){ if(i>0&&nums[i]==nums[i-1]&&used[i-1]==false){ continue; } if(used[i]==false){ used[i]=true; path.push_back(nums[i]); backtracking(nums, used); path.pop_back(); used[i]=false; } } } vector<vector<int>> permuteUnique(vector<int>& nums) { sort(nums.begin(),nums.end()); vector<bool> used(nums.size(),false); backtracking(nums,used); return res; } };
|
重新安排行程
https://leetcode.cn/problems/reconstruct-itinerary/description/
给你一份航线列表 tickets
,其中tickets[i] = [fromi, toi]
表示飞机出发和降落的机场地点。请你对该行程进行重新规划排序。所有这些机票都属于一个从JFK
(肯尼迪国际机场)出发的先生,所以该行程必须从JFK
开始。如果存在多种有效的行程,请你按字典排序返回最小的行程组合。
- 例如,行程
["JFK", "LGA"]
与["JFK", "LGB"]
相比就更小,排序更靠前。
思路:
【困难】
首先第一步:确定终止条件,遇到的机场个数,如果达到了(航班数量+1)
记录航班的数量,使用unordered_map<string, map<string, int>> targets;
来记录航班的映射关系,我定义为全局变量。
当然把参数放进函数里传进去也是可以的,我是尽量控制函数里参数的长度。参数里还需要ticketNum,表示有多少个航班
回溯的过程中,如何遍历一个机场所对应的所有机场呢?
这里刚刚说过,在选择映射函数的时候,不能选择unordered_map<string, multiset<string>> targets
,因为一旦有元素增删multiset的迭代器就会失效,当然可能有牛逼的容器删除元素迭代器不会失效,这里就不在讨论了。
可以说本题既要找到一个对数据进行排序的容器,而且还要容易增删元素,迭代器还不能失效。
所以我选择了unordered_map<string, map<string, int>> targets
来做机场之间的映射
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
| class Solution { public: vector<string> res; unordered_map<string, map<string, int>> targets; bool backtracking(int ticketnum, vector<string>& res){ if(res.size()==ticketnum+1){ return true; } for(pair<const string, int>& target: targets[res[res.size()-1]]){ if(target.second>0){ res.push_back(target.first); target.second--; if(backtracking(ticketnum, res)) return true; res.pop_back(); target.second++; } } return false; } vector<string> findItinerary(vector<vector<string>>& tickets) {
for(const vector<string>& vec: tickets){ targets[vec[0]][vec[1]]++; } res.push_back("JFK"); backtracking(tickets.size(), res); return res; } };
|
N皇后
https://leetcode.cn/problems/n-queens/description/
n 皇后问题 研究的是如何将 n
个皇后放置在 n×n
的棋盘上,并且使皇后彼此之间不能相互攻击。
给你一个整数 n
,返回所有不同的 n皇后问题 的解决方案。
每一种解法包含一个不同的 n 皇后问题的棋子放置方案,该方案中 'Q'
和 '.'
分别代表了皇后和空位。
1 2 3
| 输入:n = 4 输出:[[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]] 解释:如上图所示,4 皇后问题存在两个不同的解法。
|
思路:
这道题关键在于用好数据结构和写好合法性的判断
关键在于定义好chessboard
第二步是把合法性位置判断写好
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| class Solution { public: vector<vector<string>> res; bool isvalid(int row, int col, vector<string>& chessboard,int n){ for(int j=0;j<n;j++){ if(chessboard[row][j]=='Q') return false; } for(int i=0;i<n;i++){ if(chessboard[i][col]=='Q') return false; } for (int i = row - 1, j = col - 1; i >=0 && j >= 0; i--, j--) { if (chessboard[i][j] == 'Q') { return false; } } for(int i = row-1,j=col+1;i>=0&&j<n;i--,j++){ if(chessboard[i][j]=='Q'){ return false; } } return true; } void backtracking(vector<string>& chessboard, int row, int n){ if(row==n) { res.push_back(chessboard); return; } for(int col = 0;col<n;col++){ if(isvalid(row,col,chessboard,n)){ chessboard[row][col]='Q'; backtracking(chessboard,row+1,n); chessboard[row][col]='.'; } } }
vector<vector<string>> solveNQueens(int n) { std::vector<std::string> chessboard(n, std::string(n, '.')); backtracking(chessboard,0,n); return res; } };
|
解数独
编写一个程序,通过填充空格来解决数独问题。
数独的解法需 遵循如下规则:
- 数字
1-9
在每一行只能出现一次。 - 数字
1-9
在每一列只能出现一次。 - 数字
1-9
在每一个以粗实线分隔的 3x3
宫内只能出现一次。(请参考示例图)
数独部分空格内已填入了数字,空白格用 '.'
表示。
1 2
| 输入:board = [["5","3",".",".","7",".",".",".","."],["6",".",".","1","9","5",".",".","."],[".","9","8",".",".",".",".","6","."],["8",".",".",".","6",".",".",".","3"],["4",".",".","8",".","3",".",".","1"],["7",".",".",".","2",".",".",".","6"],[".","6",".",".",".",".","2","8","."],[".",".",".","4","1","9",".",".","5"],[".",".",".",".","8",".",".","7","9"]] 输出:[["5","3","4","6","7","8","9","1","2"],["6","7","2","1","9","5","3","4","8"],["1","9","8","3","4","2","5","6","7"],["8","5","9","7","6","1","4","2","3"],["4","2","6","8","5","3","7","9","1"],["7","1","3","9","2","4","8","5","6"],["9","6","1","5","3","7","2","8","4"],["2","8","7","4","1","9","6","3","5"],["3","4","5","2","8","6","1","7","9"]]
|
思路:
深度优先搜索,加上合法性判断
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
| class Solution { public: bool isvalid(vector<vector<char>>& board, int row, int col, char a){ for(int j=0;j<9;j++){ if(board[row][j]==a) return false; } for(int i=0;i<9;i++){ if(board[i][col]==a) return false; } for(int i= (row/3)*3;i<(row/3)*3+3;i++){ for(int j=(col/3)*3; j<(col/3)*3+3;j++){ if(board[i][j]==a) return false; } } return true; } bool backtracking(vector<vector<char>>& board){ for(int i=0;i<board.size();i++){ for(int j=0;j<board[0].size();j++){ if(board[i][j]=='.'){ for(char a='1';a<='9';a++){ if(isvalid(board,i,j,a)){ board[i][j]=a; if(backtracking(board)) return true; board[i][j]='.'; } } return false; } } } return true; } void solveSudoku(vector<vector<char>>& board) { backtracking(board); } };
|
贪心算法
分发饼干
https://leetcode.cn/problems/assign-cookies/description/
假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。对每个孩子i
,都有一个胃口值g[i]
,这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干j
,都有一个尺寸 s[j]
。如果s[j] >= g[i]
,我们可以将这个饼干 j
分配给孩子 i
,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。
示例 :
1 2 3 4 5 6
| 输入: g = [1,2,3], s = [1,1] 输出: 1 解释: 你有三个孩子和两块小饼干,3个孩子的胃口值分别是:1,2,3。 虽然你有两块小饼干,由于他们的尺寸都是1,你只能让胃口值是1的孩子满足。 所以你应该输出1。
|
示例 :
1 2 3 4 5 6
| 输入: g = [1,2], s = [1,2,3] 输出: 2 解释: 你有两个孩子和三块小饼干,2个孩子的胃口值分别是1,2。 你拥有的饼干数量和尺寸都足以让所有孩子满足。 所以你应该输出2.
|
思路:
这里的局部最优就是大饼干喂给胃口大的,充分利用饼干尺寸喂饱一个,全局最优就是喂饱尽可能多的小孩。
可以尝试使用贪心策略,先将饼干数组和小孩数组排序。
然后从后向前遍历小孩数组,用大饼干优先满足胃口大的,并统计满足小孩数量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| class Solution { public: int findContentChildren(vector<int>& g, vector<int>& s) { sort(s.begin(),s.end()); sort(g.begin(),g.end()); int index=s.size()-1; int num= 0; for(int i=g.size()-1; i>=0;i--){ if(index>=0&&s[index]>=g[i]){ num++; index--; } } return num; } };
|
摆动序列
https://leetcode.cn/problems/wiggle-subsequence/description/
如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为摆动序列。第一个差(如果存在的话)可能是正数或负数。仅有一个元素或者含两个不等元素的序列也视作摆动序列。
- 例如,
[1, 7, 4, 9, 2, 5]
是一个摆动序列 ,因为差值 (6, -3, 5, -7, 3)
是正负交替出现的。
给你一个整数数组 nums
,返回 nums
中作为摆动序列 的 最长子序列的长度
示例 :
1 2 3
| 输入:nums = [1,7,4,9,2,5] 输出:6 解释:整个序列均为摆动序列,各元素之间的差值为 (6, -3, 5, -7, 3)
|
1 2 3 4
| 输入:nums = [1,17,5,10,13,15,10,5,16,8] 输出:7 解释:这个序列包含几个长度为 7 摆动序列。 其中一个是 [1, 17, 10, 13, 10, 16, 8] ,各元素之间的差值为 (16, -7, 3, -3, 6, -8)
|
思路:
本题异常情况的本质,就是要考虑平坡,平坡分两种,一个是 上下中间有平坡,一个是单调有平坡,如图
同时需要注意的是在判断条件语句的时候,不能简单的用判断相乘法小于0作为判断,因为存在平坡的情况
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| class Solution { public: int wiggleMaxLength(vector<int>& nums) { if(nums.size()<=1){ return nums.size(); } int num=1; vector<int> differ; for(int i=1;i<nums.size();i++){ differ.push_back(nums[i]-nums[i-1]); } int preDiff=0; for(int i=0;i<differ.size();i++){ if((preDiff<=0&& differ[i]>0)||(differ[i]<0&&preDiff>=0)){ num++; preDiff = differ[i]; } } return num; } };
|
最大子数组和
https://leetcode.cn/problems/maximum-subarray/description/
给你一个整数数组 nums
,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
子数组 是数组中的一个连续部分。
示例:
1 2 3
| 输入:nums = [-2,1,-3,4,-1,2,1,-5,4] 输出:6 解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。
|
思路:
这道题使用的是局部的最优贪心的思路,如果遇到让总的值小于0,那么久立刻让总的值变成0,那么下一轮就从头开始记了,同时max会每一轮进行判断是否有比当前的最大值大,如果有那么就进行替换
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class Solution { public: int maxSubArray(vector<int>& nums) { int res = INT32_MIN; int count = 0; for(int i=0;i<nums.size();i++){ count+=nums[i]; if(count>res){ res = count; } if(count<=0) count = 0; } return res; } };
|
买卖股票的最佳时机
https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-ii/description/
给你一个整数数组 prices
,其中 prices[i]
表示某支股票第 i
天的价格。在每一天,你可以决定是否购买和/或出售股票。你在任何时候最多 只能持有 一股股票。你也可以先购买,然后在 同一天 出售。
返回 你能获得的 最大 利润 。
示例 1:
1 2 3 4 5
| 输入:prices = [7,1,5,3,6,4] 输出:7 解释:在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5 - 1 = 4 。 随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6 - 3 = 3 。 总利润为 4 + 3 = 7 。
|
思路:
把利润分解为每天为单位的维度,而不是从 0 天到第 3天整体去考虑!
那么根据 prices可以得到每天的利润序列:(prices[i] - prices[i - 1]).....(prices[1] - prices[0])
相当于是每天的利润之差和0的比较,只选择为正的值,负数的情况直接忽略
1 2 3 4 5 6 7 8 9 10
| class Solution { public: int maxProfit(vector<int>& prices) { int res = 0; for(int i=1;i<prices.size();i++){ res+=max(prices[i]-prices[i-1],0); } return res; } };
|
动态规划
状态转移公式(递推公式)是很重要,但动规不仅仅只有递推公式。
对于动态规划问题,我将拆解为如下五步曲,这五步都搞清楚了,才能说把动态规划真的掌握了!
- 确定dp数组(dp table)以及下标的含义
- 确定递推公式
- dp数组如何初始化
- 确定遍历顺序
- 举例推导dp数组
注意:动态规划的问题一般只会输出最后的一个结果,不会输出比如中间的路径等相关的值
斐波那契数列
https://leetcode.cn/problems/fibonacci-number/
斐波那契数 (通常用 F(n)
表示)形成的序列称为 斐波那契数列 。该数列由0
和 1
开始,后面的每一项数字都是前面两项数字的和。也就是:
1 2
| F(0) = 0,F(1) = 1 F(n) = F(n - 1) + F(n - 2),其中 n > 1
|
给定 n
,请计算 F(n)
。
示例:
1 2 3
| 输入:n = 2 输出:1 解释:F(2) = F(1) + F(0) = 1 + 0 = 1
|
思路:
因为这道题给出了递推公式:F(n) = F(n - 1) + F(n - 2)
动规五部曲:
这里我们要用一个一维dp数组来保存递归的结果
确定dp数组以及下标的含义:dp[i]的定义为:第i个数的斐波那契数值是dp[i]
确定递推公式F(n) = F(n - 1) + F(n - 2)
dp数组如何初始化
确定遍历顺序
从递归公式dp[i] = dp[i - 1] + dp[i - 2];中可以看出,dp[i]是依赖 dp[i- 1] 和 dp[i - 2],那么遍历的顺序一定是从前到后遍历的
举例推导dp数组
1 2 3 4 5 6 7 8 9 10 11 12 13
| class Solution { public: int fib(int n) { if(n<=1) return n; vector<int>dp(n+1); dp[0]=0; dp[1]=1; for(int i=2;i<=n;i++){ dp[i]=dp[i-1]+dp[i-2]; } return dp[n]; } };
|
爬楼梯
假设你正在爬楼梯。需要 n
阶你才能到达楼顶。每次你可以爬1
或 2
个台阶。你有多少种不同的方法可以爬到楼顶呢?
示例 1:
1 2 3 4 5
| 输入:n = 2 输出:2 解释:有两种方法可以爬到楼顶。 1. 1 阶 + 1 阶 2. 2 阶
|
思路:
动态规划简单题,递推公式:dp[i] = dp[i-2]+dp[i-1];
1 2 3 4 5 6 7 8 9 10 11 12 13
| class Solution { public: int climbStairs(int n) { if(n<=2) return n; vector<int> dp(n+1); dp[1]=1; dp[2]=2; for(int i=3;i<=n;i++){ dp[i] = dp[i-2]+dp[i-1]; } return dp[n]; } };
|
最小费用爬楼梯
https://leetcode.cn/problems/min-cost-climbing-stairs/description/
给你一个整数数组 cost
,其中 cost[i]
是从楼梯第 i
个台阶向上爬需要支付的费用。一旦你支付此费用,即可选择向上爬一个或者两个台阶。你可以选择从下标为0
或下标为 1
的台阶开始爬楼梯。请你计算并返回达到楼梯顶部的最低花费。
示例 1:
1 2 3 4 5
| 输入:cost = [10,15,20] 输出:15 解释:你将从下标为 1 的台阶开始。 - 支付 15 ,向上爬两个台阶,到达楼梯顶部。 总花费为 15 。
|
思路:
动态规划可以有两个途径得到dp[i],一个是dp[i-1]一个是dp[i-2]。
dp[i - 1] 跳到 dp[i] 需要花费dp[i - 1] + cost[i - 1]
。
dp[i - 2] 跳到 dp[i] 需要花费dp[i - 2] + cost[i - 2]
。
那么究竟是选从dp[i - 1]跳还是从dp[i - 2]跳呢?
一定是选最小的,所以dp[i] = min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2])
;
1 2 3 4 5 6 7 8 9 10 11 12 13
| class Solution { public: int minCostClimbingStairs(vector<int>& cost) { int n = cost.size(); vector<int> dp(n+1); dp[0]= 0; dp[1] = 0; for(int i=2;i<=n;i++){ dp[i]=min(dp[i-1]+cost[i-1], dp[i-2]+cost[i-2]); } return dp[n]; } };
|
不同路径
一个机器人位于一个 m x n
网格的左上角(起始点在下图中标记为 “Start”)。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish” )。问总共有多少条不同的路径?
思路:
简单的动态规划问题,只需要保证每次迭代都从上面和左边进行叠加
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| class Solution { public: int uniquePaths(int m, int n) { vector<vector<int>>dp(m, vector<int>(n, 0)); for(int i=0;i<m;i++){ dp[i][0]=1; } for(int i=0;i<n;i++){ dp[0][i]=1; } for(int i=1;i<m;i++){ for(int j=1;j<n;j++){ dp[i][j]=dp[i-1][j]+dp[i][j-1]; } } return dp[m-1][n-1]; } };
|
不同路径II
https://leetcode.cn/problems/unique-paths-ii/description/
一个机器人位于一个 m x n
网格的左上角(起始点在下图中标记为 “Start”)。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?网格中的障碍物和空位置分别用1
和 0
来表示。
思路:
和上一题的思路一样,都是需要遍历路径就行,但是这里加入了一个新的数组用来存储有障碍物的位置,因此需要额外进行标记&&obstacleGrid[i][0]==0
的信息,同时遇到障碍物就不改变对应的值,直接continue
就好
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class Solution { public: int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) { vector<vector<int>>dp(obstacleGrid.size(), vector<int>(obstacleGrid[0].size(), 0)); for(int i=0;i<obstacleGrid.size()&&obstacleGrid[i][0]==0;i++) dp[i][0]=1; for(int i=0;i<obstacleGrid[0].size()&&obstacleGrid[0][i]==0;i++) dp[0][i]=1; for(int i=1;i<obstacleGrid.size();i++){ for(int j=1;j<obstacleGrid[0].size();j++){ if(obstacleGrid[i][j]==1) continue; dp[i][j]=dp[i-1][j]+dp[i][j-1]; } } return dp[obstacleGrid.size()-1][obstacleGrid[0].size()-1]; } };
|
整数拆分
https://leetcode.cn/problems/integer-break/description/
给定一个正整数 n
,将其拆分为 k
个正整数 的和( k >= 2
),并使这些整数的乘积最大化。返回 你可以获得的最大乘积 。
示例 :
1 2 3
| 输入: n = 2 输出: 1 解释: 2 = 1 + 1, 1 × 1 = 1。
|
思路:
给出递推公式一个是j * (i - j)
直接相乘。一个是j * dp[i - j]
,相当于是拆分(i - j)
,在遍历j的过程中其实都计算过了。那么从1遍历j,比较(i - j) * j和dp[i - j] * j
取最大的。递推公式:dp[i] = max(dp[i], max((i - j) * j, dp[i - j] * j))
;
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| class Solution { public: int integerBreak(int n) { vector<int> dp(n + 1); dp[2] = 1; for (int i = 3; i <= n ; i++) { for (int j = 1; j <= i / 2; j++) { dp[i] = max(dp[i], max((i - j) * j, dp[i - j] * j)); } } return dp[n]; } };
|
背包问题解题框架
0-1背包
dp[j]
为容量为j
的背包所背的最大价值,那么如何推导dp[j]
呢?dp[j]
可以通过dp[j - weight[i]]
推导出来,dp[j - weight[i]]
表示容量为j - weight[i]
的背包所背的最大价值。
dp[j - weight[i]] + value[i]
表示 容量为 j
- 物品i重量 的背包 加上物品i的价值。(也就是容量为j的背包,放入物品i了之后的价值即:dp[j])
此时dp[j]有两个选择,一个是取自己dp[j]
相当于二维dp数组中的dp[i-1][j]
,即不放物品i,一个是取dp[j - weight[i]] + value[i]
,即放物品i,指定是取最大的,毕竟是求最大价值,
递推公式:
1
| dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
|
初始化:
全部初始化为0
遍历顺序:
1 2 3 4 5
| for(int i = 0; i < weight.size(); i++) { for(int j = bagWeight; j >= weight[i]; j--) { dp[j] = max(dp[j], dp[j - weight[i]] + value[i]); } }
|
整体的代码结构是:
1 2 3 4 5 6 7 8 9 10 11 12 13
| void test_1_wei_bag_problem() { vector<int> weight = {1, 3, 4}; vector<int> value = {15, 20, 30}; int bagWeight = 4; vector<int> dp(bagWeight + 1, 0); for(int i = 0; i < weight.size(); i++) { for(int j = bagWeight; j >= weight[i]; j--) { dp[j] = max(dp[j], dp[j - weight[i]] + value[i]); } } cout << dp[bagWeight] << endl; }
|
完全背包问题
有N件物品和一个最多能背重量为W的背包。第i件物品的重量是weight[i],得到的价值是value[i]。每件物品都有无限个(也就是可以放入背包多次),求解将哪些物品装入背包里物品价值总和最大。
完全背包和01背包问题唯一不同的地方就是,每种物品有无限件。
在代码层面的区别在于背包遍历的时候是从头开始到尾遍历,int j = weight[i]; j <= bagWeight; j++
,因为所有的背包内部都是无限的
1、先遍历物品再遍历背包
1 2 3 4 5 6 7 8 9 10 11 12
| void test_CompletePack() { vector<int> weight = {1, 3, 4}; vector<int> value = {15, 20, 30}; int bagWeight = 4; vector<int> dp(bagWeight + 1, 0); for(int i = 0; i < weight.size(); i++) { for(int j = weight[i]; j <= bagWeight; j++) { dp[j] = max(dp[j], dp[j - weight[i]] + value[i]); } } cout << dp[bagWeight] << endl; }
|
2、先遍历背包再遍历物品
1 2 3 4 5 6
| for(int j = 0; j <= bagWeight; j++) { for(int i = 0; i < weight.size(); i++) { if (j - weight[i] >= 0) dp[j] = max(dp[j], dp[j - weight[i]] + value[i]); } cout << endl; }
|
分割等和子集
https://leetcode.cn/problems/partition-equal-subset-sum/description/
给你一个只包含正整数的非空 数组nums
。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等示例:
1 2 3
| 输入:nums = 输出:true 解释:数组可以分割成 和
|
单调栈
每日温度
给定一个整数数组 temperatures
,表示每天的温度,返回一个数组 answer
,其中answer[i]
是指对于第 i
天,下一个更高温度出现在几天后。如果气温在这之后都不会升高,请在该位置用0
来代替。
示例 1:
1 2
| 输入: temperatures = [73,74,75,71,69,72,76,73] 输出: [1,1,4,2,1,1,0,0]
|
思路:
可以选择使用单调栈的方法来求解,具体的思路是设置一个栈,遍历数组的时候和栈顶元素进行比较,小于栈顶元素的时候就需要将当前元素放入栈中
如果大于当前的栈顶元素的值,那么就要进行比较while
循环,只要还是大于当前栈顶的元素都需要对栈顶的元素进行pop()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| class Solution { public: vector<int> dailyTemperatures(vector<int>& temperatures) { stack<int> st; vector<int> res(temperatures.size(),0); st.push(0); for(int i=1;i<temperatures.size();i++){ if(temperatures[i]<=temperatures[st.top()]){ st.push(i); }else{ while (!st.empty()&& temperatures[i]>temperatures[st.top()]){ res[st.top()]=i-st.top(); st.pop(); } st.push(i); } } return res; } };
|
图论
深度优先搜索理论
广度优先搜索理论
所有可能的路径
https://leetcode.cn/problems/all-paths-from-source-to-target/description/
给你一个有 n
个节点的有向无环图(DAG),请你找出所有从节点 0
到节点 n-1
的路径并输出(不要求按特定顺序) graph[i]
是一个从节点 i
可以访问的所有节点的列表(即从节点i
到节点 graph[i][j]
存在一条有向边)。
1 2 3
| 输入:graph = [[1,2],[3],[3],[]] 输出:[[0,1,3],[0,2,3]] 解释:有两条路径 0 -> 1 -> 3 和 0 -> 2 -> 3
|
思路:
深度优先搜索
注意在用dfs做题的时候需要初始化path.push_back(0)
每一次都需要初始化输入这个数值起点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| class Solution { public: vector<vector<int>> res; vector<int> path; void dfs(vector<vector<int>>& graph, int x){ if(x == graph.size()-1){ res.push_back(path); return; } for(int i=0;i<graph[x].size();i++){ path.push_back(graph[x][i]); dfs(graph,graph[x][i]); path.pop_back(); } } vector<vector<int>> allPathsSourceTarget(vector<vector<int>>& graph) { path.push_back(0); dfs(graph, 0); return res; } };
|
岛屿数量
https://leetcode.cn/problems/number-of-islands/description/
给你一个由 '1'
(陆地)和'0'
(水)组成的的二维网格,请你计算网格中岛屿的数量。岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
示例 :
1 2 3 4 5 6 7
| 输入:grid = [ ["1","1","1","1","0"], ["1","1","0","1","0"], ["1","1","0","0","0"], ["0","0","0","0","0"] ] 输出:1
|
深度优先搜索版本:
思路在于利用dfs来对岛屿中的数量进行标记是否能visited,必须是联通的才能继续标记为res++
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
| class Solution { public: int dir[4][2] ={0,1,1,0,-1,0,0,-1}; void dfs(vector<vector<char>>& grid, vector<vector<bool>>& visited, int x, int y){ for(int i=0;i<4;i++){ int nextx = x+dir[i][0]; int nexty = y+dir[i][1]; if(nextx < 0||nextx>=grid.size()||nexty<0 ||nexty>=grid[0].size()) continue; if(!visited[nextx][nexty]&&grid[nextx][nexty]=='1'){ visited[nextx][nexty]=true; dfs(grid, visited , nextx, nexty); } } } int numIslands(vector<vector<char>>& grid) { int n = grid.size(), m = grid[0].size(); vector<vector<bool>> visited = vector<vector<bool>>(n,vector<bool>(m,false)); int res = 0; for(int i=0;i<n;i++){ for(int j = 0;j<m;j++){ if(!visited[i][j] && grid[i][j]=='1'){ visited[i][j]=true; res++; dfs(grid, visited, i,j); } } } return res; } };
|
]]>
diff --git a/search.xml b/search.xml
index 4b120fe..955f802 100644
--- a/search.xml
+++ b/search.xml
@@ -2709,6 +2709,28 @@ 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; } };
|
+接雨水
+给定 n
个非负整数表示每个宽度为 1
+的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
+示例 1:
+
+输入:height = [0,1,0,2,1,0,1,3,2,1,2,1] 输出:6 解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。
|
+示例 2:
+输入:height = [4,2,0,3,2,5] 输出:9
|
+思路:
+找到最大的左边和最大的右边并相减
+class Solution { public: int trap(vector<int>& height) { if(height.size()<=2) return 0; vector<int> maxLeft(height.size(), 0); vector<int> maxRight(height.size(), 0); int size = maxRight.size(); maxLeft[0] = height[0]; for(int i=1;i<size;i++){ maxLeft[i] = max(height[i],maxLeft[i-1]); } maxRight[size-1] = height[size-1]; for(int i=size-2;i>=0;i--){ maxRight[i] = max(height[i],maxRight[i+1]); } int sum=0; for(int i=0;i<size;i++){ int count = min(maxLeft[i], maxRight[i])-height[i]; if(count > 0) sum+=count; } return sum; } };
|
+柱形图中的最大矩形
+https://leetcode.cn/problems/largest-rectangle-in-histogram/description/
+
+输入:heights = [2,1,5,6,2,3] 输出:10 解释:最大的矩形为图中红色区域,面积为 10
|
+class Solution { public: int largestRectangleArea(vector<int>& heights) { vector<int> minLeft(heights.size()); vector<int> minRight(heights.size()); int size = heights.size();
minLeft[0] = -1; for(int i=1;i<size;i++){ int t= i-1; while(t>=0&&heights[t]>=heights[i]) t=minLeft[t]; minLeft[i]=t; } minRight[size-1]=size; for(int i=size -2;i>=0;i--){ int t=i+1; while(t<size&&heights[t]>=heights[i]) t=minRight[t]; minRight[i]=t; }
int res=0; for(int i=0;i<size;i++){ int sum=heights[i]*(minRight[i]-minLeft[i]-1); res = max(sum,res); }
return res; } };
|
二叉树
深搜回溯
深度优先搜索的三部曲:
@@ -2741,6 +2763,16 @@ href="https://leetcode.cn/problems/combinations/description/">https://leetcode.c
组合问题III
https://leetcode.cn/problems/combination-sum-iii/submissions/496823507/
+找出所有相加之和为 n
的 k
+个数的组合,且满足下列条件:
+
+返回 所有可能的有效组合的列表
+。该列表不能包含相同的组合两次,组合可以以任何顺序返回。
+示例:
+输入: k = 3, n = 7 输出: [[1,2,4]] 解释: 1 + 2 + 4 = 7 没有其他符合的组合了。
|
思路:简单的深度优先搜索,但需要注意的是可以适当采用减枝操作和必要的时候添加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; } };
|
为了优化可以做一个剪枝操作
@@ -3139,6 +3171,99 @@ href="https://leetcode.cn/problems/unique-paths-ii/description/">https://leetcod
&&obstacleGrid[i][0]==0
的信息,同时遇到障碍物就不改变对应的值,直接
continue
就好
class Solution { public: int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) { vector<vector<int>>dp(obstacleGrid.size(), vector<int>(obstacleGrid[0].size(), 0)); for(int i=0;i<obstacleGrid.size()&&obstacleGrid[i][0]==0;i++) dp[i][0]=1; for(int i=0;i<obstacleGrid[0].size()&&obstacleGrid[0][i]==0;i++) dp[0][i]=1; for(int i=1;i<obstacleGrid.size();i++){ for(int j=1;j<obstacleGrid[0].size();j++){ if(obstacleGrid[i][j]==1) continue; dp[i][j]=dp[i-1][j]+dp[i][j-1]; } } return dp[obstacleGrid.size()-1][obstacleGrid[0].size()-1]; } };
|
+整数拆分
+https://leetcode.cn/problems/integer-break/description/
+给定一个正整数 n
,将其拆分为 k
个
+正整数 的和( k >= 2
+),并使这些整数的乘积最大化。返回 你可以获得的最大乘积 。
+示例 :
+输入: n = 2 输出: 1 解释: 2 = 1 + 1, 1 × 1 = 1。
|
+思路:
+给出递推公式一个是j * (i - j)
+直接相乘。一个是j * dp[i - j]
,相当于是拆分(i - j)
,在遍历j的过程中其实都计算过了。那么从1遍历j,比较(i - j) * j和dp[i - j] * j
+取最大的。递推公式:dp[i] = max(dp[i], max((i - j) * j, dp[i - j] * j))
;
+class Solution { public: int integerBreak(int n) { vector<int> dp(n + 1); dp[2] = 1; for (int i = 3; i <= n ; i++) { for (int j = 1; j <= i / 2; j++) { dp[i] = max(dp[i], max((i - j) * j, dp[i - j] * j)); } } return dp[n]; } };
|
+背包问题解题框架
+
+0-1背包
+dp[j]
为
+容量为j
的背包所背的最大价值,那么如何推导dp[j]
呢?dp[j]
可以通过dp[j - weight[i]]
推导出来,dp[j - weight[i]]
表示容量为j - weight[i]
的背包所背的最大价值。
+dp[j - weight[i]] + value[i]
表示 容量为 j
+- 物品i重量 的背包 加上
+物品i的价值。(也就是容量为j的背包,放入物品i了之后的价值即:dp[j])
+此时dp[j]有两个选择,一个是取自己dp[j]
相当于
+二维dp数组中的dp[i-1][j]
,即不放物品i,一个是取dp[j - weight[i]] + value[i]
,即放物品i,指定是取最大的,毕竟是求最大价值,
+递推公式:
+dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
|
+初始化:
+全部初始化为0
+遍历顺序:
+for(int i = 0; i < weight.size(); i++) { for(int j = bagWeight; j >= weight[i]; j--) { dp[j] = max(dp[j], dp[j - weight[i]] + value[i]); } }
|
+整体的代码结构是:
+void test_1_wei_bag_problem() { vector<int> weight = {1, 3, 4}; vector<int> value = {15, 20, 30}; int bagWeight = 4; vector<int> dp(bagWeight + 1, 0); for(int i = 0; i < weight.size(); i++) { for(int j = bagWeight; j >= weight[i]; j--) { dp[j] = max(dp[j], dp[j - weight[i]] + value[i]); } } cout << dp[bagWeight] << endl; }
|
+完全背包问题
+有N件物品和一个最多能背重量为W的背包。第i件物品的重量是weight[i],得到的价值是value[i]
+。每件物品都有无限个(也就是可以放入背包多次),求解将哪些物品装入背包里物品价值总和最大。
+完全背包和01背包问题唯一不同的地方就是,每种物品有无限件。
+在代码层面的区别在于背包遍历的时候是从头开始到尾遍历,int j = weight[i]; j <= bagWeight; j++
,因为所有的背包内部都是无限的
+1、先遍历物品再遍历背包
+void test_CompletePack() { vector<int> weight = {1, 3, 4}; vector<int> value = {15, 20, 30}; int bagWeight = 4; vector<int> dp(bagWeight + 1, 0); for(int i = 0; i < weight.size(); i++) { for(int j = weight[i]; j <= bagWeight; j++) { dp[j] = max(dp[j], dp[j - weight[i]] + value[i]); } } cout << dp[bagWeight] << endl; }
|
+2、先遍历背包再遍历物品
+for(int j = 0; j <= bagWeight; j++) { for(int i = 0; i < weight.size(); i++) { if (j - weight[i] >= 0) dp[j] = max(dp[j], dp[j - weight[i]] + value[i]); } cout << endl; }
|
+分割等和子集
+https://leetcode.cn/problems/partition-equal-subset-sum/description/
+给你一个只包含正整数的非空 数组
+nums
+。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等示例:
+输入:nums = 输出:true 解释:数组可以分割成 和
|
+单调栈
+每日温度
+给定一个整数数组 temperatures
+,表示每天的温度,返回一个数组 answer
,其中
+answer[i]
是指对于第 i
+天,下一个更高温度出现在几天后。如果气温在这之后都不会升高,请在该位置用
+0
来代替。
+示例 1:
+输入: temperatures = [73,74,75,71,69,72,76,73] 输出: [1,1,4,2,1,1,0,0]
|
+思路:
+可以选择使用单调栈的方法来求解,具体的思路是设置一个栈,遍历数组的时候和栈顶元素进行比较,小于栈顶元素的时候就需要将当前元素放入栈中
+如果大于当前的栈顶元素的值,那么就要进行比较while
循环,只要还是大于当前栈顶的元素都需要对栈顶的元素进行pop()
+class Solution { public: vector<int> dailyTemperatures(vector<int>& temperatures) { stack<int> st; vector<int> res(temperatures.size(),0); st.push(0); for(int i=1;i<temperatures.size();i++){ if(temperatures[i]<=temperatures[st.top()]){ st.push(i); }else{ while (!st.empty()&& temperatures[i]>temperatures[st.top()]){ res[st.top()]=i-st.top(); st.pop(); } st.push(i); } } return res; } };
|
+图论
+深度优先搜索理论
+广度优先搜索理论
+所有可能的路径
+https://leetcode.cn/problems/all-paths-from-source-to-target/description/
+
+给你一个有 n
个节点的
+有向无环图(DAG),请你找出所有从节点 0
+到节点 n-1
+的路径并输出(不要求按特定顺序) graph[i]
+是一个从节点 i
可以访问的所有节点的列表(即从节点
+i
到节点 graph[i][j]
存在一条有向边)。
+输入:graph = [[1,2],[3],[3],[]] 输出:[[0,1,3],[0,2,3]] 解释:有两条路径 0 -> 1 -> 3 和 0 -> 2 -> 3
|
+思路:
+深度优先搜索
+注意在用dfs做题的时候需要初始化path.push_back(0)
每一次都需要初始化输入这个数值起点
+class Solution { public: vector<vector<int>> res; vector<int> path; void dfs(vector<vector<int>>& graph, int x){ if(x == graph.size()-1){ res.push_back(path); return; } for(int i=0;i<graph[x].size();i++){ path.push_back(graph[x][i]); dfs(graph,graph[x][i]); path.pop_back(); } } vector<vector<int>> allPathsSourceTarget(vector<vector<int>>& graph) { path.push_back(0); dfs(graph, 0); return res; } };
|
+岛屿数量
+https://leetcode.cn/problems/number-of-islands/description/
+给你一个由 '1'
(陆地)和
+'0'
(水)组成的的二维网格,请你计算网格中岛屿的数量。岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
+示例 :
+输入:grid = [ ["1","1","1","1","0"], ["1","1","0","1","0"], ["1","1","0","0","0"], ["0","0","0","0","0"] ] 输出:1
|
+深度优先搜索版本:
+思路在于利用dfs来对岛屿中的数量进行标记是否能visited,必须是联通的才能继续标记为res++
+class Solution { public: int dir[4][2] ={0,1,1,0,-1,0,0,-1}; void dfs(vector<vector<char>>& grid, vector<vector<bool>>& visited, int x, int y){ for(int i=0;i<4;i++){ int nextx = x+dir[i][0]; int nexty = y+dir[i][1]; if(nextx < 0||nextx>=grid.size()||nexty<0 ||nexty>=grid[0].size()) continue; if(!visited[nextx][nexty]&&grid[nextx][nexty]=='1'){ visited[nextx][nexty]=true; dfs(grid, visited , nextx, nexty); } } } int numIslands(vector<vector<char>>& grid) { int n = grid.size(), m = grid[0].size(); vector<vector<bool>> visited = vector<vector<bool>>(n,vector<bool>(m,false)); int res = 0; for(int i=0;i<n;i++){ for(int j = 0;j<m;j++){ if(!visited[i][j] && grid[i][j]=='1'){ visited[i][j]=true; res++; dfs(grid, visited, i,j); } } } return res; } };
|
]]>
LeetCode算法