ch3_8三数之和 & ch3_9 四数之和

1. 三数之和

lc15 , 三数之和

关键点 去重的情况, 以及去重元素的意义;

理解以下两点:

  1. 第一类情况: 遍历数值中, 初始 i i i位置上的 元素去重;

  2. 第二类情况, 基于当前 i i i 位置上的元素, 对左指针和右指针位置上的元素去重;

而在第二种情况下, 每次去重 元素的意义不一样;

– 去重的意义:
理解以下两种情况:
在等于0 和不等于0的情况下, 两种情况下的去重,意义是不一样的:

  1. 不等于0 时的去重:
    此时去重的意义在于:去除的相同元素是不满足和为零条件的元素; (即这两个元素都是不满足条件的重复元素,所以继续移动;

    具体而言,便是:
    1.1 三数之和大于0:右指针往左移动后,
    此时判断的是: 右指针当前指针位置上的元素是否和移动前位置上的元素是否相同, 若相同,表明当前指针位置上的元素,仍然不满足和为0 的条件, 所以右指针继续左移;
    1.2 三数之和大于0:左指针往右移动后,
    原理同上,请自行理解;

  2. 等于0 时的去重:
    此时的去重, 去除的元素是满足和为零条件的元素:
    2.1 此时右指针比较重复元素时, 当前指针位置上的元素和移动后指针位置上的元素是否相同, 若相同, 表明他们是满足和为零条件的重复元素; 去除重复元素;
    2.2 此时左指针比较重复元素时, 当前指针位置上的元素和移动后指针位置上的元素是否相同, 若相同, 表明他们是满足和为零条件的重复元素; 去除重复元素;

去除重复元素后, 在当前 i i i 基础上, 左右指针同时往中间移动;

1.1 解题步骤:

  1. 先对数组排序;

  2. 对排序后的数组开始遍历:每一次左指针, 右指针都是建立在当前 i i i 所在的相对位置上;
    排序之后, 如果i 的第一位置 大于0 , 则直接返回结果;

    2.1. 三数之和大于0:右指针往左移动时, 判断右边重复项,并去除右边重复元素;
    2.2. 三数之和小于0:左指针往右移动时, 判断左边重复项, 并去除左边重复元素;
    2.3 否则,当前位置便是,三数之和 = 0: 将当前的三元组压入到结果集中, 继续去重复:
    2.3.1 此时继续判断去重, 右指针元素是否和前一个元素相同, 相同, 右指针左移;
    2.3.2 左指针元素, 是否和后一个元素相同, 相同, 左指针右移;

1.2 code

#include "vector"
#include "algorithm"
using namespace std;


class Solution{
public:
    vector<vector<int>>  threeSum(vector<int>& nums){
        vector<vector<int>>   result;  // 用于存放三数之和为零的下标;

        // 1.先对数组排序;
        sort(nums.begin(), nums.end());
        //  遍历排序后的数组, i 从0开始, 在i的基础上建立左指针, 右指针从末尾开始;
        for( int i = 0; i < nums.size(); i++){
            // 排序后数组的第一个元素,如果大于0 直接返回result;
            if(nums[i] > 0)  return  result;

            // 1.   先对i 位置上的元素 去重
            if( i > 0  && nums[i] == nums[i - 1])  //  退出当前循环。 开始下一次循环;
              continue;
            //  2. 开始赋予左指针,右指针;
            int left = i +1,  right = nums.size() -1;
            while(left < right){

                //  大于零时, 去除不满足条件的重复元素;
                if( nums[i] + nums[left]  + nums[right] > 0){
                    // 移动右指针, 将数值减小;
                    right--;
                    //  只要 left < right,  并且更新后的右指针位置上的数字 和 右指针更新之前位置上的数字相同, 则继续移动,因为重复的元素同样是不满足和为零的条件;
                    while(left < right && nums[right] == nums[right + 1])   right--;
                } else if (nums[i] + nums[left]  + nums[right] < 0 ){
                    left++;
                    while(left  < right && nums[left] == nums[left - 1]) left++;
                }

                else {  // 否则的化, 便是等于= 0, 满足三数之和的情况; 此时去除的是满足条件的重复元素;
                    result.push_back(vector<int>{nums[i], nums[left],  nums[right]});
                    //   此时去重元素, 是当前元素和 下一个待更新位置上的元素;
                     while(left < right  && nums[right] == nums[right - 1])    right--;
                     while (left < right  && nums[left] == nums[left + 1] )   left++;

                     //  在去重之后, 在当前的i 的基础上, 继续移动 left, right , 寻找三数之和 = 0 的  三元组;
                     left++;
                     right--;
                }
            }

        }
        return  result;
    }
};

2. 四数之和

lc 18 四数之和

题意:给定一个包含 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] ]

2.1 思路:

三重循环 , 两层for 循环 + 一层 while 循环;

第一层for 循环, s1 从数组下标0 开始;
第二层for 循环, s2 从数组下标 s1 + 1 开始;
第三层while 循环, while( left < right),
left 从数组下标 s2 +1 开水, right 从数组下标 nums.size() -1 开始;

四数之和的三指针解法是两层for循环nums[s1] + nums[s2]为确定值,

第三层循环内有left和right下标作为双指针,
找出nums[s1] + nums[s2] + nums[left] + nums[right] == target的情况,

三数之和的时间复杂度是 O ( n 2 ) O(n^2) O(n2),四数之和的时间复杂度是 O ( n 3 ) O(n^3) O(n3)

2.2 解题步骤

class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target){
        // 新建向量容器, 用于存放四元数组;
        vector<vector<int>> result;
        // 排序;
        sort(nums.begin(), nums.end());
        //  开始第一层循环
        for(int s1 = 0; s1 < nums.size()  ;  s1 ++){
            //  第一层循环时,遇到重复元素,跳出本次循环, 继续下一轮循环;
            if(s1 > 0 && nums[s1] == nums[s1 - 1]) continue;

            // 开始第二层循环,起始位置 s1 + 1;
            for(int s2 = s1 + 1;  s2 < nums.size(); s2++){
                // 遇到重复元素,跳出本次循环,继续下一轮循环;
                if(s2 > s1 +1 && nums[s2] == nums[s2 - 1]) continue;

                // 开始第三重循环, left < right;   找出满足 target 条件的 四元数组,
                // 起始位置, left = s2 + 1; right = nums.size() -1;
                int left = s2 + 1, right = nums.size() - 1;
                while(left < right){
                    // 去重两中类型的重复元素, 不满足target 条件的重复元素,  以及满足target条件的重复元素;
                    //if(nums[s1] + nums[s2]  + nums[left] + nums[right] > target  )
                    //  这种表达形式会 导致溢出 错误;
                    if(nums[s1] + nums[s2]   > target - (nums[left] + nums[right]) ){
                        // 大于target, 右指针左移;
                        right--;
                        // 移动后指针位置上的元素, 如果和移动之前指针位置上的元素重复,去重;
                        while(left < right && nums[right]== nums[right + 1] ){ right--;}
                    }else if(nums[s1] + nums[s2]  < target - (nums[left] + nums[right]) ){
                        //  小于 target,  左指针右移动;
                        left++;
                        while(left < right && nums[left]== nums[left - 1] ){ left++;}
                    }else {  //  此时是满足target 条件的 四元数,
                        result.push_back({nums[s1], nums[s2], nums[left], nums[right]});
                        //  此时的去重元素, 是满足target 条件的元素去重;
                        while (left < right && nums[right] == nums[right -1]) {right--;}
                        while (left < right && nums[left] == nums[left + 1 ]) {left++;}
                        // 去重重复元素后, 在当前的 s1, s2 基础上, 去寻找满足条件的 四元数;
                        left++;
                        right--;
                    }
                }

            }
        }
     return result;
    }

};




3. 溢出问题

上述代码中

//if(nums[s1] + nums[s2]  + nums[left] + nums[right] > target  )
 //  这种表达形式会 导致溢出 错误

3.1 整型溢出

#include <stdio.h>
 
int main(int argc, char **argv)
{
    unsigned short int a;
    signed short int b;
 
    a = 50000;
    b = 50000;
 
    printf("\n a = %d \t b = %d \n", a, b);
 
    return 0;
}


在不编译运行的情况下,请写出这段代码的输出。写好以后,那么编译运行吧,答案可能会出乎你的意料!


a = 50000    b = -15536

为什么明明给a, b两个数赋值都是50000,而输出的结果却不同呢?

这里涉及到整数溢出的问题!我们知道,无论是钟表还是汽车的里程表,一旦达到或者超过它能表示的最大值时,就会从起点开始重新计数。C语言中的整数溢出问题和这类问题如出一辙。简单来说,C语言中各个数据类型所能表示的数都是有一定的取值范围的,一旦要表示的数超出了该数据类型的取值范围,就会从起点开始重新计数。

针对本例的分析:"short int"类型,在计算机中一般用2个字节(Byte)来存储,每个字节由8位(bit)组成(1 Byte = 8 bit),每位表示一个二进制数0或1,所以,“short int"类型能表示 28∗2=216=6553628∗2=216=65536 个整数,以整数0为中点划分为(-32768 ~ 32767),其表示范围就是(-32768 ~ 32767)。而"unsigned short int"类型,因为声明了"unsigned”(无符号),所以其表示范围是(0 ~ 65535)。

由以上分析可知,"unsigned short int"类型,可以表示整数50000,并且没有溢出;而用"signed short int"类型来表示整数50000,就会出现整数溢出现象。

既然溢出了,那么溢出后原来的数会变为多少呢?分析如下:"signed short int"类型最大能表示的整数为32767,一旦超过了32767,就会像钟表或者汽车的里程表一样,从起点重新开始计数,那么,"signed short int"类型的起点是多少呢?是-32768!所以,32768就被表示为-32768了,那么50000会被表示为多少呢?总共有(50000 - 32767) = 17233个数超过了范围,这17233个数要从起点-32768开始计数,相当于从-32768开始往大了数17233个数。好了,下面我们开始来数数,第一个数1对应于-32768,第二个数2对应于-32767,…,依次类推下去,最后答案是(-32768 + 17233) - 1 = -15536,之所以最后要减一是因为-32768对应的数是1而不是0。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值