LeetCode热题 100

两数之和

暴力枚举

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

哈希表

方法一的时间复杂度较高的原因是寻找target-x的时间复杂度过高。

因此,我们需要一种更优秀的方法,能够快速寻找数组中是否存在目标元素。如果存在,我们需要找到它的索引。

使用哈希表,可以将寻找target-x的时间复杂度从O(N)降到O(1)。

cpp 复制代码
class Solution{
public:
	vector<int> twoSum(vector<int>& nums, int target){
		unordered_map<int, int> hashtable;
		for(int i=0; i<nums.size(); i++){
			auto it = hashtable.find(target-nums[i]);
			if(it != hashtable.end()){
				return {it->second,i};
			}
			hashtable[nums[i]] = i;
		}
		return {];
	}
};

字母异位词分组

给一个字符串数组,请你将字母异位词组合在一起。

排序

由于互为字母异位词的两个字符串包含的字母相同。

cpp 复制代码
class Solution {
public:
    vector<vector<string>> groupAnagrams(vector<string>& strs) {
        unordered_map<string, vector<string>> strMap;
        for(string& str:strs){
        	string key = str;
        	sort(key.begin(),key.end());
        	mp[key].emplace_back(str);
        }
        
        vector<vector<string>> ans;
        for(auto it=mp.begin(); it!=mp.end();it++){
        	ans.empalce_back(it->second);
        }
        return ans;
    }
};

最长连续序列

给定一个未排序的整数数组nums,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。

请设计并实现时间复杂度为O(n)的算法解决此问题。

哈希表

我们考虑枚举数组中的每个数x,考虑以其为起点,不断尝试匹配x+1,x+2,...是否存在,假设最长匹配到了x+y,那么以x为起点的最长连续序列即为x,x+1,...,x+y,其长度为y+1,我们不断枚举并更新答案即可。

对于匹配的过程,暴力的方法是O(n)遍历数组去看是否存在这个数,但其实更高效的方法是使用一个哈希表存储数组中的数,这样查看一个数是否存在即能优化至O(1)的时间复杂度。

仅仅是这样,我们的算法时间复杂度最坏情况下还是会达到O(n²)(外存需要枚举O(n)个数,内存需要暴力匹配O(n)次),无法满足题目的要求。

仔细分析这个过程,会发现其中执行了很多不必要的枚举,如果已知有一个从x开始的连续序列,而我们却从x+1开始,那么得到的结果肯定不会优于枚举x为起点的答案,因此我们在外层循环的时候碰到这种情况跳过即可。

cpp 复制代码
class Solution {
public:
    int longestConsecutive(vector<int>& nums) {
        unordered_set<int> hashTable;
        for(const int&num:nums){
        	hashTable.insert(num);
        }
		
		int maxLength = 0;
		for(const int&num:hashTable){
			if(!hashTable.count(num-1)){
				int currentNum = num;
				int currentLen = 1;
				
				while(hashTable.count(currentNum+1)){
					currentNum++;
					currentLen++;
				}
				maxLength = max(maxLength, currentLen);
			}
		}
		return maxLength;
    }
};

移动零

给定一个数组nums,编写一个函数将所有0移动到数组的末尾,同时保持非零元素的相对顺序。

双指针

使用双指针,左指针指向当前已处理好的序列的尾部,右指针指向待处理序列的头部。

右指针不断向右移动,每次右指针指向非零数,则将左右指针对应的数交换,同时左指针右移。

无重复字符的最长子串

cpp 复制代码
class Solution {
public:
    int lengthOfLongestSubstring(string s) {
    	int n = s.size();
    	if(n <= 1){
    		return n;
    	}

		int l = 0;
		int r = 1;
		unordered_set<char> charMap;
		charMap.insert(s[l]);
		int maxLen = 1;

		while(r < n){
			if(charMap.count(s[r])){
				maxLen = max(maxLen, r-l);
				charMap.remove(s[l]);
				l++;
			}else{
				charMap.insert(s[r]);
				r++;
			}
		}
		maxLen = max(maxLen,r-l);
		return maxLen;
    }
};

和为k的子数组

枚举

cpp 复制代码
class Solution {
public:
    int subarraySum(vector<int>& nums, int k) {
        int count = 0;
        for(int i=0; i<nums.size(); i++){
            int sum = 0;
            for(int j=i; j>=0; j--){
                sum += nums[j];
                if(sum == k){
                    count++;
                }
            }
        }
        return count;
    }
};

前缀和+哈希表优化

方法一的瓶颈在于对每个i,我们需要枚举所有的j来判断是否符合条件。

定义pre[i]为0,...,i的和

cpp 复制代码
pre[i] = pre[i-1] + nums[i]

[j,...i]子数组和为k:

cpp 复制代码
pre[i] - pre[j-1] == k

pre[j-1] = pre[i] - k;

最大子数组和

找出一个具有最大和的连续子数组,返回最大和(至少包含一个元素)。

动态规划

用f(i)代表以第i个数结尾的连续子数组的最大和。

可以考虑nums[i]单独成为一段还是加入f(i-1)对应的一段。

f(i) = max{nums[i],f(i-1)+nums[i]}

不难给出一个时间复杂度、空间复杂度均为O(n)的实现,即用一个f数组来保存f(i)的值,用一个循环求出所有f(i)。

f(i)只和f(i-1)有关,所以可以用一个变量维护即可,类似于滚动数组。

cpp 复制代码
class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int n = nums.size();
        if(n == 1){
            return nums[0];
        }
        int sum = nums[0];
        int maxSum = sum;
        for(int i=1; i<n; i++){
            if(sum < 0){
                sum = nums[i];
            }else{
                sum += nums[i];
            }
            maxSum = max(maxSum,sum);
        }
        return maxSum;
    }
};

合并区间

合并所有重叠的区间,并返回一个不重叠的区间。
排序

如果按照区间的左端点排序,那么在排完序的列表中,可以合并的区间一定是连续的。

用数组merged存储最终的答案。

首先,将列表中的区间按照左端点升序排序。

然后我们将第一个区间加入merged数组中,并按顺序考虑之后的每个区间:

  • 如果当前区间的左端点在数组merged中最后一个区间的右端点之后,那么它们不会重合,直接将区间加入数组merged的末尾。
  • 否则,重合,需要将当前区间的右端点更新数组merged中最后一个区间的右端点,将其重置为两者的较大值。
cpp 复制代码
class Solution {
public:
    vector<vector<int>> merge(vector<vector<int>>& intervals) {
        vector<vector<int>> merged;
        
        sort(intervals.begin(), intervals.end());

        for(int i=0; i<intervals.size(); i++){
            int L = intervals[i][0], R = intervals[i][1];
            if(!merged.size() || merged.back()[1] < L){
                merged.push_back({L,R});
            }else{
                merged.back()[1] = max(merged.back()[1], R);
            }
        }
        return merged;
    }
};

轮转数组

cpp 复制代码
class Solution {
public:
    void reverseNum(vector<int>& nums,int start,int end){
        while(start < end){
            swap(nums[start++],nums[end--]);
        }
    }
    void rotate(vector<int>& nums, int k) {
        int n = nums.size();
        k = k%n;

        reverseNum(nums,0,n-1);
        reverseNum(nums,0,k-1);
        reverseNum(nums,k,n-1);
    }
};

使用额外的数组

可以使用额外的数组来将每个元素放在正确的位置。

除自身以外数组的乘积

answer[i]等于nums中除nums[i]之外其余各元素的乘积。

左右乘积列表

利用索引左侧所有数字的乘积和右侧所有数字的乘积(即前缀与后缀)相乘得到答案。

  1. 初始化两个空数组L和R。对于给定索引i,L[i]代表的是i左侧所有数字的乘积,R[i]代表的是右侧所有数字的乘积。
  2. 我们需要用两个循环来填充L和R数组的值。对于数组L,L[0]应该是1。L[i] = L[i-1]*nums[i];
  3. R[Length-1] = 1。R[i] = R[i+1]*nums[i]。
cpp 复制代码
class Solution {
public:
    vector<int> productExceptSelf(vector<int>& nums) {
        int n = nums.size();
        vector<int> L(n,1);
        vector<int> R(n,1);
        vector<int> res(n);

        for(int i=1; i<n; i++){
            L[i] = L[i-1]*nums[i-1];
        }
        for(int i=n-2; i>=0; i--){
            R[i] = R[i+1]*nums[i+1];
        }
        for(int i=0;i<n;i++){
            res[i] = L[i]*R[i];
        }
        return res;
    }
};

空间复杂度O(1)的方法

仅管上面的方法已经能够很好地解决这个问题,但是空间复杂度并不为常数。

由于输出数组不算在空间复杂度内,那么我们可以将L或R数组用输出数组来计算。

先把输出数组当做L数组来计算,然后再动态构造R数组得到结果。

  1. 初始化answer数组,对于给定索引i,answer[i]代表的是i左侧所有数字的乘积。
  2. 没有构造R数组。用一个遍历来跟踪右边元素的乘积。并更新数组answer[i] = answer[i] * R,R=R*nums[i]
cpp 复制代码
class Solution {
public:
    vector<int> productExceptSelf(vector<int>& nums) {
        int n = nums.size();
        vector<int> res(n,1);
        for(int i=1; i<n; i++){
            res[i] *= res[i-1]*nums[i-1];
        }
        int R = 1;
        for(int i=n-1; i>=0; i--){
            res[i] = res[i]*R;
            R *= nums[i];
        }
        return res;
    }
};
相关推荐
ZSYP-S1 小时前
Day 15:Spring 框架基础
java·开发语言·数据结构·后端·spring
yuanbenshidiaos1 小时前
C++----------函数的调用机制
java·c++·算法
唐叔在学习1 小时前
【唐叔学算法】第21天:超越比较-计数排序、桶排序与基数排序的Java实践及性能剖析
数据结构·算法·排序算法
ALISHENGYA1 小时前
全国青少年信息学奥林匹克竞赛(信奥赛)备考实战之分支结构(switch语句)
数据结构·算法
chengooooooo1 小时前
代码随想录训练营第二十七天| 贪心理论基础 455.分发饼干 376. 摆动序列 53. 最大子序和
算法·leetcode·职场和发展
jackiendsc1 小时前
Java的垃圾回收机制介绍、工作原理、算法及分析调优
java·开发语言·算法
姚先生972 小时前
LeetCode 54. 螺旋矩阵 (C++实现)
c++·leetcode·矩阵
游是水里的游3 小时前
【算法day20】回溯:子集与全排列问题
算法
yoyobravery3 小时前
c语言大一期末复习
c语言·开发语言·算法
Jiude3 小时前
算法题题解记录——双变量问题的 “枚举右,维护左”
python·算法·面试