LeetCode Array 数组问题-归纳1(题号1, 4,11,15,16,18,26)

1. Two Sum (Array, Hash Table) 两数之和

题目描述:

给定一个整数数组和一个目标值,找出数组中和为目标值的两个数的索引。

你可以假设每个输入只对应一种答案,且同样的元素不能被重复利用。

示例:

给定 nums = [2, 7, 11, 15], target = 9

因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]

思路分析:

为了找到两个数索引,可以固定一个nums[i],然后再在数组中搜索(target-nums[i])的索引。

1. 暴力破解

两层遍历,固定一个元素,循环搜索另外一个元素,时间复杂度0(n^2),效率太低舍弃;

2. 哈希表(hash table)

由于对Hash表的搜索是0(1),所以我们可以先把数组元素全部放入Hash表(因为要求返回元素索引,所以让数组元素作键,数组元素索引作值)。这样只需一遍遍历数组元素即可,时间复杂度0(n)。


class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        vector<int> vi;
        unordered_map<int,int> hmap;//声明一个Hash Table
        for(auto i = 0; i < nums.size(); i++){
            hmap.insert(pair<int,int>(nums[i],i));
        }
        for(auto j = 0; j < nums.size(); j++){
            if(hmap.find(target - nums[j]) != hmap.end()){
                int k = hmap[target - nums[j]];                
                if(k < j){//为了避免重复,按序输出
                    vi.push_back(k);
                    vi.push_back(j);                 
                }
               
            }
        }
        return vi;
    }
};
4. Median of Two Sorted Arrays(Array,Binary Search,Divide and Conquer)两个排序数组的中位数

题目描述:

给定两个大小为 m 和 n 的有序数组 nums1 和 nums2 

请找出这两个有序数组的中位数。要求算法的时间复杂度为 O(log (m+n)) 。

示例 1:

nums1 = [1, 3]
nums2 = [2]

中位数是 2.0

示例 2:

nums1 = [1, 2]
nums2 = [3, 4]

中位数是 (2 + 3)/2 = 2.5

思路分析:

本题需要考虑两个方面:1.两数组总数为奇数,直接返回中间值。2.两数组总数为偶数时,返回中间两个值得均值。

接下来需要使用递归:在递归函数中,要保证第一个数组总是小于第二个数组;如果第一个数组为空,直接返回第二个数组中间值;若求第一个最小的元素,则返回两数组两个元素较小的一个。

再接下来使用二分搜索:具体看下面程序

class Solution {
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) { //Divide Conquer
        int m = nums1.size();
        int n = nums2.size();
        //重新创建两个vector<int>对象,目的防止在总个数为偶数时传参出错
        vector<int> x = nums1;
        vector<int> y = nums2;
        int sum = m + n; 
        if(sum & 0x1){ // 当总个数为奇数时直接返回中间值
            return findTop_K_Value(nums1, m, nums2, n, sum / 2 + 1);
        }
        else{// 当总个数为偶数时返回中间两个值的均值
            return ( findTop_K_Value(nums1, m, nums2, n, sum / 2)
                + findTop_K_Value(x, m, y, n, sum / 2 + 1) ) / 2.0; //x,y的目的防止在总个数为偶数时传参出错
        }
        
    }
    
    double findTop_K_Value(vector<int>& a, int m, vector<int>& b, int n, int Top_K){
        if(m > n){//保证第一个数组总是小于第二个数组
            return findTop_K_Value(b, n, a, m, Top_K);
        }
        if(m == 0){//如果第一个数组为空,直接返回第二个数组的中间值
            return b[Top_K - 1];
        }
        if(Top_K == 1){//如果要求的是第一个返回的最小值,则求a[0]和b[0]较小的一个
            return min(a[0], b[0]);
        }
        //将Top_K分成两部分,二分搜索
        int part_a = min(Top_K / 2, m), part_b = Top_K - part_a;
        if(a[part_a - 1] < b[part_b -1]){
            a.erase(a.begin(), a.begin() + part_a); //删除a[part_a]前面元素
            return findTop_K_Value(a, m - part_a, b, n, Top_K - part_a);
        }
        else if(a[part_a - 1] > b[part_b -1]){
            b.erase(b.begin(), b.begin() + part_b); //删除b[part_b]前面元素
            return findTop_K_Value(a, m, b, n - part_b, Top_K - part_b);
        }
        else
            return a[part_a - 1];
    }      
};

11.    Container With Most Water(Array,Two Pointers)盛最多水的容器

题目描述:

给定 n 个非负整数 a1a2,...,an,每个数代表坐标中的一个点 (iai) 。画 n 条垂直线,使得垂直线 i 的两个端点分别为 (iai) 和 (i, 0)。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。

注意:你不能倾斜容器,n 至少是2。

思路分析:

1.首先假设我们找到能取最大容积的纵线为 i , j (假定i<j),那么得到的最大容积 C = min( ai , aj ) * ( j- i) ;

2.下面我们看这么一条性质:

①: 在 j 的右端没有一条线会比它高! 假设存在 k |( j<k && ak > aj) ,那么  由 ak> aj,所以 min( ai,aj, ak) =min(ai,aj) ,所以由i, k构成的容器的容积C' = min(ai,aj ) * ( k-i) > C,与C是最值矛盾,所以得证j的后边不会有比它还高的线;

②:同理,在i的左边也不会有比它高的线;

这说明什么呢?如果我们目前得到的候选: 设为 x, y两条线(x< y),那么能够得到比它更大容积的新的两条边必然在  [x,y]区间内并且 ax' > =ax , ay'>= ay;

3.所以我们从两头向中间靠拢,同时更新候选值;在收缩区间的时候优先从  x, y中较小的边开始收缩。 

算法时间复杂度O(n)

此处参考:https://blog.youkuaiyun.com/runningtortoises/article/details/45568305

class Solution {
public:
    int maxArea(vector<int>& height) {
        int Max = -1, area, left = 0, right = height.size()-1,k;
        while(left < right){//从两头向中间靠拢,同时更新候选值
            area = (height[left] < height[right] ? height[left] : height[right]) * (right - left);
            Max = Max < area ? area : Max;
            if(height[left] < height[right]){//在收缩区间的时候优先从较小的边开始收缩
                k = left;
                while(left < right && height[left] <= height[k])
                    ++left;
            }
            else{
                k = right;
                while(left < right && height[right] <= height[k])
                    --right;
            }
        }
        return Max;
    }
};
15. 3Sum(Array,TwoPointers) 三数之和

题目描述:

给定一个包含 n 个整数的数组 num,判断 num 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?找出所有满足条件且不重复的三元组。

注意:答案中不可以包含重复的三元组。

例如, 给定数组 nums = [-1, 0, 1, 2, -1, -4],

满足要求的三元组集合为:
[
  [-1, 0, 1],
  [-1, -1, 2]
]
思路分析:

    1 排序

    2 前面的两个小数和后面的一个大数相加比较,如果小于0,那么前面的数往前进,增大; 如果大于0,那么后面的数往前进,减小。
    3 前面的数和后面的数相遇,本次循环结束。
    如本程序,第一个i可以看做是定标,j是前面的数,k是后面的数。    还有注意的就是:记得越过重复的数,以避免答案重复。

时间复杂度O(n^2)

class Solution {
public:
    /*时间复杂度O(n^2)
    1 排序
    2 前面的两个小数和后面的一个大数相加比较,如果小于0,那么前面的数往前进,增大; 如果大于0,那么后面的数往前进,减小。
    3 前面的数和后面的数相遇,本次循环结束。
    如本程序,第一个i可以看做是定标,j是前面的数,k是后面的数。
    还有注意的就是:记得越过重复的数,以避免答案重复。当然也可以使用额外的数据结果,如set来作为容器,不允许重复数据的,或者最后使用unique处理掉重复元素;
    最好还是踏踏实实用实例走一走,就可以发现很多原来无法发现的问题。用的实例最好包括特例的实例,例如空,重复,边界重复等。
    */
    vector<vector<int>> threeSum(vector<int>& num) {        
        vector<vector<int>> ret;
        sort(num.begin(), num.end());
        int len = num.size();
        if(len < 3)
            return ret;
        for(int i = 0; i < len - 2; ++i){
            if(i > 0 && num[i] == num[i - 1]) continue;//防止第一个数字重复, 避免重复项
            
            // 此处剪枝格外重要
            if(num[i] + num[len - 2] + num[len - 1] < 0) continue;
            if(num[i] + num[i + 1] + num[i + 2] > 0) return ret;
            
            for(int j = i + 1, k = len -1; j < k;){
                if(num[i] + num[j] + num[k] < 0) ++j;
                else if(num[i] + num[j] + num[k] > 0) --k;
                else{
                    vector<int> tmp = {num[i],num[j],num[k]};
                    ret.push_back(tmp);
                    do{
                        ++j;
                    }while(j < k && num[j] == num[j - 1]);
                    do{
                        --k;
                    }while(j < k && num[k] == num[k + 1]);
                }
            }
            
        }
        return ret;
    }
};

16. 3Sum Closest(Array,TwoPointers) 最接近的三数之和

题目描述:

给定一个包括 n 个整数的数组 nums 和 一个目标值 target。找出 nums 中的三个整数,使得它们的和与 target 最接近。返回这三个数的和。假定每组输入只存在唯一答案。

例如,给定数组 nums = [-1,2,1,-4], 和 target = 1.

与 target 最接近的三个数的和为 2. (-1 + 2 + 1 = 2).
思路分析:

    1 排序

    2 本题与上题在技巧上处理,把三个数加和后与target作差作为判断依据 。 如果小于0,那么前面的数往前进,增大; 如果大于0,那么后面的数往前进,减小。
    3 前面的数和后面的数相遇,本次循环结束。
    如本程序,第一个i可以看做是定标,j是前面的数,k是后面的数。本 还有注意的就是:记得越过重复的数,以避免答案重复。

时间复杂度O(n^2)

class Solution {
public:
    int threeSumClosest(vector<int>& nums, int target) {
        int closest = INT_MAX;
        sort(nums.begin(), nums.end());
        for(int i = 0; i < nums.size() - 2; ++i){
            for(int j = i + 1, k = nums.size() - 1; j < k;){
                int formula = nums[i] + nums[j] + nums[k] - target;
                if(formula == 0)
                    return target;
                else if(formula > 0){
                    --k; 
                    if(formula < abs(closest))  closest = formula;                  
                }
                else{
                    ++j;
                    if(-formula < abs(closest))  closest = formula;
                }                
            }
        }
        return closest + target;        
    }
};
18. 4Sum (Array,HashTable,TwoPointers) 四数之和

题目描述:

给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target 相等?找出所有满足条件且不重复的四元组。

注意:

答案中不可以包含重复的四元组。

示例:

给定数组 nums = [1, 0, -1, 0, -2, 2],和 target = 0。

满足要求的四元组集合为:
[
  [-1,  0, 0, 1],
  [-2, -1, 1, 2],
  [-2,  0, 0, 2]
]

思路分析:

1. 通用法: 先排序后夹逼,将K-sum问题退化到K-1 Sum问题,最终退化到2Sum问题,K-sum问题最好能做到O(n^(K-1))复杂度,有严格数学证明

class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& num, int target) {
        //通用法 退化到2Sum问题,K-sum问题最好能做到O(n^(K-1))复杂度,有严格数学证明      
        vector<vector<int>> ret; //ret是return的缩写 
        if(num.size() < 4) return ret;  
        int n = num.size();   
        sort(num.begin(), num.end());  
          
        for(int i = 0; i < n - 3; ++i)   //(1) trick 1 确定范围到倒数第四个数  
        {  
            if(i > 0 && num[i] == num[i-1]) continue;  //防止第一个数字重复  (2)trick 2 避免重复项  
            for(int j = i+1; j < n - 2; ++j)  
            {  
                if(j > i + 1 && num[j] == num[j-1]) continue;  //防止第二个数字重复  
                int newtarget = target - num[i] - num[j];  
                int k = j + 1;  
                int l = n - 1;  
                  
                while(k < l)  
                {  
                    if(num[k] + num[l] == newtarget)  
                    {  
                        vector<int> tmp{num[i], num[j], num[k], num[l]};  
                        ret.push_back(tmp);                    
                        ++k;  --l;                           
                        while(k < l && num[k] == num[k-1])  ++k;       //trick 3 跳过重复项           
                        while(k < l && num[l] == num[l+1])  --l; 
                    }  
                    else if(num[k] + num[l] > newtarget)    --l;    
                    else   ++k;   
                }  
            }  
        }            
        return ret;
}
};

2. 经过剪枝处理后的高效通用法

class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& num, int target) {     //better solution!
        vector<vector<int>> ret;
        int sz = num.size();        
        sort(num.begin(), num.end());
        for (int a = 0; a < sz - 3; ++a) {
            if (a > 0 && num[a] == num[a-1]) continue;//防止第一个数字重复, 避免重复项 
            
            // 此处剪枝格外重要,未剪枝60ms,剪枝15ms, nice!
            if (num[a] + num[sz - 3] + num[sz - 2] + num[sz - 1] < target) continue;
            if (num[a] + num[a + 1] + num[a + 2] + num[a + 3] > target) return ret;

            for (int b = a + 1; b < sz - 2; ++b) {            
                if (b > a + 1 && num[b] == num[b-1])  continue;//防止第二个数字重复, 避免重复项
                
                // 此处剪枝可从15ms到12ms,要注意若>target, 不能直接return了,只是内层b循环不必再试,外层a循环还要继续
                if (num[a] + num[b] + num[sz - 2] + num[sz - 1] < target) continue;
                if (num[a] + num[b] + num[b + 1] + num[b + 2] > target) break;
            
                int c = b + 1, d = sz - 1;
                while (c < d) { // c和d在此循环中相向而行,寻找和为target的情况
                    if (num[a] + num[b] + num[c] + num[d] > target) --d;
                    else if (num[a] + num[b] + num[c] + num[d] < target) ++c;
                    else {
                        vector<int> tmp{num[a], num[b], num[c], num[d]};
                        ret.push_back(tmp);
                        do {
                            --d;
                        } while (c < d && num[d] == num[d+1]);//防止第四个数字重复, 避免重复项
                        do {
                            ++c;
                        } while (c < d && num[c] == num[c-1]);//防止第三个数字重复, 避免重复项
                    }
                }
            }
        }
        return ret;
        
    }
};

26. Remove Duplicates from Sorted Array(Array,TwoPointers)删除排序数组中的重复项

题目描述:

给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。

不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。

示例 1:

给定数组 nums = [1,1,2], 

函数应该返回新的长度 2, 并且原数组 nums 的前两个元素被修改为 1, 2。 
你不需要考虑数组中超出新长度后面的元素。

示例 2:

给定 nums = [0,0,1,1,1,2,2,3,3,4],

函数应该返回新的长度 5, 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4。

你不需要考虑数组中超出新长度后面的元素。

说明:

为什么返回数值是整数,但输出的答案是数组呢?

请注意,输入数组是以“引用”方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。

你可以想象内部操作如下:

// nums 是以“引用”方式传递的。也就是说,不对实参做任何拷贝
int len = removeDuplicates(nums);

// 在函数里修改输入数组对于调用者是可见的。
// 根据你的函数返回的长度, 它会打印出数组中该长度范围内的所有元素。
for (int i = 0; i < len; i++) {
    print(nums[i]);
}
思路分析:

初始化一个新变量index记录非重复元素个数,for循环查找不同元素,并记录,最终返回index。

class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
        int index = 0;
        if(nums.size() <= 1)
            return nums.size();
        
        nums[index++] = nums[0];
        for(int i = 0; i < nums.size() - 1; ++i){
            if(nums[i] != nums[i + 1])
                nums[index++] = nums[i + 1];
        }
        return index;
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值