目录
回溯
17. 电话号码的字母组合
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
代码如下:
class Solution {
public:
vector<string> letterCombinations(string digits) {
unordered_map<char, string> table{
{'0', " "}, {'1',"*"}, {'2', "abc"},
{'3',"def"}, {'4',"ghi"}, {'5',"jkl"},
{'6',"mno"}, {'7',"pqrs"},{'8',"tuv"},
{'9',"wxyz"}};
vector<string> res;
if(digits == "") return res;
func(res, "", digits, table, 0);
return res;
}
void func(vector<string> &res, string str, string &digits, unordered_map<char, string> &m, int k){
if(str.size() == digits.size()){
res.push_back(str);
return;
}
string tmp = m[digits[k]];
for(char w : tmp){
str += w;
func(res, str, digits, m, k+1);
str.pop_back();
}
return ;
}
};
216. 组合总和 III
找出所有相加之和为 n 的 k 个数的组合,且满足下列条件:
只使用数字1到9
每个数字 最多使用一次
返回 所有可能的有效组合的列表 。该列表不能包含相同的组合两次,组合可以以任何顺序返回。
代码如下:
class Solution {
public:
// 全局变量,为了每层 空间复杂度共用
vector<vector<int>> result;
vector<int> path;
int getSum(vector<int>& path) {
int sum=0;
for(int ele:path) sum+= ele;
return sum;
}
void backtracking(int n, int k, int startIndex){
// 检查路径是否满足
if (path.size() == k) {
if (getSum(path) == n) result.push_back(path);
return;
}
// 当前层节点处理逻辑
for(int i=startIndex; i<10; i++) {
// 做选择
path.push_back(i);
backtracking(n, k, i+1);
// 撤销
path.pop_back();
}
}
vector<vector<int>> combinationSum3(int k, int n) {
if(k>9 || n <1 || n>45) return result;
backtracking(n, k, 1);
return result;
}
};
动态规划 - 一维
1137. 第 N 个泰波那契数
泰波那契序列 Tn 定义如下:
T0 = 0, T1 = 1, T2 = 1, 且在 n >= 0 的条件下 Tn+3 = Tn + Tn+1 + Tn+2
给你整数 n,请返回第 n 个泰波那契数 Tn 的值。
代码如下:
class Solution {
public:
int tribonacci(int n) {
long long int dp[40];
dp[0]=0;
dp[1]=1;
dp[2]=1;
for(int i=3;i<=n;i++)
dp[i]=dp[i-1]+dp[i-2]+dp[i-3];
return dp[n];
}
};
746. 使用最小花费爬楼梯
给你一个整数数组 cost ,其中 cost[i] 是从楼梯第 i 个台阶向上爬需要支付的费用。一旦你支付此费用,即可选择向上爬一个或者两个台阶。
你可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯。
请你计算并返回达到楼梯顶部的最低花费。
代码如下:
class Solution {
int f[10010];
public:
int minCostClimbingStairs(vector<int>& cost) {
f[0] = f[1] = 0;
int n = cost.size();
for(int i = 2;i <= n;i++)
f[i] = min(f[i - 1] + cost[i - 1] , f[i - 2] + cost[i - 2]);
return f[n];
}
};
198. 打家劫舍
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
代码如下:
class Solution {
public:
int rob(vector<int>& nums) {
// Corner cases
if (nums.size() == 0) {
return 0;
} else if (nums.size() == 1) {
return nums[0];
} else if (nums.size() == 2) {
return nums[0] > nums[1] ? nums[0] : nums[1];
}
// Starter
int dpLastLast = nums[0]; // Rob the first house
int dpLast = nums[0] > nums[1] ? nums[0] : nums[1]; // Rob the second house
int dpCur = 0;
for (int i = 2; i < nums.size(); ++i) {
int skipThis = dpLast;
int robThis = dpLastLast + nums[i];
dpCur = robThis > skipThis ? robThis : skipThis;
dpLastLast = dpLast;
dpLast = dpCur;
}
return dpCur;
}
};
790. 多米诺和托米诺平铺
代码如下:
class Solution {
private:
const int MOD=1e9+7;
public:
int numTilings(int n) {
if(n==1) return 1;
long f[n+1];
f[0]=f[1]=1;
f[2]=2;
for(int i=3;i<=n;++i){
f[i]=(f[i-1]*2+f[i-3])%MOD;
}
return f[n];
}
};
动态规划 - 多维
62. 不同路径
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。
问总共有多少条不同的路径?
代码如下:
class Solution {
public:
int uniquePaths(int m, int n) {
vector<vector<int>> dp(m, vector<int>(n));
for (int i = 0; i < n; i++) {
dp[0][i] = 1;
}
for (int i = 0; i < m; i++) {
dp[i][0] = 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];
}
};
1143. 最长公共子序列
给定两个字符串 text1 和 text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0 。
一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。
例如,"ace" 是 "abcde" 的子序列,但 "aec" 不是 "abcde" 的子序列。
两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列。
代码如下:
class Solution {
public:
int longestCommonSubsequence(string s, string t) {
int m = t.length();
int f[m + 1];
memset(f, 0, sizeof(f));
for (char x : s)
for (int j = 0, pre = 0; j < m; ++j) {
int tmp = f[j + 1];
f[j + 1] = x == t[j] ? pre + 1 : max(f[j + 1], f[j]);
pre = tmp;
}
return f[m];
}
};
714. 买卖股票的最佳时机含手续费
给定一个整数数组 prices,其中 prices[i]表示第 i 天的股票价格 ;整数 fee 代表了交易股票的手续费用。
你可以无限次地完成交易,但是你每笔交易都需要付手续费。如果你已经购买了一个股票,在卖出它之前你就不能再继续购买股票了。
返回获得利润的最大值。
注意:这里的一笔交易指买入持有并卖出股票的整个过程,每笔交易你只需要为支付一次手续费。
代码如下:
class Solution {
public:
int maxProfit(vector<int>& prices, int fee) {
int n = prices.size();
int buy , sell = 0;
buy = -prices[0];
for(int i = 1 ; i < n ; i++){
sell = max(sell , buy + prices[i] - fee);
buy = max(buy , sell - prices[i]);
}
return sell;
}
};
72. 编辑距离
代码如下:
class Solution {
public:
int minDistance(string word1, string word2) {
int len1 = word1.size();
int len2 = word2.size();
int dp[len1+1][len2+1];
for(int i = 0; i <= len1; i++) dp[i][0] = i;
for(int i = 0; i <= len2; i++) dp[0][i] = i;
for(int i = 1; i <= len1; i++){
for(int j = 1; j <= len2; j++){
if(word1[i-1]==word2[j-1]) dp[i][j] = dp[i-1][j-1];
else{
dp[i][j] = min(min(dp[i-1][j],dp[i-1][j-1]),dp[i][j-1]) + 1;
}
}
}
return dp[len1][len2];
}
};
位运算
338. 比特位计数
给你一个整数 n
,对于 0 <= i <= n
中的每个 i
,计算其二进制表示中 1
的个数 ,返回一个长度为 n + 1
的数组 ans
作为答案。
代码如下:
class Solution {
public:
vector<int> countBits(int n) {
vector<int> ans(n+1, 0);
for (int i=1;i<=n;++i) {
ans[i] = bitset<32>(i).count();
}
return ans;
}
};
// class Solution {
// public:
// vector<int> countBits(int n) {
// vector<int> ans(n+1, 0);
// for (int i=1;i<=n;++i) {
// ans[i] = i & 1 ? ans[i-1]+1 : ans[i >> 1];
// }
// return ans;
// }
// };
// my sol
// class Solution {
// public:
// vector<int> countBits(int n) {
// vector<int> ans(n+1, 0);
// int high = 0;
// for (int i=1;i<=n;++i) {
// if ((i & (i-1)) == 0) {ans[i] = 1;high = i;} // 2的幂
// else {ans[i] = ans[high]+ans[i-high];}
// }
// return ans;
// }
// };
136. 只出现一次的数字
给你一个 非空 整数数组 nums ,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
你必须设计并实现线性时间复杂度的算法来解决此问题,且该算法只使用常量额外空间。
代码如下:
class Solution {
public:
int singleNumber(vector<int>& nums) {
int sum=0;
for(int i=0;i<nums.size();i++)
{
sum^=nums[i]; //对变量进行位异或操作,并将结果赋值给变量本身。
}
return sum;
}
};
1318. 或运算的最小翻转次数
给你三个正整数 a、b 和 c。
你可以对 a 和 b 的二进制表示进行位翻转操作,返回能够使按位或运算 a OR b == c 成立的最小翻转次数。
「位翻转操作」是指将一个数的二进制表示任何单个位上的 1 变成 0 或者 0 变成 1 。
代码如下:
class Solution {
public:
int minFlips(int a, int b, int c) {
int sum = 0 ;
if((a | b) == c) {
return 0;
}
while(a != 0 || b !=0 || c != 0 )
{
if(((a | b) & 0x01) == (c & 0x01))
{
a = a >> 1;
b = b >> 1;
c = c >> 1;
}
else
{
if((a & 0x01) == (b & 0x01))
{
if((a & 0x01) == 0)
{
sum += 1;
a = a >> 1;
b = b >> 1;
c = c >> 1;
}
else
{
sum = sum + 2;
a = a >> 1;
b = b >> 1;
c = c >> 1;
}
}
else
{
sum++;
a = a >> 1;
b = b >> 1;
c = c >> 1;
}
}
}
return sum;
}
};
前缀树
208. 实现 Trie (前缀树)
Trie(发音类似 "try")或者说 前缀树 是一种树形数据结构,用于高效地存储和检索字符串数据集中的键。这一数据结构有相当多的应用情景,例如自动补完和拼写检查。
请你实现 Trie 类:
Trie() 初始化前缀树对象。
void insert(String word) 向前缀树中插入字符串 word 。
boolean search(String word) 如果字符串 word 在前缀树中,返回 true(即,在检索之前已经插入);否则,返回 false 。
boolean startsWith(String prefix) 如果之前已经插入的字符串 word 的前缀之一为 prefix ,返回 true ;否则,返回 false 。
代码如下:
// class Trie {
// public:
// unordered_map<int, string> mp;
// int count;
// Trie() {
// count = 0;
// }
// void insert(string word) {
// mp.insert(make_pair(count, word));
// count = count + 1;
// }
// bool search(string word) {
// for(auto it:mp){
// if(it.second==word){
// return true;
// }
// }
// return false;
// }
// bool startsWith(string prefix) {
// int len = prefix.length();
// for(auto it:mp){
// if(it.second.length()<len){
// continue;
// }
// else{
// string temp = it.second.substr(0, len);
// if(temp == prefix){
// return true;
// }
// }
// }
// return false;
// }
// };
class Trie {
private:
bool isEnd;
Trie* next[26];
public:
Trie() {
isEnd = false; // 是否是一个单词的结尾
memset(next, 0, sizeof(next));
}
void insert(string word) {
Trie* node = this;
for(auto it : word){
if(node->next[it-'a'] == nullptr){
node->next[it-'a'] = new Trie();
}
node = node->next[it-'a'];
}
node->isEnd = true;
}
bool search(string word) {
Trie * node = this;
for(auto it : word){
if(node->next[it-'a'] == nullptr){
return false;
}
node = node->next[it-'a'];
}
return node->isEnd;
}
bool startsWith(string prefix) {
Trie * node = this;
for(auto it : prefix){
if(node->next[it-'a'] == nullptr){
return false;
}
node = node->next[it-'a'];
}
return true;
}
};
/**
* Your Trie object will be instantiated and called as such:
* Trie* obj = new Trie();
* obj->insert(word);
* bool param_2 = obj->search(word);
* bool param_3 = obj->startsWith(prefix);
*/
1268. 搜索推荐系统
给你一个产品数组 products 和一个字符串 searchWord ,products 数组中每个产品都是一个字符串。
请你设计一个推荐系统,在依次输入单词 searchWord 的每一个字母后,推荐 products 数组中前缀与 searchWord 相同的最多三个产品。如果前缀相同的可推荐产品超过三个,请按字典序返回最小的三个。
请你以二维列表的形式,返回在输入 searchWord 每个字母后相应的推荐产品的列表。
代码如下:
class Solution {
public:
vector<vector<string>> suggestedProducts(vector<string>& products, string searchWord) {
int n = products.size(), m = searchWord.size();
vector<vector<string>> res(m);
sort(products.begin(), products.end());
int s = 0, t = n - 1;
for (int i = 0; i < m; ++i) {
while (s <= t && (products[s].size() < i || products[s][i] != searchWord[i]))
s++;
while (s <= t && (products[t].size() < i || products[t][i] != searchWord[i]))
t--;
for (int j = s; j <= min(s + 2, t); ++j)
res[i].push_back(products[j]);
}
return res;
}
};
区间集合
435. 无重叠区间
给定一个区间的集合 intervals ,其中 intervals[i] = [starti, endi] 。返回 需要移除区间的最小数量,使剩余区间互不重叠 。
代码如下:
class Solution {
public:
struct Range
{
int l,r;
bool operator< (const Range &W)
{
return r<W.r;
}
};
int eraseOverlapIntervals(vector<vector<int>>& intervals) {
int N = 100010;
Range range[N];
int n = intervals.size();
int res = 0;
int ed = -1e9;
for(int i = 0 ; i < n; i++)
{
range[i].l = intervals[i][0];
range[i].r = intervals[i][1];
}
sort(range,range+n);
for(int i = 0; i < n; i++)
{
if(range[i].l >= ed)
{
res++;
ed = range[i].r;
}
}
return n-res ;
}
};
452. 用最少数量的箭引爆气球
有一些球形气球贴在一堵用 XY 平面表示的墙面上。墙面上的气球记录在整数数组 points ,其中points[i] = [xstart, xend] 表示水平直径在 xstart 和 xend之间的气球。你不知道气球的确切 y 坐标。
一支弓箭可以沿着 x 轴从不同点 完全垂直 地射出。在坐标 x 处射出一支箭,若有一个气球的直径的开始和结束坐标为 xstart,xend, 且满足 xstart ≤ x ≤ xend,则该气球会被 引爆 。可以射出的弓箭的数量 没有限制 。 弓箭一旦被射出之后,可以无限地前进。
给你一个数组 points ,返回引爆所有气球所必须射出的 最小 弓箭数 。
代码如下:
class Solution {
public:
struct Circle
{
int l, r;
bool operator< (const Circle& t) const
{
return r < t.r;
}
}circle[100010];
int findMinArrowShots(vector<vector<int>>& points) {
int len = points.size();
for (int i = 0; i < len; i ++)
{
circle[i].l = points[i][0];
circle[i].r = points[i][1];
}
sort(circle, circle + len);
int res = 0, last = 0;
for (int i = 0; i < len; i ++)
{
if (!i)
{
res ++;
last = circle[i].r;
}
else
{
if (circle[i].l > last)
{
res ++;
last = circle[i].r;
}
}
}
return res;
}
};
单调栈
739. 每日温度
给定一个整数数组 temperatures ,表示每天的温度,返回一个数组 answer ,其中 answer[i] 是指对于第 i 天,下一个更高温度出现在几天后。如果气温在这之后都不会升高,请在该位置用 0 来代替。
代码如下:
class Solution {
public:
vector<int> dailyTemperatures(vector<int>& temperatures) {
int n = temperatures.size();
vector<int> result(temperatures.size(),0);
if(temperatures.size() == 1) return result;
int N = 100010;
int stk[N];//stk存储下标的单调递减栈
int tt = 0;
for(int i = n - 1; i >= 0; i --)
{
while(tt && temperatures[stk[tt]] <= temperatures[i]) tt--;
if(tt){
result[i] = stk[tt] - i;
}
else{
result[i] = 0;
}
stk[++tt] = i;
}
return result;
}
};
901. 股票价格跨度
设计一个算法收集某些股票的每日报价,并返回该股票当日价格的 跨度 。
当日股票价格的 跨度 被定义为股票价格小于或等于今天价格的最大连续日数(从今天开始往回数,包括今天)。
例如,如果未来 7 天股票的价格是 [100,80,60,70,60,75,85],那么股票跨度将是 [1,1,1,2,1,4,6] 。
实现 StockSpanner 类:
StockSpanner() 初始化类对象。
int next(int price) 给出今天的股价 price ,返回该股票当日价格的 跨度 。
class StockSpanner {
public:
StockSpanner() {
static auto speedup = [](){ ios::sync_with_stdio(false); cin.tie(nullptr); return nullptr; }();
prices.push({-1, INT_MAX});
idx = -1;
}
int next(int price) {
idx++;
while (price >= prices.top().second) prices.pop();
int ret = idx - prices.top().first;
prices.push({idx, price});
return ret;
}
private:
stack<pair<int, int>> prices;
int idx;
};
/**
* Your StockSpanner object will be instantiated and called as such:
* StockSpanner* obj = new StockSpanner();
* int param_1 = obj->next(price);
*/