数组题目汇总

一、题目:

  一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。例如数组为{1,3,5,7,1,3,5,9},找出7和9。

二、解答:

1、运用异或运算符的解法

   我们先考虑上述问题的简单版本:   一个数组里面只有一个数字出现一次,其他都出现两次,请找出这个数字。

        这个问题可以可以使用用异或的性质解决。异或的性质:对于整数a,有

                         (1) a^a=0

                         (2)a^0=a

                         (2)a^b^c=a^(b^c)=(a^c)^b

      利用以上的性质,上面的题目的解法为:

public static void findNumAppearOnce(int[] arr) {
        int result = 0;
        for (int i = 0; i < arr.length; i++) {
            result ^= arr[i];
        }
        System.out.println(result);

 

   有了以上的基础,解文章开始时提出的问题的思路如下:

  (1)对于出现两次的元素,使用“异或”操作后结果肯定为0,那么我们就可以遍历一遍数组,对所有元素使用异或操作,那么得到的结果就是两个出现一次的元素的异或结果。

  (2)因为这两个元素不相等,所以异或的结果肯定不是0,也就是可以再异或的结果中找到1位不为0的位,例如异或结果的最后一位不为0。

  (3)这样我们就可以最后一位将原数组元素分为两组,一组该位全为1,另一组该位全为0。

  (4)再次遍历原数组,最后一位为0的一起异或,最后一位为1的一起异或,两组异或的结果分别对应着两个结果。

 

/*
 解法1:
1、思路:
(1)对于出现两次的元素,使用“异或”操作后结果肯定为0,那么我们就可以遍历一遍数组,对所有元素使用异或操作,那么得到的结果就是两个出现一次的元素的异或结果。
(2)因为这两个元素不相等,所以异或的结果肯定不是0,也就是可以再异或的结果中找到1位不为0的位,例如异或结果的最后一位不为0。
(3)这样我们就可以最后一位将原数组元素分为两组,一组该位全为1,另一组该位全为0。
(4)再次遍历原数组,最后一位为0的一起异或,最后一位为1的一起异或,两组异或的结果分别对应着两个结果。
2、复杂度:
  (1)时间复杂度:第一次循环,将所有元素异或得到对应结果,时间开销为O(n);第二次循环,找出第一次异或结果为1的位,时间开销为O(32);第三次循环,根据为1的位将元素分为两组进行异或得到两个结果,时间复杂度为O(n),所以总的时间复杂度为T(n) = 2*O(n)+O(32) = O(n)。
  (2)空间复杂度:常数,因为只分配了两个空间用于结果的保存,因此空间复杂度为常数。
 */

  

//暴力解法

// class Solution {
// public:
//     vector<int> singleNumbers(vector<int>& nums) {
//         unordered_map<int, int> res;
//         vector<int> number;
//         for (int i = 0; i < nums.size(); i++) {
//             res[nums[i]]++;
//         }
//         for (auto c : nums) {
//             if (res[c] == 1) {
//                 number.emplace_back(c);
//             }
//         }
//         return number;
//     }
// };


class Solution {
public:
    vector<int> singleNumbers(vector<int>& nums) {
        //用于将所有的数异或起来
        int k = 0;
        for (int num : nums) {
            k ^= num;
        }
        //获得k中最低位的1,K里面两数不同,故肯定有一位1是不同的,可以以此区分
        int mask = 1;
        while ((k & mask) == 0) {
            mask <<= 1;
        }
        int a = 0; 
        int b = 0;
        for (int num : nums) {
            if ((num & mask) == 0) {
                a ^= num;  // 找到那位不为1的元素, 其余数为偶数,会自动消去
            } else {
                b ^= num;  // 找到那位为1的元素
            }
        }
        return {a, b};
    }
};

  

 

2、运用HashMap的解法

(1)思路:

  使用一个Map,Map对应的键值key就是数组中的元素,value就是这个元素出现的次数。这样我通过一次遍历数组中的元素,
如果元素出现在map中,则将其对应的value加1,否则将元素添加到map中,这样遍历一遍数组,我们就可以得到数组中每个元素对应出现的次数,
然后再通过遍历一遍map,返回value为1对应的key就是我们需要求得元素。

 

class Solution {
public:
    vector<int> singleNumbers(vector<int>& nums) {
        unordered_map<int, int> res;
        vector<int> number;
        for (int i = 0; i < nums.size(); i++) {
            res[nums[i]]++;
        }
        for (auto c : nums) {
            if (res[c] == 1) {
                number.emplace_back(c);
            }
        }
        return number;
    }
};

  

 或者对数组进行排序,然后遍历一遍数组比较元素是否相等,来判断只出现一次的元素;

 

题目描述

在一个数组 nums 中除一个数字只出现一次之外,其他数字都出现了三次。请找出那个只出现一次的数字。

示例 1:

输入:nums = [3,4,3,3]
输出:4
示例 2:

输入:nums = [9,1,7,9,7,9,7]
输出:1

 

创新解法:

根据题意,需要设计一个运算,使得一次运算为自己,三次运算为0,用二进制表示即00 -> 01 -> 10 -> 00这样的运算流程。


00 (输入0) 00
00 (输入1) 01
01 (输入0) 01
01 (输入1) 10
10 (输入0) 10
10 (输入1) 00
令ab为原始数据,c为输入,可以看到:
a输出1的情况有两种: "01 (输入1) 10", "10 (输入0) 10", 写作: a = abc + abc
b输出1的情况有两种: "00 (输入1) 01", "01 (输入0) 01", 写作: b = abc + abc = ab^c

根据题意,最终出现三次的数都走到了00的状态,只有一个数只01的状态,此时a被清零,所以最终结果返回b即可。

int singleNumber(vector<int>& nums) {
    int a = 0, b = 0, t = 0;
    for (auto c : nums) {
        t = (~a & b & c) + (a & ~b & ~c);
        b = ~a & (b ^ c);
        a = t;
    }
    return b;
}

  

常规暴力解法:

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        unordered_map<int, int> hash_map;
        for(int n : nums) hash_map[n] ++;
        int ans;
        for(auto K_V : hash_map){
            if(K_V.second == 1){
                ans = K_V.first;
                break;
            }
        }
        return ans;
    }
};

  

 

 二、和为K的子数组

给定一个整数数组和一个整数 k,你需要找到该数组中和为 k 的连续的子数组的个数。

示例 1 :

输入:nums = [1,1,1], k = 2
输出: 2 , [1,1] 与 [1,1] 为两种不同的情况。
说明 :

数组的长度为 [1, 20,000]。
数组中元素的范围是 [-1000, 1000] ,且整数 k 的范围是 [-1e7, 1e7]。

思路1:两层循环,计算出所有的累加和,然后与K比较

 

class Solution {
public:
    int subarraySum(vector<int>& nums, int k) {
        int count = 0;
        for (int start = 0; start < nums.size(); ++start) {
            int sum = 0;
            for (int end = start; end >= 0; --end) {
                sum += nums[end];
                if (sum == k) {
                    count++;
                }
            }
        }
        return count;
    }
};

  

思路2:

用pre[i]表示从0到i序列的和, [j,i]序列的和为pre[i]-pre[j-1],题目意思即是要找到pre[i]-pre[j-1]=k的序列,
即pre[j-1]=pre[i]-k的序列,可以从0到n-1遍历数组,将sum[i]保存在map中,形式为<sum[i],次数>,表示从0到i的序列中,
和为sum[i]的序列出现过多少次,对于sum[i],如果前面出现过pre[i]-k,则count+=次数。

class Solution {
public:
    int subarraySum(vector<int>& nums, int k) {
        int n=nums.size(),sum(0),count(0),pre;
        map<int,int> mp;
        mp[0] = 1;
        for(int i=0;i<n;i++)
        {
            sum+=nums[i];
            pre=sum-k;
            //出现过,则累加出现次数
            if(mp.find(pre)!=mp.end())
                count+=mp[pre];
            mp[sum]++;
        }
        return count;
    }
};

  

 

215. 数组中的第K个最大元素

在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

示例 1:

输入: [3,2,1,5,6,4] 和 k = 2
输出: 5
示例 2:

输入: [3,2,3,1,2,4,5,5,6] 和 k = 4
输出: 4
说明:

你可以假设 k 总是有效的,且 1 ≤ k ≤ 数组的长度。

 

最小堆:

最小堆就是 所有的父节点都要比子节点要小,反之所有的父节点都要比子节点大的完全二叉树是最大堆
像这样支持插入和寻找最大值和最小值元素的数据 结构称之为优先队列。
如果使用普通队列来实现这两个功能,那么寻找最大元素或者最小元素都要枚举整个队列,这样的时间复杂度比较高。
如果是已经排序好的数组,那么插入一个元素需要移动很多元素,时间复杂度依旧很高,而堆就是一种优先队列的实现,可以很好的解决这两个问题。
堆还经常用来寻找第k大或第k小的元素,比如现在要找第k大的元素,现在只需要建立一个个数为k的最小堆,堆顶元素就是第k大的元素。

例如现在有10个数,要找第3大的元素,那么就先建立一个数目为3个的最小堆,然后拿第4个元素和堆顶元素比较,
如果比他小这个数就丢弃,如果比他大,就将第4个元素替换原来的堆顶,然后重新调整堆,
后面的5-10个元素一次这样重复,遍历完成后,堆顶元素就是第k大的元素。

相反的,寻找第k小的元素,建立数目为k的最大堆,比堆顶大的元素丢弃,小的替换后重新向下调整堆
那么,求k个最大元素和k个最小元素,则遍历完成后,最小堆和最大堆中的元素就是k个最大元素和最小元素。

还要记录的一点就是向下调整和向上调整。
向下调整是从堆顶开始,将最小的元素放在父节点上,直至当前节点是叶子节点为止。
向下调整主要是用于删除堆顶元素,在堆顶上重新添加一个新的元素或者删除堆顶元素时候用到
(删除堆顶元素是,将最后一个元素放在堆顶元素,再使用向下调整,保证最小堆或者最大堆),还用于创建一个最大或最小堆

向上调整用于在末尾增加一个元素时候用到。
还有使用堆排序的时间复杂度是NO(logN),例如从小到大排列,有两种方法,一种就是用最小堆,每次去堆顶元素,
还有一种就是更好的方法就是使用最大堆,因为从小到大排序,大的在后面,小的在前面。那么每次将堆顶元素放在和最后一个元素替换,
即时heap[0]和heap[n]替换。然后n--,在从heap[0]向下调整堆。

 

定义:

priority_queue<int,vector,less> q;最大堆(默认为最大堆)
priority_queue<int,vector,greater> q;最小堆

 

参数:第一个参数是存储对象的类型,第二个参数是存储元素的底层容器,第三个参数是函数对象,它定义了一个用来决定元素顺序的断言
第一个参数T:元素(element)类型
第二个参数Container:必须是容器类型Container,用来存储元素(element),其类型必须是第一个参数
第三个参数Compare:比较形式,默认是less

 

priority_queue 操作:
q.size();//返回q里元素个数
q.empty();//返回q是否为空,空则返回1,否则返回0
q.push(k);//在q的末尾插入k
q.pop();//删掉q的第一个元素
q.top();//返回q的第一个元素

 

//第k 大的元素,调用priority_queue(最小堆)实现

创建:priority_queue<int, vector, greater> minHeap;

 

class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) {
        priority_queue<int, vector<int>, greater<int>> minHeap; 
        for (auto n : nums) {  //使用auto
            if(minHeap.size()==k){
                if(n > minHeap.top()){
                    minHeap.pop();
                    minHeap.push(n);
                }
            }else{
                minHeap.push(n);
            } 
        }
        return minHeap.top();
    }
};

  

 

最大堆,最小堆解释和实现

https://blog.csdn.net/geter_CS/article/details/102580332

https://blog.csdn.net/nisxiya/article/details/45725857

https://blog.csdn.net/github_36955602/article/details/89384304

 

 

 

11. 两数之和
给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。


示例:

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

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

 

思路:
1)常规思路,两层循环遍历数组,将满足条件的元素下表保存,复杂度O(n*n)
2)hash表思路

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        vector<int> twoSum;
        for(int i=0;i<nums.size();i++){
            for(int j=i+1;j<nums.size();j++){
                if((nums[i]+nums[j])==target){
                    twoSum.push_back(i);
                    twoSum.push_back(j);
                }
            }
        }
    return twoSum;
    }
};

  

vector<int> twoSum(vector<int>& nums, int target) {
      vector<int> twoSum;
      map<int, int> tmpmap;//键值为nums的值,变量值为nums下标
        for (int i = 0; i < nums.size(); i++) {
          tmpmap[nums[i]] = i;
      }
      for (int i = 0; i < nums.size(); i++) {
          if (tmpmap.count(target - nums[i]) != 0 && tmpmap[target-nums[i]]!=i) {
            twoSum.push_back(i);
            twoSum.push_back(tmpmap[target - nums[i]]);
            break;
          }
      }
      return twoSum;
  }  

  

  

先看懂题目,真正思考这个问题,然后写下问题的推导过程,最后将思路翻译成代码。

 

 


2、三数之和
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。
注意:答案中不可以包含重复的三元组。

示例:

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

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


思路:
1)如果直接对数组使用三个for循环进行寻找,时间复杂度为O(n^3);
2)排序+双指针:优化方法首先对数组进行排序,使用两数之和等于第三数的负数寻找。
使用两个指针指向首尾,向中间移动寻找合适的值。时间复杂度是O(n^2);

 

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> result;
        int num = nums.size(); 
        sort(nums.begin(),nums.end());
        if(nums.empty() || nums.front()>0 || nums.back()<0)
            return {};
        for(int i = 0;i<num-2;i++){
            if(nums[i]>0) break;
            if(i>0&&nums[i] == nums[i-1]) continue;
            int head = i+1;
            int tail = num-1;
            while(head<tail){
                if(nums[head]+nums[tail] == -nums[i]){
                    if(head == i+1 || tail == num-1){
                        result.push_back(vector<int>{nums[i],nums[head],nums[tail]});
                        head++;tail--;
                    }else if(nums[head] == nums[head-1]){
                        head++;
                    }else if(nums[tail] == nums[tail+1]){
                        tail--;
                    }else{
                        result.push_back(vector<int>{nums[i],nums[head],nums[tail]});
                        head++;tail--;
                    }
                }else if(nums[head]+nums[tail]<-nums[i]){
                    head ++;
                }else{
                    tail--;
                }
            }
        }
        return result;
    }
};

  

简洁的写法和思路:

    1. 首先对数组从大到小排序,数组的大小为 n 。
    2. 固定一个数,从其右侧的数中寻找另外两个数。
      假设我们固定的数为 nums[k], k = 0 to n-1 , 另外两个数初始时分别为 nums[l], nums[r] , 其中 l = k + 1, r = n - 1 。
    3. 令 sum = nums[k] + nums[l] + nums[r] 。
      1. 若 sum < 0 ,则我们要增大 sum ,此时只能对 l 向右挪一格,即:l ++ 。
      2. 若 sum > 0 ,则我们要减小 sum ,此时只能对 r 向左挪一格,即:r -- 。
      3. 若 sum == 0 ,此时这三个数就我们需要的数,将他们加入结果里。此时,l 和 r 之间的数还可能有我们需要的数,我们此时需要左右都向内移动,即:l ++, r -- 。。
    4. 避免重复的数据。
      1. 当 sum == 0 时,我们需要 l 和 r 都向内移动。此时需要过滤掉和当前 nums[l], nums[r] 重复的数据,我们巧妙的使用了两个while循环,同时需注意 l 要一直小于 r
        while (l < r && nums[l] == nums[++ l]) { }
        while (l < r && nums[r] == nums[-- r]) { }
      2. 同时,我们也要在 k 的循环中过滤掉和当前 nums[k] 重复的数字。这里的 k < len - 2主要是防止数组越界。
        while (k < len - 2 && nums[k] == nums[++ k]) { }
    5. 当 l >= r 时,说明与当前固定的 nums[k] 相组合的两个数已经找完,所以要进入下一个 nums[k]
class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> res;
        int len = nums.size();
        if (len < 3) {
            return res;
        }
        sort(nums.begin(), nums.end());
        int k = 0;
        while(k < len - 2 && nums[k] <= 0) {
            int l = k + 1;
            int r = len - 1;
            while (l < r) {
                int sum = nums[k] + nums[l] + nums[r];
                if (sum == 0) {
                    res.push_back({nums[k], nums[l], nums[r]});
                    while (l < r && nums[l] == nums[++ l]) { }
                    while (l < r && nums[r] == nums[-- r]) { }
                }
                else if (sum < 0) {
                    ++ l;
                }
                else {
                    -- r;
                }
            }
            while (k < len - 2 && nums[k] == nums[++ k]) { }
        }
        return res;
    }
};

 

 

3)哈希法思路:
两层for循环就可以确定 a 和b 的数值了,可以使用哈希法来确定 0-(a+b) 是否在 数组里出现过,其实这个思路是正确的,但是我们有一个非常棘手的问题,就是题目中说的不可以包含重复的三元组。
把符合条件的三元组放进vector中,然后在去去重,这样是非常费时的,很容易超时,也是这道题目通过率如此之低的根源所在。
去重的过程不好处理,有很多小细节,如果在面试中很难想到位。
时间复杂度可以做到O(n^2),但还是比较费时的,因为不好做剪枝操作。
大家可以尝试使用哈希法写一写,就知道其困难的程度了。

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> result;
        sort(nums.begin(), nums.end());
        // 找出a + b + c = 0 
        // a = nums[i], b = nums[j], c = -(a + b)
        for (int i = 0; i < nums.size(); i++) {
            // 排序之后如果第一个元素已经大于零,那么不可能凑成三元组
            if (nums[i] > 0) {
                continue;
            }
            if (i > 0 && nums[i] == nums[i - 1]) { //三元组元素a去重
                continue;
            }
            unordered_set<int> set;
            for (int j = i + 1; j < nums.size(); j++) {
                if (j > i + 2 && nums[j] == nums[j-1] && nums[j-1] == nums[j-2]) { // 三元组元素b去重
                    continue;
                }
                int c = 0 - (nums[i] + nums[j]);
                if (set.find(c) != set.end()) {
                    result.push_back({nums[i], nums[j], c});
                    set.erase(c);// 三元组元素c去重
                } else {
                    set.insert(nums[j]);
                }
            }
        }
        return result;
    }
};

  

 

3. 下一个排列

实现获取下一个排列的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列。

如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。

必须原地修改,只允许使用额外常数空间。

以下是一些例子,输入位于左侧列,其相应输出位于右侧列。
1,2,3 → 1,3,2
3,2,1 → 1,2,3
1,1,5 → 1,5,1

 

思路:

分析下,要找到下一个更大的排序,有两种情况:
第一种,此排列是最大的排列了,不存在下一个更大的排列,这样直接对数组进行升序排序就可以了,输出最小的排列。比如:3 2 1,这样直接排序之后1,2,3
第二种,比如1,2,3,5,4。它的下一个排列应该是1,2,4,3,5。要找到下一个更大的排列,原则上来说,需要将一个数字和其前面的一个比其小的数字进行交换,这样就形成了下一个大的排列,例如1,2,3,交换之后变成1,3,2。
第一步是确定将排列中的哪个数字往后交换,从原来排列中的最后一位往前比较,找到小于自身后面的数字第一个数字,作为往后交换的那个数字,因为如果这个数字是比后面的数字大,那么它往后交换一定是将这个排列变得更小,所以要找到第一个比其后面的数字小的字数作为待交换的数字。
第二步再将待交换的数字后面的排列进行升序排序,最后再将待交换的数字与后面排序好的序列中的第一个大于自身的数字进行交换,这样就得到下一个更大的排序。

 

class Solution {
public:
    void nextPermutation(vector<int>& nums) {
        int len = nums.size();

        int i=len-1;

        for(;i>0;i--){
            if(nums[i]>nums[i-1]){
                break;
            }
        }

        if(i==0){
            sort(nums.begin(),nums.end());
        }
        else{
            int temp=i-1;
            sort(nums.begin()+i,nums.end());//将其后面的数排序
            for(int k=i;k<nums.size();++k)//找到第一个大于nums[i]的数字用于交换。
            {
                if(nums[temp]<nums[k])
                {
                    i=k;
                    break;
                }
            }
            swap(nums[temp],nums[i]);
        }

        return; 
    }
};

  


3、搜索旋转排序数组
给你一个升序排列的整数数组 nums ,和一个整数 target 。

假设按照升序排序的数组在预先未知的某个点上进行了旋转。(例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。

请你在数组中搜索 target ,如果数组中存在这个目标值,则返回它的索引,否则返回 -1 。

 
示例 1:

输入:nums = [4,5,6,7,0,1,2], target = 0
输出:4
示例 2:

输入:nums = [4,5,6,7,0,1,2], target = 3
输出:-1
示例 3:

输入:nums = [1], target = 0
输出:-1

思路:

题目要求long(n),肯定需要二分法,但是因为是一个旋转数组,所以说在进行边界变换的时候条件就要相应改变。
首先应该可以在数组中找到两个有序部分,假定分别是左边部分和右边部分。

1、mid和right处于同一个有序序列。
首先,如果两者处于同一个有序序列,那么比较target是否在nums[mid]与nums[right]之间,如果在,那么l=mid+1,否则往左边移动,r=mid-1.
2、不处于同一有序序列
当mid和right不在同一个有序序列之后,那么mid和left肯定在同一个有序序列,那么比较target是否在nums[mid]与nums[left]之间,如果在,那么r=mid-1,否则往右边移动,l=mid+1.

class Solution {
public:
    int search(vector<int>& nums, int target) {
        if(nums.size()==0)return -1;//没有元素直接返回
        int l=0;
        int r=nums.size()-1;
        while(l<=r)
        {
            int mid=l+((r-l)>>1);
            if(nums[mid]==target)
                return mid;
            if(nums[mid]<=nums[r])//说明nums[mid]是在旋转后的右边有序序列
            {
                if(nums[mid]<target&&nums[r]>=target)//说明在右边序列的右边,所以l=mid+1
                {
                    l=mid+1;
                }
                else//不再右边序列的右边,直接往左边移动。
                    r=mid-1;
            }
            else//说明nums[mid]是在旋转后的左边有序序列
            {
                if(nums[mid]>target&&nums[l]<=target)//在左边部分的左边,所以往左边移动。
                    r=mid-1;
                else//不再左边部分的左边,直接往右边移动。
                    l=mid+1;
            }
        }
        return -1;
    }
};

  

 

46. 全排列
给定一个 没有重复 数字的序列,返回其所有可能的全排列。

示例:

输入: [1,2,3]
输出:
[
[1,2,3],
[1,3,2],
[2,1,3],
[2,3,1],
[3,1,2],
[3,2,1]
]

 

解决一个回溯问题,实际上就是一个决 策树的遍历过程。
你只需要思考 3 个问题:
1、路径:也就是已经做出的选择。
2、选择列表:也就是你当前可以做的选择。
3、结束条件:也就是到达决策树底层,无法再做选择的条件。

思路:利用回溯算法阶梯框架解决

// 回溯算法,解题模板
result = []
def backtrack(路径, 选择列表):
    if 满足结束条件:
        result.add(路径)
        return
    for 选择 in 选择列表:
        做选择
        backtrack(路径, 选择列表)
        撤销选择

  

 

class Solution {
public:
    // 路径:记录在 track 中
    // 选择列表:nums 中不存在于 track 的那些元素
    // 结束条件:nums 中的元素全都在 track 中出现

    /* 主函数,输入一组不重复的数字,返回它们的全排列 */
    vector<vector<int>> res;
    vector<vector<int>> permute(vector<int>& nums) {
        vector<int> track;
        backtrack(nums,track);
        return res;
    }

    void backtrack(vector<int>& nums,vector<int>& track){
        // 触发结束条件
        if(track.size()==nums.size()){
            res.push_back(track);
            return;
        }

        for(int i=0;i<nums.size();i++){
            // 做选择,排除不合法的选择
            if(find(track.begin(), track.end(),nums[i])!=track.end()){
                continue;
            }
            track.push_back(nums[i]);
            // 进入下一层决策树
            backtrack(nums,track);
            // 取消选择
            track.pop_back();
        }
    }

};

  

56. 合并区间
给出一个区间的集合,请合并所有重叠的区间。

示例 1:

输入: intervals = [[1,3],[2,6],[8,10],[15,18]]
输出: [[1,6],[8,10],[15,18]]
解释: 区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].
示例 2:

输入: intervals = [[1,4],[4,5]]
输出: [[1,5]]
解释: 区间 [1,4] 和 [4,5] 可被视为重叠区间。

思路:先按照第一个元素排序,然后依次合并

将每个vector按照第一个元素排列好,然后就开始合并,合并的规则如下

首先选取第一个vector的第一个元素作为合并区间的首部.
然后将第一个vector的第二个元素作为合并区间目前的尾部,记做num,然后依次往后检查所有待合并vector,有三种情况:
如果待合并vector的后一个元素小于num,那么说明这个vector应该被合并进区间中,继续检查下一个待合并vector。
如果待合并vector的后一个元素大于num,那么说明它应该在当前区间之外,然后继续看它的前一个元素,
如果它的前一个元素小于等于num,那说明它和当前区间是接触的,是可以合成一个区间的,那么将num更新为该vector的后一个元素,继续合并;
待合并vector的前后元素都大于num,那说明肯定它和上一个区间是没有接触的,此时可以将上一个区间放入结果数组中,并且重新开一个区间,
然后更新num为该vector的后一个元素,暂时作为新区间的尾部,该vector的第一个元素为新区间的首部,然后继续向下合并区间。

 

class Solution {
public:
    static bool func(vector<int>&a,vector<int>&b)
    {
        return a[0]<b[0];
    }
    vector<vector<int>> merge(vector<vector<int>>& intervals) {
        if(intervals.size()<=1)
            return intervals;
        sort(intervals.begin(),intervals.end(),func);

        vector<vector<int>> res;
        vector<int>temp;

        temp.push_back(intervals[0][0]);
        int num=intervals[0][1];
        int index=1;
        while(index<intervals.size())
        {
            if(intervals[index][1]>num)
           {
                if(intervals[index][0]<=num)
                    num=intervals[index][1];
                else
                {
                    temp.push_back(num);
                    res.push_back(temp);
                    temp.clear();
                    temp.push_back(intervals[index][0]);
                    num=intervals[index][1]; 
                }
            }
            index++;
        }
        temp.push_back(num);
        res.push_back(temp);
        return res;
    }
};

  

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

示例:

输入:nums = [-1,2,1,-4], target = 1
输出:2
解释:与 target 最接近的和是 2 (-1 + 2 + 1 = 2) 。

思路:和三数之和思路类似,先排序然后使用双指针完成

 

class Solution {
public:
    int threeSumClosest(vector<int>& nums, int target) {
        int len = nums.size();

        if(nums.size()==3){
            return nums[0]+nums[1]+nums[2];
        }

        int k=0;
        //先排序,然后用双指针思路
        sort(nums.begin(),nums.end());

        //初始化
        int res = nums[0] + nums[1] + nums[2];
        int diff = abs(target - res);

        while(k<len-1){
             int r=len-1;
             int l=k+1;

             while(l<r){
                if(nums[l]+nums[k]+nums[r]==target){
                    res = nums[l]+nums[k]+nums[r];
                    return target;
                }

                //更新结果值和最小差值
                if(abs(target -(nums[l]+nums[k]+nums[r])) < diff){
                    res = nums[l]+nums[k]+nums[r];
                    diff = abs(target - res);
                }
                //判断指针移动方式
                if(nums[l]+nums[k]+nums[r]>target){
                    r--;
                }else if(nums[l]+nums[k]+nums[r]<target){
                    l++;
                }

             }
             k++;
        }

        return res;

    }
};

  

 

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

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

 

示例 1:

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

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

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


思路:快慢指针,依次比较,发现和后一个不相同,则重新添加进来
快指针用来遍历数组,慢指针用来更新

 

class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
        // 别忘记空数组的判断
        if (nums.empty()) return 0;
        int len= nums.size();
        int slow = 0;
        //slow = 0 的数据一定是不重复的,所以直接 ++slow
        for(int fast=0;fast<len-1;fast++){
            if(nums[fast]!=nums[fast+1]){
                nums[++slow]=nums[fast+1];
        }
    }
    return slow +1;
    }
};

  

80. 删除排序数组中的重复项 II
排序数组,你需要在原地删除重复出现的元素,使得每个元素最多出现两次,返回移除后数组的新长度。

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

示例 1:

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

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

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

 

分析:

快指针:遍历整个数组;
慢指针:记录可以覆写数据的位置;
题目中规定每个元素最多出现两次,因此,应检查快指针指向的元素和慢指针指针所指向单元的前一个元素是否相等。
相等则不更新慢指针,只更新快指针;不相等时,先将慢指针后移一位,再将快指针指向的元素覆写入慢指针指向的单元,最后更新快指针(详见图解)。
边界:

当数组的长度小于等于 2 时,不需要操作,直接返回原数组即可。
初始化:

快指针用于遍历数组,但算法不可能操作序号小于 2 的元素,因此快指针初始值为 2;
初始状态下,慢指针应紧随快指针之后,因此初始值为 1;
结束条件:

快指针达到数组结尾。

 

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

  

 

209. 长度最小的子数组
给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的 【连续 】子数组,并返回其长度。如果不存在符合条件的子数组,返回 0。

 

示例:

输入:s = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。

思路:
由于题目中给出的数组中的元素全都是正整数并且需要不断计算该数组中子数组的和,所以我们可以利用双指针的滑动窗口法来解决这个问题。

定义两个指针left和right分别为滑动窗口的左端点和右端点,所以定义right,left的初始值为0。并且这两个指针均向右移动。在移动过程中,如果子数组的和 < s,
那么我们就需要将right向右移动并且加上当前索引的元素值以扩大移动窗口的范围,反之则需要减去当前索引的元素值并且将left向左移动以缩小移动窗口的范围。

我们还需要定义一个最终的返回值minVal。需要注意的是:我们需要将该值的初始值设定为比数组的长度大一些的值。

 

class Solution {
public:
    int minSubArrayLen(int s, vector<int>& nums) {
        if(nums.size()<1){
            return 0;
        }
        if(nums.size() == 1){
            if(nums[0]==s){
                return 1;
            }
            else{
                return 0;
            }
        }

        int len = nums.size();
        int res = len +1;

        int l=0;
        int r=0;
        int sum = 0;

        while(r<len){
            sum = sum+nums[r++];
            if(sum<s){
                continue;
            }
            while(sum>=s){
                sum =sum-nums[l];
                l++;
            }

            res = min(res,r-l+1);

        }

        if(res==len+1){
            return 0;
        }

        return res;

    }
};

  

54. 螺旋矩阵
给定一个包含 m x n 个元素的矩阵(m 行, n 列),请按照顺时针螺旋顺序,返回矩阵中的所有元素。

示例 1:

输入:
[
[ 1, 2, 3 ],
[ 4, 5, 6 ],
[ 7, 8, 9 ]
]
输出: [1,2,3,6,9,8,7,4,5]
示例 2:

输入:
[
[1, 2, 3, 4],
[5, 6, 7, 8],
[9,10,11,12]
]
输出: [1,2,3,4,8,12,11,10,9,5,6,7]


思路:
通过观察输出矩阵,可以看出输出矩阵是输入矩阵从最外层开始按顺时针输出的,其次是次外层,一直这样输出。

一圈一圈遍历,注意条件边界变更;

 

class Solution {
public:
    vector<int> spiralOrder(vector<vector<int>>& matrix) {
        vector<int>res;
        if (matrix.size()== 0)
            return res;
        int r1 = 0, r2 = matrix.size() - 1;//确定行的起始截至
        int c1 = 0, c2 = matrix[0].size() - 1;//确定列的起始截至
        while (r1 <= r2 && c1 <= c2) {//尚未遍历完
            for (int c = c1; c <= c2; c++) res.push_back(matrix[r1][c]);//向右遍历行
            for (int r = r1 + 1; r <= r2; r++) res.push_back(matrix[r][c2]);//向下遍历列
            if (r1 < r2 && c1 < c2) {
                for (int c = c2 - 1; c > c1; c--) res.push_back(matrix[r2][c]);//向左遍历行
                for (int r = r2; r > r1; r--) res.push_back(matrix[r][c1]);//向上遍历列
            }
            r1++;//清除掉已经遍历的行和列
            r2--;
            c1++;
            c2--;
        }
        return res;
    }
};

  


73. 矩阵置零
给定一个 m x n 的矩阵,如果一个元素为 0,则将其所在行和列的所有元素都设为 0。请使用原地算法。

示例 1:

输入:
[
[1,1,1],
[1,0,1],
[1,1,1]
]
输出:
[
[1,0,1],
[0,0,0],
[1,0,1]
]
示例 2:

输入:
[
[0,1,2,0],
[3,4,5,2],
[1,3,1,5]
]
输出:
[
[0,0,0,0],
[0,4,5,0],
[0,3,1,0]
]

一个简单的改进方案是使用 O(m + n) 的额外空间,但这仍然不是最好的解决方案。


思路:遍历数组,若matrix[i][j]==0,保存下i、j,用于以后更改matrix,需要额外空间O(m+n),上一种方法只需要常数的额外空间。
https://zhuanlan.zhihu.com/p/110870799

class Solution {
public:
    void setZeroes(vector<vector<int>>& matrix) {
        unordered_map<int,int>rows;
        unordered_map<int,int>cols;
        for(int i=0;i<matrix.size();++i)
        {
           for(int j=0;j<matrix[0].size();++j)
            {
                if(matrix[i][j]==0)
                {
                    rows[i]=1;
                    cols[j]=1;
                }
            }     
        }
        

        for(int i=0;i<matrix.size();++i)
        {
           for(int j=0;j<matrix[0].size();++j)
            {
                if(rows.find(i)!=rows.end()||cols.find(j)!=cols.end())
                {
                    matrix[i][j]=0;
                }
            }     
        }

    }
};

  

384. 打乱数组

打乱一个没有重复元素的数组。

 

示例:

// 以数字集合 1, 2 和 3 初始化数组。
int[] nums = {1,2,3};
Solution solution = new Solution(nums);

// 打乱数组 [1,2,3] 并返回结果。任何 [1,2,3]的排列返回的概率应该相同。
solution.shuffle();

// 重设数组到它的初始状态[1,2,3]。
solution.reset();

// 随机返回数组[1,2,3]打乱后的结果。
solution.shuffle();

 

洗牌算法

参考:洗牌算法深度详解 - 打乱数组 - 力扣(LeetCode)

每次迭代,生成一个范围在当前下标到数组末尾元素下标之间的随机整数。

接下来,将当前元素和随机选出的下标所指的元素互相交换 - 这一步模拟了每次从 “帽子” 里面摸一个元素的过程,

其中选取下标范围的依据在于每个被摸出的元素都不可能再被摸出来了(被放到前面去了)。

概率计算其实也适合上一个方法一样的逻辑。

 

https://blog.csdn.net/qq_32523711/article/details/108426543


class Solution {
public:
    Solution(vector<int>& nums) : data(nums){}
    
    /** Resets the array to its original configuration and return it. */
    vector<int> reset() {
        return data;
    }
    
    /** Returns a random shuffling of the array. */
    vector<int> shuffle() {
        vector<int> res(data);
        int n = res.size();
        for(int i = 0; i < n; ++i){
            int idx = i + rand()%(n-i);//生成一个范围在当前下标到数组末尾元素下标之间的随机整数
            swap(res[i], res[idx]); 
        }
        return res;
    }
private:
    vector<int> data;
};

/**
 * Your Solution object will be instantiated and called as such:
 * Solution* obj = new Solution(nums);
 * vector<int> param_1 = obj->reset();
 * vector<int> param_2 = obj->shuffle();
 */

 

暴力法

每次选取一个元素放入结果数组,然后将该元素删除。

证明:某个元素第k轮被选取的概率是:前k-1轮未被选中 * 第k轮选中。
展开之后, 最后总是等于1/n。

 

class Solution {
public:
    Solution(vector<int>& nums) : data(nums){}
    
    /** Resets the array to its original configuration and return it. */
    vector<int> reset() {
        return data;
    }
    
    /** Returns a random shuffling of the array. */
    vector<int> shuffle() {
        vector<int> backup(data), res;
        int n = backup.size();
        for(int i = 0; i < n; ++i){
            int idx = rand()%backup.size();
            res.push_back(backup[idx]);
            backup.erase(backup.begin()+idx);
        }
        return res;
    }
private:
    vector<int> data;
};

  

 

581. 最短无序连续子数组

给定一个整数数组,你需要寻找一个连续的子数组,如果对这个子数组进行升序排序,那么整个数组都会变为升序排序。

你找到的子数组应是最短的,请输出它的长度。

示例 1:

输入: [2, 6, 4, 8, 10, 9, 15]
输出: 5
解释: 你只需要对 [6, 4, 8, 10, 9] 进行升序排序,那么整个表都会变为升序排序。
说明 :

输入的数组长度范围在 [1, 10,000]。
输入的数组可能包含重复元素 ,所以升序的意思是<=。


思路:先找左边界,遍历数组先找到第一个发生递减的位置l,然后看l+1~n最小数的下标,移动l到合适的位置
同理,找右边界,找到最后一个递减的位置r,在0~r-1找到最大的数的下标,移动r到合适的位置

class Solution {
public:
    int findUnsortedSubarray(vector<int>& nums) {
        int n = nums.size();
        int l=0,r=n-1;
        while(l<n-1){  //找到第一个发生递减的位置
            if(nums[l]<=nums[l+1]){
                l++;
            }
            else{
                break;
            }
        }
        if(l == n-1) return 0; 
        int x = l+1;
        for(int i=l+1; i<n; i++){  //找到l+1~n最小数的下标
            if(nums[i]<=nums[x]){
                x = i;
            }
        }
        while(l>=0){  //移动l
            if(nums[l]>nums[x]) l--;
            else    break;
        }

        while(r>0){  //找到最后一个递减的位置
            if(nums[r]>=nums[r-1])  r--;
            else    break;
        }
        x = r-1;
        for(int i=r-1; i>=0; i--){  //在0~r-1找到最大的数的下标
            if(nums[i]>=nums[x]){
                x = i;
            }
        }
        while(r<n){  //移动r
            if(nums[r]<nums[x]){
                r++;
            }
            else break;
        }
        return r-l-1;
    }

};

  

 

945. 使数组唯一的最小增量
给定整数数组 A,每次 move 操作将会选择任意 A[i],并将其递增 1。

返回使 A 中的每个值都是唯一的最少操作次数。

示例 1:

输入:[1,2,2]
输出:1
解释:经过一次 move 操作,数组将变为 [1, 2, 3]。
示例 2:

输入:[3,2,1,2,1,7]
输出:6
解释:经过 6 次 move 操作,数组将变为 [3, 4, 1, 2, 5, 7]。
可以看出 5 次或 5 次以下的 move 操作是不能让数组的每个值唯一的。

思路:
1)先把数组排序;
2)然后用变量tmp初始化A[0],用于储存较大值,接下来,从后一个数开始遍历,如果发现后一个数比tmp大,则更新tmp,否则,将当前数A[i]调整为tmp+1所需次数,即tmp+1-A[i];

 

class Solution {
public:
    int minIncrementForUnique(vector<int>& A) {
        int len = A.size();
        if(len<=1){
            return 0;
        }
        sort(A.begin(),A.end());
        int res = 0;
        int tmp = A[0];
        for(int i=1;i<len;i++){
            if(A[i] > tmp){
              tmp = A[i];
            }
            else
            {
                res += tmp - A[i] + 1;
                tmp++;
            }
        }

        return res;

    }
};

  

 

 

347. 前 K 个高频元素

给定一个非空的整数数组,返回其中出现频率前 k 高的元素。

 

示例 1:

输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]
示例 2:

输入: nums = [1], k = 1
输出: [1]

思路1:使用hash map统计每个数字出现的次数,然后按照值进行排序,获取前k个即可
排序的时候使用vector标准模版排序方法;

注意:
1)pair 数据结构,元素的访问方式;
2)将map转为vector的方式;
3)标准模版库函数的定义方式;小于号<是从小到大排序,大于号>是从大到小排序;

class Solution {
public:
    static bool cmp_by_value(const pair<int, int>& lhs, const pair<int, int>& rhs) {
      return lhs.second > rhs.second;
    }


    vector<int> topKFrequent(vector<int>& nums, int k) {
        map<int,int> num_count;
        vector<int> res;
        for(auto n:nums){
            num_count[n]++;
        }

        vector<pair<int, int>> num_count_vec(num_count.begin(), num_count.end());
        sort(num_count_vec.begin(), num_count_vec.end(), cmp_by_value);

        for(int i=0;i<k;i++){
            cout<<num_count_vec[i]<<endl;
            res.push_back(num_count_vec[i].second);
        }

        return res;
    }
};

  

思路2:使用STL优先队列
注意:使用STL的优先队列 priority_queue Comparator = greater 为小堆;Comparator = less 为大堆

class Solution{
public:
struct CmpByValueLess {
    bool operator()(const pair<int, int>& lhs, const pair<int, int>& rhs) {
        return lhs.second < rhs.second;
    }
};

vector<int> topKFrequent(vector<int>& nums, int k) {

    //统计次数
    unordered_map<int, int> m;

    for (int i = 0; i < nums.size(); ++i)
    {
        ++m[nums.at(i)];
    }

    //放入优先队列   
    priority_queue<pair<int, int>, vector<pair<int, int>>, CmpByValueLess> queue;
    for (auto iter = m.begin(); iter != m.end(); ++iter)
    {
        queue.push(*iter);
    }

    vector<int> res(k);
    for (int i = 0; i < k; ++i)
    {
        res.at(i) = queue.top().first;
        queue.pop();
    }

    return res;
}
};

  

 

34. 在排序数组中查找元素的第一个和最后一个位置
给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。

你的算法时间复杂度必须是 O(log n) 级别。

如果数组中不存在目标值,返回 [-1, -1]。

示例 1:

输入: nums = [5,7,7,8,8,10], target = 8
输出: [3,4]
示例 2:

输入: nums = [5,7,7,8,8,10], target = 6
输出: [-1,-1]

 

思路:二分查找,分别找到第一次出现和最后一次出现


方法2:
先使用二分查找法查找出一个匹配的位置,然后使用双指针方法寻找上界和下界

方法3:(使用递归实现二分查找法来使时间复杂度真正达到O(log n) )
使用递归实现二分查找法,当nums[mid] == target时使用二分查找法寻找第一个匹配的元素和最后一个匹配的元素位置

 

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        int len=nums.size();

        int right = len-1;
        int left = 0;

        if(len==1&&nums[0]==target){
            return {0,0};
        }

        if(len==1&&nums[0]!=target){
            return {-1,-1};
        }  

        vector<int> res;


        while(left<=right){
            int mid=left+(right-left)/2;
            if(nums[mid]<target){
                left = mid+1;
            }
            else if(nums[mid]>target){
                right = mid-1;
            }
            if(nums[mid]==target){
                right = mid-1;
            }
        }

        if(left>=len||nums[left]!=target){
            return {-1,-1};
        }

        else{
            res.push_back(left);
            while(left<len-1){
                if(nums[left]!=nums[left+1]){
                    res.push_back(left);
                    break;
                }
                else{
                    left++;
                }
            }

        }
        if(nums[len-1]==target){
            res.push_back(len-1);
        }
        return res;

    }
};

  

 

977. 有序数组的平方

给定一个按非递减顺序排序的整数数组 A,返回每个数字的平方组成的新数组,要求也按非递减顺序排序。


示例 1:

输入:[-4,-1,0,3,10]
输出:[0,1,9,16,100]
示例 2:

输入:[-7,-3,2,3,11]
输出:[4,9,9,49,121]

思路:
1) 直接平方后,再进行排序,复杂度nlog(n)

 

class Solution {
public:
    vector<int> sortedSquares(vector<int>& A) 
    {
        vector<int> res;
        for(int i=0;i<A.size();i++)
        {
            res.push_back(A[i]*A[i]);
        }
        sort(res.begin(),res.end());
        return res;
    }
};

  


2) 找到绝对值最小的值后,使用双指针的思路进行,复杂度O(n)

class Solution {
public:
    vector<int> sortedSquares(vector<int>& A) {
        int len = A.size();

        if(len==0){
            return {};
        }

        if(len==1){
            return {A[0]*A[0]};
        }

        if(len==2){
            if(abs(A[0])>=abs(A[1])){
                return {A[1]*A[1],A[0]*A[0]};
            }else{
                return {A[0]*A[0],A[1]*A[1]};
            }
        }


        vector<int> res;

        if(A[0]>=0){
            for(int i=0;i<len;i++){
                A[i]=A[i]*A[i];
            }

            return A;

        }else{
            int min_num_index=0;
            int min_num = abs(A[0]);
            for(int i=1;i<len;i++){
                if(abs(A[i])<min_num){
                    min_num=abs(A[i]);
                    min_num_index = i;
                }
            }

            res.push_back(A[min_num_index]*A[min_num_index]);

            int left = min_num_index -1;
            int right = min_num_index+1;
    

            while(left>=0||right<=len-1){
                if(left>=0&&right<=len-1){
                    if(abs(A[left])>=abs(A[right])){
                        res.push_back(A[right]*A[right]);
                        right++;
                    }else{
                        res.push_back(A[left]*A[left]);
                        left--;
                    }
                }else if(left<0){
                    res.push_back(A[right]*A[right]);
                    right++;
                }else if(right>(len-1)){
                    res.push_back(A[left]*A[left]);
                    left--;
                }
            }
        }

        return res;

    }
};

  

 


2、统计有序数组里平方和的数目:
给你一个有序整数数组,数组中的数可以是正数、负数、零,请实现一个函数,这个函数返回一个整数:
返回这个数组所有数的平方值中有多少种不同的取值。举例:

nums = {-1,1,1,1},

那么你应该返回的是:1。因为这个数组所有数的平方取值都是1,只有一种取值

nums = {-1,0,1,2,3}

你应该返回4,因为nums数组所有元素的平方值一共4种取值:1,0,4,9

方法一:
暴力解法,用set保存平方。
直接先遍历一次得到平方,将平方和放入集合中,输出集合的大小。
时间复杂度为O(n),空间复杂度为O(n),有序的条件没有被用到。

 

int diffSquareNum(const vector<int>& nums) {
    if (!nums.size())
        return 0;
    set<int> s;
    for(auto n:nums){
        s.insert(n*n);
    }

    int len = s.size();

    return len;
}

  

方法二:双指针思路
需要计算的是不同的平方和,那么平方和相同时有两种情况,
1)两个元素是相邻元素,元素值本身相同;
2)两个元素绝对值相等,互为相反数;
对于第一种情况可以直接比较是否与相邻元素相等,相等时不计数;
对于第二种情况利用数组有序的条件,使用双指针分别指向数组的头和尾,相等时不计数。两指针向中间移动。
有序数组普遍是利用双指针问题求解,这里也一样。每次检查头尾指针的和是等于0、大于0还是小于0.
根据这3中情况分析。注意数字可能重复,需要去重。

 

int diffSquareNum(vector<int>& nums) {
    if (!nums.size())
        return 0;
    int left = 0;
    int right = nums.size() - 1;
    int sum = 0;
    while (left <= right) {
        if (nums[left] + nums[right] == 0) {
            sum++;
            int t = nums[left];
            //这里是为了去重
            while (left <= right && nums[left] == t)
                left++;
            while (left <= right && nums[right] == -t)
                right--;
        }
        else if (nums[left] + nums[right] < 0) {
            sum++;
            int t = nums[left];
            while (left <= right && nums[left] == t)
                left++;
        }
        else{
            sum++;
            int t = nums[right];
            while (left <= right && nums[right] == t)
                right--;
        }
    }
    return sum;
}

  

 

剑指 Offer 40. 最小的k个数
输入整数数组 arr ,找出其中最小的 k 个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。

示例 1:

输入:arr = [3,2,1], k = 2
输出:[1,2] 或者 [2,1]
示例 2:

输入:arr = [0,1,2,1], k = 1
输出:[0]


思路1:快速排序 O(nlogn)。 先sort排序,然后再取最小的k个

class Solution {
public:
    vector<int> GetLeastNumbers_Solution(vector<int> input, int k) {
        if (input.size() == 0 || k <= 0 || k > input.size()) return {};
        sort(input.begin(), input.end());
        vector<int> res(k);   //初始化k个默认值为0的元素 ,(k,0)
        for (int i = 0; i < k; i++)
            res[i]=input[i];
        return res;
    }
};

  

思路2:推排序 O(nlogn) 。 构造大顶堆
1)使用库函数

class Solution {
public:
    vector<int> GetLeastNumbers_Solution(vector<int> input, int k) {
        if (input.size() == 0 || k <= 0 || k > input.size()) return {};
        vector<int> res(input.begin(), input.begin() + k);
        make_heap(res.begin(),res.end());  //建堆
        for (int i = k; i < input.size(); i++) {
            if (input[i] < res[0]) {
                pop_heap(res.begin(), res.end());
                res.pop_back();

                res.push_back(input[i]);
                push_heap(res.begin(), res.end());
            }
        }
        sort_heap(res.begin(), res.end());
        return res;
    }
};

  

2)使用优先队列 priority_queue

class Solution {
public:
    vector<int> getLeastNumbers(vector<int>& arr, int k) {
        if(arr.size() == 0 || k > arr.size()|| k <= 0 ){
            return {};
        }

        vector<int> res;
        priority_queue<int> q;
        for(int i=0;i<arr.size();i++){
            if(q.size()<k){
                q.push(arr[i]);
            }
            else{
                if(q.top()>arr[i]){
                    q.pop();
                    q.push(arr[i]);
                }
            }
        }

        while(!q.empty()){
            res.push_back(q.top());
            q.pop();
        }

        return res;

    }
};

  


思路3:快速选择(快速排序的思想应用)
复杂度:O(N) + O(1)
只有当允许修改数组元素时才可以使用
快速排序的 partition() 方法,会返回一个整数 j 使得 a[l..j-1] 小于等于 a[j],且 a[j+1..h] 大于等于 a[j],
此时 a[j] 就是数组的第 j 大元素。可以利用这个特性找出数组的第 K 个元素,这种找第 K 个元素的算法称为快速选择算法。

class Solution {
public:
    vector<int> GetLeastNumbers_Solution(vector<int> input, int k) {
        int len = input.size();
        if(!len || !k || k>len) return vector<int> ();
        if(k == len) return input;
        int begin = 0, end = len-1, index = Partition(input, begin, end);
        while(index != (k-1) && begin < end) {
            if(index > k-1) {
                end = index-1;
                index = Partition(input, begin, end);
            } else {
                begin = index+1;
                index = Partition(input, begin, end);
            }
        }
       return  vector<int> (input.begin(), input.begin()+k);
    }
    int Partition(vector<int>& input, int begin, int end) {
        int pivot = input[begin];
        while(begin < end) {
            while(begin<end && pivot<=input[end]) end--;
            input[begin] = input[end];
            while(begin<end && pivot>=input[begin]) begin++;
            input[end] = input[begin];
        }
        input[begin] = pivot;
        return begin;
    }
};

  

 

 

 

 

 

 

posted @ 2020-09-15 18:27  静悟生慧  阅读(612)  评论(0编辑  收藏  举报