力扣热题100再刷

160.相交链表

 读一遍A,一个set存节点,遍历B的时候判断即可。复习下set的STL:set有set和unordered_set,同样有insert,find,count,对于set而言,自动从小到大排序,还有:

s.lower_bound(k)

O ( l o g N )

返回大于等于k的第一个元素的迭代器

s.upper_bound(k)

O ( l o g N )

返回大于k的第一个元素的迭代器

注意都是大于!(或大于等于)

234.回文链表

226.翻转二叉树

翻转二叉树是经典题了,必须掌握。就是递归遍历树,然后从下往上交换左右子树。(实际上很多和树相关的都会涉及递归和遍历)

class Solution {
public:void switchlr(TreeNode* root){if(root==nullptr)return;switchlr(root->left);switchlr(root->right);TreeNode*l=root->left;root->left=root->right;root->right=l;return;}TreeNode* invertTree(TreeNode* root) {switchlr(root);return root;}
};

221.最大正方形

在一个由 '0' 和 '1' 组成的二维矩阵内,找到只包含 '1' 的最大正方形,并返回其面积。(再次提醒,返回的不是边长,而是边长的平方!)

同样是经典dp了,f(i,j)表示以(i,j)为右下角的正方形的最大边长,有了dp的思路然后画个图就知道dp方程

class Solution {
public:int maximalSquare(vector<vector<char>>& matrix) {int dp[310][310];memset(dp,0,sizeof dp);int ans=0;int m=matrix.size();int n=matrix[0].size();for(int i=0;i<m;i++){if(matrix[i][0]=='0')dp[i][0]=0;else{ans=1;dp[i][0]=1;}}for(int j=0;j<n;j++){if(matrix[0][j]=='0')dp[0][j]=0;else{ans=1;dp[0][j]=1;}}for(int i=1;i<m;i++){for(int j=1;j<n;j++){if(matrix[i][j]=='0'){dp[i][j]=0;continue;}else if(matrix[i][j]=='1'){dp[i][j]=min(dp[i-1][j-1],min(dp[i-1][j],dp[i][j-1]))+1;ans=max(ans,dp[i][j]);}}}return ans*ans;}
};

 215.数组中的第k个最大元素

线性查找模板——在快排的基础上改的partion

class Solution {
public:int partion(vector<int>&nums,int l,int r,int k){int bench=nums[l];int mid=(l+r)/2;int i=l,j=r;int z=nums.size();if(l==r)return nums[k];while(i<j){while(nums[j]>=bench&&j>i)j--;while(nums[i]<=bench&&i<j)i++;if(i<j)swap(nums[i],nums[j]);else if(i>=j)break;}nums[l]=nums[i];nums[i]=bench;if(i+1==k)return nums[i];else if(i+1<k)return partion(nums,i+1,r,k);else if(i+1>k)return partion(nums,l,i,k);return -1;}int findKthLargest(vector<int>& nums, int k) {int z=nums.size();if(z==1)return nums[0];return partion(nums,0,z-1,z-k+1);}
};

208.实现trie

记住字典树的结构:每个节点由一个字符vector和bool组成(标识是否是末尾)

class Trie {
private:vector<Trie*>ch;bool isend;
public:Trie() {ch=vector<Trie*>(26);isend=false;}void insert(string word) {int m=word.size();Trie* node=this;//注意注释中给出的调用for(int i=0;i<m;i++){char c=word[i]-'a';if(node->ch[c]==nullptr){node->ch[c]=new Trie();}node=node->ch[c];}node->isend=true;}bool search(string word) {int m=word.size();Trie* node=this;//注意注释中给出的调用for(int i=0;i<m;i++){char c=word[i]-'a';if(node->ch[c]==nullptr){return false;}node=node->ch[c];}return node->isend;}bool startsWith(string prefix) {int m=prefix.size();Trie* node=this;for(int i=0;i<m;i++){char c=prefix[i]-'a';if(node->ch[c]==nullptr)return false;node=node->ch[c];}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);*/

207.课程表

拓扑排序显然是和queue相关的,使用队列记录候选头,此外,正确做法应在减少邻接节点入度时立即检查其是否为0。

class Solution {
public:queue<int>que;unordered_map<int,vector<int>>store;bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {int m=prerequisites.size();int a,b;vector<int>book(numCourses,0);vector<int>deg(numCourses,0);for(int i=0;i<m;i++){a=prerequisites[i][0];b=prerequisites[i][1];store[b].push_back(a);deg[a]+=1;}for(int i=0;i<deg.size();i++){if(deg[i]==0)que.push(i);}while(!que.empty()){int head=que.front();que.pop();book[head]=1;auto vec=store[head];for(int i=0;i<vec.size();i++){a=vec[i];deg[a]--;if(deg[a]==0&&book[a]==0)que.push(a);}}for(int i=0;i<book.size();i++){if(book[i]==0){return false;}}return true;}
};

236.二叉树的最近公共祖先

核心:从递归的思路出发,寻求找祖先的逻辑!

class Solution {public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {if(root == null || root == p || root == q)return root;TreeNode r = lowestCommonAncestor(root.right , p , q);TreeNode l = lowestCommonAncestor(root.left , p , q);return l == null ? r : (r == null ? l : root);}
}

206.反转链表

强调一些语法:for (auto it = store.rbegin(); it != store.rend(); ++it),rend() 表示反向迭代器的结束位置(指向容器第一个元素的​​前一个位置​​),it确实是++,只不过从末尾往开头走,it代表迭代器!使用&it代表获取迭代器的地址(x),而*it代表获取迭代器代表的元素(√)

739.每日温度

给定一个整数数组 temperatures ,表示每天的温度,返回一个数组 answer ,其中 answer[i] 是指对于第 i 天,下一个更高温度出现在几天后。如果气温在这之后都不会升高,请在该位置用 0 来代替。

显然是从后往前遍历,维护一个day数组,day[a]=b;比a温度高的是第b天  这样如果前面天的温度更高,j=day[j]; 直接跳到比j温度高的天比较,然后继续跳……

class Solution {
public:vector<int> dailyTemperatures(vector<int>& temperatures) {int k=temperatures.size();vector<int>day(k,-1);//day[a]=b;比a温度高的是第b天for(int i=k-1;i>0;i--){int j=i;while(j!=-1){if(temperatures[i-1]<temperatures[j]){day[i-1]=j;break;}else{j=day[j];}}}vector<int>ans;for(int i=0;i<k;i++){if(day[i]==-1)ans.push_back(0);else{ans.push_back(day[i]-i);}}return ans;}
};

200.岛屿数量

给你一个由 '1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。

dfs,染色和岛屿数量由color决定。

class Solution {
public:int board[310][310];int color;int dire[4][2]={{1,0},{0,-1},{-1,0},{0,1}};void paint(const vector<vector<char>>&grid,int x,int y){int m=grid.size();int n=grid[0].size();int tx,ty;board[x][y]=color;for(int i=0;i<4;i++){tx=x+dire[i][0];ty=y+dire[i][1];if(tx<0||tx>=m||ty<0||ty>=n||grid[tx][ty]=='0'||board[tx][ty]!=0)continue;paint(grid,tx,ty);}}int numIslands(vector<vector<char>>& grid) {memset(board,0,sizeof board);color=0;int m=grid.size();int n=grid[0].size();for(int i=0;i<m;i++){for(int j=0;j<n;j++){if(board[i][j]==0&&grid[i][j]=='1'){color+=1;paint(grid,i,j);}}}return color;}
};

198.打家劫舍

老dp了,关于i-1和i-2的,拿与不拿(注意下只有一家的特殊情况)。

class Solution {
public:int rob(vector<int>& nums) {int k=nums.size();vector<int>dp(k,0);if(k==1)return nums[0];dp[0]=nums[0];dp[1]=max(nums[0],nums[1]);for(int i=2;i<k;i++)dp[i]=max(dp[i-2]+nums[i],dp[i-1]);return dp[k-1];}
};

169.多数元素

238.除自身以外数组的乘积

给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。

题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在  32 位 整数范围内。

请 不要使用除法,且在 O(n) 时间复杂度内完成此题。

看到O(n)就应该想到双指针,两个数组,一个存前缀乘积一个存后缀乘积

注意一个小点:vector已经声明了空间长度,就不要再用push_back!

class Solution {
public:vector<int> productExceptSelf(vector<int>& nums) {int m=nums.size();vector<int>pre(m,1);vector<int>behind(m,1);for(int i=1;i<m;i++){pre[i]=(pre[i-1]*nums[i-1]);}for(int i=m-2;i>=0;i--){behind[i]=(behind[i+1]*nums[i+1]);}vector<int>ans(m);for(int i=0;i<m;i++)ans[i]=(pre[i]*behind[i]);return ans;}
};

155.最小栈

要强调一个点:这里不等同于单调栈!(如果是单调栈,请问获取栈顶的元素和获取最小元素有什么区别)

方法很简单——同时维护一个栈,记录此时此刻在栈中的最小值就行。(与元素栈同步插入与删除,用于存储与每个元素对应的最小值。)

class MinStack {
private:stack<int>store,work;
public:MinStack() {work.push(INT_MAX);}void push(int val) {store.push(val);work.push(min(work.top(),val));}void pop() {store.pop();work.pop();}int top() {return store.top();}int getMin() {return work.top();}
};

152.乘积最大子数组

给你一个整数数组 nums ,请你找出数组中乘积最大的非空连续 子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。

看题目就想到求连续最大值的一遍扫描,只不过这里是乘法,需要考虑负负得正的情况,办法很简单,引入两个变量代表当前乘积最大和最小值,然后同时与nums[i]相乘并比较就好!

框架肯定不变的,就看怎么解决乘积,所以想到引入变量

int maxProduct(vector<int>& nums) {int neg = nums[0], pos = nums[0], ans = nums[0]; for (int i = 1; i < nums.size(); i++) {int pre_neg = neg; int pre_pos = pos;neg = min({nums[i], nums[i] * pre_neg, nums[i] * pre_pos});pos = max({nums[i], nums[i] * pre_neg, nums[i] * pre_pos});ans = max(ans, pos);}return ans;
}

比较易错的是这样:

for(int i=1;i<k;i++){neg=trimin(nums[i],nums[i]*neg,nums[i]*pos);pos=trimax(nums[i],nums[i]*neg,nums[i]*pos);ans=max(ans,pos);} 

这样肯定不对,neg被更新后立即用于计算新的 pos,而不是上一轮的原始值

148.排序链表

略。

其实严格意义上,这里不应该图省事用快排sort,标准想考查的事链表的归并排序。

sort(store.begin(), store.end(), Comparator());// 使用结构体比较而非 Lambda

成员函数后的 const 保证函数不修改结构体状态

两个括号:

operator()实现函数调用语义bool operator()(...)
private:// 定义比较结构体struct Comparator {bool operator()(const ListNode* a, const ListNode* b) const {return a->val < b->val;}};

补充:常数空间内找到链表中点——快慢指针法!!!

141.环形链表

给你一个链表的头节点 head ,判断链表中是否有环。

略。链表中的经典套路——快慢指针 

142.环形链表2

加一个返回入环的结点(如果有环的话)。略。遍历的同时维护一个unordered_set

146.LRU缓存

 究极面试经典题。关键在于put这个点要是常数复杂度,所以用栈是不行的,堆也是不行的(设计到删除插入),只能考虑双向链表,头部最新,尾部最老(单向的话删除需要遍历找pre,也不行);缓存使用一个map。

还有就是插删的细节问题,以及初始化操作。

class LRUCache {
struct linknode{int key,value;linknode*next,*pre;linknode():key(0),value(0),next(nullptr),pre(nullptr){}linknode(int key,int val):key(key),value(val),next(nullptr),pre(nullptr){}
};
private:unordered_map<int,linknode*>umap;linknode* head;linknode* tail;int maxcap,cap;
public:void removenode(linknode* p){linknode* p1=p->pre;linknode* p2=p->next;p1->next=p2;p2->pre=p1;return;}void addtohead(linknode* p){p->pre=head;p->next=head->next;head->next->pre=p;head->next=p;return;}LRUCache(int capacity) {head=new linknode();tail=new linknode();head->next=tail;tail->pre=head;this->maxcap=capacity;cap=0;return;}int get(int key) {if(umap.find(key)==umap.end())return -1;linknode* p=umap[key];removenode(p);addtohead(p);return p->value;}void put(int key, int value) {if(umap.find(key)!=umap.end()){linknode* p=umap[key];removenode(p);p->value=value;addtohead(p);}else{linknode* p=new linknode(key,value);addtohead(p);umap[key]=p;cap++;if(cap>maxcap){linknode* p=tail->pre;removenode(p);umap.erase(p->key);delete p;cap--;}}}
};

139.单词拆分

给你一个字符串 s 和一个字符串列表 wordDict 作为字典。如果可以利用字典中出现的一个或多个单词拼接出 s 则返回 true

注意:不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。

直接开搜,但注意搜索可能会导致超时,所以采取一些策略:首先是引入memo这个map,用于缓存已经计算过的子问题的结果,以避免重复计算;第二个是全部改为引用调用,这样有的递归调用都共享同一个,如果不这样,则每个递归分支都有自己的独立副本(即memo是每个分支独立的),也就失去了memo的意义。

此外,强烈建议再看STL中各种string的函数,很有用!

class Solution {
public:bool search(string s,const vector<string>&wordDict,unordered_map<string,bool>&memo){if(s=="")return true;if(memo.find(s)!=memo.end())return memo[s];for(auto word:wordDict){if(s.size()>=word.size()&&s.substr(0,word.size())==word){if(search(s.substr(word.size()),wordDict,memo)){memo[s]=true;return true;}}}memo[s]=false;return false;}bool wordBreak(string s, vector<string>& wordDict) {unordered_map<string,bool>memo;return search(s,wordDict,memo);}
};

136.只出现一次的数字

给你一个 非空 整数数组 nums ,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。

你必须设计并实现线性时间复杂度的算法来解决此问题,且该算法只使用常量额外空间。

找元素,且只用常量空间——往位运算上面想!  异或^=

647.回文子串

给你一个字符串 s ,请你统计并返回这个字符串中 回文子串 的数目。

注意一个点:回文的判断用双指针

class Solution {
public:int isregu(const string &s,int sta,int len){int i=sta;int j=sta+len-1;while(i<=j){if(s[i]==s[j]){j--;i++;continue;}else return 0;}return 1;}int countSubstrings(string s) {int ans=0;int k=s.size();for(int i=1;i<=k;i++){for(int j=0;j+i-1<k;j++){ans+=isregu(s,j,i);}}return ans;}
};

128.最长连续序列

给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。要求是O(n)的复杂度

输入:nums = [100,4,200,1,3,2]
输出:4
解释:最长数字连续序列是 [1, 2, 3, 4]。它的长度为 4。

从基础版的代码开始一步步想解决办法:x,x+1,x+2,⋯,x+y 的连续序列,而我们却重新从 x+1,x+2 或者是 x+y 处开始尝试匹配,那么得到的结果肯定不会优于枚举 x 为起点的答案。所以将基础暴力n^2降到n的做法是确定这个数是不是开头,如果是才遍历,如果不是就跳过,即判断set中有没有num-1!        ——这种从基础到最终的解决思想是值得学习的

class Solution {
public:int longestConsecutive(vector<int>& nums) {//遍历开头的做法unordered_set<int>store;for(int num:nums)store.insert(num);int ans=0;for(int num:store){if(!store.count(num-1)){int cur=num;int curlen=1;while(store.count(cur+1)){cur++;curlen++;}ans=max(ans,curlen);}}return ans;}
};

124.二叉树中的最大路径和

二叉树中的 路径 被定义为一条节点序列,序列中每对相邻节点之间都存在一条边。同一个节点在一条路径序列中 至多出现一次 。该路径 至少包含一个 节点,且不一定经过根节点。

路径和 是路径中各节点值的总和。

给你一个二叉树的根节点 root ,返回其 最大路径和 。

显然是递归:显然每次返回的应该是单边的最大值(而不是折线,意会一下),但是,这个可能是折线的最大路径和完全可以在递归的过程中更新呀~ 具体看代码,还是很经典的

class Solution {
public:int ans;int dfs(TreeNode* root){if(root==nullptr){return 0;}int maxleft=max(dfs(root->left),0);int maxright=max(dfs(root->right),0);//更新最大路径和ans=max(ans,maxleft+maxright+root->val);//返回的值又是另一回事://他的贡献只能带其中一个孩子(带大孩子走),整个路径才能向上延伸return root->val+max(maxleft,maxright);}int maxPathSum(TreeNode* root) {ans=INT_MIN;dfs(root);return ans;}
};

322.零钱兑换

给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。

计算并返回可以凑成总金额所需的最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。(每种硬币数量无限)

这里使用闫式dp法

class Solution {
public:int coinChange(vector<int>& coins, int amount) {if(amount==0)return 0;vector<int>dp(amount+1,amount+1);dp[0]=0;for(int i=1;i<=amount;i++){for(auto coin:coins){if(coin>i)continue;dp[i]=min(dp[i],dp[i-coin]+1);}}return dp[amount]>amount?-1:dp[amount];}
};

试了一下dfs+剪枝似乎也能过:不过这种求最值的问题还是优先dp!(必须想到)

复杂度amount*len(nums),这里的剪枝搜索也是模板! 此外,倒排序:用rbegin

class Solution {
private:vector<int>store;
public:int dfs(const vector<int>&coins,int amount){if(amount==0)return 0;if(store[amount]!=-2)return store[amount];int min_count=0x3f3f3f;for(int coin:coins){if(coin>amount)continue;int res=dfs(coins,amount-coin);if(res!=0x3f3f3f){min_count=min(min_count,res+1);}}store[amount]=min_count;return store[amount];}int coinChange(vector<int>& coins, int amount) {sort(coins.rbegin(),coins.rend());store=vector<int>(amount+1,-2);int result=dfs(coins,amount);return result==0x3f3f3f?-1:result;}
};

494.目标和

输入:nums = [1,1,1,1,1], target = 3
输出:5
解释:一共有 5 种方法让最终目标和为 3 。
-1 + 1 + 1 + 1 + 1 = 3
+1 - 1 + 1 + 1 + 1 = 3
+1 + 1 - 1 + 1 + 1 = 3
+1 + 1 + 1 - 1 + 1 = 3
+1 + 1 + 1 + 1 - 1 = 3

超级经典的动态规划!

所以转化为背包问题,选哪些组合可以把package_b装满(使用b来判断,b为负有0种情况)

461.汉明距离

注意左移运算符:>>= 此外,左移是高位补0

class Solution {
public:int hammingDistance(int x, int y) {int ans=0;//x和y任意一个不为零都循环while(x|y){ans+=(x&1)^(y&1);x>>=1;y>>=1;}return ans;}
};

448.找到所有数组中消失的数字

438.找到字符串中的所有异位词

给定两个字符串 s 和 p,找到 s 中所有 p 的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。

令人惊讶的是不考虑任何优化,直接每次考虑两个map是否相等也能过。

class Solution {
public:unordered_map<char,int>pstore,sstore;int thesame(){for(auto it=pstore.begin();it!=pstore.end();it++){char ch=it->first;if(sstore.find(ch)==sstore.end()||sstore[ch]!=pstore[ch])return 0;}return 1;}vector<int> findAnagrams(string s, string p) {int k1=s.size();int k2=p.size();for(int i=0;i<k2;i++){pstore[p[i]]+=1;}vector<int>ans;if(k1<k2)return ans;for(int i=0;i<k2;i++){sstore[s[i]]+=1;}for(int i=1;i+k2<=k1;i++){if(thesame())ans.push_back(i-1);sstore[s[i-1]]-=1;sstore[s[i+k2-1]]+=1;}if(thesame())ans.push_back(k1-k2);return ans;}
};

显然是只用比较移入和移出之后的变化的,所以可以有更快的做法:

避免在每次滑动窗口时完整比较两个哈希表,我们可以改用固定大小的数组(大小128,覆盖所有ASCII字符),并利用数组直接比较是否相等。

class Solution {
public:vector<int> findAnagrams(string s, string p) {vector<int> ans;if (p.empty()) {for (int i = 0; i <= s.size(); i++) ans.push_back(i);return ans;}if (s.size() < p.size()) return ans;vector<int> p_count(128, 0);vector<int> s_count(128, 0);int pLen = p.size();for (char c : p) p_count[c]++;for (int i = 0; i < pLen; i++) s_count[s[i]]++;if (p_count == s_count) ans.push_back(0);for (int i = pLen; i < s.size(); i++) {s_count[s[i - pLen]]--;s_count[s[i]]++;if (p_count == s_count) ans.push_back(i - pLen + 1);}return ans;}
};

437.路径综合3

给定一个二叉树的根节点 root ,和一个整数 targetSum ,求该二叉树里节点值之和等于 targetSum 的 路径 的数目。

路径 不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。

深度搜索的同时引入一个前缀和哈希进行判断!

class Solution {
public:unordered_map<long, int> prefixSumCount; // 存储前缀和出现次数的哈希表int totalCount; // 记录满足条件的路径总数void dfs(TreeNode* node, long currentSum, int targetSum) {if (!node) return;// 更新当前路径和currentSum += node->val;// 检查是否存在前缀和使得 currentSum - prefixSum = targetSumif (prefixSumCount.find(currentSum - targetSum) != prefixSumCount.end()) {totalCount += prefixSumCount[currentSum - targetSum];}// 更新当前前缀和的出现次数prefixSumCount[currentSum]++;// 递归遍历左右子树dfs(node->left, currentSum, targetSum);dfs(node->right, currentSum, targetSum);// 回溯:当前节点访问完成后,从哈希表中去除当前前缀和prefixSumCount[currentSum]--;if (prefixSumCount[currentSum] == 0) {prefixSumCount.erase(currentSum);}}int pathSum(TreeNode* root, int targetSum) {prefixSumCount.clear();prefixSumCount[0] = 1; // 重要:初始化空路径的前缀和为0的出现次数为1totalCount = 0;dfs(root, 0, targetSum);return totalCount;}
};

416.分割等和子集

给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。

略。经典拆成0-1背包。

406.根据身高重建队列

其实是个不错的锻炼思维的题,题目有点绕,核心是输入的数组是乱序的,让你给排序。

解法为将输入按照身高升序排序,然后遍历,这样每次遍历的时候必然是当前最小的身高。

class Solution {
public:// struct ope{//     bool operator()(const vector<int>&a,const vector<int>&b){//         if(a[0]<b[0])return true;//         else if(a[0]==b[0]&&a[1]<b[1])return true;//     }// };vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {unordered_map<int,vector<int>>store;int k=people.size();int height[k+5];memset(height,-1,sizeof height);sort(people.begin(),people.end());for(int i=0;i<k;i++){int h=people[i][0];int pos=people[i][1];int count=0;for(int j=0;j<k;j++){if(pos==count){while(j<k&&height[j]!=-1)j++;height[j]=h;store[j]=people[i];break;}if(height[j]==-1&&count<pos){count++;}else if(height[j]>=h&&count<pos){count++;}else if(height[j]!=-1&&height[j]<h)continue;}}vector<vector<int>>ans;for(int i=0;i<k;i++){ans.push_back(store[i]);}return ans;}
};

399.除法求值

 除法问题,首先的思路是找基准,比如a/b以b为基准……但显然这个基准不好找,所以想到的是并查集,通过并查集能将相关联的字符归为一个基准!即:根节点(祖先)为基准,且这是个带权的并查集(它的压缩与合并必须掌握)

不同的字符串(这个题好像不约分,即ab/bc就是ab/bc,而并不等于a/c)用哈希表存储,将字符串映射为int,这样就和并查集结合了。

class Solution {
private:class Union{//建立并查集private:vector<int>parents;//weights[x] 表示变量 x 相对于其​​当前直接父节点​​的权重值//如果父节点为p,那么x=p×weights[x]vector<double>weights;public:Union(int n):parents(n),weights(n,1.0){for(int i=0;i<n;i++)parents[i]=i;//并查集初始化}int findfa(int x){if(parents[x]==x)return parents[x];else{//注意这里!带权并查集的压缩的写法!int ori=parents[x];parents[x]=findfa(parents[x]);weights[x]*=weights[ori];}return parents[x];}//合并,将x所在的子树连到y所在的子树void unite(int x,int y,double value){int rootx=findfa(x);int rooty=findfa(y);if(rootx==rooty)return;parents[rootx]=rooty;//x把y当做基weights[rootx]=weights[y]*value/weights[x];}double isconnected(int x,int y){int rootx=findfa(x);int rooty=findfa(y);if(rootx==rooty)return weights[x]/weights[y];else return -1.0;}};
public:vector<double> calcEquation(vector<vector<string>>& equations, vector<double>& values, vector<vector<string>>& queries) {int k=equations.size();//注意初始化的表达Union theunion(2*k);//变量到id的映射unordered_map<string,int>hashmap(2*k);int id=0;for(int i=0;i<k;i++){string var1=equations[i][0];string var2=equations[i][1];if(!hashmap.count(var1))hashmap[var1]=id++;if(!hashmap.count(var2))hashmap[var2]=id++;//建立两个字符串的连接,字符串用id映射表示theunion.unite(hashmap[var1],hashmap[var2],values[i]);}int q=queries.size();vector<double>res(q);for(int i=0;i<q;i++){string var1=queries[i][0];string var2=queries[i][1];//新出现的变量if(!hashmap.count(var1)||!hashmap.count(var2))res[i]=-1.0;//计算else res[i]=theunion.isconnected(hashmap[var1],hashmap[var2]);}return res;}
};

394.字符串解码

给定一个经过编码的字符串,返回它解码后的字符串。

编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。

你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。

输入:s = "2[abc]3[cd]ef"
输出:"abcabccdcdcdef"

很不错的一道递归题,对于一个字符串,它可能很多地方都会有压缩(即[]),压缩处的标志即数字(这里要注意数字可能有多位),所以当在数字标记处时,求得当前压缩的次数,while结束后进入压缩(跳过[,进入下一轮递归,出来的时候res加上压缩串),其他情况则为没有压缩的字符,正常加即可。

class Solution {
public:string decodeString(string s) {int i = 0;return decodeSub(s, i);}private:string decodeSub(const string& s, int& i) {string res;while (i < s.size() && s[i] != ']') {if (isdigit(s[i])) {int num = 0;while (i < s.size() && isdigit(s[i])) {num = num * 10 + (s[i] - '0');i++;}if (i < s.size() && s[i] == '[') {i++; // 跳过左括号string t = decodeSub(s, i);for (int j = 0; j < num; j++) {res += t;}}} else {res += s[i];i++;}}if (i < s.size() && s[i] == ']') {i++;}return res;}
};

347.前k个高额元素

给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。

其实正确做法应该是用堆排序和优先队列(这种看题就应该想到)

class Solution {
public:vector<int> topKFrequent(vector<int>& nums, int k) {unordered_map<int,int>store;int u=nums.size();for(int i=0;i<u;i++){store[nums[i]]+=1;}map<int,vector<int>>temp;for(auto it=store.begin();it!=store.end();it++){int a=it->second;int b=it->first;temp[a].push_back(b);}vector<int>ans;for(auto it=temp.rbegin();it!=temp.rend();it++){auto vec=it->second;if(k>0){for(int j=0;j<vec.size();j++){ans.push_back(vec[j]);k--;}}else break;}return ans;}
};

338.比特位计数

略。(此为第37题)

337.打家劫舍3

 经典的树形dp了!可以看之前acwing的记录:

这类问题都有一个共同点,或者说同时都属于dp问题的状态机类问题,所谓状态机:

当问题的状态转移涉及​​多个条件或选择​​时,直接定义状态转移方程可能变得复杂。状态机通过显式地划分状态和转移条件,使动态规划的逻辑更清晰,避免遗漏可能性。(多个dp空间,这里用map存储+树的遍历)

class Solution {
public:unordered_map<TreeNode*,vector<int>>store;void search(TreeNode* root){if(root==nullptr)return;else if(root->left==nullptr&&root->right==nullptr){store[root].push_back(0);store[root].push_back(root->val);return;}else if(root->left==nullptr&&root->right!=nullptr){search(root->left);search(root->right);vector<int>t(2,0);t[0]=max(store[root->right][0],store[root->right][1]);t[1]=store[root->right][0]+root->val;store[root]=t;}else if(root->left!=nullptr&&root->right==nullptr){search(root->left);search(root->right);vector<int>t(2,0);t[0]=max(store[root->left][0],store[root->left][1]);t[1]=store[root->left][0]+root->val;store[root]=t;}else{search(root->left);search(root->right);vector<int>t(2,0);t[0]=max(store[root->left][0],store[root->left][1])+max(store[root->right][0],store[root->right][1]);t[1]=store[root->left][0]+store[root->right][0]+root->val;store[root]=t;}return;}int rob(TreeNode* root) {search(root);return max(store[root][0],store[root][1]);}
};

121.买卖股票的最佳时期

给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。

你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。

返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。

简单题,不难,关键是想清楚思路:不断更新截止到第i天的股票最低点。

class Solution {
public:int maxProfit(vector<int>& prices) {int k=prices.size();int sta=0;int ans=0;for(int i=0;i<k;i++){if(prices[sta]>=prices[i]){sta=i;continue;}else if(prices[sta]<prices[i]){ans=max(ans,prices[i]-prices[sta]);}}return ans;}
};

312.戳气球

有 n 个气球,编号为0 到 n - 1,每个气球上都标有一个数字,这些数字存在数组 nums 中。

现在要求你戳破所有的气球。戳破第 i 个气球,你可以获得 nums[i - 1] * nums[i] * nums[i + 1] 枚硬币。 这里的 i - 1 和 i + 1 代表和 i 相邻的两个气球的序号。如果 i - 1或 i + 1 超出了数组的边界,那么就当它是一个数字为 1 的气球。

求所能获得硬币的最大数量。

动态规划解决的问题,需要满足三个条件:最优子结构,无后效性和子问题重叠。

第一次戳哪个气球不满足无后效性

那么就试试最后一次戳哪个气球——这是核心!戳最后一个气球!

因为是最后一个被戳爆的,所以它周边没有球了!没有球了!只有这个开区间首尾的 i 和 j 了!!
这就是为什么DP的状态转移方程是只和 i 和 j 位置的数字有关

class Solution {
public:int maxCoins(vector<int>& nums) {int n = nums.size();vector<vector<int>> rec(n + 2, vector<int>(n + 2));vector<int> val(n + 2);val[0] = val[n + 1] = 1;for (int i = 1; i <= n; i++) {val[i] = nums[i - 1];}for (int i = n - 1; i >= 0; i--) {for (int j = i + 2; j <= n + 1; j++) {for (int k = i + 1; k < j; k++) {int sum = val[i] * val[k] * val[j];sum += rec[i][k] + rec[k][j];rec[i][j] = max(rec[i][j], sum);}}}return rec[0][n + 1];}
};

309.买卖股票的最佳时期含冷冻时期

dp神题了——分析状态——多个状态,那么和前面打家劫舍一样多维的dp就行

// f[i][0]: 手上持有股票的最大收益

// f[i][1]: 手上不持有股票,并且处于冷冻期中的累计最大收益

// f[i][2]: 手上不持有股票,并且不在冷冻期中的累计最大收益

class Solution {
public:int maxProfit(vector<int>& prices) {if(prices.size()==0)return 0;int k=prices.size();vector<vector<int>>dp(k,vector<int>(3));dp[0][0]=-1*prices[0];for(int i=1;i<k;i++){//持有股票,要么是i-1才买的,要么是之前买的dp[i][0]=max(dp[i-1][0],dp[i-1][2]-prices[i]);//不持有股票且冷冻,只能是前一天卖了dp[i][1]=dp[i-1][0]+prices[i];//不持有股票不冷冻,可能是前面卖了dp[i][2]=max(dp[i-1][1],dp[i-1][2]);}return max(dp[k-1][1],dp[k-1][2]);}
};

301.删除无效的括号

给你一个由若干括号和字母组成的字符串 s ,删除最小数量的无效括号,使得输入的字符串有效。

返回所有可能的结果。答案可以按 任意顺序 返回。

valid函数判断s有不有效(即左右括号是否相同),要求返回删最小数量括号后的有效集合,显然是宽度优先搜索!——对于每个括号,都给他删了看是不是有效的。

不错的题,想想思路。

class Solution {
public:bool isValid(string s) {int count = 0;for (char c : s) {if (c == '(') {count++;} else if (c == ')') {count--;if (count < 0) return false;}}return count == 0;}vector<string> removeInvalidParentheses(string s) {vector<string> res;queue<string> q;unordered_set<string> visited;q.push(s);visited.insert(s);bool found = false;while (!q.empty()) {int size = q.size();unordered_set<string> nextLevelSet;for (int i = 0; i < size; i++) {string cur = q.front(); q.pop();if (isValid(cur)) {res.push_back(cur);found = true;}if (found) continue;for (int j = 0; j < cur.size(); j++) {if (cur[j] != '(' && cur[j] != ')') continue;string nextStr = cur.substr(0, j) + cur.substr(j + 1);if (visited.find(nextStr) == visited.end()) {nextLevelSet.insert(nextStr);visited.insert(nextStr);}}}if (found) break;for (auto& str : nextLevelSet) {q.push(str);}}return res;}
};

300.最长递增子序列

给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。

子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。

最经典的一道dp题!dp[i][j]表示以i为始(i必选),到j的递增子序列的最长长度,(至少得确定选一个才有dp的必要),所以有:

class Solution {
public:int lengthOfLIS(vector<int>& nums) {int n = (int)nums.size();if (n == 0) {return 0;}vector<int> dp(n, 0);for (int i = 0; i < n; ++i) {dp[i] = 1;for (int j = 0; j < i; ++j) {if (nums[j] < nums[i]) {dp[i] = max(dp[i], dp[j] + 1);}}}return *max_element(dp.begin(), dp.end());}
};

但是显然,dp复杂度为n^2(题目n最大2500)

如何优化到nlogn?

需要记住一个点——dp的数组本身,其实是具有单调性的!

此题在acwing中亦有记载

287.寻找重复数

给定一个包含 n + 1 个整数的数组 nums ,其数字都在 [1, n] 范围内(包括 1 和 n),可知至少存在一个重复的整数。

假设 nums 只有 一个重复的整数 ,返回 这个重复的数 。

你设计的解决方案必须 不修改 数组 nums 且只用常量级 O(1) 的额外空间。

Floyd判圈算法——快慢指针

class Solution {
public:int findDuplicate(vector<int>& nums) {int slow = 0, fast = 0;// 第一阶段:寻找相遇点do {slow = nums[slow];         // 慢指针走一步fast = nums[nums[fast]];   // 快指针走两步} while (slow != fast);        // 持续直到相遇// 第二阶段:找到环入口(重复数)int slow2 = 0;while (slow != slow2) {slow = nums[slow];slow2 = nums[slow2];}return slow; // 返回重复数}
};

283.移动零

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。

请注意 ,必须在不复制数组的情况下原地对数组进行操作。

核心就是那个注意点,需要意识到,令一个变量po统计非零的个数,po的值肯定小于等于遍历时数组下标i(因为它只有数非0才++),这就意味着,我修改数组时,po的位置处已经被遍历了没有用了。

class Solution {
public:void moveZeroes(vector<int>& nums) {int po=0;int k=nums.size();for(int i=0;i<k;i++){if(nums[i]==0)continue;else{nums[po++]=nums[i];}}for(int i=po;i<k;i++)nums[i]=0;return;}
};

279.完全平方数

给你一个整数 n ,返回 和为 n 的完全平方数的最少数量 。

完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,149 和 16 都是完全平方数,而 3 和 11 不是。

从某种意义上讲,能用搜索搞的是不是都可以往dp想?

——dp的思想其实就是把一类子问题当成一个看待(或者说一个集合),这也是闫式dp的思想

——如果一个问题,可以由子问题组成,那么应该就可以用dp?

——所以先判断dp能不能用!即先看这个集合能不能被拆成更小的子集合!

——显然这里是可以的,即dp[i]=min(dp[i],dp[i-j]+dp[j]);

class Solution {
public:bool judge(int i){for(int z=0;z<=i;z++){if(z*z<i)continue;else if(z*z==i)return true;else if(z*z>i)break;}return false;}int numSquares(int n) {vector<int>dp(n+5,0);for(int i=0;i<=n;i++)dp[i]=i;dp[1]=1;dp[4]=1;for(int i=1;i<=n;i++){if(judge(i)){dp[i]=1;continue;}for(int j=1;j<i;j++){dp[i]=min(dp[i],dp[i-j]+dp[j]);}}for(int i=1;i<=n;i++)cout<<dp[i]<<" ";return dp[n];}
};

240.搜索二维矩阵2

编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性:

  • 每行的元素从左到右升序排列。
  • 每列的元素从上到下升序排列。

核心就一个点——从右上角看,构成是一个排序的二叉树!(看到题想到这个就行)

class Solution {
public:bool ans;bool trace(int x,int y,vector<vector<int>>& matrix,int target){int m=matrix.size();int n=matrix[0].size();if(y==-1||x==m){return false;}if(matrix[x][y]==target){ans=true;return true;}else if(matrix[x][y]<target){return trace(x+1,y,matrix,target);}else if(matrix[x][y]>target){return trace(x,y-1,matrix,target);}return false;}bool searchMatrix(vector<vector<int>>& matrix, int target) {ans=false;trace(0,matrix[0].size()-1,matrix,target);return ans; }
};

 22.括号生成

数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。

经典搜索框架了,搜索类题注意的就是边界条件!(一定小心)

class Solution {
public:vector<string>ans;void trace(int left,int right,int n,string&t){if(left>n||right>n)return;if(left==right&&left==n){ans.push_back(t);return;}if(left>right){t.push_back('(');trace(left+1,right,n,t);t.pop_back();t.push_back(')');;trace(left,right+1,n,t);t.pop_back();}else if(left==right){t.push_back('(');trace(left+1,right,n,t);t.pop_back();}else if(left<right)return;}vector<string> generateParenthesis(int n) {string temp="";trace(0,0,n,temp);return ans;}
};

49.字母异位词分组

给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。

输入: strs = ["eat", "tea", "tan", "ate", "nat", "bat"]

输出: [["bat"],["nat","tan"],["ate","eat","tea"]]

异位词的核心是找一个基准!——比如找升序排列(string可以sort cba变为abc)

相同的异位词sort后相等,就可以用map存储。

class Solution {
public:vector<vector<string>> groupAnagrams(vector<string>& strs) {unordered_map<string,vector<string>>mmap;for(auto str:strs){string t=str;sort(t.begin(),t.end());mmap[t].push_back(str);}vector<vector<string>>ans;for(auto it=mmap.begin();it!=mmap.end();it++){ans.push_back(it->second);}return ans;}
};

48.旋转图像

给定一个 × n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。

你必须在 原地 旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。

 核心:分层四元素交换!

遍历每边元素​​(除最后一个,避免重复):

  • 同时交换四个位置的元素(上→右→下→左→上)。
class Solution {
public:int n;void subrot(vector<vector<int>>& matrix, int z) {int len = n - 2 * z;  // 当前层边长for (int i = 0; i < len - 1; i++) {int temp = matrix[z][z + i];  // 保存上边// 左 → 上(左边从下往上取元素)matrix[z][z + i] = matrix[n - 1 - z - i][z];// 下 → 左(下边从右往左取元素)matrix[n - 1 - z - i][z] = matrix[n - 1 - z][n - 1 - z - i];// 右 → 下(右边从上往下取元素)matrix[n - 1 - z][n - 1 - z - i] = matrix[z + i][n - 1 - z];// 上 → 右(用保存的上边元素)matrix[z + i][n - 1 - z] = temp;}}void rotate(vector<vector<int>>& matrix) {n = matrix.size();// 逐层处理从外层(0)到内层(n/2 - 1)for (int i = 0; i < n / 2; i++) {subrot(matrix, i);}}
};

46.全排列 

给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。

同样是非常基础的搜索题,搜索题模板。明晰标记与回退

class Solution {
public:int book[15];vector<vector<int>>ans;vector<int>t;void trace(int step,vector<int>&nums){if(step==nums.size()){ans.push_back(t);return ;}for(int i=0;i<nums.size();i++){if(book[i])continue;else{book[i]=1;t.push_back(nums[i]);trace(step+1,nums);book[i]=0;t.pop_back();}}return;}vector<vector<int>> permute(vector<int>& nums) {trace(0,nums);return ans;}
};

42.接雨水

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

一种做法是一行一行来——显然是会超时的(时间复杂度m*n)

class Solution {
public:int trap(vector<int>& height) {int ans=0;int k=height.size()-1;int count=0;while(true){int l=-1;int r=k+1;for(int i=0;i<=k;i++){if(height[i]!=0){l=i;break;}}for(int i=k;i>=0;i--){if(height[i]!=0){r=i;break;}}if(l>=r||l==-1||r==k+1){return ans;}for(int i=l;i<=r;i++){if(height[i]==0)ans++;else if(height[i]>0)height[i]-=1;}}return ans;}
};

基本上,看到接雨水这类题,就得想到双指针!

  • 维护两个指针 left 和 right,以及两个变量 left_max 和 right_max 分别记录左右遍历时的最大高度。
  • 在每个位置,能接的雨水量取决于当前侧的最大高度与当前柱子高度之差。
  • 从左边的视角看整个数组,看过去;再从右边的视角看!
class Solution {
public:int trap(vector<int>& height) {int n=height.size();if(n<=2)return 0;int l=0;int r=n-1;int ans=0;int l_max=0;int r_max=0;while(l<r){//先把满足左边部分的求了if(height[l]<height[r]){if(height[l]>=l_max)l_max=height[l];else{//height[l]<l_max,height[l]<height[r]//即在前面部分有左边挡板l_max,后面有右边挡板,此时可以接ans+=l_max-height[l];}l++;}//右边指针的高度更小或相等//一样的,求右边部分的影响else{if(height[r]>=r_max)r_max=height[r];else ans+=r_max-height[r];r--;}}return ans;}
};

39.组合总和

给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。

candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。 

对于给定的输入,保证和为 target 的不同组合数少于 150 个。

这里不再是简单的搜索就行,还涉及到对结果的去重,套用前面的做法似乎不太现实:unordered_set 需要自定义哈希函数才能存储 vector<int>,没有的话会编译报错。

所以只能从根源入手——遍历时限制其实的位置,让它不要找以前的

(每次遍历时的i=start也保证了该元素的可重复性)

class Solution {
public:vector<vector<int>> combinationSum(vector<int>& candidates, int target) {vector<vector<int>> ans;vector<int> path;backtrack(0, target, candidates, path, ans);return ans;}void backtrack(int start, int target, vector<int>& candidates, vector<int>& path, vector<vector<int>>& ans) {if (target < 0) return;if (target == 0) {ans.push_back(path);return;}for (int i = start; i < candidates.size(); i++) {path.push_back(candidates[i]);backtrack(i, target - candidates[i], candidates, path, ans);path.pop_back();}}
};

543.二叉树的直径

给你一棵二叉树的根节点,返回该树的 直径 。

二叉树的 直径 是指树中任意两个节点之间最长路径的 长度 。这条路径可能经过也可能不经过根节点 root 。

两节点之间路径的 长度 由它们之间边数表示。

涉及数的递归时刻记住递归的意义!——trace表示子树的深度

class Solution {
public:int ans;int trace(TreeNode* root){if(root==nullptr)return 0;int le=trace(root->left);int ri=trace(root->right);ans=max(ans,le+ri);cout<<ans<<" ";return max(le,ri)+1;}int diameterOfBinaryTree(TreeNode* root) {ans=0;trace(root);return ans;}
};

34.在排序数组中查找元素的第一个和最后一个位置

给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。

如果数组中不存在目标值 target,返回 [-1, -1]

你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。

也是经典题了!——核心是考查对二分搜索的理解

class Solution {
public:vector<int> searchRange(vector<int>& nums, int target) {if(nums.empty())return {-1,-1};int le=0;int ri=nums.size()-1;while(le<ri){int mid=le+(ri-le)/2;if(target>nums[mid]){le=mid+1;}else if(target<=nums[mid]){ri=mid;     }}if(target!=nums[le])le=-1;int a=le;le=0;ri=nums.size()-1;while(le<ri){int mid=le+(ri-le+1)/2;if(target>=nums[mid]){le=mid;}else if(target<nums[mid]){ri=mid-1;}}if(target!=nums[le])le=-1;int b=le;return {a,b};}
};

无非就是两次搜索,一次确定左边,一次确定右边。如何使二分搜索按照左边界和按照右边界搜?

左边界——对左边动,即le+1  右边界,对右边动,即ri-1;

运用迭代来实现二分   然后注意确定边界通过while来,le>=ri时退出。

33.搜索旋转排序数组

整数数组 nums 按升序排列,数组中的值 互不相同 。

在传递给函数之前,nums 在预先未知的某个下标 k0 <= k < nums.length)上进行了 旋转,使数组变为 [nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2,4,5,6,7] 在下标 3 处经旋转后可能变为 [4,5,6,7,0,1,2] 。

给你 旋转后 的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的下标,否则返回 -1 。

你必须设计一个时间复杂度为 O(log n) 的算法解决此问题

本题的核心在于对顺序数组进行二分,所以要记住——与nums[0]进行判断,然后两次if判断都是针对每个部分的顺序数组做二分!

class Solution {
public:int search(vector<int>& nums, int target) {int k=nums.size();if(k==1){return nums[0]==target?0:-1;}int le=0;int ri=k-1;while(le<=ri){int mid=(le+ri)/2;if(nums[mid]==target)return mid;if(nums[mid]>=nums[0]){//高部分的顺序if(target<nums[mid]&&target>=nums[0]){ri=mid-1;}else le=mid+1;}else{//低部分的顺序if(target>nums[mid]&&target<=nums[k-1]){le=mid+1;}else ri=mid-1;}}return -1;}
};

31.下一个排列

整数数组的一个 排列  就是将其所有成员以序列或线性顺序排列。

  • 例如,arr = [1,2,3] ,以下这些都可以视作 arr 的排列:[1,2,3][1,3,2][3,1,2][2,3,1] 。

整数数组的 下一个排列 是指其整数的下一个字典序更大的排列。更正式地,如果数组的所有排列根据其字典顺序从小到大排列在一个容器中,那么数组的 下一个排列 就是在这个有序容器中排在它后面的那个排列。如果不存在下一个更大的排列,那么这个数组必须重排为字典序最小的排列(即,其元素按升序排列)。

  • 例如,arr = [1,2,3] 的下一个排列是 [1,3,2] 。
  • 类似地,arr = [2,3,1] 的下一个排列是 [3,1,2] 。
  • 而 arr = [3,2,1] 的下一个排列是 [1,2,3] ,因为 [3,2,1] 不存在一个字典序更大的排列。

给你一个整数数组 nums ,找出 nums 的下一个排列。

必须 原地 修改,只允许使用额外常数空间。

思路!

例如 2, 6, 3, 5, 4, 1 这个排列, 我们想要找到下一个刚好比他大的排列,于是可以从后往前看
我们先看后两位 4, 1 能否组成更大的排列,答案是不可以,同理 5, 4, 1也不可以
直到3, 5, 4, 1这个排列,因为 3 < 5, 我们可以通过重新排列这一段数字,来得到下一个排列
因为我们需要使得新的排列尽量小,所以我们从后往前找第一个比3更大的数字,发现是4
然后,我们调换3和4的位置,得到4, 5, 3, 1这个数列
因为我们需要使得新生成的数列尽量小,于是我们可以对5, 3, 1进行排序,可以发现在这个算法中,我们得到的末尾数字一定是倒序排列的,于是我们只需要把它反转即可
最终,我们得到了4, 1, 3, 5这个数列
完整的数列则是2, 6, 4, 1, 3, 5

class Solution {
public:void nextPermutation(vector<int>& nums) {int n = nums.size();int i = n - 2;// 从后向前找第一个升序的位置(即 nums[i] < nums[i+1])while (i >= 0 && nums[i] >= nums[i + 1]) {i--;}if (i >= 0) {int j = n - 1;// 从后向前找第一个大于 nums[i] 的数while (j > i && nums[j] <= nums[i]) {j--;}swap(nums[i], nums[j]);}// 反转 i 之后的部分(无论 i 是否找到)reverse(nums.begin() + i + 1, nums.end());}
};

再次凝练核心思路三部分:

  1. ​从右往左查找第一个升序位置​​:我们需要找到最右边的一个位置 i,使得 nums[i] < nums[i + 1]。这个位置 i 是可以增大的位置,因为其右侧的子数组已经是降序排列,无法再增大。
  2. ​从右往左查找第一个更大元素​​:如果找到了这样的位置 i,我们需要从右往左找到第一个比 nums[i] 大的元素 nums[j]
  3. ​交换这两个元素​​:交换 nums[i] 和 nums[j],这样可以确保当前排列尽可能小地增大。
  4. ​反转右侧子数组​​:由于 i 右侧的子数组原本是降序排列的,交换后仍然保持降序,因此反转该子数组可以使其变为升序,从而得到最小的下一个排列。

538.把二叉搜索树转换为累加树

给出二叉 搜索 树的根节点,该树的节点值各不相同,请你将其转换为累加树(Greater Sum Tree),使每个节点 node 的新值等于原树中大于或等于 node.val 的值之和。

使用反中序遍历(右-根-左)并维护一个全局累加和!

class Solution {
public:void traverse(TreeNode* root, int& sum) {if (!root) return;traverse(root->right, sum);sum += root->val;        // 累加当前节点的原始值root->val = sum;         // 更新当前节点为累加和traverse(root->left, sum);}TreeNode* convertBST(TreeNode* root) {int sum = 0;traverse(root, sum);return root;}
};

53.最大子数组

给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

子数组是数组中的一个连续部分。

经典错误代码:

class Solution {
public:int maxSubArray(vector<int>& nums) {int k=nums.size();int a=INT_MIN;int b=0;for(int i=0;i<k;i++){if(b>=0){b+=nums[i];a=max(a,b);cout<<b<<" ";}else if(b<0)b=0;}return a;}
};

在b<0时将b置为0就结束了,压根就没处理当前元素。

正确的实现——经典的Kadane算法!

class Solution {
public:int maxSubArray(vector<int>& nums) {int k = nums.size();int a = nums[0]; // 初始化为第一个元素(确保至少包含一个元素)int b = nums[0]; // 当前子数组和从第一个元素开始for (int i = 1; i < k; i++) {if (b > 0) {    // 相当于 b + nums[i] > nums[i]b += nums[i];} else {b = nums[i]; // 当前和小于等于0,重新开始}a = max(a, b);  // 更新全局最大和}return a;}
};

将更新放在最后统一更新,b小于0直接从当前开始。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/87009.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/87009.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MySQL常用函数性能优化及索引影响分析

MySQL 常用函数性能优化指南&#xff08;含索引影响分析&#xff09; 以下是 MySQL 函数使用指南&#xff0c;新增性能影响评级、索引失效分析和优化方案&#xff0c;帮助您高效使用函数&#xff1a; &#x1f4dc; 一、字符串处理函数&#xff08;含性能分析&#xff09; 函…

莫队(基础版)优雅的暴力

莫队算法是一种离线算法&#xff0c;常用于高效处理区间查询问题。它通过合理排序和移动左右端点来减少时间复杂度。 基本思想 莫队算法的核心思想是将所有查询离线排序&#xff01;&#xff01;&#xff08;找出一个过起来最快的查询顺序&#xff09;&#xff0c;然后通过移动…

✨ Python 高级定制 | 美化 Word 表格边框与样式(收货记录增强版)

之前我们完成了 Excel 数据提取、Word 表格写入与合并&#xff0c;现在继续 为 Word 表格添加高级样式 装扮&#xff0c;包括单元格边框、背景填色、居中对齐、粗体、高亮行/列等&#xff0c;进一步增强表格的可读性与专业性。 &#x1f58c;️ 样式设置函数 1. 设置单元格边框…

Clickhouse源码分析-TTL执行流程

第一种情况&#xff1a;无ttl_only_drop_parts配置 总体示例以及说明 如果没有ttl_only_drop_parts的配置&#xff0c;过期数据的删除&#xff08;这里是删除&#xff0c;是将过期的数据从这个part删除&#xff0c;并将过期的数据构成一个part&#xff0c;这个过期的part标记…

elementui修改radio字体的颜色和圆圈的样式

改完 <div class"choose"><el-radio-group v-model"radioNum"><el-radio label"1" size"large">Option 1</el-radio><el-radio label"2" size"large">Option 2</el-radio>&l…

力扣3381. 长度可被 K 整除的子数组的最大元素和

由于数据范围是2*10^5所以必然是遍历一次&#xff0c;子数组必定要用到前缀和&#xff0c;之前的题目中总是遇到的是子数组的和能不能被k整除&#xff0c;而这里不一样的是子数组的长度能不能被k整除&#xff0c;如果单纯的枚举长度必定超时&#xff0c;而看看题解得出的思路&a…

基于SSM的勤工助学系统的设计与实现

第1章 摘要 基于SSM框架的勤工助学系统旨在为学生、用工部门和管理员提供高效便捷的管理平台。系统包括学生端、用工部门端和管理员端&#xff0c;涵盖了从岗位发布、申请审核、工时记录、薪资管理到数据统计等完整的功能需求。 学生可以通过系统首页浏览最新的岗位信息和公告&…

2025年06月30日Github流行趋势

项目名称&#xff1a;twenty 项目地址 URL&#xff1a;https://github.com/twentyhq/twenty项目语言&#xff1a;TypeScript历史 star 数&#xff1a;31,774今日 star 数&#xff1a;1,002项目维护者&#xff1a;charlesBochet, lucasbordeau, FelixMalfait, Weiko, bosiraphae…

creo 2.0学习笔记

Creo软件从入门到精通——杜书森 1.1 Creo基本建模过程介绍 新建-零件-改名称-取消使用默认模板&#xff0c;是因为默认的是英制尺寸&#xff0c;自定义可选择mmns_part_solid&#xff0c;模板主要是设置模型的单位拉伸-选取FRONT-点击草绘视图&#xff0c;可进行草绘旋转——…

ZNS初步认识—GPT

1. ZNS SSD 的基本概念 Zoned Namespace (ZNS): ZNS 是一种新的NVMe接口规范&#xff0c;它将SSD的逻辑块地址空间划分为多个独立的、固定大小的“区域”&#xff08;Zones&#xff09;。区域 (Zone): ZNS SSD 的基本管理单元。每个区域都有自己的写入指针&#xff08;write p…

【seismic unix生成可执行文件-sh文件】

Shell脚本文件&#xff08;.sh文件&#xff09;简介 Shell脚本文件&#xff08;通常以.sh为扩展名&#xff09;是一种包含Shell命令的文本文件&#xff0c;用于在Unix/Linux系统中自动化执行任务。它由Shell解释器&#xff08;如Bash、Zsh等&#xff09;逐行执行&#xff0c;常…

Debezium日常分享系列之:在 Kubernetes 上部署 Debezium

Debezium日常分享系列之&#xff1a;在 Kubernetes 上部署 Debezium 先决条件步骤部署数据源 (MySQL)登录 MySQL db将数据插入其中部署 Kafka部署 kafdrop部署 Debezium 连接器创建 Debezium 连接器 Debezium 可以无缝部署在 Kubernetes&#xff08;一个用于容器编排的开源平台…

利润才是机器视觉企业的的“稳定器”,机器视觉企业的利润 = (规模经济 + 技术差异化 × 场景价值) - 竞争强度

影响机器视觉企业盈利能力的关键因素。这个公式本质上反映了行业的核心动态:利润来自成本控制(规模化效应)和差异化优势(技术壁垒与场景稀缺性的协同),但被市场竞争(内卷程度)所侵蚀。下面我将一步步拆解这个公式,结合机器视觉行业的特点(如工业自动化、质检、安防、…

EPLAN 中定制 自己的- A3 图框的详细指南(一)

EPLAN 中定制 BIEM - A3 图框的详细指南 在智能电气设计领域&#xff0c;图框作为图纸的重要组成部分&#xff0c;其定制的规范性和准确性至关重要。本文将以北京经济管理职业学院人工智能学院的相关任务为例&#xff0c;详细介绍在 EPLAN 软件中定制 BIEM - A3 图框的全过程…

macbook开发环境的配置记录

前言&#xff1a;好多东西不记录就会忘记 git ssh配置 当我们的没有配置git ssh的时候&#xff0c;使用ssh下载的时候会显示报错“make sure you have the correct access rights and respository exits" 如何解决&#xff0c;我们先在命令行检查检查一下用户名和邮箱是…

GitLab 18.1 高级 SAST 已支持 PHP,可升级体验!

GitLab 是一个全球知名的一体化 DevOps 平台&#xff0c;很多人都通过私有化部署 GitLab 来进行源代码托管。极狐GitLab 是 GitLab 在中国的发行版&#xff0c;专门为中国程序员服务。可以一键式部署极狐GitLab。 学习极狐GitLab 的相关资料&#xff1a; 极狐GitLab 官网极狐…

[学习]M-QAM的数学原理与调制解调原理详解(仿真示例)

M-QAM的数学原理与调制解调原理详解 QAM&#xff08;正交幅度调制&#xff09;作为现代数字通信的核心技术&#xff0c;其数学原理和实现方法值得深入探讨。本文将分为数学原理、调制解调原理和实现要点三个部分进行系统阐述。 文章目录 M-QAM的数学原理与调制解调原理详解一、…

图书管理系统练习项目源码-前后端分离-使用node.js来做后端开发

前端学习了这么久了&#xff0c;node.js 也有了一定的了解&#xff0c;知道使用node也可以来开发后端&#xff0c;今天给大家分享 使用node 来做后端&#xff0c;vue来写前端&#xff0c;做一个简单的图书管理系统。我们在刚开始学习编程的时候&#xff0c;需要自己写大量的项目…

【甲方安全视角】企业建设下的安全运营

文章目录 一、安全运营的概念与起源二、安全运营的职责与定位三、安全运营工程师的核心能力要求四、安全运营的典型场景与应对技巧1. 明确责任划分,避免“医生做保姆”2. 推动机制:自下而上 vs. 自上而下3. 宣传与内部影响力建设五、安全运营的战略意义六、为何需要安全原因在…

03认证原理自定义认证添加认证验证码

目录 大纲 一、自定义资源权限规则 二、自定义登录界面 三、自定义登录成功处理 四、显示登录失败信息 五、自定义登录失败处理 六、注销登录 七、登录用户数据获取 1. SecurityContextHolder 2. SecurityContextHolderStrategy 3. 代码中获取认证之后用户数据 4. 多…