leetcode学习记录_字符串

不管做什么类型的题,当然都是从简单的开始😢

179. 最大数

给定一组非负整数 nums,重新排列每个数的顺序(每个数不可拆分)使之组成一个最大的整数。

注意:输出结果可能非常大,所以你需要返回一个字符串而不是整数。
示例 :

输入:nums = [3,30,34,5,9]
输出:"9534330"

来源:力扣(LeetCode)

题解
具体思路来自这位大佬:(个人只修改了一点点,基本没差别)
https://leetcode-cn.com/problems/largest-number/solution/bao-ni-miao-dong-de-chao-jian-dan-shi-xi-sys4/

class Solution {
public:
    string largestNumber(vector<int>& nums) {
        vector<string> buf;
        buf.reserve(nums.size());//预先分配空间
        for(int i : nums)
        {//把int转换成string并添加进buf
            buf.emplace_back(to_string(i));
        }
//本题重点,sort的自定义排序,第三个参数就是自定义的规则        
        sort(buf.begin() , buf.end() , [](string&a , string&b){return a+b > b+a;});
        if(buf[0] == "0") return "0";
        string res;
        for(string i : buf)
        {//拼接字符串
            res+=i;
        }
        return res;//返回结果
    }
};


剑指 Offer 48. 最长不含重复字符的子字符串

请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。

示例 :

输入: “abcabcbb”
输出: 3
解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。

来源:力扣(LeetCode)

class Solution {
public:
   	int lengthOfLongestSubstring(string s) {
           int N = s.size();
		if (N ==0) return 0;//特殊情况
		if (N == 1) return 1;//特殊情况
		unordered_map<char, int> map;
		int L = 0 , max = 0;//L左指针,max是最大长度
        for(int R = 0;R<N;R++)//R是右指针
        {
        //查找当前R所指的字符是否出现过
            if(map.find(s[R]) != map.end())
//这里的意思是把L的值移动到重复的数字右边,因为我们用map记录了下标
//所以+1就可以到重复字符的右边了,这个比较是为了防止L左移         
          {  L = L>map[s[R]] + 1?L:map[s[R]] + 1;  }
           // 记录当前字符的下标,会更新的,所以写在if外边
            map[s[R]] = R;
            //+1是因为L和R是闭区间
            max = max>(R-L+1)?max:(R-L+1);
        }
        return max;
	}
};


415. 字符串相加
给定两个字符串形式的非负整数 num1 和num2 ,计算它们的和。用字符串返回
提示:

num1 和num2 的长度都小于 5100
num1 和num2 都只包含数字 0-9
num1 和num2 都不包含任何前导零
你不能使用任何內建 BigInteger 库, 也不能直接将输入的字符串转换为整数形式
来源:力扣(LeetCode)
不能直接转,就算直接转了,也会因为数据过大而溢出,各种bug,所以考虑用上次写链表数字相加一样的方法:
缺位补0,当然了,结果是反的,所以最后用reverse反转我们的结果string
联动:
题目
文章

class Solution {
public:
    string addStrings(string num1, string num2) {
        int N1 = num1.size()-1 , N2 = num2.size()-1;
        string res;
        int sum = 0 , carry = 0; 
        while(N1 >=0 || N2 >= 0)
        {
            char n1 = N1>=0?num1[N1]:'0';
            char n2 = N2>=0?num2[N2]:'0';
            sum = (n1-'0') + (n2-'0') + carry;
            carry = sum/10;
            res += sum%10 + '0'; 
            N1--;N2--;   
        }
        if(carry > 0) res += carry + '0';
        reverse(res.begin(),res.end());
        return res;
    }
};


剑指 Offer 50. 第一个只出现一次的字符
第一眼看见题目就想到哈希表,第一次遍历时将所有元素存入哈希表,并且每出现一次,key对应的data就+1,于是,只出现一次的字符的data就是1,
然后第二次遍历字符串,遇见data为1就输出,如果遍历完了都找不到,就说明没有不重复的字符,所以输出空格 ’ ’

class Solution {
public:
    char firstUniqChar(string s) {
        unordered_map<char , int> map;
        for(int i = 0;i<s.size();++i)
        {
            ++map[s[i]];
        }
        for(int i = 0;i<s.size();++i)
        {
            if(map[s[i]] == 1)
            return s[i];
        }
        return (char)' ';
    }
};


28. 实现 strStr()

实现 strStr() 函数。

给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串出现的第一个位置(下标从 0 开始)。如果不存在,则返回 -1 。

来源:力扣(LeetCode)

重点来了,字符串匹配,我除了暴力法以外,也只知道一个kmp了,于是就用kmp来吧

kmp算法的重点就是next数组了,算出了next数组,整个算法也差不多完成了

那next数组是什么呢,其实就是一个字符串里各个长度的公共前后缀
如下图,ababaaaba的next数组就是【001231123】
当然也可以把数组里的数字都减个1 , 就变成了 【-1-10120012】
在这里插入图片描述
那么怎么用代码来实现呢?
next数组
(这里实现的是不减1的 , 减1版本的只需要做小小的修改,会在代码里标出来)

void getNext(string& s , vector<int>&next)
{
	j = 0;//j = -1;
	next[0] = 0;//next[0] = -1;
//这里记得初始化i为1,因为next[0]我们已经初始化过了	
	for(int i = 1;i<s.size();i++)
	{
		while(j > 0 && s[j] != s[i])//j > 0  ——  j >= 0
		{//如果不匹配,就回溯j的位置直到匹配或者j到达初始位置
			j = next[j-1];// j = next[j];
		}
//如果匹配,j就往右走,看是不是有更长的公共前后缀		
		if(s[j] == s[i])//if(s[j+1] == s[i])
		{
			j++;
		}
		next[i] = j;//存放公共前后缀的长度到对应的位置
	}
}

为什么说算出了next数组,整个算法也差不多完成了?
因为主程序里的利用next数组匹配的算法思路和代码也是和
算next数组时差不多的

int strStr(string haystack, string needle)
{
	vector<int> next;
	next.resize(needle.size());
	j = 0;//-1
	getNext(needle,next);//算出next数组
//下面的i要初始化为0,因为要从头开始匹配	
	for(int i = 0;i<haystack.size();i++)
	{
		while(j > 0 && haystack[i] != needle[j])//j > 0 —— j >= 0
		{//回溯
			j = next[j-1];// j = next[j];
		}
		if(haystack[i] == needle[j])//if(haystack[i] == needle[j+1])
		{//前进
			j++;
		}
//完美匹配,就返回位置		
		if(j == needle.size())//if(j == needle.size()-1)
		{
			return (i - needle.size() + 1);
		}
	}
	return -1;//如果上面没返回,就说明没用匹配到,返回-1;
}

可以发现,都是一个for遍历字符串
然后while来回溯不匹配的情况
if来前进
只是细节有一点不同,框架都是一样的


14. 最长公共前缀

编写一个函数来查找字符串数组中的最长公共前缀。
如果不存在公共前缀,返回空字符串 “”。
示例 1:

输入:strs = [“flower”,“flow”,“flight”]
输出:“fl”
示例 2:

输入:strs = [“dog”,“racecar”,“car”]
输出:""
解释:输入不存在公共前缀。
来源:力扣(LeetCode)

思路:
纵向扫描,遍历每一个字符串,依次比对每一个字符串对应位置的字符,看有没有不相同的,如果有不相同的,就用string::substr提取前几位然后返回

class Solution {
public:
    string longestCommonPrefix(vector<string>& strs) {
        int size = strs.size();//获取字符串数量
        if(size == 0) return "";//如果是空的就返回空字符串
        int length = strs[0].size();//获取第一个字符串的长度
//这里其实获取谁的都可以,但是第一个比较方便,
//我们就用第一个字符串作为基准取进行匹配        
		for(int i = 0;i<length;i++)//遍历第一个字符串
		{
			char ch = strs[0][i];//提取第一个字符串的对应字符
			for(int j = 1;j<size;++j)//遍历所有字符串的对应字符
			{	
				if(strs[j][i] != ch || i == strs[j].size())
				{//找到最大公共前缀了,提取,返回
					return strs[0].substr(0,i);
				}
			}
		}
//这里是正常情况退出循环,即strs[0]都被我们遍历完了,
//发现strs[0]里的每一个字符都与其他字符串的对应位置匹配,
//那咋办,直接返回呗!	
		return strs[0];
    }
};


1190. 反转每对括号间的子串
来个简单的,放松一下😘

给出一个字符串 s(仅含有小写英文字母和括号)。

请你按照从括号内到外的顺序,逐层反转每对匹配括号中的字符串,并返回最终的结果。

注意,您的结果中 不应 包含任何括号。
示例 1:
输入:s = “(abcd)”
输出:“dcba”

示例 2:
输入:s = “(u(love)i)”
输出:“iloveu”
来源:力扣(LeetCode)

思路:用栈和队列来实现,不知道为什么,一看见反转,我就会想到栈
在这里插入图片描述

class Solution {
public:
    string reverseParentheses(string s) {
        stack<char> st;
        queue<char> buf;
        for(char ch : s)
        {
            if(ch != ')')//不是右括号就压入栈
            st.push(ch);
            else
            {//如果是右括号,就放入队列,再放回来
                while(st.top()!='(')
                {//放入队列
                    buf.push(st.top());
                    st.pop();
                }
                st.pop();//删掉多余的左括号
                while(!buf.empty())
                {//放回栈内
                    st.push(buf.front());
                    buf.pop();
                }
            }
        }
        string res;
        res.resize(st.size());//这里不能忘,不然无法用下标访问
//从后往前给新字符串赋值        
        for(int i = res.size()-1;i>=0;i--)
        {
            res[i] = st.top();
            st.pop();
        }
        return res;
    }
};


6. Z 字形变换

将一个给定字符串 s 根据给定的行数 numRows ,以从上往下、从左到右进行 Z 字形排列。

比如输入字符串为 “PAYPALISHIRING” 行数为 3 时,排列如下:

P   A   H   N
A P L S I I G
Y   I   R

来源:力扣(LeetCode)
一开始我还没搞懂什么叫z字变换,因为示例里只给除了3行的情况,其实结合一下4行的情况看一下就很好理解了
在这里插入图片描述
又根据题目要求,变换后的字符串是一行一行读取的,所以中间没必要留空,如下图,直接放到后面就行了
在这里插入图片描述
所以,我们就不用考虑横向的变化了,只考虑上下的变化就好,用一个bool型变量flag来决定往下走还是往上走;
说一下特殊情况:
1:
指定行数为1,这样的话,反正s也只会复制一份,为了避免中间不必要的运算,直接返回s
2:
行数小于等于字符串长度,这样的话,每一行最多也就一个字符,拼接完成后还是s,所以直接返回s

class Solution {
public:
    string convert(string s, int numRows) {
        int N = s.size();
//解决特殊情况        
        if(numRows == 1 || N<=numRows) return s;
        int row = numRows, cur = 0;
        bool flag = false;
        vector<string>buf(row);
        for(char ch : s)
        {
            buf[cur] = buf[cur]+ch;
            if(cur == 0) flag =true;
            else if(cur == row-1) flag = false;
            cur = cur+(flag?1:-1);
        }
        string res;
//拼接,用引用节省空间提高效率        
        for(string&temp : buf)
        res += temp;
        return res;
    }
};


1047. 删除字符串中的所有相邻重复项

给出由小写字母组成的字符串 S,重复项删除操作会选择两个相邻且相同的字母,并删除它们。
在 S 上反复执行重复项删除操作,直到无法继续删除。
在完成所有重复项删除操作后返回最终的字符串。答案保证唯一。
来源:力扣(LeetCode)
这一题挺有意思的,一开始没反应过来,还想用双指针的,后来发现这就是个匹配括号的题目而已,一下就清晰了,用栈即可,而且
C++的string也支持类似栈的操作,top就用back , pop就是pop_back , push就是push_back甚至可以直接用+法,不过为了看上去统一一点,这里还是用push_back
代码:

class Solution {
public:
    string removeDuplicates(string S) {
        string res;
        for(char ch : S)
        {
            if(res.empty() || ch != res.back()) res.push_back(ch);
            else if(ch == res.back() && !res.empty()) res.pop_back();
        }
        return res;
    }
};


43. 字符串相乘

给定两个以字符串形式表示的非负整数 num1 和 num2,返回 num1 和 num2 的乘积,它们的乘积也表示为字符串形式。
示例 1:
输入: num1 = “2”, num2 = “3”
输出: “6”
示例 2:
输入: num1 = “123”, num2 = “456”
输出: “56088”
来源:力扣(LeetCode)

思路:
其实就是用平时做乘法的思路
在这里插入图片描述
我们能发现,三位乘三位,尽管是最大值的999*999,他们的乘积也没有大于6位,其实所有的乘积都一样,满足这个规律,然后为了运算方便,我们用一个数组buf [ m+n ]储存中间值,其实用字符串也可以,但是运算还得用数字的逻辑,转来转去太麻烦,我就偷懒用了数组,其实用字符串的话,最后删掉多余的0就可以返回了,比较节省空间

常规乘法的思路,是把所有乘积算出来一起加,代码里为了节省空间,只能算出一个加一个了buf就是存放这些值的缓冲数组

class Solution {
public:
    string multiply(string num1, string num2) {
        if(num1 == "0" || num2 == "0") return "0";
        int n1 = num1.size(), n2 = num2.size();
        vector<int> buf(n1+n2,0);
        for(int i = n1-1;i>=0;--i)
        {
            for(int j = n2-1;j>=0;--j)
            {//这里注意前两句,都得是 += ,不然就没效果了,别忘了乘法的思路
                buf[i+j+1] += (num1[i]-'0')*(num2[j]-'0');//先保存完整数字
                buf[i+j] += buf[i+j+1]/10;//给前面一位赋值为进位
                buf[i+j+1] %= 10;//保留个位
            }
        }
        string res;
        int i = 0;
        while(buf[i] == 0) i++;//跳过开头多余的0
        while(i< buf.size()) res += (char)(buf[i++]+'0');
        return res;
    }
};


49. 字母异位词分组

给定一个字符串数组,将字母异位词组合在一起。字母异位词指字母相同,但排列不同的字符串。

示例:

输入: [“eat”, “tea”, “tan”, “ate”, “nat”, “bat”]
输出:
[
[“ate”,“eat”,“tea”],
[“nat”,“tan”],
[“bat”]
]

来源:力扣(LeetCode)

思路:
哈希表+排序,如何判断两个字符串是不是字母异位词?这就得用上排序了,很明显我们知道"acb" 和 "bac"是字母异位词,然后他们排序后的字符串都是"abc"我们就用这个作为哈希表的key,原字符串作为值,因为可以有多个值,所以值用字符串数组来填入所有满足条件的字符串,

遍历原字符串数组,每次遍历都用一个临时字符串存放排序过后的字符串,然后在哈希表里查找是否有这个key,如果没有就建立,有的话就在对应的值里添加被我们遍历到的字符串

遍历完以后,所有字符串都被我们分好组了,最后按组把字符串填入结果二维数组就行了

代码:

class Solution {
public:
    vector<vector<string>> groupAnagrams(vector<string>& strs) {
        unordered_map<string,vector<string>>map;
        for(string&temp:strs)//for循环遍历字符串数组
        {
            string buf = temp;//存放排序后的字符串
            sort(buf.begin(),buf.end());
            if(map.find(buf)!=map.end())//如果已经这样的组了,就加上去
            {
                map[buf].emplace_back(temp);
            }
            else
            {//如果没有找到这样的组,就建立一个字符串数组,并把当前遍历到的字符串填进去
                vector<string>tstr;
                tstr.emplace_back(temp);
                map[buf] = tstr;
            }
        }
        vector<vector<string>>res;
        res.reserve(map.size());
        for(auto& temp:map)
        {//将哈希表里的字符串数组填入结果二维数组
            res.emplace_back(temp.second);
        }
        return res;
    }
};

其实这里还可以优化一下,就是哈希表的的值,可以用字符串数组的下标,也就是从vector< string >换成vector< int >,这样在最后填入结果数组的时候会稍微麻烦一点,但是能节约一点内存的使用,并且不会有时间上损失,甚至更快了,因为不用重复的在内存中复制字符串

代码:

class Solution {
public:
    vector<vector<string>> groupAnagrams(vector<string>& strs) {
        unordered_map<string,vector<int>>map;
        for(int i = 0;i<strs.size();++i)
        {
            string& temp = strs[i];
            string buf = temp;
            sort(buf.begin(),buf.end());
            if(map.find(buf)!=map.end())   
            map[buf].emplace_back(i);
            else                           
            map[buf] = vector<int>{i};
        }
        vector<vector<string>>res(map.size());
        int count = 0;//这个
        for(auto& temp:map)
        {
            for(int i = 0;i<temp.second.size();++i)//这个for是给二维数组里的一维数组赋值
            {//这里还可以再优化一下,先给一维数组一个确定的大小,既能节省空间,也能节省时间
                res[count].emplace_back(strs[temp.second[i]]);
            }
            ++count;//这个count决定给哪个一维数组赋值
        }
        return res;
    }
};

在这里插入图片描述


剑指 Offer 58 - I. 翻转单词顺序

输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变。为简单起见,标点符号和普通字母一样处理。例如输入字符串"I am a student. “,则输出"student. a am I”。
示例 1:
输入: “the sky is blue”
输出: “blue is sky the”
规则:
无空格字符构成一个单词。
输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。
如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。
来源:力扣(LeetCode)

思路就是用双指针把有效的单词给提取出来,从后往前拼在一起并在中间添加一个空格
拼接可以用字符串数组缓冲,也可以直接加在结果字符串上,按遍历顺序不同,推荐使用的方法也不同

思路很简单,实现的方法也很多

一开始用的不是双指针,而是直接遍历+数组缓冲,但是这样会多提取几个无效的空字符串"",得在拼接的时候判断是否进行拼接

后来就用了双指针,在一次遍历中用双指针指向每一个有效的单词,这样可以有效的避免没必要的提取,然后我没用数组缓冲,直接对结果字符串进行加法运算,并且提取的的时候用的是substr提取目标字符串的子串,最后发现内存的使用差的离谱,我就猜是不是因为重复的对结果字符串进行加法的关系,在内存中疯狂的进行无谓的复制
在这里插入图片描述
然后我改成了字符串数组+提取子串的方法,内存占用大大降低
在这里插入图片描述
最后一步,我把提取子串的方式,改成了一个个字符的添加

在这里插入图片描述
速度也有了一点提升,都是在4ms和8ms之间,不像提取子串,都是8ms和12ms
但是内存占用排名还是很低,而且代码略显繁琐,如下
代码:

class Solution {
public:
    string reverseWords(string s) {
        string res;
        string temp;
        vector<string>buf;
        int L = 0; while(s[L]==' ')++L;//跳过前缀0
        int R = L;
        while(L<s.size())
        {
            if(s[R]!=' ' && R < s.size())
            {
                temp.push_back(s[R]);//这是单个字符的添加进temp(二选一)
                ++R;
            } 
            else
            {
                //temp = s.substr(L,R-L);//这是提取子串的添加进temp
                buf.emplace_back(temp);//用数组缓冲,推荐
                //res = temp+' '+res;//用加法,不推荐
                temp.clear();
                while(s[R] ==' ' && R<s.size())++R;
                L = R;
            }
        }
        for(int idx = buf.size()-1;idx>=0;idx--)//从后往前,把单词添加进结果字符串
        res+=buf[idx]+' ';//在每个单词后面加上一个空格
        res.pop_back();//删除结果后方多余的空格
        return res;
    }
};

最后,我还是忍不住看了大佬们的题解,终于恍然大悟,终于明白了为什么做加法效率那么低
最主要的还是遍历的顺序
从前往后的话,因为顺序的关系 我的res在做加法的时候不能使用 +=
字符串的+=效率很高,res = res + temp 和 res += temp效率差的不是一点两点,看图
在这里插入图片描述
在这里插入图片描述
所以才导致了那种局面,但是如果从后往前遍历得话,就可以用+=了,完全不用数组缓冲,虽然效率没高多少,但是代码简洁了很多!

代码:

class Solution {
public:
    string reverseWords(string s) {
        int n = s.size(), L, R = n - 1;
        string res;
        while(R >= 0){  
            while(R >= 0 && s[R] == ' ') --R; 
            if(R < 0) break;
            for(L = R; L >= 0 && s[L] != ' '; --L); 
            res += (s.substr(L + 1, R - L) + " ");
            R = L;
        }
        res.pop_back();
        return res;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

timathy33

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值