/*
2.区间DP 时间复杂度和空间复杂度都是O(n^2)
*/
class Solution {
public:
string longestPalindrome(string s) {
int n = s.size();
bool dp[1001][1001];//dp[i][j]表示(i,j)是否为回文子串
string ans;
for(int len = 0; len < n; ++len){//len表示j与i相差几个元素
for(int i = 0; i+len < n; ++i){
int j = i + len;
dp[i][j] = s[i]==s[j];
if(len > 1)//len==0或1表示区间只有一个或两个元素,严格大于1表示区间至少有三个元素
dp[i][j] = dp[i+1][j-1] && dp[i][j];
if(dp[i][j] && len+1 > ans.size())
ans = s.substr(i,len+1);
}
}
return ans;
}
};
/*
左右两半边子串相等,左边子串正序哈希值和右边子串倒序哈希值相等
回文串两大类,长度分为奇数和偶数
先看奇数类,枚举中心点,二分求出当前中心点的最大半径
对原来字符串变形,使得所有回文串长度都是奇数,在每两个字母之间加上一个没出现的字符即可
*/
#include <iostream>
#include <string.h>
using namespace std;
typedef unsigned long long ULL;
const int N = 2000010, base = 131;
char str[N];
ULL hl[N], hr[N], p[N];//正序逆序所有字符串的哈希值
ULL get(ULL h[], int l, int r)
{//求子串哈希值
return h[r] - h[l - 1] * p[r - l + 1];
}
int main()
{
int T = 1;
while (scanf("%s", str + 1), strcmp(str + 1, "END"))
{
int n = strlen(str + 1);
for (int i = n * 2; i; i -= 2)
{
str[i] = str[i / 2];
str[i - 1] = 'a' + 26;
}
n *= 2;
p[0] = 1;
for (int i = 1, j = n; i <= n; i ++, j -- )
{//预处理正序,逆序所有前缀哈希值和次方数组
hl[i] = hl[i - 1] * base + str[i] ;
hr[i] = hr[i - 1] * base + str[j] ;
p[i] = p[i - 1] * base;
}
int res = 0, k=0;
//char str_res[N];
for (int i = 1; i <= n; i ++ )
{//枚举中点,二分半径
int l = 0, r = min(i - 1, n - i);
while (l < r)
{
int mid = l + r + 1 >> 1;
//下标,正序第i - mid到第i - 1个,倒数第 n - (i + mid) + 1到第n - (i + 1) + 1)个
if (get(hl, i - mid, i - 1) == get(hr, n - (i + mid) + 1, n - (i + 1) + 1)) l = mid;
else r= mid-1;
}
//这样求出来是包括*的长度 整个回文子串要么是*比字母多一个,要么是字母比*多一个
if (str[i - l] <= 'z') res = max(res, l + 1);//如果边界是字母,说明字母多一个
else res = max(res, l);
// if (str[i - l] <= 'z') {
// if (l+1>res) res=l+1,k=i;
// }
// else{
// if (l>res) res=l,k=i;
// }
}
// for(int j=k-res, m=0;j<=k+res;j++){
// if(str[j]<='z') str_res[m++]=str[j];
// }
//cout<<str_res<<endl;
printf("Case %d: %d\n", T ++ , res);
}
return 0;
}
class Solution {
public:
/*
先进行正向和反向hash
假设中点在 mid,臂长为l
如果当前mid-l的哈希值不等于mid+l的哈希值
说明当前中点到两边的回文长度小于前面的 直接continue进行下一个
如果哈希值相等 则判断l+1之后的两字符是否相等,如果相等,说明l+1后还为回文串继续判断l+1直到不满足
储存更新的 mid和l
输出
*/
typedef unsigned long long ULL;
int t=0,P=131;
char a[3000];
ULL hl[3000],hr[3000],p[3000];
string longestPalindrome(string s) {
string res;
int ans=0,mid=0,l;
a[++t]='#';
for(int i=0;i<s.size();i++)
{//对原来字符串变形,使得所有回文串长度都是奇数
a[++t]=s[i];
a[++t]='#';
}
a[t+1]='\0';
p[0]=1;
for (int i = 1, j = t; i <= t; i ++, j -- )
{//预处理正序,逆序所有前缀哈希值和次方数组
hl[i]=hl[i-1]*P+a[i];
hr[i]=hr[i-1]*P+a[j] ;
p[i]=p[i-1]*P;
}
for(int i=1;i<=t;i++)
{//枚举中点,探索半径
l=ans;//ans是遍历前面字符所找到的最长回文子串的长度,探索在这个基础上能否变长
if(i+l>t||i-l<1) continue;//说明遍历到最后一小部分,不用继续判断了
//下标,正序第i - l到第i - 1个,倒数第 n - (i + mid) + 1到第n - (i + 1) + 1)个
if(hl[i-1]-hl[i-l-1]*p[l]!=hr[t-i]-hr[t-i-l]*p[l]) continue;
while(i+l+1<=t&&i-l-1>=1&&a[i+l+1]==a[i-l-1]) l++;
if(l>=ans)
{
ans=l;
mid=i;
}
}
for(int i=mid-ans;i<=mid+ans&&mid+ans<=t;i++)
{
if(a[i]!='#')
res+=a[i];
}
return res;
}
};
LeetCode 6. Z 字形变换
/*
找规律,第一行是以0(下标)开头的,公差是2n-2的等差数列;中间的行可以拆成两个等差数列,分为在竖线上的数和在斜线上的数,最后一行也是一个等差数列,所有等差数列的公差都是2n-2
竖线上的开头元素是0到n-1,斜线上的开头是2n-2-竖线上的开头
*/
class Solution {
public:
string convert(string s, int n) {
string res;//定义答案序列
if (n == 1) return s;//边界条件判断,因为如果不判断的话,在n==1的情况下,公差算出来为0,会死循环
for (int i = 0; i < n; i ++ ) {//枚举每一行
if (i == 0 || i == n - 1) {//第一行或最后一行只有一个等差数列
for (int j = i; j < s.size(); j += 2 * n - 2)
res += s[j];
}
else {
for (int j = i, k = 2 * n - 2 - i; j < s.size() || k < s.size(); j += 2 * n - 2, k += 2 * n - 2) {//两个等差数列,每一行两个等差数列的首项分别是i和2n-2-i
if (j < s.size()) res += s[j];
if (k < s.size()) res += s[k];
}
}
}
return res;
}
};
LeetCode 7. 整数反转
/*
1.转换成字符串,反转之后变成整数
2.数学做法
先看怎么把数的每一位抠出来,以正数为例, x%10得到x的最后一位,x/10去掉最后一位
注意数学中取模之后,得到的数大于等于0,c++中对正数取模得正数,负数取模得负数,这样之后,这个代码也可以适用于负数
*/
class Solution {
public:
int reverse(int x) {
//先用longlong来写,最后改成int
long long r=0;
while(x){
r=r*10+x%10;//不用单独考虑正负情况,因为cpp取模的性质
x/=10;
}
if(r>INT_MAX) return 0;
if(r<INT_MIN) return 0;
return r;
}
};
class Solution {
public:
int reverse(int x) {
int r=0;
while(x){//先试探一步,可能和三数之和这道题无关,但试探这个思想很像
if(r>0&&r>(INT_MAX-x%10)/10) return 0;
if(r<0&&r<(INT_MIN-x%10)/10) return 0;
r=r*10+x%10;
x/=10;
}
return r;
}
};
LeetCode 8. 字符串转换整数 (atoi)
class Solution {
public:
int myAtoi(string str) {
int k = 0;
while (k < str.size() && str[k] == ' ') k ++ ;//先把前面的空格删掉
if (k == str.size()) return 0;//如果整个字符串都是空格的话,返回0
int minus = 1;//对可能出现的正负符号处理
if (str[k] == '-') minus = -1, k ++ ;
else if (str[k] == '+') k ++ ;
//先用longlong来存,最后再改成int
// long long res=0;
// while(k<str.size()&&str[k]>='0'&&str[k]<='9'){//k指向数字字符
// res=res*10+str[k]-'0';//字符转换为数字
// k++;
// if(res>INT_MAX) break;//此时的res必然为正数,只是输出的时候考虑要不要加符号
// }
// res*=minus;
// if(res>INT_MAX) return INT_MAX;
// if(res<INT_MIN) return INT_MIN;
// return res;
//longlong改成int,就是加一些比较麻烦的特判,也就是判断res=res*10+str[k]-'0'这一步操作之后是否溢出,分为正负特判
int res = 0;
while (k < str.size() && str[k] >= '0' && str[k] <= '9') {
int x = str[k] - '0';
//正数res * 10 + x>INT_MAX前者会溢出,转化为res > (INT_MAX - x) / 10
//负数-res * 10 - x <INT_MIN,转化成-res < (INT_MIN + x) / 10
if (minus > 0 && res > (INT_MAX - x) / 10) return INT_MAX;
if (minus < 0 && -res < (INT_MIN + x) / 10) return INT_MIN;
//注意负数绝对值比正数绝对值多1,使用特判负无穷,注意这个只能单独判断,绝对不能在上面改成小于等于,因为这样的话像
//"-2147483647"也会输出-2147483648
if (-res * 10 - x == INT_MIN) return INT_MIN;
res = res * 10 + x;
k ++ ;
}
res *= minus;
return res;
}
};
LeetCode 9. 回文数
//数值方法,类似于LeetCode 7. 整数反转,而且这道题并没有long long限制
class Solution {
public:
bool isPalindrome(int x) {
if(x<0||x&&x%10==0) return false;//如果x本身是负数,或者说x非0但是它最后一位是0的话,直接return
int y=x;//先把所给整数存下来
long long res=0;
//从个位开始把x的每一位抠出来放前面
while(x){
res=res*10+x%10;
x/=10;
}
return y==res;
}
};
class Solution {
public:
bool isPalindrome(int x) {
//数值方法,有longlong限制,只能用int,可以只翻转后一半,再判断是否和前一半相等即可
if (x < 0 || x && x % 10 == 0) return false;
int s = 0;
while (s <= x)
{
s = s * 10 + x % 10;
if (s == x || s == x / 10) return true; // 分别处理整数长度是奇数或者偶数的情况
x /= 10;
}
return false;
}
};
class Solution {
public:
bool isPalindrome(int x) {
//字符串方法,转换成字符串
if (x < 0) return 0;
string s = to_string(x);
return s == string(s.rbegin(), s.rend());
}
};
LeetCode 10. 正则表达式匹配
/*
类似dp问题很多,比如给两个字符串,求最长公共子序列,能否匹配
正则表达式匹配,'.' 匹配任意单个字符,'*' 表示它前面的那一个字符可以出现任意多次(包括0次)
动态规划(序列模型):
状态表示 f[i,j]
集合:所有s[1-i]和p[1-j]的匹配方案
属性:bool 是否存在合法方案
状态计算
如果p[j]不是'*',先看s[i]和p[j]是否匹配,两种情况:s[i]==p[j]或者p[j]=='.',并且f[i-1,j-1]也匹配
如果p[j]是'*',枚举一下,这个'*'表示多少个字符,如果是0个字符f[i,j-2],1个字符f[i-1,j-2]&&s[i]匹配
2个字符f[i-2,j-2]&&s[i]匹配&&s[i-1]匹配...
f[i,j] =f[i,j-2] | f[i-1,j-2]&s[i] | f[i-2,j-2]&s[i]&s[i-1]...
f[i-1,j]=f[i-1,j-2] | f[i-2,j-2]&s[i-1] | f[i-3,j-2]s[i-1]&s[i-2]...
上面一个式子: f[i,j] =f[i,j-2] | f[i-1,j] & s[i]匹配 优化和完全背包很像
*/
class Solution {
public:
bool isMatch(string s, string p) {
int n = s.size(), m = p.size();
s = ' ' + s, p = ' ' + p;//下标都从1开始,所以前面补上一个空格
vector<vector<bool>> f(n + 1, vector<bool>(m + 1));//布尔数组
f[0][0] = true;//初始化
for (int i = 0; i <= n; i ++ )//它可以从0开始,因为没有字符的时候也可能匹配
for (int j = 1; j <= m; j ++ ) {
//如果j==0的话,因为f[0][0]已经初始化过了,其他i不为0的情况一定不匹配,所以j从0开始没有意义
//*和前面一个字符看作一个整体,如果遇到类似a*的a的话要跳过a
if (j + 1 <= m && p[j + 1] == '*') continue;
if ( p[j] != '*') {//如果i指向某个非空字符,并且p[j]!='*',i从1开始,否则i-1没有意义
f[i][j] = i &&f[i - 1][j - 1] && (s[i] == p[j] || p[j] == '.');
} else if (p[j] == '*') {
f[i][j] = f[i][j - 2] || i && f[i - 1][j] && (s[i] == p[j - 1] || p[j - 1] == '.');
}
}
return f[n][m];
}
};
LeetCode 11. 盛最多水的容器
/*
给定一个数组,数组中有n个非负整数,表示二维平面的n条直线的高度,找到其中的两条直线使得它们与 x 轴共同构成的容器可以容纳最多的水
(贪心 + 双指针) O(n)
储水容量取决于边界的间距以及两边的短板高度;
移动较高的边界,储水容量一定会减少,反之则未必,所以可以贪心的移动较短的边界,并不断更新答案;
利用反证法也可以严格证明这样操作的答案必然是最优解。
证明:假设左边指针先到达最优解的一边,要证明的是右边指针会一直向前移动到最优解的右边,反证法,假设某个时刻右边高度大于等于左边,右指针就不会往前移动,此时对应的面积一定大于最优解,矛盾
*/
class Solution {
public:
int maxArea(vector<int>& height) {
if (height.empty()) return 0;
//res要取最大值,初始化一般为0或者负无穷,因为题目中均为非负整数,且求的是面积,初始化为0即可
int i = 0, j = height.size() - 1, res = 0;
while (i < j){
res = max(res, (j - i) * min(height[i], height[j]));//每次是先更新一下最大值,再移动
if (height[i] <= height[j]) i++;
else j--;
}
return res;
}
};
LeetCode 12. 整数转罗马数字
/*
基本字符 I V X L C D M
阿拉伯数字 1 5 10 50 100 500 1000
1.相同的数字连写,所表示的数等于这些数字相加得到的数,如:III=3;
2.小的数字在大的数字的右边,所表示的数等于这些数字相加得到的数,如:VIII=8, XII=12;
3.小的数字在大的数字的左边(限于 IV、IX、XL、XC、CD和CM),所表示的数等于大数减小数得到的数,如:IV=4, IX=9;
4.正常使用时,连写的数字重复不得超过三次;
将所有减法操作看做一个整体,当成一种新的单位。从大到小整理所有单位得到:
M CM D CD C XC L XL X IX V IV I
1000 900 500 400 100 90 50 40 10 9 5 4 1
此时我们可以将目标整数看成这些单位值的加和,且同一种单位不能使用超过3次。所以我们尽可能优先使用值较大的单位
时间复杂度分析:计算量与最终罗马数字的长度成正比,对于每一位阿拉伯数字,罗马数字最多用4个字母表示(比如VIII=8),所以罗马数字的长度和阿拉伯数字的长度是一个数量级的,而阿拉伯数字的长度是O(logn),因此时间复杂度是 O(logn),时间复杂度和位数成正比,一个整数的位数是O(logn)级别。
*/
class Solution {
public:
string intToRoman(int num) {
int values[] = {
1000,
900, 500, 400, 100,
90, 50, 40, 10,
9, 5, 4, 1
};
string reps[] = {
"M",
"CM", "D", "CD", "C",
"XC", "L", "XL", "X",
"IX", "V", "IV", "I",
};
string res;
for (int i = 0; i < 13; i ++ ) {
while (num >= values[i]) {
num -= values[i];
res += reps[i];
}
}
return res;
}
};
LeetCode 13. 罗马数字转整数
class Solution {
public:
int romanToInt(string s) {
unordered_map<char, int> hash;
hash['I'] = 1, hash['V'] = 5;
hash['X'] = 10, hash['L'] = 50;
hash['C'] = 100, hash['D'] = 500;
hash['M'] = 1000;
int res = 0;
for (int i = 0; i < s.size(); i ++ ) {
if (i + 1 < s.size() && hash[s[i]] < hash[s[i + 1]])
res -= hash[s[i]];
else
res += hash[s[i]];
}
return res;
}
};
LeetCode 14. 最长公共前缀
class Solution {
public:
string longestCommonPrefix(vector<string>& strs) {
/*
两重循环暴力搜索 O(nm)
*/
string res;
if (strs.empty()) return res;//边界条件,判断是否为空
for (int i = 0;i < strs[0].size(); i ++ ) {//枚举第i个字母是否完全一致
char c = strs[0][i];
for (auto& str: strs)
if (str.size() <= i || str[i] != c)
return res;
res += c;
}
return res;
}
};
class Solution {
public:
string longestCommonPrefix(vector<string>& strs) {
//字符串数组为空则置res为空,否则置为第一个字符串
string res = strs.empty() ? "" : strs[0];
//遍历字符串数组
for (string s : strs)
{
/*
在字符串s中查找res并返回首字母的位置(find函数)
如果首地址不为零,每次令res-1以缩短公共前缀
比如说再flow中查找flower,没有找到,返回值为迭代器结尾(非0)
公共前缀会减掉最后一个字母,为flowe。继续循环直到为flow
如果是首字母不一样则公共前缀会清空
*/
while (s.find(res) != 0)
{
res = res.substr(0, res.length() - 1);
}
}
return res;
}
};
class Solution {
public:
/*
这道题是非常经典的dfs问题,dfs问题的话考虑清楚还是简单的,画一个递归搜索树即可,而且也不用回溯,遍历到某一条路径的终点的时候,直接把结果存下来就行,也不用干其他的事情
在递归的时候需要存下来方案或者说路径是什么,string path; 还需要存下来当前是第几位int u
这个做法时间复杂度和长度有关,最坏情况下每个数字有四种选择,总的就是4^n,外加push_back需要O(n)的时间复杂度
所有总的时间复杂度是n*4^n
*/
vector<string> ans;
string strs[10] = {
"", "", "abc", "def",
"ghi", "jkl", "mno",
"pqrs", "tuv", "wxyz",
};//每个数字可能的情况
vector<string> letterCombinations(string digits) {
if (digits.empty()) return ans;
dfs(digits, 0, "");//当前遍历第几位,最开始的路径
return ans;
}
void dfs(string& digits, int u, string path) {//path是dfs当前的路径
if (u == digits.size()) ans.push_back(path);//遍历到最后一位,在答案当中加入当前的方案
else {
for (auto c : strs[digits[u] - '0'])//u的下标从0开始,但strs里面的下标,数字2就是对于2
dfs(digits, u + 1, path + c);
}
}
};
LeetCode 18. 四数之和
//双指针
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
sort(nums.begin(), nums.end());
vector<vector<int>> res;
for (int i = 0; i < nums.size(); i ++ ) {
if (i && nums[i] == nums[i - 1]) continue;
for (int j = i + 1; j < nums.size(); j ++ ) {
if (j > i + 1 && nums[j] == nums[j - 1]) continue;
for (int k = j + 1, u = nums.size() - 1; k < u; k ++ ) {
if (k > j + 1 && nums[k] == nums[k - 1]) continue;
while (u - 1 > k && nums[i] + nums[j] + nums[k] + nums[u - 1] >= target) u -- ;
if (nums[i] + nums[j] + nums[k] + nums[u] == target) {
res.push_back({nums[i], nums[j], nums[k], nums[u]});
}
}
}
}
return res;
}
};
/*
由最开始的二数之和,到三数之和,到现在的四数之和,虽然都可以用双指针,但如果扩张到n数之和呢,可以尝试dfs,又因为要不能有重复元素,所以要么哈希表,要么排序,这里我就直接sort排个序
深搜的搜索顺序,依次探索每个位置可以放哪些元素,如果放这个元素的话,下一个位置怎么放合适
另外这道题深搜必须加上剪枝,否则一定会超时
1.如果数组中剩余可选的数字数量少于待找数量n,则剪掉
2.如果 当前数字 + 已确定数字的和 + (n - 1) * 排序后数组中当前数字的下一个数字 > target,则说明后面的数无论怎么选,加起来都一定大于target,所以剪掉(递归返回)
3.如果 当前数字 + 已确定数字的和 + (n - 1) * 排序后数组最后一个数字 < target,则说明后面的数无论怎么选,加起来都一定小于target,所以剪掉(进行下一次循环)
*/
class Solution
{
private:
vector<vector<int>> ans;
vector<int> myNums, subans;
int tar, numSize;//这四个变量除了subans之外,其他定义成全局变量是为了dfs时也可以使用,否则只能在dfs时传入这些参数
void DFS(int low, int sum)//前者是探索第low个位置可以放哪些数(从0开始),sum是已经选了的数的和
{
if (sum == tar && subans.size() == 4) {
ans.push_back(subans);
return;
}
for (int i = low; i < numSize; ++i) {
if (i > low && myNums[i] == myNums[i - 1]) //去重
continue;
if (numSize - i < int(4 - subans.size())) //剪枝
return;
if (i < numSize - 1 && sum + myNums[i] + int(3 - subans.size()) * myNums[i + 1] > tar) //剪枝
return;
if (i < numSize - 1 && sum + myNums[i] + int(3 - subans.size()) * myNums[numSize - 1] < tar) //剪枝
continue;
subans.push_back(myNums[i]);
DFS(i + 1, myNums[i] + sum);
subans.pop_back();
}
return;
}
public:
vector<vector<int>> fourSum(vector<int>& nums, int target)
{
sort(nums.begin(), nums.end());
myNums = nums;
tar = target;
numSize = nums.size();
// if (numSize < 4)
// return ans;
DFS(0, 0);
return ans;
}
};
LeetCode 19. 删除链表的倒数第N个节点
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int k) {
//两次循环
auto dummy = new ListNode(-1);//常用技巧,凡是头结点可能改变,建一个虚拟头结点
dummy->next = head;
int n = 0;//一次遍历找到链表总长度
for (auto p = dummy; p; p = p->next) n ++ ;
auto p = dummy;
//遍历到倒数第k个点,从dummy开始,走1步,到达倒数第n-1个点,走n-k-1步,到达倒数第k+1个点
for (int i = 0; i < n - k - 1; i ++ ) p = p->next;
p->next = p->next->next;
return dummy->next;
}
};
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int k) {
//一次循环
auto dummy = new ListNode(-1); // 首先新建一个链表的结点
dummy->next = head; //令这个结点指向head
auto first = dummy, second = dummy; //同时设置双指针指向该节点
for (int i = 0; i < k; i ++ )
//将first指针向前走k步,second 指针不动,还是在最前面,此时first和second这个节点相差k
{
first = first->next;
}
while (first -> next)
{
//始终保持两个指针之间间隔n个结点,在first到达终点时,second的下一个结点就是从结尾数第n个结点
first = first->next;
second = second->next;
}
//直接删掉这个倒数第n个结点
second->next = second->next->next;
return dummy->next;
}
};
LeetCode 20. 有效的括号
class Solution {
public:
bool isValid(string s) {
/*
非常经典的括号序列问题,这道题是很简单的栈,但括号序列会有很多衍生的问题,可以考dfs,也可以考dp,也可以考数据结构,也可以什么卡特函数相关
*/
stack<char> stk;
for (auto c : s) {
if (c == '(' || c == '[' || c == '{') stk.push(c);
else {
if (stk.size() && abs(stk.top() - c) <= 2) stk.pop();
else return false;
}
}
return stk.empty();
}
};
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
/*
注意是交换节点,而不是值,关于链表题目,首先会发现链表头结点会改变,所以建一个虚拟头结点
*/
ListNode* swapPairs(ListNode* head) {
auto dummy = new ListNode(-1);
dummy->next = head;//p指向要交换的两个节点的前面一个点
for (auto p = dummy; p->next && p->next->next;) {
auto a = p->next, b = a->next;
p->next = b;
a->next = b->next;
b->next = a;
p = a;
}
return dummy->next;
}
};
LeetCode 25. K 个一组翻转链表
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
/*
上一道题是交换相邻两个元素,这道题是翻转相邻k个元素,头结点会改变,创建虚拟头结点,这道题和上一道题类似,弄一根指针,每次指向需要交换的k个元素的前一个元素
首先判断是否足够k
如果够k的话,翻转:
其中先将k个节点内部的边反向
然后是将前面的边搞定
最后是后面的边
*/
ListNode* reverseKGroup(ListNode* head, int k) {
auto dummy = new ListNode(-1);//定义虚拟头结点
dummy->next = head;
for (auto p = dummy;;) {//从虚拟头结点开始遍历整个链表,p指向需要交换的k个元素的前一个元素
auto q = p;//先遍历p后面够不够k个元素
for (int i = 0; i < k && q; i ++ ) q = q->next;//往后走k步看q是否为空
if (!q) break;//不足k个,这个for循环是常数次
auto a = p->next, b = a->next;//指向每一组的前两个元素
for (int i = 0; i < k - 1; i ++ ) {//要交换k-1个相邻的位置,要走k-1步
auto c = b->next;//先存下来b的下一个元素
b->next = a;
a = b, b = c;
}
auto c = p->next;//先存下来p的下一个元素
p->next = a, c->next = b;
p = c;//结束之后p继续指向下一组k个元素的前一个位置
}
return dummy->next;
}
};
LeetCode 26. 删除排序数组中的重复项
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
/*
原地算法就是不能开数组,不能写递归
双指针算法
*/
int k = 0;
for (int i = 0; i < nums.size(); i ++ )
if (!i || nums[i] != nums[i - 1])
nums[k ++ ] = nums[i];
return k;
}
};
LeetCode 27. 移除元素
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int k = 0;
for (int i = 0; i < nums.size(); i ++ )
if (nums[i] != val)
nums[k ++ ] = nums[i];
return k;
}
};
LeetCode 28. 实现 strStr()
public:
/*
这道题是KMP的裸题,给定两个字符串s和p,输出p在s当中第一次出现的位置
KMP核心求next数组,最长前缀后缀,所有p[i-i]的相等的前缀和后缀中长度的最大值,不考虑平凡即只有自己的情况
*/
int strStr(string s, string p) {
if (p.empty()) return 0;
int n = s.size(), m = p.size();
s = ' ' + s, p = ' ' + p;//字符串改成下标从1开始
vector<int> next(m + 1);
for (int i = 2, j = 0; i <= m; i ++ ) {
while (j && p[i] != p[j + 1]) j = next[j];
if (p[i] == p[j + 1]) j ++ ;
next[i] = j;
}
for (int i &