刷穿LeetCode——Task03

这篇博客记录第3天刷题的解题思路与心得体会。

11. 盛最多水的容器

给你 n 个非负整数 a1,a2,…,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0) 。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
说明:你不能倾斜容器。
示例 1:
输入:[1,8,6,2,5,4,8,3,7]
输出:49
解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。
MaxArea
示例 2:
输入:height = [1,1]
输出:1
示例 3:
输入:height = [4,3,2,1,4]
输出:16
提示:
n = height.length
2 <= n <= 3 * 104
0 <= height[i] <= 3 * 104

分析:这道题可以用暴力算法依次对 0 ≤ i < h e i g h t . s i z e ( ) − 1 0 \le i <height.size()-1 0i<height.size()1 1 ≤ j < h e i g h t . s i z e ( ) 1 \le j < height.size() 1j<height.size()遍历求 m i n ( h e i g h t [ i ] , h e i g h t [ j ] ) × ( j − i ) min(height[i],height[j]) \times (j-i) min(height[i],height[j])×(ji) 的最大值,但算法复杂度为 O ( n 2 ) O(n^2) O(n2)超出时间限制。可以使用贪心算法求解。这里引用LeetCode评论区大佬的分析:

使用两个指针,值小的指针向内移动,这样就减小了搜索空间 因为面积取决于指针的距离与值小的值乘积,如果值大的值向内移动,距离一定减小,而求面积的另外一个乘数一定小于等于值小的值,因此面积一定减小,而我们要求最大的面积,因此值大的指针不动,而值小的指针向内移动遍历。

class Solution {
    
    
public:
    int maxArea(vector<int>& height) {
    
    
        if (height.size() <= 1)
            return -1; 
        int i = 0, j = height.size()-1;
        int Area = 0;
        while(i < j) {
    
    
            Area = max(Area, min(height[i], height[j]) * (j-i));
            //贪心求解最大面积,较大值指针不动,较小值指针向内移动
            if(height[i] < height[j]) 
                i++;
            else
                j--;
        }
        return Area;
    }
};

14. 最长公共前缀

编写一个函数来查找字符串数组中的最长公共前缀。
如果不存在公共前缀,返回空字符串 “”。
示例 1:
输入:strs = [“flower”,“flow”,“flight”]
输出:“fl”
示例 2:
输入:strs = [“dog”,“racecar”,“car”]
输出:""
解释:输入不存在公共前缀。
提示:
0 <= strs.length <= 200
0 <= strs[i].length <= 200
strs[i] 仅由小写英文字母组成

分析:随便选择一个字符串作为标准,依次取它的前缀字符进行依次遍历,每个都与其他所有字符串的相同下标字符比较,看是否相同,直到遇到不全部相同的下标。这样取公共前缀子串的时间性能是 O ( n ∗ m ) O(n*m) O(nm),参考大佬的代码如下:

class Solution {
    
    
public:
    string longestCommonPrefix(vector<string>& strs) {
    
    
        if(strs.empty())    return "";
        if (strs.size() == 1)   return strs[0];

        string prefix = "";
        int idx = 0;
        bool go_on = true;
        while(true) {
    
    
            for (int i = 1; i < strs.size(); i++) {
    
    
                if (!(idx < strs[0].size() && idx < strs[i].size() && strs[0][idx] == strs[i][idx])) {
    
    
                    go_on = false;
                    break;
                }        
            }
            if(go_on) 
                prefix.push_back(strs[0][idx++]);
            else 
                break;        
        }
        return prefix;      
    }
};

15. 三数之和

给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
示例 1:
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
示例 2:
输入:nums = []
输出:[]
示例 3:
输入:nums = [0]
输出:[]
提示:
0 <= nums.length <= 3000
-105 <= nums[i] <= 105

分析:这题如果用三重循环暴力搜索很容易超时,后二重循环可以转换为双指针问题,且为了防止找到重复的三元组,先对原数组进行排序,这里直接摘抄评论区大佬的分析如下:

如果用暴力解,用一个三重循环遍历那么时间复杂度在O( N 3 N^3 N3),然后稍微进行优化,根据题目:找到三元组不能重复。可以想到,如果要排序(能保证重复出现的数字在一起,并且时间复杂度为 O ( N l o g N ) O(NlogN) O(NlogN),没啥影响。还能在第二重循环的枚举中找到不小于当前第一重循环的枚举元素,和第三重循环同理,找到不小于第二重循环的枚举元素。
那么能想到了排序,但是本质上还是三重循环,那么时间复杂度还是O( N 3 N^3 N3),继续优化,将下面的两重循环变成一重循环:
可以发现我们是固定了第一个数然后去找其他两个数的,那么可以将后面两个数看成一个数,那么问题就变成了在有序数组中从[i+1, len-1]这个范围内找到一个符合要求的数,那么就变成了双指针问题,而这个数的值不再是mid,而是两个边界left和right的和。而指针的移动条件就是:如果当前的sum值太大,那么右指针就移动;如果sum太小,那么左指针就移动;如果值正好,那么就是当前值,并且左指针右移,右指针左移(因为是找到所有满足的解);循环的结束条件就是左右指针相遇 而双指针情况下,第二三重循环就从O( N 2 N^2 N2)变成 O ( N ) O(N) O(N).

C++代码如下:

#include <iostream>
#include <vector>    
#include <stdlib.h>
#include <algorithm>

using namespace std;

class Solution {
    
    
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
    
    
        vector<vector<int>> res;      //二重vectorv数组
        
        if (nums.size() < 3)
            return res;
        
        //从三重循环暴力搜索到二重循环双指针求解
        sort(nums.begin(), nums.end());   //由小到大排序,保证重复数字在一起且方便搜索
        for (int i = 0; i < nums.size() - 2; i++) {
    
    
            if(nums[i] > 0)       //一重循环元素(<0)<二重<三重
                return res;
            if(i > 0 && nums[i] == nums[i-1])  //一重循环跳过重复数字
                continue;
            int left = i + 1, right = nums.size() - 1;   //[i+1, len-1]上的左右指针
            while(left < right) {
    
    
                int sum = nums[i] + nums[left] + nums[right];
                if (sum < 0)                //当前sum值太小,左指针移动
                    left++;
                else if (sum > 0)           //当前sum值太大,右指针移动
                    right--;
                else {
    
                         //sum=0,左指针和右指针都移动继续搜索
                    res.push_back({
    
    nums[i], nums[left], nums[right]});
                    while (left < right && nums[left] == nums[left + 1]) left++;   //跳过重复数字
                    while (left < right && nums[left] == nums[right - 1]) right--;
                    left++; 
                    right--;
                }
            }                       
        } 
        return res;
    }
};

int main(){
    
    
	Solution S;
    int a[] = {
    
    -1, 0 , 1, 2, -1, -4};
    vector<int> array(begin(a), end(a));
    vector<vector<int>> threeArr = S.threeSum(array);
    for (int i = 0; i < threeArr.size(); i++) {
    
    
        for (int j = 0; j < threeArr.at(i).size(); j++)
            cout << (threeArr.at(i)).at(j)<<" ";
        cout<<endl;
}
	system("pause");
	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_33007293/article/details/112598290