力扣75——数组与字符串

总结leetcode75中数组与字符串的算法题解题思路。

以下代码大部分为本人所写,少部分为官方示例代码。

力扣75——数组与字符串

  • 1 交替合并字符串
  • 2 字符串的最大公因子
  • 3 拥有最多糖果的孩子
  • 4 种花问题
  • 5 反转字符串中的元音字母
  • 6 除自身以外数组的乘积
  • 7 递增的三元子序列
  • 8 压缩字符串
  • 1-8 解题总结

1 交替合并字符串

题目:

给你两个字符串 word1 和 word2 。请你从 word1 开始,通过交替添加字母来合并字符串。如果
一个字符串比另一个字符串长,就将多出来的字母追加到合并后字符串的末尾。

返回 合并后的字符串 。

题解:
先用string.size()计算出较短的长度wsize,
然后用for循环把word1和word2各自的前wsize个字符交替合并,
接着用string.substr()把长的那一串字符的剩余部分追加到末尾。

class Solution {
public:
    string mergeAlternately(string word1, string word2) {
        string nw="";
        int w1size = word1.size();
        int w2size = word2.size();
        int wsize = min(w1size,w2size);
        for (int i =0; i<wsize;++i){
            nw += word1[i];
			nw += word2[i];
        }
        if(wsize<w1size) nw +=word1.substr(wsize, w1size-wsize);
        if(wsize<w2size) nw +=word2.substr(wsize, w2size-wsize);
        return nw;       
    }
};

2 字符串的最大公因子

题目:

对于字符串 s 和 t,只有在 s = t + ... + t(t 自身连接 1 次或多次)时,我们才认定
 “t 能除尽 s”。

给定两个字符串 str1 和 str2 。返回 最长字符串 x,要求满足 x 能除尽 str1 且 x 
能除尽 str2 。

题解:枚举法
先用string.length()计算出较短的长度i,
然后用for循环把枚举每一个可能得长度,
如果i能够整除len1和len2,则用check函数检查长度为i的这段字符串能够是不是str1和str2的公因子。

class Solution {
    bool check(string t,string s){
        int lenx = (int)s.length() / (int)t.length();
        string ans = "";
        for (int i = 1; i <= lenx; ++i){
            ans = ans + t;
        }
        return ans == s;
    }
public:
    string gcdOfStrings(string str1, string str2) {
        int len1 = str1.length(), len2 = str2.length();
        for (int i = min(len1, len2); i >= 1; --i){ // 从长度大的开始枚举
            if (len1 % i == 0 && len2 % i == 0){
                string X = str1.substr(0, i);
                if (check(X, str1) && check(X, str2)) return X;
            }
        }
        return "";
    }
};

3 拥有最多糖果的孩子

题目:

给你一个数组 candies 和一个整数 extraCandies ,其中 candies[i] 代表第 i 个孩子拥有
的糖果数目。

对每一个孩子,检查是否存在一种方案,将额外的 extraCandies 个糖果分配给孩子们之后,此
孩子有 最多 的糖果。注意,允许有多个孩子同时拥有 最多 的糖果数目。

题解:
先用*max_element(candies.begin(), candies.end())找到最大值max_num,
然后for循环查看每个孩子加上extraCandies后是否大于max_num。

class Solution {
 public:
	 vector<bool> kidsWithCandies(vector<int>& candies, int extraCandies) {
		 int clen = candies.size();
		 vector<bool> rev(clen,false);
		 int max_num = *max_element(candies.begin(), candies.end());
		 for (int i = 0; i < clen; ++i) {
			 if (candies[i] >= max_num - extraCandies)
				 rev[i] = true;
		 }
		 return rev;
	 }
 };

4 种花问题

题目:

假设有一个很长的花坛,一部分地块种植了花,另一部分却没有。可是,花不能种植在相邻的
地块上,它们会争夺水源,两者都会死去。

给你一个整数数组 flowerbed 表示花坛,由若干 01 组成,其中 0 表示没种植花,1 表示
种植了花。另有一个数 n ,能否在不打破种植规则的情况下种入 n 朵花?能则返回 true ,
不能则返回 false

题解:贪心,为了种得多,需要在任意两朵花之间种上最多的花。
prev记录上一朵花种在哪里,初始时可以认为是位置-1,用count记录可以种下几朵花。用for循环遍历每一个位置,如果找到一个位置上是有花的,则查看上一朵花是在哪,然后计算出两朵花之间能够种多少朵花。
情况1:遍历到第一朵花(prev<0),此时,只能在[0,i-2]上种花,总共有i-1个位置,
根据规则,可以种 ⌊ i / 2 ⌋ \lfloor i/2 \rfloor i/2朵花(i为奇数:0,2,…i-3;i为偶数:0,2,…i-2)。
情况2:遍历到中间的某多花(prev>=0)且循环未结束,此时,只能在[prev+2,i-2]上种花,总共有i-prev-3个位置,根据规则,种 ⌊ i − p r e v − 2 / 2 ⌋ \lfloor i-prev-2/2 \rfloor iprev2/2朵花。
情况3:遍历完发现没有一个位置有花for循环结束且(prev<0),此时,能在[0,m-1]上种花,总共有m个位置,根据规则,可以种 ⌊ m + 1 / 2 ⌋ \lfloor m+1/2 \rfloor m+1/2朵花。
情况4:遍历完后,发现最后一朵花后面还有空花坛for循环结束且(prev>=0),此时,只能在[prev+2,m-1]上种花,总共有m-prev-2个位置,根据规则,种 ⌊ m − p r e v − 1 / 2 ⌋ \lfloor m-prev-1/2 \rfloor mprev1/2朵花。

class Solution {
public:
    bool canPlaceFlowers(vector<int>& flowerbed, int n) {
        int count = 0;
        int m = flowerbed.size();
        int prev = -1;
        for (int i = 0; i < m; ++i) {
            if (flowerbed[i] == 1) {
                if (prev < 0) {
                    count += i / 2;
                } else {
                    count += (i - prev - 2) / 2;
                }
                prev = i;
            }
        }
        if (prev < 0) {
            count += (m + 1) / 2;
        } else {
            count += (m - prev - 1) / 2;
        }
        return count >= n;
    }
};

5 反转字符串中的元音字母

题目:

给你一个字符串 s ,仅反转字符串中的所有元音字母,并返回结果字符串。

元音字母包括 'a''e''i''o''u',且可能以大小写两种形式出现不止一次。

题解:双指针
unordered_set存储所有元音字母,unordered_set.count()判断字符是否为元音。
while循环,分别从两头遍历,如果两边指针都是元音字母则用swap调换它们,否则指针指向下一个。

class Solution {
 public:
	 string reverseVowels(string s) {
		 unordered_set<char> mp = {
			 {'a'},
			 {'e'},
			 {'i'},
			 {'o'},
			 {'u'},
			 {'A'},
			 {'E'},
			 {'I'},
			 {'O'},
			 {'U'}
		 };
		 int i = 0, j = s.length()-1;
		 while(i<j) {
			 if (mp.count(s[i]) > 0 && mp.count(s[j]) > 0) {
				 swap(s[i], s[j]);
				 i++;
				 j--;
				 continue;
			 }
			 if (mp.count(s[i]) <= 0) {
				 i++;
				//  continue;
			 }
			 if (mp.count(s[j]) <= 0) {
				 j--;
				//  continue;
			 }

		 }
		 return s;
	 }
 };

6 除自身以外数组的乘积

题目:

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

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

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

题解:左右乘积
先用vector类型的left存储每个位置左侧所有数的乘积。
然后for循环反向遍历,用tmp记录每个位置右侧所有数的乘积,并把tmp乘以left[i]得到位置i的结果。

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

7 递增的三元子序列

题目:

给你一个整数数组 nums ,判断这个数组中是否存在长度为 3 的递增子序列。

如果存在这样的三元组下标 (i, j, k) 且满足 i < j < k ,使得 nums[i] < nums[j] < nums[k] ,返回 true ;否则,返回 false

题解:贪心
stack类型st3记录递增序列,如果它长度flag等于3则证明找到了。
情况1:如果有连续3个递增的数,那肯定能连续入栈st3,使其长度为3。
情况2:如果有连续2个递增a和b,然后出现一个c,它小于b大于a。则把b出栈,让c入栈,然后继续遍历。之所以这么做,是因为如果后续的元素中出现了某个元素e比c大,那就有ace符合条件,如果e大于b,由于c小于b那e同样大于c。所以把b出栈,让c入栈是合理的。
情况3:如果有连续2个递增a和b,然后出现一个c,它小于a和b。先用一个变量max2(初始化时赋值为INT_MAX)记录下b的值,然后把a和b都出栈,让c入栈。继续遍历。之所以要用max来记录b的值,是因为如果后续有一个变量e大于b(也大于c),那么实际上abe已经满足条件了, 但我们把它出栈了,导致现在栈里只有c一个,ce的长度只有2反而不满足。所以用max2来记录下这种特殊情况。
情况4:如果当前遍历的值e大于max2,则证明之前存在两个递增数a和b,且a 情况5:如果当前遍历的值e大于栈顶元素且栈中已经有两个元素了,则证明有3个递增数,满足条件。

class Solution {
 public:
	 bool increasingTriplet(vector<int>& nums) {
		 int numsize = nums.size();
		 if (numsize < 3) return false;
		 int flag = 1;
		 stack<int> st3;
		 st3.push(INT_MAX);
		 int max2 = INT_MAX;
		 for (int i = 0; i < numsize; ++i) {
			 if (nums[i] > st3.top()) {
				 flag++;
				 if (flag == 3|| max2 < nums[i]) return true;
				 st3.push(nums[i]);
			 }
			 else if (nums[i] < st3.top()) {
				 if(flag ==2) max2 = st3.top();
				 while (!st3.empty()&& nums[i] <= st3.top()) {
					 st3.pop();
					 flag--;
				 }
				 st3.push(nums[i]);
				 flag++;
			 }
		 }
		 return false;
	 }
 };

8 压缩字符串

题目:

给你一个字符数组 chars ,请使用下述算法压缩:

从一个空字符串 s 开始。对于 chars 中的每组 连续重复字符 :

如果这一组长度为 1 ,则将字符追加到 s 中。
否则,需要向 s 追加字符,后跟这一组的长度。
压缩后得到的字符串 s 不应该直接返回 ,需要转储到字符数组 chars 中。需要注意的是,
如果组长度为 1010 以上,则在 chars 数组中会被拆分为多个字符。

请在 修改完输入数组后 ,返回该数组的新长度。
你必须设计并实现一个只使用常量额外空间的算法来解决此问题。

题解:双指针
用lenc记录已压缩子串的长度,用pre记录上一个位置是哪种字符,用lennow记录pre字符的长度。
chars[i]!=pre时,证明有新的字符了,计算下lennow的长度(对应的字符表示暂存于tmp中),然后将pre和对应长度存入chars的相应位置(lenc)中。
chars[i]==pre时,lennow+=1
for循环结束后,需要将最后一个字符及其长度也压缩存入chars中。

class Solution {
 public:
	 int compress(vector<char>& chars) {
		 int lenc = 0, chlen = chars.size(), lennow = 1;
		 char pre = chars[0];
		 string tmp = "";
		 for (int i = 1; i < chlen; ++i) {
			 if (pre == chars[i]) {
				 lennow++;
			 }
			 else {

				 chars[lenc++] = pre;
				 if (lennow>1){
					 while (lennow > 0) {
						 tmp += to_string(lennow % 10);
						 lennow /= 10;
					 }
					 for (int j = tmp.size() - 1; j >= 0; --j) {
						 chars[lenc++] = tmp[j];
					 }
					 tmp="";
				 }
				 pre = chars[i];
				 lennow = 1;
			 }
		 }

		 chars[lenc++] = pre;
		 if (lennow > 1) {
			 while (lennow > 0) {
				 tmp += to_string(lennow % 10);
				 lennow /= 10;
			 }
			 for (int j = tmp.size() - 1; j >= 0; --j) {
				 chars[lenc++] = tmp[j];
			 }
		 }
		 return lenc;
	 }
 };

1-8 解题总结

a. 涉及字符、数组的题,往往都是需要for循环遍历的。
b. 考察的点一般是如何减少遍历复杂度,比如不要使用嵌套for循环。
c. 有时候也会考察你是否能够在原数组上做改动或者只定义1个新的数组而不是2个来实现,比如第8题要求只能在原数组上修改,不能定义新的O(n)级别的变量。还有第6题本来是可以用一个left和一个right数组来分别记录左累乘和右累乘,然后用一个rev数组来计算最终结果的,但为了减低空间复杂度,可以只用一个left数组和一个right变量来实现。
d. 常用方法:贪心、双指针。

你可能感兴趣的:(算法题,leetcode,算法,c++)