1、不用加号的加法


思路:不能用算术运算符,因此考虑位运算来实现加法。

class Solution {
public:int add(int a, int b) {int sum=0;int carry = 0;while(b!=0)     // 当进位为0,说明计算结束{sum = a ^ b;  // 异或计算未进位部分carry = (uint32_t)(a & b) << 1; // 与计算进位部分,进位必须是无符号数a = sum;      // 保存未进位部分b = carry;   // 保存进位部分}return a;}
};

2、最长单词


思路:注意题目说的是,该单词由其他单词组合而成,因此不一定是两个单词组合,可能是多个单词组合,那么需要不断递归下去。如果当前字符串中长度为len的子串,出现在单词哈希表中,那么就去掉这一子串,接着递归剩余串能否在单词哈希表中找到。由于可能重复使用单词,因此单词哈希表不做删除。

class Solution {
public:bool iscompose(string word, unordered_set<string> & tmp){if(word.size()==0)      // 如果字符串为空,那肯定能组合{return true;}for(int i=1;i<=word.size();i++)      // 由于word中是被不知长度的若干子串组合,因此这里枚举子串的长度。{if(tmp.count(word.substr(0, i)) && iscompose(word.substr(i), tmp))  // 第一个判断条件是找到第一个子串,那么第二个条件就是递归剩余子串能否在哈希表中找到{return true;}}return false;}string longestWord(vector<string>& words) {if(words.size()==0){return "";}string ans="";unordered_set<string> mp(words.begin(), words.end());   // 构建单词哈希表for(int i=0;i<words.size();i++){string word = words[i];unordered_set<string> tmp = mp;tmp.erase(word);     // 自己不能组合自己,因此在哈希表中先删除自己if(iscompose(word, tmp))    // 如果在哈希表中发现,这个单词能够被组合,那就判断长度{if(word.size()>ans.size()){ans = word;}else if(word.size()==ans.size()){ans = min(word, ans);}}}return ans;}
};

3、计算器


思路: 这里没有要求括号,并且都是非负整数,因此可以直接遍历字符串,然后遇到运算符就处理,维护一个数字栈,将数字入栈.

class Solution {
public:void trim(string & s){int index = 0;if(!s.empty()){while( (index = s.find(' ',index)) != string::npos){s.erase(index,1);}}}stack<int> s_num;int calculate(string s) {trim(s);     // 去除字符串中的所有空格int num=0;char c = '+';  // 可以视为表达式: 0 + 表达式,这样不改变值,而第一个运算符是+for(int i=0;i<=s.size();i++)   // 这里可以取到i=s.size(),是因为字符串末尾是'\0',如果不遍历到最后的话,会漏掉最后一个运算数字{if(isdigit(s[i])){num = num*10 + (s[i] - '0');}else{if(c=='+'){      // 遇到+、-先不运算,直接入栈s_num.push(num);}else if(c=='-'){s_num.push(-num);}else if(c=='*'){     // 遇到*、/ 要运算完再入栈int tmp = s_num.top();s_num.pop();num *= tmp;s_num.push(num);}else{int tmp = s_num.top();s_num.pop();num = tmp / num;s_num.push(num);}num=0;c = s[i];}}int ans=0;while(s_num.size()){        // 栈里面的数字,直接相加,没有乘除法ans+=s_num.top();s_num.pop();}return ans;}
};

4、字母与数字


思路:数组只存放字母和数字,而要找子数组里面包含字母和数字的个数相同(不考虑字母、数字的长度),因此可以将数字看成1,字母看成-1,计算前缀和.
对于前缀和, 有两种情况:

  1. prefix[i]=0, 说明 0~i 的长度是包含字母和数字个数相同的子数组;
  2. 如果遇到prefix[i]==prefix[j] , 也就是相同前缀和, 那么意味着 i~j 这个区间内存在包含字母和数字个数相同的子数组.(因为相同前缀和, 意味着区间内数字有变大或变小, 但最终回到起始, 说明区间内不管怎么变, 总增量为0)
class Solution {
public:vector<string> findLongestSubarray(vector<string>& array) {int n=array.size();vector<int> prefix(n,0);unordered_map<int,int> M;   //key,left_indexint left=0,right=-1;for(int i=0;i<n;++i){   // 这里将数组改造为遇到字母标记-1, 遇到数字标记1char ch=array[i][0];if(ch>='A' && ch<='z') prefix[i]=-1;else prefix[i]=1;}// 计算前缀和for(int i=1;i<n;++i){prefix[i]+=prefix[i-1];}for(int i=0;i<n;++i){auto it=M.find(prefix[i]);if(prefix[i]==0){  // 如果前缀和为0, 那就比较当前满足题目的长度 和 下标到数组开始位置的长度 谁更长. 因此此时表示0~i 是满足题目的子数组.if(right-left+1 < i+1){right=i;left=0;}continue;}if(it==M.end()) M[prefix[i]]=i;  // 第一次遇到这个前缀和, 那就记录对应的下标else {if(right-left+1 < i-it->second){   // 不是第一次遇到这个前缀和, 那就将当前下标和最左端的下标 长度进行比较right=i;left=it->second+1;}}}// 给出结果vector<string> ans;for(int i=left;i<=right;++i) ans.push_back(array[i]);return ans;}
};

5、2出现的次数


思路:直接暴力肯定超时. 需要分别统计数字中每一位数字能出现2的次数,而在看每一位数字时,要考虑其前缀、后缀.

class Solution {
public:int numberOf2sInRange(int n) {long ans=0;long base = 1;    // 基底int prex = n/10;   // 前缀int lastx = n%10;   // 看每一位数字int post=0;    // 后缀while(n!=post)     //  当后缀和原来一样大时,说明已经遍历完了{if(lastx>2)    // 如果当前数字大于2, 那么2必然出现, 前缀有多大, 就会出现多少次2.   因此统计次数为: 0~pre, 共pre+1次, 再乘上基底.{ans+=(prex+1)*base;}else if(lastx==2){     // 如果刚好等于2, 那么不管数字出现多少, 都会被统计ans+= prex*base + post + 1;}else{ans+= prex * base;    // 如果小于2, 那么只考虑前缀出现的次数}post += lastx*base;lastx = prex%10;prex = prex/10;base*=10;}return ans;}
};

6、婴儿名字


思路: 用哈希表来统计名字和对应的次数, 但是这里有含义相同的名字, 需要进行归类. 这里采用并查集的思想, 每个名字都保留其父亲名字(实质上是含义相同且字典序最小), 这样每个名字都有对应, 记录在另一个哈希表中. 最后遍历名字和对应的次数即可.

class Solution {
public:unordered_map<string, int> name2num;unordered_map<string, string> parent;   // 保存string find(string s){if(parent.count(s)==0)return s;string root = find(parent[s]);parent[s] = root;return root;}void m_union(string s1, string s2){s1 = find(s1);s2 = find(s2);if(s1!=s2){if(s1<s2){parent[s2]=s1;}else{parent[s1]=s2;}}}vector<string> trulyMostPopular(vector<string>& names, vector<string>& synonyms) {for(auto name : synonyms){int pos = name.find(',');string n1 = name.substr(1, pos - 1);string n2 = name.substr(pos + 1, name.size() - pos - 2);m_union(n1,n2);}for(auto name : names){int pos = name.find('(');string nm = name.substr(0, pos);int ifre = stoi(name.substr(pos + 1, name.size() - pos - 2));name2num[find(nm)] += ifre;}vector<string> result;for (auto& name : name2num){string fre = to_string(name.second);result.push_back(name.first + "(" + fre + ")");}return result;}};

7、主要元素


思路: 超过一半的元素, 其实是众数. 可以通过投票法来找到众数, 但是这里可能不存在, 而投票法的前提是众数一定存在, 因此投票完后要再验证一次是否满足条件.

根据主要元素的定义,主要元素的出现次数大于其他元素的出现次数之和,因此在遍历过程中,主要元素和其他元素两两抵消,最后一定剩下至少一个主要元素,此时 candidate为主要元素,且 count≥1.

class Solution {
public:int majorityElement(vector<int>& nums) {int piao=0;int pepole = nums[0];for(int i=0;i<nums.size();i++){if(piao==0){pepole = nums[i];}if(pepole!=nums[i]){piao--;}else{piao++;}}int no=0;for(int i=0;i<nums.size();i++)    // 再验证一次是否为众数{if(nums[i]==pepole){no++;}}if(no>nums.size()/2){return pepole;}else{return -1;}}
};

8、拿出最少的魔法豆(第280场周赛,超时)


思路: (1) 由于只拿出豆子, 不放回豆子, 因此数量必然是减少的. 又因为剩余非空袋子的豆子数量相同, 因此拿走豆子数量=总和-每袋豆子数量*袋数.
(2) 要使拿掉豆子的数量最少, 那先对数组排序, 从最少的豆子开始拿.
(3) 遍历数组, 考虑第 i 个位置, 基于(2), 0 ~ i-1 位置的豆子都要拿完, 而后面的袋子豆子数需要都等于x. 那这个x 最大值只能取beans[i], 如果比当前位置的豆子更多, 那么当前位置不能算入袋子, 必须拿空, 这样豆子被拿掉的数量更多了.

class Solution {
public:long long minimumRemoval(vector<int>& beans) {if(beans.size()==1){return 0;}int len = beans.size();sort(beans.begin(), beans.end());long long sum=0;for(int i=0;i<len;i++){sum+=beans[i];     // 计算所有豆子总数} long long ans=LLONG_MAX;for(int i=0;i<len;i++){long long res = (long long)beans[i]*(len-i);    // 计算当前位置及后面位置的袋子, 都以beans[i]为豆子数, 所剩余的豆子总数.ans = min(ans, sum - res);   //  sum - res 表示被拿掉的豆子数量}return ans;}
};

9、马戏团人塔(超时)


思路: 第一次用动态规划解,超时了. 看了题解, 用贪心 + 二分来做.

class Solution {
public:int bestSeqAtIndex(vector<int>& height, vector<int>& weight) {if(height.size()==0){return 0;}vector<pair<int, int> > man;for(int i=0;i<height.size();i++){man.push_back(make_pair(height[i], weight[i]));}//身高升序, 相同身高体重降序. 这样的话保证在数组中, 后面的身高必然大于前面的,可以直接取; 而当身高相同的话, 体重更小的人应该用来替换, 体现了贪心,能够使数组尽可能的长.sort(man.begin(), man.end(),[](const pair<int, int> & a, const pair<int, int> &b){if(a.first==b.first){return a.second>b.second;}else{return a.first<b.first;}});vector<int> res;res.push_back(man[0].second);   // 第一个人的体重可以直接加入for(int i=1;i<height.size();i++){if(man[i].second > res.back())     // 如果当前的体重比数组末尾的人更大, 可以直接加入, 满足条件(因为身高是升序, 必然满足){res.push_back(man[i].second);}else{     // 如果当前体重更小, 而之前排序是按身高升序, 身高相同再体重降序, 体重更小说明身高相同. 那就从数组中找到恰好大于等于当前体重的人(此人身高和当前的身高相同), 进行体重的替换(换上了体重更小的人). 这样体现了贪心思想, 使体重更紧凑, 可以尽可能加入更多的人.int index = lower_bound(res.begin(), res.end(), man[i].second) - res.begin();res[index] = man[i].second;}}return res.size();}
};

10、骑士在棋盘上的概率(dfs超时)



思路: 定义dp[step][i][j]表示从(i, j)出发走了step步后还停留在棋盘上的概率。当(i,j)不在棋盘上时,dp[step][i][j]=0;当(i,j)在棋盘上且step=0时,dp[step][i][j]=1。而其他情况,dp[step][i][j] += dp[step - 1][ni][nj] / 8,是由上一步的8种情况统计得到概率。

class Solution {
public:vector<vector<int>> dirs = {{-2, -1}, {-2, 1}, {2, -1}, {2, 1}, {-1, -2}, {-1, 2}, {1, -2}, {1, 2}};double knightProbability(int n, int k, int row, int column) {vector<vector<vector<double>>> dp(k + 1, vector<vector<double>>(n, vector<double>(n)));for (int step = 0; step <= k; step++) {for (int i = 0; i < n; i++) {for (int j = 0; j < n; j++) {if (step == 0) {dp[step][i][j] = 1;} else {for (auto & dir : dirs) {int ni = i + dir[0], nj = j + dir[1];if (ni >= 0 && ni < n && nj >= 0 && nj < n) {dp[step][i][j] += dp[step - 1][ni][nj] / 8;}}}}}}return dp[k][row][column];}
};

11、最短超串(不会)


思路:遇到满足什么什么条件的连续区间问题,可以考虑用滑动窗口来解决。
滑动窗口的解题步骤:
1)先初始化l=0,r=0。
2)然后不断将右指针右移进行遍历,此时滑动窗口相应发生变化。
3)当区间满足条件后,再将左指针右移进行收缩区间,最终找到最短的满足条件的连续区间。

class Solution {
public:vector<int> shortestSeq(vector<int>& big, vector<int>& small) {unordered_map<int, int> need;for(int i=0;i<small.size();i++){need[small[i]]++;      // 统计所要找的这个数组中,每个数字以及出现的次数}int diff=small.size();   // 表示所要找到的数字总数int l=0, r=0;    // 定义左、右指针int min_len = INT_MAX;vector<int> res;for(;r<big.size();r++)   // 先将右指针不断右移{if(need.find(big[r])!=need.end() && --need[big[r]]>=0)   // 如果当前右指针的数字是small中需要的数字,并且该数字的出现次数还有多余,则diff--,表示要找的数字数量变少了{diff--;}while(diff==0)    // diff==0 表示数字都被滑动窗口找到了,即当前窗口满足条件{if(r - l < min_len)    // 比较窗口大小{min_len = r - l;res = {l, r};}if(need.find(big[l])!=need.end() && ++need[big[l]]>0)      // 如果左指针的数字是small中的数字,并且假设左指针右移后该数字的出现次数>0了,说明当前窗口已经不再满足条件,因为漏了一个数字出去。因此diff++,表示要找的数字数量变多了。{diff++;}l++;    // 将左指针右移,进行区间的收缩}}return res;}
};

12、煎饼排序(不会)


思路:从arr.size()大小开始遍历,每次找当前数组里的最大值,然后通过两次翻转将最大值放到当前数组的尾部;而随着数组长度的缩减,每次都能将最大值排到末尾,最后当数组长度=1时,已经有序了。

class Solution {
public:vector<int> pancakeSort(vector<int>& arr) {vector<int> ans;int len = arr.size();for(int i=len;i>1;i--){int index = max_element(arr.begin(), arr.begin()+i) - arr.begin();   // 找当前长度为 i 的情况下,数组最大值if(index==i-1)   // 如果最大值的索引已经在尾部,那就不用动{continue;}reverse(arr.begin(), arr.begin()+index+1);    // 进行这样两次翻转reverse(arr.begin(), arr.begin() + i);ans.push_back(index+1);ans.push_back(i);}return ans;}
};

13、最大子矩阵(不会)


思路:实际上是最长子序列和的二维版本。详情可看题解。

class Solution {
public:vector<int> getMaxMatrix(vector<vector<int>>& matrix) {int m = matrix.size();int n = matrix[0].size();vector<int> b(n, 0);      // 记录的是,矩形中每一列的元素和。这样就把求二维矩阵的和,转化成求b的子序列和。vector<int> ans(4, 0);int max_sum = INT_MIN;int ans_r1=0;int ans_c1=0;int sum=0;for(int i=0;i<m;i++)      // 定义矩形上界{// 每次清空b数组,因为上界变了,矩形也变了for(int t=0;t<n;t++){b[t]=0;}for(int j=i;j<m;j++)    // 定义矩形下界,下界不断向下拓展,意味着矩形的高在增加{sum=0;for(int k=0;k<n;k++)    // 遍历每一列{b[k]+=matrix[j][k];  if(sum>0){sum+=b[k];}else{sum = b[k];ans_r1 = i;ans_c1 = k;}if(sum>max_sum){max_sum = sum;ans[0] = ans_r1;ans[1] = ans_c1;ans[2] = j;ans[3] = k;}}}}return ans;}
};

14、元素和为目标值的子矩阵数量


思路:这题和上一题可以采用相同的降维方法,定义每一列的和。然后进行全局遍历,只要和等于目标值就++。思路比较简单。

class Solution {
public:int numSubmatrixSumTarget(vector<vector<int>>& matrix, int target) {int ans=0;int sum=0;int m = matrix.size();int n = matrix[0].size();vector<int> b(n, 0);  // 记录每一列的和for(int i=0;i<m;i++){for(int j=0;j<n;j++)    // 每次枚举上边界,就要把b清空{b[j]=0;}for(int k=i;k<m;k++)   // 枚举下边界{for(int j=0;j<n;j++)   {b[j]+=matrix[k][j];}for(int p=0;p<n;p++)   // 这里就判断,一维数组b中,有多少子序列和为target即可{sum=0;for(int q=p;q<n;q++){sum+=b[q];if(sum==target){ans++;}}}}}return ans;}
};

15、二叉搜索树序列


思路:其实这题意思是要遍历二叉搜索树的所有可能性,

以这个为例:
1)路径的第一个元素必然是根节点12,而下一个元素的选择必然是5或19,和顺序无关;
2)假设选了5,当前路径为【12,5】,那么接下来可以选的是2、9、19,也同样和顺序无关;
3)后续同理,直到没有可选的节点了,就是一个完整的路径。
因此这里是回溯的做法,可以定义一个队列来保存之后可选择的节点,如果队列为空意味着没得选,那就路径完成。
而在(1)中,给出了5、19两种选择,因此假设当前选了5进入下一层递归,那么当递归结束返回时(准备选19),要将5再加入队列,此时路径也要去掉最后一个元素(也就是5),这样就把5留在下一层递归中去选择。

/*** Definition for a binary tree node.* struct TreeNode {*     int val;*     TreeNode *left;*     TreeNode *right;*     TreeNode(int x) : val(x), left(NULL), right(NULL) {}* };*/
class Solution {
public:vector<vector<int> > ans;vector<int> path;   // 保存当前路径void dfs(deque<TreeNode*>& q){if(q.empty())    // 已经没得选了,那就已经得到了完整路径{ans.push_back(path);return;}for(int i=0;i<q.size();i++)   // 还有的选,那就依次遍历可选节点{TreeNode* node = q.front();q.pop_front();path.push_back(node->val);   // 选了当前节点,然后下面分别把左右孩子入队(这是下一层递归中的可选节点)if(node->left){q.push_back(node->left);}if(node->right){q.push_back(node->right);}dfs(q);   // 进行下一层递归// 递归结束,这时候要消除当前节点的影响,就剔除左右孩子入队的情况if(node->left){q.pop_back();}if(node->right){q.pop_back();}q.push_back(node);     // 当前节点要再次入队,例如原来是【5,19】,现在递归选过5后,要变成【19,5】,下次选19path.pop_back();    // 同时路径也要剔除5}}vector<vector<int>> BSTSequences(TreeNode* root) {if(root==NULL){return {{}};}deque<TreeNode*> q;   // 定义双端队列,来保留下一个候选的节点q.push_back(root);   // 第一个候选节点肯定是根节点dfs(q);return ans;}
};

回溯模版:

void backtracking(参数) {if (终止条件) {存放结果;return;}for (选择:本层集合中元素) {处理结点;backtracking(路径,选择列表)); // 递归回溯,撤销处理结果;}
}作者:dong-men
链接:https://leetcode-cn.com/problems/bst-sequences-lcci/solution/pei-tu-hui-su-mo-ban-xiang-xi-zhu-shi-by-dong-men/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

16、布尔运算(不会)


思路:区间dp的做法,定义一个三维的dp数组。
dp[i][j][0]代表第i个字符到第j个字符,result=0的可能性个数。
dp[i][j][1]代表第i个字符到第j个字符,result=1的可能性个数。

class Solution {
public:int countEval(string s, int result) {int n = s.size();vector<vector<vector<int> > > dp(n, vector<vector<int> > (n, vector<int>(2,0)));// 初始化,只有一个字母的情况for(int i=0;i<n;i+=2){int tmp=0;if(s[i]=='1')tmp=1;dp[i][i][0] = 1-tmp;dp[i][i][1] = tmp;}// 这里step表示一个块的长度-1。  例如有一个块 1&1,那么step=2。for(int step=0;step<n;step+=2)    // 枚举所有可能出现的块大小{for(int i=0;i+step<n;i+=2)       // i 表示这个块的起始位置,一定是数字{for(int j=i+1;j<i+step;j+=2)   // j 表示符号位,因此一开始是 i+1,也是+=2遍历{// 以遍历的符号 j 为界,可以分成左右两边,对左右两边进行联合dp推//step = 6时为例 假设此时块为0&1|1^0//i = 0,j = 1就把快分为(0)和(1|1^0)两部分进行dp的递推//i = 0,j = 3就把块分为(0&1)和 (1^0)两部分部分进行dp的递推//i = 0,j = 5就把块分为(0&1|1)和 (0)两部分部分进行dp的递推int left0 = dp[i][j-1][0];int left1 = dp[i][j-1][1];int right0 = dp[j+1][i+step][0];int right1 = dp[j+1][i+step][1];if(s[j]=='&')  {dp[i][i+step][0]+=left0*(right0+right1) + left1*right0;   // 要想这个块的结果为0,那必须左=0(就不管右边是多少),或者左边=1,右边必须=0dp[i][i+step][1]+=left1*right1; }else if(s[j]=='|'){dp[i][i+step][0]+=left0*right0;dp[i][i+step][1]+=left1*(right0+right1)+left0*right1;}else if(s[j]=='^'){dp[i][i+step][0]+=left0*right0+left1*right1;dp[i][i+step][1]+=left0*right1+left1*right0;}}}}return dp[0][n-1][result];}
};

17、最大黑方阵(只会暴力枚举)


思路:动态规划,cnt[r][c][0/1]分别保存(r,c)右侧、下侧连续的黑色像素的个数。

class Solution {
public:vector<int> findSquare(vector<vector<int>>& matrix) {vector<int> ans(3, 0);int n = matrix.size();if(n == 0) return {};if(n == 1){if(matrix[0][0] == 0)return {0, 0, 1};elsereturn {};}//cnt[r][c][0/1],0右侧,1下侧vector<vector<vector<int>>> cnt(n, vector<vector<int>>(n, vector<int>(2)));for(int r = n-1; r >= 0; r--){      // 要从方阵右下角开始遍历,因为这样才能让索引小的cnt保存到后面的值for(int c = n-1; c >= 0; c--){if(matrix[r][c] == 1)cnt[r][c][0] = cnt[r][c][1] = 0;else{//统计cnt[r][c][0/1]if(r < n-1) cnt[r][c][1] = cnt[r+1][c][1] + 1;else cnt[r][c][1] = 1;if(c < n-1) cnt[r][c][0] = cnt[r][c+1][0] + 1;else cnt[r][c][0] = 1;//更新当前最大子方阵int len = min(cnt[r][c][0], cnt[r][c][1]);//最大的可能的边长,要取短边,不然不能构成方阵while(len >= ans[2]){//要答案r,c最小,所以带等号if(cnt[r+len-1][c][0] >= len && cnt[r][c+len-1][1] >= len){   // 再看看另外两条边是否满足长度,注意题目只要求4条边均为黑色,而不是整个方阵都是黑色//可以构成长为len的方阵ans = {r, c, len};break;}len--;}}}}return ans;}
};

18、三数之和(超时)


思路:基本思路是三重循环,而题目要求不重复的三元组,因此要考虑去重。
1)对数组先排序,这样就避免获得重复的三元组,要保证a<=b<=c。
2)排序后,相邻元素可能是相同的,这也要避免。
例如-1,0,1,1
先选了第一个1,但是下次遍历可能又选到第二个1,组成了相同的三元组。

// 伪代码
nums.sort()
for first = 0 .. n-1// 只有和上一次枚举的元素不相同,我们才会进行枚举if first == 0 or nums[first] != nums[first-1] thenfor second = first+1 .. n-1if second == first+1 or nums[second] != nums[second-1] thenfor third = second+1 .. n-1if third == second+1 or nums[third] != nums[third-1] then// 判断是否有 a+b+c==0check(first, second, third)作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/3sum/solution/san-shu-zhi-he-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

3)在从小到大遍历b的时候,要从大到小遍历c。因为假设a+b1+c1=0,那下一个b2>b1,因此若存在a+b2+c2=0,c2一定小于c1,也就是左侧。

class Solution {
public:vector<vector<int>> threeSum(vector<int>& nums) {int n = nums.size();sort(nums.begin(), nums.end());vector<vector<int>> ans;// 枚举 afor (int first = 0; first < n; ++first) {// 需要和上一次枚举的数不相同if (first > 0 && nums[first] == nums[first - 1]) {continue;}// c 对应的指针初始指向数组的最右端int third = n - 1;int target = -nums[first];    // 此时题目变为了两数之和=target,可以用双指针来使时间复杂度从O(n2)降为O(n)// 枚举 bfor (int second = first + 1; second < n; ++second) {// 需要和上一次枚举的数不相同if (second > first + 1 && nums[second] == nums[second - 1]) {continue;}// 需要保证 b 的指针在 c 的指针的左侧while (second < third && nums[second] + nums[third] > target) {--third;}// 如果指针重合,随着 b 后续的增加// 就不会有满足 a+b+c=0 并且 b<c 的 c 了,可以退出循环if (second == third) {break;}if (nums[second] + nums[third] == target) {ans.push_back({nums[first], nums[second], nums[third]});}}}return ans;}
};作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/3sum/solution/san-shu-zhi-he-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

19、组合


思路:有1~n个数,要选其中k个数。其实就是回溯的思维,遍历每个数的时候有选、不选两种情况,进行分别的回溯即可。

class Solution {
public:vector<vector<int> > ans;vector<int> tmp;void dfs(int cur, int n, int k){// 剪枝。当前已经遍历到cur位置,如果已有的数量tmp.size + 剩余的数字数量,还小于k。说明就算把剩余的数字全选上也不能满足k个数字的要求,因此可以提前返回。if(tmp.size() + (n - cur + 1) < k){return;}if(tmp.size()==k){ans.push_back(tmp);return;}// 选取当前的curtmp.push_back(cur);dfs(cur+1, n, k);// 回溯状态tmp.pop_back();// 不选当前的cur,那就直接进入下一个状态cur+1dfs(cur+1, n, k);}vector<vector<int>> combine(int n, int k) {dfs(1, n, k);return ans;}
};

20、下一个排列 (不会)


思路:题目要找下一个序列,这个序列要是字典序刚好更大一点的序列,如果当前序列已经是字典序最大序列(降序排列),那么下一个序列是字典序最小的序列(升序排列)。
1、首先要从后往前遍历,找到第一个i 且 nums[i]<nums[i+1]。此时i+1 ~ n-1 的序列都满足降序排列。
2、再从后往前遍历,找到第一个j 且 nums[j]>nums[i]。交换 i 和 j ,再将i+1~n-1的序列进行从小到大排列(其实就是翻转,因为有第1步可知,后面都是降序的,这样可以满足使序列变化最小),得到满足题目的下一个序列。

class Solution {
public:void nextPermutation(vector<int>& nums) {int i=nums.size()-2;while(i>=0 && nums[i]>=nums[i+1]){i--;}if(i>=0){int j=nums.size()-1;while(j>=0 && nums[j]<=nums[i]){j--;}swap(nums[i], nums[j]);   // 找到比 i 更大的第一个数,交换之后,整个序列比原始序列更大了reverse(nums.begin()+i+1, nums.end());  // 使后面从小到大排列,使序列变化幅度最小}else{   // 说明此时是完全倒序(字典序最大),直接翻转整个数组即得到字典序最小的序列reverse(nums.begin(), nums.end());}}
};

21、Z字形变换


思路:直接模拟可行,就是比较复杂。而题解中有一种巧妙的思路,用一个标志位来判断当前遍历是从上往下,还是从下往上。

class Solution {
public:string convert(string s, int numRows) {if(numRows<2){return s;}vector<string> vec(numRows, "");   // 定义每一行的字符串int i=0; int flag=-1;    // 正向取、反向取的标记,控制了Z字形读取的顺序for(int k=0;k<s.size();k++){vec[i]+=s[k];if(i==0||i==numRows-1)   // 遇到i==0,此时应该从上往下;反之是从下往上{flag = -flag;}i+=flag;}string ans="";for(auto str: vec){ans+=str;}return ans;}
};

22、适合打劫银行的日子


思路:这种前后都要判断的,可以先遍历数组,把前后的情况都保存下来(这里是动态规划预处理),再进行比较。设left数组,left[i]表示i之前非递减的天数;设right数组同理。

class Solution {public:vector<int> goodDaysToRobBank(vector<int>& security, int time) {int n = security.size();vector<int> left(n, 0);vector<int> right(n, 0);for(int i=1;i<n-1;i++){if(security[i]<=security[i-1]){left[i] = left[i-1]+1;}if(security[n-i-1]<=security[n-i]){right[n-i-1] = right[n-i]+1;}}vector<int> ans;for(int i=time;i<n-time;i++){if(left[i]>=time && right[i]>=time){ans.push_back(i);}}return ans;}
};

23、最长回文子串


思路1 : 中心扩展的方法,遍历每个位置,然后以该位置为回文子串的中心点向左右两边扩展,直到不构成回文串为止。

class Solution {public:pair<int, int> expand(string s, int left, int right){while(left>=0&&right<s.size() && s[left]==s[right]){left--;right++;}return {left+1, right-1};}string longestPalindrome(string s) {int start=0;int end=0;int max_len=1;for(int i=0;i<s.size();i++){auto [left1, right1] = expand(s, i, i);  // 中心点可能是单独的auto [left2, right2] = expand(s, i, i+1);  // 中心点可能是两个点  如 abbaif(right1 - left1 + 1>max_len){start = left1;end = right1;max_len = right1 - left1+1;}if(right2 - left2 + 1>max_len){start = left2;end = right2;max_len = right2 - left2+1;}}return s.substr(start, max_len);}
};

思路2: 动态规划,设dp[i][j]表示i~j内是回文子串。

class Solution {public:string longestPalindrome(string s) {int n = s.size();if(n<2){return s;}vector<vector<int> > dp(n, vector<int>(n, 0));for(int i=0;i<n;i++){dp[i][i]=1;    // 单个字符当然能构成回文}int start=0;int max_len = 1;for(int len=2;len<=n;len++)   // 回文子串的长度从2开始{for(int i=0;i<n;i++)   // 枚举左边界{int j = i+len-1;  // 右边界if(j>=n)   // 超出边界{break;}if(s[i]!=s[j])   // 左右边界不相等,那么肯定不是回文{dp[i][j]=0;}else{if(j-i<3)     // 当长度不超过3,说明是回文{dp[i][j]=1;}else{         // 超过3,要取决于内部是不是回文dp[i][j]=dp[i+1][j-1];}}if(dp[i][j] && j-i+1>max_len){max_len = j-i+1;start=i;}}}return s.substr(start, max_len);}
};

24、

Leetcode刷题总结(三)相关推荐

  1. leetcode刷题(三)——容斥原理

    leetcode刷题系列三.这一节的内容主要是容斥原理的题目和题解. 百度百科上容斥原理的解释: 在计数时,必须注意没有重复,没有遗漏.为了使重叠部分不被重复计算,人们研究出一种新的计数方法,这种方法 ...

  2. leetcode刷题 15.三数之和

    分析: 1.特判,对于数组长度 nn,如果数组为 null 或者数组长度小于 33,返回 []. 2.对数组进行排序. 3.遍历排序后数组: 若 nums[i]>0:因为已经排序好,所以后面不可 ...

  3. leetcode刷题:三数之和

    题目: 分析: 这是最容易想到的做法,但是有明显的问题,时间复杂度达到0(n3),并且没有去重. class Solution { public:vector<vector<int> ...

  4. LeetCode刷题笔记- 15.三数之和

    LeetCode刷题笔记- 15.三数之和 C语言 题目 注意点 C语言 /*** Return an array of arrays of size *returnSize.* The sizes ...

  5. leetcode刷题之树(三)

    翻转二叉树,感觉做二叉树的问题好像都是那一套公式,除了个别的问题解决不了,用上篇博客leetcode刷题之树(二)的模型基本可以解决.总体来说就是树基本都可以利用递归和迭代的方法去解决,在涉及到树的遍 ...

  6. LeetCode刷题记录7——824. Goat Latin(easy)

    LeetCode刷题记录7--824. Goat Latin(easy) 目录 LeetCode刷题记录7--824. Goat Latin(easy) 题目 语言 思路 后记 题目 题目需要将一个输 ...

  7. LeetCode刷题记录5——441. Arranging Coins(easy)

    LeetCode刷题记录5--441. Arranging Coins(easy) 目录 LeetCode刷题记录5--441. Arranging Coins(easy) 题目 语言 思路 后记 题 ...

  8. LeetCode刷题记录1——717. 1-bit and 2-bit Characters(easy)

    LeetCode刷题记录1--717. 1-bit and 2-bit Characters(easy) LeetCode刷题记录1--717. 1-bit and 2-bit Characters( ...

  9. Leetcode刷题 463题:岛屿的周长(基于Java语言)

    ** Leetcode刷题 463题:岛屿的周长(基于Java语言) ** 一. 题目描述: 给定一个包含 0 和 1 的二维网格地图,其中 1 表示陆地 0 表示水域. 网格中的格子水平和垂直方向相 ...

  10. c语言贪心算法合并箭,LeetCode刷题题库:贪心算法

    LeetCode刷题笔记:贪心算法 自大学开始,我便陆陆续续的学习一些 算法和数据结构 方面的内容,同时也开始在一些平台刷题,也会参加一些大大小小的算法竞赛.但是平时刷题缺少目的性.系统性,最终导致算 ...

最新文章

  1. 安卓学习-性能最佳实战
  2. raid5数据恢复方法,服务器磁盘阵列数据恢复成功案例
  3. 软件体系架构阅读笔记一
  4. 应用存储和持久化数据卷:核心知识
  5. 山水风景照数据集_空寂灵动 -- 李良山水画
  6. 如何调试bash脚本
  7. wxWidgets:wxCalculateLayoutEvent类用法
  8. cpp之间函数引用和类引用的方法
  9. 关于Windows平台下安装mysql软件
  10. 后台接收数组_微信小程序如何与后台api接口进行数据交互(微信报修小程序源码讲解七)...
  11. [pytorch、学习] - 5.9 含并行连结的网络(GoogLeNet)
  12. P4231-三步必杀【差分】
  13. 数据结构与算法 完整版单链表(附GIF)
  14. 安装Windows 和linux双系统失败导致Windows无法引导的解决方法
  15. hdoj1290切球形蛋糕(递推和划分问题)
  16. ibm服务器aix系统查看cpu,IBM AIX系统硬件信息查看命令(shell脚本)
  17. 巧用主力进出、主力买卖指标进行波段操作——逃顶和抄底
  18. 二建 机电工程常用材料及工程设备
  19. c语言中max的用法
  20. for in在python中什么意思_python中for in的用法详解

热门文章

  1. iOS开发 tabbar自定义转场动画
  2. DE-MerTOX™ 重金属排毒剂-道格拉斯实验室
  3. hbase写入一段时间后变的越来越慢
  4. 《Java核心技术 卷Ⅰ》读书笔记一
  5. Java核心技术 卷1-总结-18
  6. 火山视窗文本数组类增删查改操作
  7. 【数据库管理】数据库自动维护任务介绍
  8. 隐形的翅膀怎么用计算机弹出来,《隐形的翅膀》原版吉他谱分享,用音阶指法弹简谱其实很简单 ... ......
  9. 基于ssm的校园二手物品交换系统
  10. iOS开发-审核被拒原因总结[持续更新]