两天速通力扣HOT100[DAY2] (55~100)
本题解旨在以最简单的语言总结hot100各题思路,为每一题提供一个思考入口,但想要手撕出来,需要自己认真推理细节。
2024-10-04日晚:86~95题没做,62题没想明白代码,两天速通还是失败了,明天再做吧。。。
欲速则不达,很多完成了的题依然感觉很心虚,再来一遍还是做不出来。建议踏踏实实看题解,自己能手撕出来才是真的。
目录
55、全排列
思路
回溯基本思想:DFS + 状态还原
面对前方n种选择的时候,循环选择其中一种,做出对应的改变并进入下一层,退出时恢复原状。
code
class Solution {
public:
vector<vector<int>> permute(vector<int>& nums) {
dfs(nums, 0);
return res;
}
private:
vector<vector<int>> res;
void dfs(vector<int> nums, int x) {
if (x == nums.size() - 1) {
res.push_back(nums); // 添加排列方案
return;
}
for (int i = x; i < nums.size(); i++) {
swap(nums[i], nums[x]); // 交换,将 nums[i] 固定在第 x 位
dfs(nums, x + 1); // 开启固定第 x + 1 位元素
swap(nums[i], nums[x]); // 恢复交换
}
}
};
作者:Krahets
链接:https://leetcode.cn/problems/permutations/solutions/2363882/46-quan-pai-lie-hui-su-qing-xi-tu-jie-by-6o7h/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
56、子集
思路
回溯暴力
code
class Solution {
public:
vector<int> t;
vector<vector<int>> ans;
void dfs(int cur, vector<int>& nums) {
if (cur == nums.size()) { // 递归终点
ans.push_back(t);
return;
}
t.push_back(nums[cur]); // 选择当前元素
dfs(cur + 1, nums); // 进入下一层
t.pop_back(); // 不选择当前元素
dfs(cur + 1, nums); // 进入下一层
}
vector<vector<int>> subsets(vector<int>& nums) {
dfs(0, nums);
return ans;
}
};
57、电话号码的字母组合
思路
回溯暴力
每个键遍历选择某个字母,进入下一个键
code
class Solution {
private:
vector<string> m = {
"abc","def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"
};
string digits;
public:
void dfs(vector<string>& res, string& one, int index) {
if(index == digits.size()) {
res.push_back(one);
return ;
}
int t = digits[index] - '0' - 2;
for(int i = 0; i < m[t].size(); i++) {
one += m[t][i];
dfs(res, one, index + 1);
one.pop_back();
}
}
vector<string> letterCombinations(string digits) {
vector<string> res;
if(digits.empty()) return res;
string one = "";
this->digits = digits;
dfs(res, one, 0);
return res;
}
};
58、组合总和
思路
回溯:对每个数字有选or不选两种选择,维护好每种情况下剩余target,回溯遍历即可。
剪枝:由于每个数字都是正数,所以当target < 0时无需继续遍历,直接return即可。
code
class Solution {
public:
void dfs(vector<int>& candidates, int target, vector<vector<int>>& ans, vector<int>& combine, int idx) {
if (idx == candidates.size() || target < 0) {
return;
}
if (target == 0) {
ans.emplace_back(combine);
return;
}
// 直接跳过
dfs(candidates, target, ans, combine, idx + 1);
// 选择当前数
if (target - candidates[idx] >= 0) {
combine.emplace_back(candidates[idx]);
dfs(candidates, target - candidates[idx], ans, combine, idx);
combine.pop_back();
}
}
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
vector<vector<int>> ans;
vector<int> combine;
dfs(candidates, target, ans, combine, 0);
return ans;
}
};
59、括号生成
思路
记录已放置左右括号的数量,当左括号数量小于n时可以放左括号,当右括号数量小于左括号数量时可以放右括号,根据数量来判断有几种选择,对每种选择回溯遍历即可。
code
class Solution {
void backtrack(vector<string>& ans, string& cur, int open, int close, int n) {
if (cur.size() == n * 2) {
ans.push_back(cur);
return;
}
if (open < n) {
cur.push_back('(');
backtrack(ans, cur, open + 1, close, n);
cur.pop_back();
}
if (close < open) {
cur.push_back(')');
backtrack(ans, cur, open, close + 1, n);
cur.pop_back();
}
}
public:
vector<string> generateParenthesis(int n) {
vector<string> result;
string current;
backtrack(result, current, 0, 0, n);
return result;
}
};
60、单词搜索
思路
从多个入口出发,回溯遍历
code
class Solution {
public:
int m, n;
vector<vector<int>> used;
bool inBoard(int i, int j) {
return 0 <= i && i < m && 0 <= j && j < n;
}
bool dfs(vector<vector<char>>& board, string word, int index, int i, int j) {
if(index == word.size() - 1) return true;
bool res = false;
if(inBoard(i - 1, j) && board[i-1][j] == word[index + 1] && used[i-1][j] == 0) {
used[i-1][j] = 1;
res |= dfs(board, word, index + 1, i - 1, j);
used[i-1][j] = 0;
}
if(inBoard(i, j - 1) && board[i][j-1] == word[index + 1] && used[i][j-1] == 0) {
used[i][j-1] = 1;
res |= dfs(board, word, index + 1, i, j - 1);
used[i][j-1] = 0;
}
if(inBoard(i + 1, j) && board[i+1][j] == word[index + 1] && used[i+1][j] == 0) {
used[i+1][j] = 1;
res |= dfs(board, word, index + 1, i + 1, j);
used[i+1][j] = 0;
}
if(inBoard(i, j + 1) && board[i][j+1] == word[index + 1] && used[i][j+1] == 0) {
used[i][j+1] = 1;
res |= dfs(board, word, index + 1, i, j + 1);
used[i][j+1] = 0;
}
return res;
}
bool exist(vector<vector<char>>& board, string word) {
this->m = board.size();
this->n = board[0].size();
vector<int> line(n);
used.resize(m, line);
bool res = false;
for(int i = 0; i < m; ++i) {
for(int j = 0; j < n; ++j) {
if(board[i][j] == word[0]){
used[i][j] = 1;
res |= dfs(board, word, 0, i, j);
used[i][j] = 0;
}
}
}
return res;
}
};
61、分割回文串
思路
回溯 + 动态规划预处理
为求出所有可能,需要回溯遍历,思路如下:
当遍历到i
位置时,认为前0~i-1
的字符串已分割完毕,从i
出发寻找j
,使得s[i:j]
也是回文串,这时有两种选择:
- 将
s[i:j]
加入结果集,跳到j+1
位置继续遍历 - 忽略,继续
j++
寻找新的回文串右边界
如此便形成标准回溯模式。
优化:每次判断s[i:j]
是否为回文串耗时,可使用动态规划预处理,将判断某一段是否为回文串提前计算出来,后续直接取用即可。
dp[i][j]
表示字符串s[i:j]
是否为回文串,是取1
,否取0
,递推式如下:
d
p
[
i
]
[
j
]
=
{
t
r
u
e
i
≥
j
d
p
[
i
+
1
,
j
−
1
]
&
(
s
[
i
]
=
=
s
[
j
]
)
i
<
j
\begin{equation} dp[i][j] =\left\{ \begin{array}{ll} true & i \geq j \\ dp[i+1, j-1] \& (s[i] == s[j]) & i < j \end{array}\right. \end{equation}
dp[i][j]={truedp[i+1,j−1]&(s[i]==s[j])i≥ji<j
code
class Solution {
private:
vector<vector<int>> f;
vector<vector<string>> ret;
vector<string> ans;
int n;
public:
void dfs(const string& s, int i) {
if (i == n) {
ret.push_back(ans);
return;
}
for (int j = i; j < n; ++j) {
if (f[i][j]) {
ans.push_back(s.substr(i, j - i + 1));
dfs(s, j + 1);
ans.pop_back();
}
}
}
vector<vector<string>> partition(string s) {
n = s.size();
f.assign(n, vector<int>(n, true));
for (int i = n - 1; i >= 0; --i) {
for (int j = i + 1; j < n; ++j) {
f[i][j] = (s[i] == s[j]) && f[i + 1][j - 1];
}
}
dfs(s, 0);
return ret;
}
};
62、N皇后
思路
N
个皇后需要放置在N
行N
列中,可以将结果保存为一个长度为N
的数组,记录第i
行的皇后放置在哪一列。对第一行来说,有N
种选择,第二行有N-1
种,第三行有N-3
种,回溯遍历,遍历途中判断是否违背了斜线规则。
code
暂时没想明白,改天再写
63、搜索插入位置
思路
二分查找,个人感觉左闭右闭的边界控制好写
code
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int left = 0, right = nums.size() - 1;
while(left <= right) {
int mid = (left + right) >> 1;
if(nums[mid] == target) {
return mid;
} else if(nums[mid] > target) {
right = mid - 1;
} else {
left = mid + 1;
}
}
return left;
}
};
64、搜索二维矩阵
思路
- 一次二分查找定位所在行,一次二分查找定位到所在列
- 该二维矩阵每行头尾拼接可以降为一维数组,按一维的二分查找即可
code
class Solution {
public:
bool searchMatrix(vector<vector<int>>& matrix, int target) {
int m = matrix.size(), n = matrix[0].size();
int left = 0, right = m * n - 1;
while(left <= right) {
int mid = (left + right) >> 1;
int r = mid / n, c = mid % n;
if(matrix[r][c] == target) {
return true;
} else if(matrix[r][c] > target) {
right = mid - 1;
} else {
left = mid + 1;
}
}
return false;
}
};
65、在排序数组中查找元素的第一个和最后一个位置
思路
二分查找在找到目标元素时,是直接返回;若想找到第一个和位置,只需在mid找到时,将mid排除出查找区间继续查找即可,直到left == right == mid
code
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
int left = 0, right = nums.size() - 1;
int first = -1, last = -1;
while(left <= right) {
int mid = (left + right) >> 1;
if(nums[mid] == target) {
first = mid;
right = mid - 1; // 将mid排除出查找区间继续查找
} else if(nums[mid] > target) {
right = mid - 1;
} else {
left = mid + 1;
}
}
left = 0; right = nums.size() - 1;
while(left <= right) {
int mid = (left + right) >> 1;
if(nums[mid] == target) {
last = mid;
left = mid + 1; // 将mid排除出查找区间继续查找
} else if(nums[mid] > target) {
right = mid - 1;
} else {
left = mid + 1;
}
}
return {first, last};
}
};
66、搜索旋转排序数组
思路
- 一次二分找到最小的位置,也就是旋转点;再按逻辑二分查找,将旋转数组映射到升序数组
- 根据二分位置的值判断哪边是右序的,再判断target位于有序的一边还是无序的一边。有序的就正常二分,无序的就去碰碰运气。
code
class Solution {
public:
int search(vector<int>& nums, int target) {
int min = 0, n = nums.size();
for(int l = 1, r = n - 1; l <= r; ) {
int mid = (l + r) >> 1;
if(nums[0] < nums[mid]) l = mid + 1;
else {
r = mid - 1;
min = mid;
}
}
for(int l = min, r = l + n - 1; l <= r; ) {
int mid = (l + r) >> 1;
int i = mid % n;
if(target < nums[i]) r = mid - 1;
else if(target > nums[i]) l = mid + 1;
else return i;
}
return -1;
}
};
67、寻找旋转排序数组中的最小值
思路
同上题
code
class Solution {
public:
int findMin(vector<int>& nums) {
if(nums[nums.size() - 1] > nums[0]) return nums[0];
int res = 0;
int left = 1, right = nums.size() - 1; // left 从 1 开始
while(left <= right) {
int mid = (right + left) >> 1;
if(nums[mid] > nums[0]) { // nums[mid] > nums[0] 说明mid在有序一侧,要往无序一侧寻找
left = mid + 1;
} else { // mid在无序一侧,最小值一定在mid左边
res = mid;
right = mid - 1;
}
}
return nums[res];
}
};
68、寻找两个正序数组的中位数
思路
两个数组共m + n
个数,寻找合并起来的中位数,代表在数组1中找到一个位置i,数组2中找到一个位置j,使得A[i] < B[j + 1] && B[j] < A[i + 1]
,其中,i, j
满足关系i + j = (m + n + 1) / 2
但是关于数组越界等细节比较繁琐,需要仔细想想。
code
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
if (nums1.size() > nums2.size()) {
return findMedianSortedArrays(nums2, nums1);
}
int m = nums1.size();
int n = nums2.size();
int left = 0, right = m;
// median1:前一部分的最大值
// median2:后一部分的最小值
int median1 = 0, median2 = 0;
while (left <= right) {
// 前一部分包含 nums1[0 .. i-1] 和 nums2[0 .. j-1]
// 后一部分包含 nums1[i .. m-1] 和 nums2[j .. n-1]
int i = (left + right) / 2;
int j = (m + n + 1) / 2 - i;
// nums_im1, nums_i, nums_jm1, nums_j 分别表示 nums1[i-1], nums1[i], nums2[j-1], nums2[j]
int nums_im1 = (i == 0 ? INT_MIN : nums1[i - 1]);
int nums_i = (i == m ? INT_MAX : nums1[i]);
int nums_jm1 = (j == 0 ? INT_MIN : nums2[j - 1]);
int nums_j = (j == n ? INT_MAX : nums2[j]);
if (nums_im1 <= nums_j) {
median1 = max(nums_im1, nums_jm1);
median2 = min(nums_i, nums_j);
left = i + 1;
} else {
right = i - 1;
}
}
return (m + n) % 2 == 0 ? (median1 + median2) / 2.0 : median1;
}
};
作者:力扣官方题解
链接:https://leetcode.cn/problems/median-of-two-sorted-arrays/solutions/258842/xun-zhao-liang-ge-you-xu-shu-zu-de-zhong-wei-s-114/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
69、有效的括号
思路
左括号入栈,右括号判断与栈顶是否匹配,匹配则出栈,不匹配则失败。
code
class Solution {
public:
bool isValid(string s) {
char stack[10001];
int top =0;
for (int i = 0; s[i]; ++i) {
if (s[i] == '(' || s[i] == '[' || s[i] == '{') stack[top++] = s[i];
else {
if ((--top) < 0) return false;//先减减,让top指向栈顶元素
if (s[i] == ')' && stack[top] != '(') return false;
if (s[i] == ']' && stack[top] != '[') return false;
if (s[i] == '}' && stack[top] != '{') return false;
}
}
return (!top);//防止“【”这种类似情况
}
};
70、最小栈
思路
一个栈存已有的数据,一个栈存目前的最小值
code
class MinStack {
public:
stack<int> stack1, stack2;
MinStack() {
}
void push(int val) {
stack2.push(stack1.empty() ? val : min(stack2.top(), val));
stack1.push(val);
}
void pop() {
stack1.pop();
stack2.pop();
}
int top() {
return stack1.top();
}
int getMin() {
return stack2.top();
}
};
/**
* Your MinStack object will be instantiated and called as such:
* MinStack* obj = new MinStack();
* obj->push(val);
* obj->pop();
* int param_3 = obj->top();
* int param_4 = obj->getMin();
*/
71、字符串解码
思路
栈保存一个二元组(int count, string str)
,遇到数字则计算count,遇到左括号则取str入栈,遇到右括号则出栈
code
class Solution {
public:
string decodeString(string s) {
stack<pair<string,int>> st;
string res="";
int mul=0;
for (auto c:s){
if(c=='['){
st.emplace(res,mul);
res="";
mul=0;
}else if(c==']'){
auto [last_res,cur_mul]=st.top();
st.pop();
string tmp=last_res;
for(int i=0;i<cur_mul;i++) tmp+=res;
res=tmp;
}else if('0'<=c && c<='9'){
mul=mul*10+(c-'0');
}else{
res += c;
}
}
return res;
}
};
72、每日温度
思路
单调栈
code
class Solution {
public:
vector<int> dailyTemperatures(vector<int>& temperatures) {
int n = temperatures.size();
vector<int> ans(n);
stack<int> s;
for (int i = 0; i < n; ++i) {
while (!s.empty() && temperatures[i] > temperatures[s.top()]) {
int previousIndex = s.top();
ans[previousIndex] = i - previousIndex;
s.pop();
}
s.push(i);
}
return ans;
}
};
作者:力扣官方题解
链接:https://leetcode.cn/problems/daily-temperatures/solutions/283196/mei-ri-wen-du-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
73、柱状图中最大的矩形
思路
- 对于一个高度,如果能得到向左和向右的边界
- 那么就能对每个高度求一次面积
- 遍历所有高度,即可得出最大面积
- 使用单调栈,在出栈操作时得到前后边界并计算面积
code
class Solution {
public:
int largestRectangleArea(vector<int>& heights)
{
int ans = 0;
vector<int> st;
heights.insert(heights.begin(), 0);
heights.push_back(0);
for (int i = 0; i < heights.size(); i++)
{
while (!st.empty() && heights[st.back()] > heights[i])
{
int cur = st.back();
st.pop_back();
int left = st.back() + 1;
int right = i - 1;
ans = max(ans, (right - left + 1) * heights[cur]);
}
st.push_back(i);
}
return ans;
}
};
74、数组中的第k个最大元素
思路
- 大根堆
- 基于快排的选择方法
code
class Solution {
public:
int quickselect(vector<int> &nums, int l, int r, int k) {
if(l == r) return nums[k];
int partition = nums[l], i = l - 1, j = r + 1;
while(i < j) {
do i++; while(nums[i] < partition);
do j--; while(nums[j] > partition);
if(i < j) swap(nums[i], nums[j]);
}
if(k <= j) return quickselect(nums, l, j, k);
else return quickselect(nums, j + 1, r, k);
}
int findKthLargest(vector<int> &nums, int k) {
int n = nums.size();
return quickselect(nums, 0, n - 1, n - k);
}
};
75、前K个高频元素
思路
哈希表 + 小根堆
code
class Solution {
public:
static bool cmp(pair<int, int>& m, pair<int, int>& n) {
return m.second > n.second;
}
vector<int> topKFrequent(vector<int>& nums, int k) {
unordered_map<int, int> mp;
priority_queue<pair<int, int>, vector<pair<int, int>>, decltype(&cmp)> pq(cmp); // 优先队列自定义排序的写法
vector<int> res;
for(int num : nums) {
mp[num]++;
}
for(auto& [num, count] : mp) {
if(pq.size() == k) {
if(pq.top().second < count) {
pq.pop();
pq.emplace(num, count);
}
} else {
pq.emplace(num, count);
}
}
while(k--) {
res.push_back(pq.top().first);
pq.pop();
}
return res;
}
};
76、数据流的中位数
思路
一个大根堆保存较小的一半元素,一个小根堆保存较大的一半元素,两个堆顶即为中位数。
code
class MedianFinder {
public:
priority_queue<int, vector<int>, less<int>> queMin;
priority_queue<int, vector<int>, greater<int>> queMax;
MedianFinder() {}
void addNum(int num) {
if (queMin.empty() || num <= queMin.top()) {
queMin.push(num);
if (queMax.size() + 1 < queMin.size()) {
queMax.push(queMin.top());
queMin.pop();
}
} else {
queMax.push(num);
if (queMax.size() > queMin.size()) {
queMin.push(queMax.top());
queMax.pop();
}
}
}
double findMedian() {
if (queMin.size() > queMax.size()) {
return queMin.top();
}
return (queMin.top() + queMax.top()) / 2.0;
}
};
77、买卖股票的最佳时机
思路
维护一个整数当前可获得的最大利润,维护一个正整数表示当前见过的最小值,遍历即可
code
class Solution {
public:
int maxProfit(vector<int>& prices) {
int res = 0, min_price = prices[0];
for(int i = 1; i < prices.size(); ++i) {
if(prices[i] - min_price > res) res = prices[i] - min_price;
if(prices[i] < min_price) min_price = prices[i];
}
return res;
}
};
78、跳跃游戏
思路
维护当前能跳到的最远位置
code
class Solution {
public:
bool canJump(vector<int>& nums) {
int end = nums[0];
for(int i = 1; i < nums.size(); ++i) {
if(i > end) return false;
end = max(end, i + nums[i]);
}
return true;
}
};
79、跳跃游戏II
思路
维护当前能跳到的最远位置,和上次的最远位置(边界),当到达边界时,更新边界为当前能到达的最远位置,跳跃次数+1
code
class Solution {
public:
int jump(vector<int>& nums) {
int maxPos = 0, n = nums.size(), end = 0, step = 0;
for (int i = 0; i < n - 1; ++i) {
if (maxPos >= i) {
maxPos = max(maxPos, i + nums[i]);
if (i == end) {
end = maxPos;
++step;
}
}
}
return step;
}
};
作者:力扣官方题解
链接:https://leetcode.cn/problems/jump-game-ii/solutions/230241/tiao-yue-you-xi-ii-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
80、划分字母区间
思路
记录每个字母最后一次出现的位置,遍历字符串更新当前区间右边界,抵达时划分区间
code
class Solution {
public:
vector<int> partitionLabels(string s) {
int last[26];
int length = s.size();
for (int i = 0; i < length; i++) {
last[s[i] - 'a'] = i;
}
vector<int> partition;
int start = 0, end = 0;
for (int i = 0; i < length; i++) {
end = max(end, last[s[i] - 'a']);
if (i == end) {
partition.push_back(end - start + 1);
start = end + 1;
}
}
return partition;
}
};
作者:力扣官方题解
链接:https://leetcode.cn/problems/partition-labels/solutions/455703/hua-fen-zi-mu-qu-jian-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
81、爬楼梯
思路
- 动规
d p [ i ] = d p [ i − 1 ] + d p [ i − 2 ] ; dp[i] = dp[i - 1] + dp[i - 2]; dp[i]=dp[i−1]+dp[i−2];
- 矩阵快速幂。妙不可言
code
递推的代码没什么好说的,这是矩阵快速幂的代码。
class Solution {
public:
vector<vector<long long>> multiply(vector<vector<long long>> &a, vector<vector<long long>> &b) {
vector<vector<long long>> c(2, vector<long long>(2));
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 2; j++) {
c[i][j] = a[i][0] * b[0][j] + a[i][1] * b[1][j];
}
}
return c;
}
vector<vector<long long>> matrixPow(vector<vector<long long>> a, int n) {
vector<vector<long long>> ret = {{1, 0}, {0, 1}};
while (n > 0) {
if ((n & 1) == 1) {
ret = multiply(ret, a);
}
n >>= 1;
a = multiply(a, a);
}
return ret;
}
int climbStairs(int n) {
vector<vector<long long>> ret = {{1, 1}, {1, 0}};
vector<vector<long long>> res = matrixPow(ret, n);
return res[0][0];
}
};
作者:力扣官方题解
链接:https://leetcode.cn/problems/climbing-stairs/solutions/286022/pa-lou-ti-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
82、杨辉三角
思路
模拟
code
class Solution {
public:
vector<vector<int>> generate(int numRows) {
vector<vector<int>> ret(numRows);
for (int i = 0; i < numRows; ++i) {
ret[i].resize(i + 1);
ret[i][0] = ret[i][i] = 1;
for (int j = 1; j < i; ++j) {
ret[i][j] = ret[i - 1][j] + ret[i - 1][j - 1];
}
}
return ret;
}
};
83、打家劫舍
思路
d p [ i ] = m a x ( d p [ i − 1 ] , d p [ i − 2 ] + n u m s [ i ] ) dp[i] = max(dp[i - 1], dp[i - 2] + nums[i]) dp[i]=max(dp[i−1],dp[i−2]+nums[i])
code
class Solution {
public:
int rob(vector<int>& nums) {
int n = nums.size();
if(n == 0) return 0;
if(n == 1) return nums[0];
vector<int> dp(n, 0);
dp[0] = nums[0];
dp[1] = max(nums[0], nums[1]);
for(int i = 2; i < n; ++i) {
dp[i] = max(dp[i - 1], dp[i - 2] + nums[i]);
}
return dp[n - 1];
}
};
84、完全平方数
思路
动态规划
f
[
i
]
=
1
+
m
i
n
j
=
1
i
f
[
i
−
j
2
]
f[i] = 1 + min _{j = 1} ^{\sqrt{i}} f[i - j^2]
f[i]=1+minj=1if[i−j2]
code
class Solution {
public:
int numSquares(int n) {
vector<int> f(n + 1);
for (int i = 1; i <= n; i++) {
int minn = INT_MAX;
for (int j = 1; j * j <= i; j++) {
minn = min(minn, f[i - j * j]);
}
f[i] = minn + 1;
}
return f[n];
}
};
作者:力扣官方题解
链接:https://leetcode.cn/problems/perfect-squares/solutions/822940/wan-quan-ping-fang-shu-by-leetcode-solut-t99c/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
85、零钱兑换
思路
动态规划
f
[
i
]
=
m
i
n
j
=
0
n
−
1
f
(
i
−
c
j
)
+
1
f[i] = min _{j=0} ^{n-1} \ f(i - c_j) + 1
f[i]=minj=0n−1 f(i−cj)+1
code
class Solution {
public:
int coinChange(vector<int>& coins, int amount) {
int Max = amount + 1;
vector<int> dp(amount + 1, Max);
dp[0] = 0;
for (int i = 1; i <= amount; ++i) {
for (int j = 0; j < (int)coins.size(); ++j) {
if (coins[j] <= i) {
dp[i] = min(dp[i], dp[i - coins[j]] + 1);
}
}
}
return dp[amount] > amount ? -1 : dp[amount];
}
};
作者:力扣官方题解
链接:https://leetcode.cn/problems/coin-change/solutions/132979/322-ling-qian-dui-huan-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
96、只出现一次的数字
思路
异或运算
code
class Solution {
public:
int singleNumber(vector<int>& nums) {
int ret = 0;
for (auto e: nums) ret ^= e;
return ret;
}
};
97、多数元素
思路
摩尔投票法:随变立一个候选者,遍历遇到和它相等的则票数加一,不等则票数减一,若减为负数,改立新的候选者。
code
class Solution {
public:
int majorityElement(vector<int>& nums) {
int candidate = nums[0];
int cnt = 1;
for(int i = 1; i < nums.size(); ++i) {
if(nums[i] == candidate) cnt++;
else {
cnt--;
if(cnt < 0) {
candidate = nums[i];
cnt = 1;
}
}
}
return candidate;
}
};
98、颜色分类
思路
双指针一个指向下一个0
应该在的位置,一个指向下一个2
应该在的位置,从两头向中间移动。
i
从0
向后移动,遇到0
就和第一个指针交换,遇到2
就和第二个交换,遇到1
就不理会。
code
class Solution {
public:
void sortColors(vector<int>& nums) {
int n = nums.size();
int p0 = 0, p2 = n - 1;
for (int i = 0; i <= p2; ++i) {
while (i <= p2 && nums[i] == 2) {
swap(nums[i], nums[p2]);
--p2;
}
if (nums[i] == 0) {
swap(nums[i], nums[p0]);
++p0;
}
}
}
};
99、下一个排列
思路
- 在 尽可能靠右的低位 进行交换,需要 从后向前 查找
- 将一个 尽可能小的「大数」 与前面的「小数」交换。比如 123465,下一个排列应该把 5 和 4 交换而不是把 6 和 4 交换
- 将「大数」换到前面后,需要将「大数」后面的所有数 重置为升序,升序排列就是最小的排列。
code
class Solution {
public:
void nextPermutation(vector<int>& nums) {
next_permutation(nums.begin(), nums.end());
}
};
不要学,还是老老实实看题解吧!
100、寻找重复数
思路
把该序列看做一个图,nums[i]
表示 从 i
到 nums[i]
有一条边。由于nums.length == n + 1
且 1 <= nums[i] <= n
,所以如果没有重复数,该图应该是一个链表,有重复数,该图应该是一个有环的链表。
题目变为:返回链表第一个入环节点
参考 26.环形链表II。妙不可言!
code
class Solution {
public:
int findDuplicate(vector<int>& nums) {
int slow = 0, fast = 0;
do {
slow = nums[slow];
fast = nums[nums[fast]];
} while (slow != fast); // 相遇
slow = 0; // 一个指针回到首部重新走
while (slow != fast) {
slow = nums[slow];
fast = nums[fast];
}
return slow;
}
};