代码随想录-day9 哈希表(3) & 字符串(1)

代码随想录-day9 哈希表(3) & 字符串(1)

1、LeetCode 15 三数之和

题目分析:
本题的名字很容易让人联想到我们之前做的四数之和Ⅱ,但是这两道题的方法不尽相同。
其中最重要的不同就是,之前有多个数组,所以在考虑排列的时候,不用考虑重复的情况。但是本题就一个数组,肯定是要去除重复的。
在这里插入图片描述
这里出现了两个-1,我们就得保证就是说两个-1并不是一个位置的,而是两个位置的!!
本题使用哈希表的方法也试过,最后的去重问题很难考虑清楚,在本文最后把相应的解答过程写一下。而解决这道题比较好的方法并不是哈希表法,而是使用使用双指针。以下是思路步骤:

  • 首先对nums进行排序,这是双指针法的关键所在。
  • 其次我们采用定一移二的方法,即固定三个数中的一个数(记作i),然后定义两个指针,左指针(left)指向第二个数,右指针(right)指向第三个数。
  • 如果i+left+right > 0,说明right太右了,那么right–,否则就是left++。
  • 这里面涉及到两个重要问题,去重和剪枝。去重指的是如果已经满足条件了,left和right移动了之后,但是和之前是一样的,那么还要继续移动;剪枝指的是i在移动的过程中,如果发现nums[i]和nums[i-1]一样,就跳过。
    细节: 如果经过排序后一旦出现了某个元素大于0的情况,那么就可以直接跳过了。

题目解答:

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        //  首先对nums进行排序
        sort(nums.begin(), nums.end());
        vector<vector<int>> ans;
        for (int i = 0; i < nums.size(); i++) {
            if (nums[i] > 0 ) break;  // 排序之后的数组,如果存在已经大于0的数字,那么肯定后面就不用考虑了
            if (i > 0 && nums[i] == nums[i - 1]) continue;  // 剪枝
            int left = i + 1;  // 左指针从当前元素的下一个元素
            int right = nums.size() - 1;
            while (left < right) {
                if (nums[i] + nums[left] + nums[right] > 0) right--;
                else if(nums[i] + nums[left] + nums[right] < 0) left++;
                else {  // 存放结果并去重
                    ans.push_back({nums[i], nums[left], nums[right]});  // 存放结果
                    while (left < right && nums[right] == nums[right - 1]) right--;  // 去重
                    while (left < right && nums[left] == nums[left  + 1]) left++;
                    // 一旦找到了,left和right都需要更新,只更新一个的话代码会多运行一次,不更新的话会陷入循环
                    left++;
                    right--; 
                }
            }
        }
        return ans;
    }
};

2、LeetCode 18 四数之和

题目分析:
这个题目的要求和实现和三数之和基本完全一致,就是由于有四个数需要定二移二的思路来进行,需要用三个循环来解决。另外,这个题的和不是0,所以特殊情况的判定也是需要更改的。

题目解答:

class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        // 使用双指针法解决
        sort(nums.begin(), nums.end());
        vector<vector<int>> ans;
        for (int i = 0; i < nums.size(); i++) {  // a的控制
            if (nums[i] > target) break;  // 如果nums[i]已经比target大了,没有继续下去的必要,nums已经经过排序了
            // 第一次剪枝
            if (i > 0 && nums[i] == nums[i - 1]) continue;
            for (int j = i + 1; j < nums.size(); j++) {  // b的控制,从i的下一个开始,保证不重复
                if (nums[i] + nums[j] > target) break;  // 理由同上
                // 第二次剪枝
                if (j > i + 1 && nums[j] == nums[j - 1]) continue;
                int left = j + 1;
                int right = nums.size() - 1;
                while (left < right) {
                    if (nums[i] + nums[j] + nums[left] + nums[right] > target) right--; 
                    else if (nums[i] + nums[j] + nums[left] + nums[right] > target) left++;
                    else {  // 结果存放以及去重
                        ans.push_back({nums[i], nums[j], nums[left], nums[right]});
                        while (left < right && nums[right] == nums[right - 1]) right--;
                        while (left < right && nums[left] == nums[left + 1]) left++;
                        // 一旦找到了,索引更新
                        left++;
                        right--;
                    }
                }
            }
        }
        return ans;
    }
};

注意这两个题目与之前的四数之和Ⅱ的不同点。
接下来就是字符串章节相关的题目。

3、LeetCode 344 反转字符串

题目分析:
对于任何语言而言,肯定都有反转字符串的函数,但是从训练的角度而言,最好不要使用。只有说当使用函数是自己的算法中的一小步,可以使用内置函数。
在之前的反转链表的时候也使用过双指针的思路来操作,这里依然用的双指针的思路。

题目解答:

class Solution {
public:
    void reverseString(vector<char>& s) {
        for (int i = 0, j = s.size() - 1; i < j; i++, j--) {
            swap(s[i], s[j]);  // 交换
        }
    }
};

4、LeetCode 541 反转字符串Ⅱ

题目分析:
其实这里面只有两种情况:

  • 当前位置距离末尾大于等于k时,从当前开始到第k个这一部分反转,i加2k;
  • 当前位置距离末尾小于k,直接从当前位置到末尾这一部分反转。

题目解答:

class Solution {
public:
    string reverseStr(string s, int k) {
        for (int i = 0; i < s.length(); i++) {
            if (i + k - 1 < s.length()) {  // 这里直接用i + k - 1作为判断条件即可,只要剩余的大于k,说白了都是反转k个
                reverse(s, i, i + k - 1);
                i += 2 * k - 1;
            }
            else {
                reverse(s, i, s.length() - 1);  // 如果少于k个,那直接进行反转所有就行了
                break;  // 处理就是说k>len的情况,这个时候直接退出即可
            }

        }
        return s;
    }

    void reverse(string& s, int start, int end) {
        for (int i = start, j = end; i < j; i++, j--) {
            swap(s[i], s[j]);
        }
    }
};

5、剑指Offer 5 替换空格

题目分析:
本题可以考虑在不开辟额外空间的基础上进行操作。

  • 首先统计空格,resize一下s的大小;
  • 然后从后往前依次替换即可。

注意这里为什么是从后往前替换,设想一下,假设我们从前往后替换,那么每替换一个,所有的的字符都要往后移动,时间复杂度为O(n^2),但是如果我们从后往前替换,只需要遍历一次字符串即可,时间复杂度为O(n)。

题目解答:

class Solution {
public:
    string replaceSpace(string s) {
        // 本题可以在原字符串上进行操作,而不额外申请空间,提前扩充s的长度
        int cnt = 0;
        for (auto& ch : s) if (ch == ' ') cnt++;
        s.resize(s.length() + 2 * cnt);
        // 注意这里替换操作,如果从前往后,需要替换的所有都需要往后移动,时间复杂度是O(n^2)
        // 但是如果从后往前进行替换的,只需要遍历一次就可以了,时间复杂度为O(n)
        for (int i = s.size() - 1; i >= 0; i--) {
            // cnt代表空格的字数,i如果是新数组的索引,那么i - 2 * cnt就是旧数组的索引
            if (s[i - 2 * cnt] != ' ') s[i] = s[i - 2 * cnt];
            else {
                s[i] = '0';
                s[--i] = '2';
                s[--i] = '%';
                cnt--;  // 前面的空格数目减1
            }
        }
        return s;
    }
};

注意这种小小的技巧,我们上面的方法既节省了时间的开销,同时也节省了空间的开销。在平时写题的时候,我们要多思考这些方法。

所谓活着的人,就是不断的挑战的人,不断攀登命运险峰的人。——雨果

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值