算法总结(哈希、字符串)

引言

454. 四数相加 II - 力扣(LeetCode)

15. 三数之和 - 力扣(LeetCode)

18. 四数之和 - 力扣(LeetCode)

151. 反转字符串中的单词 - 力扣(LeetCode)

第一题

这一题的主要思路是先随机选择两个数组计算这两个数组的和,那么如果要四个数组相加等于0,也就是另外两个数组相加是0,所以我们只需要计算前者的所有情况,但是因为这个不需要去重,并且还要计算每一个数的出现次数,所以我们这里选择用map来解决问题

cpp 复制代码
class Solution {
public:
    int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
        unordered_map<int, int> map;
        for(int a : nums1) {
            for(int b : nums2) {
                map[a + b]++;
            }
        }
        int count = 0;
        for (int c : nums3) {
            for(int d : nums4) {
                if(map.find(-(c + d)) != map.end()) {
                    count += map[-(c + d)];
                }
            }
        }
        return count;
    }
};

第二题

这一题的困难点就是在于题目要求的是不重复的三元组,所以如果我们盲目使用哈希的话,但是对于哈希来说去重是非常困难的,所以这一题使用哈希是不合适的。可能有的人会想,哈希不就是用来去重的吗?举个例子,{-1, -1, 2}这是一个符合答案的数组,但是如果用哈希先去重的话,那么就是{-1,2},直接给答案丢掉了,所以这里说的去重是对于答案的去重。

那么应该怎么思考呢?我们这里考虑双指针的思想,首先对数组排序(一定要排序,不然我们的操作没有任何的意义),之后遍历一个i,然后不断移动left和right(如果大于0就移动right,小于0就移动left,希望和不断地趋近于0)这样子就可以找到以i为起点的所有答案。

那么我们就要面临第一个去重,{-1,-1,0,1}如果有个数组长这样,那么我们很明显要去重,因为我们的数组已经被排序,所以,相等的元素一定是挨在一起的,如此我们如果发现numsi == numsi - 1,就continue。这里还是有个坑,有的人会写成numsi == numsi + 1,这个意思就完全不同了,i是当前的元素,还没有做任何的操作,而i+1是下一个元素,更不可能做任何的操作,两个没有任何操作过的元素进行去重,是一个很错误的选择。而i - 1是我们已经计算过的元素,如果和一个和我们已经计算过的元素相等,那么就要去重.

i的去重是是起点,而第二个去重是对于left和right,{-1,-1,-1,-1,1,1,1,1,1,1}这样的一个数组我们不仅仅是i会有重复,在确定一个i之后,我们的left和right也可能重复,也会导致最后的三元组出现元素的重复,这个去重的操作和i不一样 ,因为left和right的移动是建立在left和right已经比较过了,我们把这个操作放在push_back后面,所以如果再和left-1==left比较,那么就是两个已经计算过的元素进行比较,没有任何的意义,反而我们要和left+1 == left比较。不过还是要注意的是,这个只是去重,也就是说,经过这个操作之后,我们得到的指针的位置正好是最后一个重复的元素,所以整个结束之后,我们不要忘记了left++和right--。

cpp 复制代码
class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> res;
        sort(nums.begin(), nums.end());
        for(int i = 0; i < nums.size(); i++) {
            if (nums[i] > 0) {
                break;
            }
            if (i > 0 && nums[i] == nums[i - 1]) {
                continue;
            }
            int right = nums.size() - 1;
            int left = i + 1;
            while(left < right) {
                if (nums[i] + nums[left] + nums[right] > 0) {
                    right--;
                } else if (nums[i] + nums[left] + nums[right] < 0) {
                    left++;
                } else {
                    res.push_back({nums[i], nums[left], nums[right]});
                    while (left < right && nums[left + 1] == nums[left]) {
                        left++;
                    }
                    while (left < right && nums[right - 1] == nums[right]) {
                        right--;
                    }
                    left++;
                    right--;
                }
            }
        }
        return res;
    }
};

第三题

这一题和上一题区别其实不大,我觉的最大的区别就是目标target从0变成了任意数。如果target是正数或者是0还好说,只要nums【k】比target大,那么就直接break,但是如果是负数,我们都知道负数是越加越小的,所以我们不可以盲目的break。那么在代码里面,我们就需要提前进行判断。

这一题的整体思路其实就是在第二题的外面套一个for循环,遍历新加的一个元素,只不过去重的细节可能有点变化

cpp 复制代码
class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        vector<vector<int>> res;
        sort(nums.begin(), nums.end());
        for(int k = 0; k < nums.size(); k++) {
            if (nums[k] > target && target >= 0) {
                break;
            }
            if (k > 0 && nums[k] == nums[k - 1]) {
                continue;
            }
            for(int i = k + 1; i < nums.size(); i++) {
                if (nums[k] + nums[i] > target && target >= 0) {
                    break;
                }
                if (i > k + 1 && nums[i] == nums[i - 1]) {
                    continue;
                }
                int left = i + 1;
                int right = nums.size() - 1;
                while(left < right) {
                    if((long)nums[i] + nums[k] + nums[left] + nums[right] > target) {
                        right--;
                    } else if ((long)nums[i] + nums[k] + nums[left] + nums[right] < target) {
                        left++;
                    } else {
                        res.push_back({nums[k], nums[i], nums[left], nums[right]});
                        while (left < right && nums[left] == nums[left + 1]) {
                            left++;
                        }
                        while (left < right && nums[right] == nums[right - 1]) {
                            right--;
                        }
                        left++;
                        right--;
                    }
                }
            }
        } 
        return res;  
    }
};

第四题

这一题最难的点是移除多余的空格,题目里面说不仅仅单词之间有空格,而且第一个单词和最后一个单词后面也可能有空格,所以我们要移除多余的空格。移除多余的空格,我们这里采用的空间复杂度是O(1),这个的思想和移除元素差不多,用的也是快慢指针,当遇到我们需要去掉的元素时,快慢指针才会开始分开,而分开之后还是慢指针填充新的数组,快指针用来遍历。这个好处就是前面的空格,我们一开始就会直接去掉,因为slow开始填充字母的时候就是在索引0处,而末尾的空格,需要我们单独判断。但是因为我们中间有空格,这些空格不需要我们删除,所以我们采取一个need的变量来处理,这个逻辑是当我们遇到字母的时候才会激活need,然后当fast指针遇到空格了之后,就会进入need的if判断里面,在slow地方增加一个空格,然后重新改变need的值。

所以这一套循环操作完之后,如果字符串最后面有空格,那么我们在操作里面会在这个字符串的最后加上一个空格,当然这个空格我们不需要,要删除(记得我们这个slow是索引,但是resize()里面的参数是从1开始的,两个正好差一个)

然后在最后的主函数里面,我们for()取等是因为我们要统一条件,方便写代码,因为我们可以假设最后一个是空格,然后可以当作是一个单词一起处理。

cpp 复制代码
class Solution {
public:
    void reverse(string& s, int start, int end) {
        for(int i = start, j = end; i < j; i++, j--) {
            swap(s[i], s[j]);
        }
    }

    string removeExtraSpace(string& s) {
        int slow = 0;
        bool need = false;
        for (int fast = 0; fast < s.size(); fast++) {
            if (s[fast] != ' ') {
                s[slow++] = s[fast];
                need = true;
                continue;
            }
            if (need) {
                s[slow++] = ' ';
                need = false;
            }
        }
        if (s[slow - 1] == ' ') {
            s.resize(slow - 1);
        } else {
            s.resize(slow);
        }
        return s;
    }

    string reverseWords(string s) {
        removeExtraSpace(s);
        reverse(s, 0, s.size() - 1);
        int start = 0;
        for (int i = 0; i <= s.size(); i++) {
            if (i == s.size() || s[i] == ' ') {
                reverse(s, start, i - 1);
                start = i + 1;
            }
        }
        return s;
    }
};

总结

本篇文章到这里就结束了!!!希望可以帮助大家理解~~~