2022 春 刷题(一)

leetcode 53 最大子数组和 E && 剑指offer42 连续子数组的最大和

题目要求

输入一个整型数组nums,请你找出一个具有最大和的连续子数组,返回其最大和;

例如:

输入:nums=[-2,1,-3,4,-1,2,1,-5,4]
输出:6
这个连续数组为 [4,-1,2,1] 

代码实现

//贪心解法
class Solution
{
public:
	int maxArray(vector<int>& nums)
	{
		int res = INT_MIN;
		int n = nums.size();
		int sum = 0;
		for (int i = 0; i < n; i++)
		{
			sum += nums[i];
			res = max(res, sum);
			if (sum < 0)sum = 0;//sum小于0时 sum清零 重新开始计算结果 当给定数组中的元素都是负数时 返回的是最大的那个负数 也就是最大的和
		}
		return res; 
	}
};

//动态规划解法
class Solution
{
public:
	int maxSubArray(vector<int>& nums)
	{
		int pre = 0, maxAns = nums[0];//pre用来统计当前遍历元素的和;maxAns用来返回最大的和 初始化为数组第一个元素的值;
		for (const auto& x : nums)//遍历每个元素
		{
			pre = max(pre + x, x);//更新当前最大的和
			maxAns = max(maxAns, pre);
		}
		return maxAns;//返回子数组的最大和
	}
};

leetcode 209 长度最小的子数组 【字节】M

算法要求
给定一个含有 n 个正整数的数组和一个正整数 target

找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, …, numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。

例如:

输入 target=7 nums=[2.3.1.2.4.3]
输出:2
[4,3]是符合要求的最短数组

用滑动窗口解法
在这个题目中,和sum充当滑动窗口的角色,当滑窗内元素之和大于等于给定的目标值时,开始缩小窗口左边界,左侧的元素依次移出窗口,直到元素之和小于给定的目标值;当滑窗内元素之和小于给定的目标值时扩大窗口右边界,右侧的元素依次进入窗口;
算法实现用一个左右指针标记滑动窗口的起始和终止位置;
//在滑动的过程中,根据和的变化不断移动左右指针,不断的更新一个最小的符合要求的长度,返回这个最小长度即可
在这里插入图片描述

代码实现

class Solution
{
public:
	int minSubArrayLen(int s, vector<int>&nums)
	{
		int n = nums.size();
		if (n == 0)return 0;
		int ans = INT_MAX;//初始化ans为一个最大值用于存储输出的最小长度;如果想要得到数组中最小值,可以先将最小值赋值为INT_MAX ;同理如果想要得到数组中最大值,可以先将最大值赋值为INT_MIN ;
		int left = 0, right = 0;
		int sum = 0;
		while (right < n)//当right移动到最后一个元素时,不再移动,返回此时的ans就是最大的ans;
		{
			sum += nums[right];//sum依次累加数组nums的元素,直到其和大于给定的s,开始进入以下循环
			while (sum >= s)
			{
				ans = min(ans, right - left + 1);//更新ans为符合要求的连续数组的最短长度
				sum -= nums[left];//滑动窗口后移 从左侧开始逐个将左侧元素移出窗口 直到sum<s
				left++;
			}
			right++;
		}
		return ans == INT_MAX ? 0 : ans;
	}
};

剑指offer 2 面试题10 和为k的连续子数组 【字节】

题目要求:
给定一个整数数组nums和一个整数 k ,请找到该数组中和为 k 的连续子数组的个数

例如:

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

思路分析:
这道题和上面的题差不多是一致的 ,但是题目要求略有差别;给定的整数数组并非都是正整数,因此这道题不能用双指针解;
为什么不能用双指针?(滑动窗口解法)
这道题粗粗一看,似乎可以使用双指针来求解。左指针和右指针确定一个子数组,若子数组的和大于等于k,则右移左指针减小子数组的和,反之则右移右指针增大子数组的和。但是在这里是行不通的,因为在使用双指针基于如下假设:右移左指针相当于删除子数组中一个元素,子数组和减少,右移右指针相当于在子数组中增加一个元素,子数组和增大。如果删除、增加的元素都是正整数那么假设是成立的,但本题中数组中的元素不能保证都为正数,所以原假设不成立。这就是不能使用双指针的原因所在。

解法一:超时了 但是可以参考思路

//一个容易理解但是会超时的解法---前缀和的思路
//和为k的子数组
class Solution
{
public:
	int subArraySum(vector<int>& nums, int k)
	{
		int n = nums.size();
		vector<int>sum(n + 1);//sum[i]表示数组中前i个元素的和
		for (int i = 0; i < n; i++)
		{
			sum[i + 1] = sum[i] + nums[i];//从数组中第二个位置开始依次求得前缀和
		}
		int ret = 0;//存储目标子数组的个数
		for (int i = 0; i < n; i++)
		{
			for (int j = 0; j <= i; j++)
			{
				if (sum[i + 1] - sum[j] == k)ret++;//找到一个和为k的子数组
			}
		}
		return ret;
	}
};

解法二:哈希解法

哈希解法
哈希表保存的是出现过的累加和sum出现的次数,sum是指从0到当前元素的累加和;初始化为map[0]=1,表示和为0的连续子数组出现一次;
首先要查找map中是否出现过sum-k的元素,也就是在查找此前所有从0项开始累加的连续子项和中有没有sum-k;如果有的话,则说明从该项到当前项的连续子数组和必定为ksum-k出现的次数就是和为k的连续子数组出现的次数--逆向思维 如下图:
在这里插入图片描述

//利用哈希表进行优化 
//sum-k出现的次数就是k出现的次数;
class Solution
{
public:
	int subarraySum(vector<int>& nums, int k)
	{
		int sum = 0, res = 0;//sum用于进行累加和;res用于存储并返回和为k的子数组的个数
		unordered_map<int, int>mp;//key是一个累加和;value是累加和出现的次数
		mp[0] = 1;//初始化sum==0 出现的次数为1
		for (auto& num : nums)
		{
			sum += num;//遍历数组nums 计算从第0个元素到当前元素的和 ,用哈希表保存出现过的累加和sum的次数;
			res += mp[sum - k];//如果sum-k在哈希表中出现过,则表示从当前下标向前有连续的子数组的和为k,此时需要将res的值加一;
			mp[sum]++;//将每一个出现过的子数组的和都加一
		}
		return res;
	}
};


剑指offer II 009 乘积小于k的子数组

题目要求:
给定一个正整数数组 nums和整数 k ,请找出该数组内乘积小于 k 的连续的子数组的个数。(给定的数组是正整数,可以考虑用双指针进行求解)
例如:

输入:nums=[10,5,2,6] k=100
输出:8
这八个子数组分别是: [10], [5], [2], [6], [10,5], [5,2], [2,6], [5,2,6]

滑动窗口解法:

//乘积小于k的连续子数组
class Solution
{
public:
	int numSubarrayProductLessThanK(vector<int>& nums, int k)
	{
		int l = 0, r = 0;//左右指针
		int n = nums.size();
		int cur = 1;//cur存储当前遍历到的元素的累积,初始化为1
		int res = 0;//存储乘积小于k的子数组的个数
		for (; r < n; r++) //因为无需再次进行初始化了 所以for语句的第一部分为空
		{
			cur *= nums[r];//用cur存储乘积的结果
			while (l <= r && cur >= k)//当前乘积的结果大于给定的k
			{
				cur /= nums[l];//除以左边界元素,一直除到乘积小于k
				l++;//滑动窗口右移(左边界右移) 缩小滑窗的范围 
			}
			res += (r-l+1);//通过规律会发现符合这个式子
		}
		return res;//返回最后子数组的个数
	}
};

leetcode 260 只出现一次的数字 III M

题目要求:
给定一个整数数组 nums,其中恰好有两个元素只出现一次,其余所有元素均出现两次。 找出只出现一次的那两个元素。你可以按 任意顺序 返回答案。

例如:

输入:nums=[1,2,1,3,2,5]
输出:[3,5][5,3]

算法思想:

创建一个无序集合即哈希集合,用于存储遍历到的数组中的元素,
一开始集合中是空的,开始把元素依次放入集合中,当要放入的元素和集合中的元素有一样的时候,删除集合中的元素即可,这个元素是不符合要求的
简单说,就是不断地向集合中加入删除元素,如果集合中不存在和要放入的元素一样的元素就加入,否则删除;最终集合中保存下来的元素就是我们要的答案

代码实现:

class Solution
{
public:
	vector<int>singleNumber(vector<int>& nums)
	{
		unordered_set<int>ansSet;//创建一个无序集合 本质上是哈希表
		for (auto& x : nums)
		{
			if (ansSet.find(x) != ansSet.end())//判断集合中是否存在和数组中一样的元素 也就是判断数组中是不是有重复元素
			{
				ansSet.erase(x);//数组中有重复元素 则删除集合中的元素
			}
			else
			{
				ansSet.insert(x);//不含重复元素 在集合中加入该遍历到的元素 保存
			}
		}
	}
};

用经典的哈希解法----我自己很熟悉的方法
小注:
对范围for循环的一点理解:
for(auto &r:v);
使用auto自动类型推导可以保证类型相容,令编译器为r指定正确的类型;
使用引用类型变量&r: 当你准备修改v的值时,需要这样做;因为只有这样才可以对r执行写操作;

class Solution {
public:
    vector<int> singleNumber(vector<int>& nums) 
    {
        unordered_map<int,int>map;
        vector<int>res(2,0);
        for(auto &num:nums) //引用的方式进行遍历
        {
            map[num]++;
        }
        for(auto p:map)
        {
            if(p.second==1)
            {
                res.push_back(p.first);
            }
        }
        return res;
    }
};

类似的题目 第一次只出现一次的字符
在字符串 s 中找出第一个只出现一次的字符。如果没有,返回一个单空格。 s 只包含小写字母。
例如:
输入:s = “abaccdeff”
输出:‘b’



class Solution
{
public:
	char firstUniqChar(string s)
	{
		unordered_map<char, int>map;
		for (char c : s)//第一次遍历用于统计字符出现的次数
		{
			map[c]++;
		}
		for (char c : s)//第二次遍历用于查找出现次数为1的字符
		{
			if (map[c] == 1)return c;
		}
		return ' ';
	}
};


leetcode 274/275 H 指数

题目要求:

给你一个整数数组 citations ,其中 citations[i] 表示研究者的第 i 篇论文被引用的次数。计算并返回该研究者的 h 指数。

根据维基百科上 h 指数的定义:h 代表“高引用次数”,一名科研人员的 h指数是指他(她)的 (n 篇论文中)总共有 h 篇论文分别被引用了至少 h 次。且其余的 n - h 篇论文每篇被引用次数 不超过 h 次。

如果 h 有多种可能的值,h 指数 是其中最大的那个。

例如:


输入:citations=[3,1,6,0,5]
输出:3

算法思想:
如果在当前H指数为h并且在遍历过程中**找到当前值citations[i]>h,则说明我们找到了一篇被引用次数至少为h+1的论文** ,所有将现有的h加1继续遍历直到h无法继续增大,缩小右边界继续查找,最后返回h作为最终答案;

代码实现:

class Solution
{
public:
	int hIndex(vector<int>& citations)
	{
		sort(citations.begin(), citations.end());//对引用次数进行排序 默认升序
		int h= 0, i= citations.size() - 1;//定义h为引用次数 i表示右边界元素的下标
		while (i >= 0 && citations[i] > h){ //找到一篇引用次数至少为h+1的论文
			h++;
			i--;
		}
		return h;
	}
};

剑指offer 49 丑数 M

我们把只包含质因子2 3 5的数字称为丑数 ,求按从小到大的顺序的第n个丑数;

例如:
输入: n = 10
输出: 12
解释: 1, 2, 3, 4, 5, 6, 8, 9, 10, 12 是前 10 个丑数。
小注:
把只包含质因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但7、14不是,因为它们包含质因子7。 习惯上我们把1当做是第一个丑数。
前20个丑数为:1, 2, 3, 4, 5, 6, 8, 9, 10, 12, 15, 16, 18, 20, 24, 25, 27, 30, 32, 36

代码实现:



//丑数 动规解法/三指针解法  高效的解法
//对于任意一个丑数(除了1) 都是由小于它的某一个丑数*2或*3或*5得到的
//那么就可以将所有的丑数分为三大部分:*2得到的/ *3得到的 / *5得到的 
class Solution
{
public:
	int nthUglyNumber(int n)
	{
		int p2 = 0, p3 = 0, p5 = 0; // 初始化三个指针分别用来遍历三大部分  
		vector<int>dp(n, 0); // dp[i]表示第i个丑数 第n个丑数就是dp[n-1]
		dp[0] = 1; 
		for (int i = 1; i < n; i++)
		{
			int num2 = 2 * dp[p2];//num2 num3 num5分别是从三大部分得到的丑数
			int num3 = 3 * dp[p3];
			int num5 = 5 * dp[p5];
			//dp[i]表示当前丑数 是从num2 num3 num5中选最小值选出来的
			dp[i] = min(num2, min(num3, num5));//因为所求的丑数要求从小到大 因此取三种情况下的最小值 
			if (dp[i] == num2)p2++;//当num2 num3 num5中当选为当前丑数时  对应部分的指针加一 继续向下遍历这一部分
			if (dp[i] == num3)p3++;
			if (dp[i] == num5)p5++;
		}
		return dp[n - 1];//返回第n个丑数
	}
};

//=============================================最小堆解法 低效一些===========================================
class Solution {
public:
    int nthUglyNumber(int n) 
    {
        vector<int>factors={2,3,5};
        unordered_set<long>seen;
        priority_queue<long,vector<long>,greater<long>>heap;
        seen.insert(1L);
        heap.push(1L);
        int ugly=0;
        for(int i=0;i<n;i++)
        {
            long curr=heap.top();
            heap.pop();
            ugly=(int)curr;
            for(int factor:factors)
            {
                long next=curr*factor;
                if(!seen.count(next))
                {
                    seen.insert(next);
                    heap.push(next);
                }
            }
        }
        return ugly;
    }
};

在这里插入图片描述

//给定一个数 判断这个数字是不是丑数
class Solution {
public:
    bool isUgly(int n) 
    {
        if(n<=0)return false;
        vector<int>factors={2,3,5};//丑数集合
        for(int factor:factors)
        {
            while(n%factor==0)//任意一个丑数除以质因数2 3 5 余数都是0
            {
                n/=factor;//n=n/factor 若是丑数 最后的n肯定是factor的一倍 即n==1
            }
        }
        return n==1;
    }
};

leetcode 239 【滑动窗口最大值 && 剑指offer 59-I 滑动窗口最大值 H】

给你一个整数数组nums,有一个大小为k的滑动窗口从数组的最左侧移动到数组的最右侧,你只可以看到在滑动窗口内的k个数字,滑动窗口每次只向右移动一位;返回滑动窗口中的最大值;

例如:
在这里插入图片描述

简单高效容易理解的做法

//一个简单做法
//这个双端队列的作用
//队列只维护k个元素 随着队列的移动 滑窗里的元素动态更新 每次都返回窗口内的最大值即可
class Solution
{
public:
	vector<int>maxSlidWin(vector<int>& nums, int k)
	{
		vector<int>res;
		deque<int>q;//双端队列
		for (int i = 0; i < nums.size(); i++)
		{
			while (!q.empty() && nums[i] >= nums[q.back()])q.pop_back();//保证滑窗内队首元素是最大的
			q.push_back(i);//队列中维护的是数组中元素的下标
			if (q.back() - q.front() >= k)q.pop_front();//用一个双端队列模拟滑动窗口 超出窗口容量 依次删除队首元素;
			if (i >= k - 1)res.push_back(nums[q.front()]);//达到指定的窗口大小后 开始保存队首元素也就是滑窗内的最大值;
		}
		return res;
	}
};

题目要求:
给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。
返回 滑动窗口中的最大值 。

例如
输入:[1,3,-1,-3,5,3,6,7]
输出:[3,3,5,5,6,7]

算法思想:
双端队列的解法:
首先对变量**win** 做出以下解释:
//win的特点:
//变量的最前端即win.front()是此时遍历的最大值的下标
//当我们遇到新的数时,将新的数和双向队列的末尾即win.back()比较
//如果末尾比新数小,则把末尾扔掉,直到该队列的末尾比新数大或队列为空的时候才停止
//双向队列中的所有值都要在窗口范围内

(对size_t类型的解释:
//对size_t 类型的解释:保存了一个整数类型
// size_t 和 int的区别:
// size_t这个类型足以用来表示对象的大小,size_t的真实类型与操作系统有关;
在32位架构中是4字节,在64位架构上是8字节

// 而int无论在哪种架构下都是4字节,因此有时候使用int可能会范围不够,用size_t可以保证范围够用)

解题框架:

//第一个for循环是找到第一个滑窗内的最大值
//第二个for循环是从剩余的元素继续构建滑窗,并不断更新滑窗内的元素最大值,存储到res中

代码实现:

class Solution
{
public:
	vector<int>maxSlidingWindow(vector<int>& nums, int k)
	{
		if (k == 0)return {};
		vector<int>res;//存储最大值
		deque<size_t>win;//创建一个双端队列的滑窗。存储下标
		for (size_t i = 0; i < k; i++)//建立第一个滑窗
		{
			while (!win.empty() && nums[i] > nums[win.back()])//保证队列里的元素都是降序排列的
			{
				win.pop_back();
			}
			win.push_back(i);//遍历到的第i个位置的元素比当前窗口内的最后一个元素大,那么将最后一个元素下标删除,将较大的元素下标补充进去
		}
		//找到第一个滑窗内的最大值
		res.push_back(nums[win.front()]);//win.front()是此次遍历的最大值的下标

		for (size_t i = k; i < nums.size(); i++) //从第一个窗口后开始遍历,继续寻找下一个滑窗
		{
			if (!win.empty() && win.front() <= i - k)//保证滑窗的元素个数只有k个,多的时候开始从队首元素依次删除
			{
				win.pop_front();
			}
			while (!win.empty() && nums[i] > nums[win.back()])//将遍历到的元素和滑窗内的元素比较,将较大的元素添加到滑窗内,将较小的删除
			{
				win.pop_back();
			}
			win.push_back(i);
			res.push_back(nums[win.front()]);
		}
		return res;
	}
};


Leetcode 445 两数相加 II

题目要求:
给你两个 非空 链表来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储一位数字。将这两数相加会返回一个新的链表。
你可以假设除了数字 0 之外,这两个数字都不会以零开头。

例如:

输入: l1=[7,2,4,3] l2=[5,6,4]
输出:[7,8,0,7]

算法思想:

先将两个链表元素的数值放到栈里,再依次将元素出栈进行相加

在这里插入图片描述

代码实现:

struct ListNode
{
	int val;
	ListNode* next;
	ListNode(int x):val(x),next(NULL){}
};
class Solution
{
public:
	ListNode* addTwoNumbers(ListNode* l1, ListNode* l2)
	{
		stack<int>s1, s2;//创建两个栈,分别将两个链表的元素放入到两个栈中
		while (l1)
		{
			s1.push(l1->val);
			l1 = l1->next;//头节点l1起到指针的作用来遍历这个链表
		}
		while (l2)
		{
			s2.push(l2->val);
			l2 = l2->next;
		}
		int carry = 0;//存储进位
		int res=0;//存储余数
		ListNode* temp = nullptr;//遍历新链表的辅助节点
		while (!s1.empty() or !s2.empty() or carry != 0)
		{
			//因为栈的先进后出,a b分别代表两个链表的末尾元素
			int a = s1.empty() ? 0 : s1.top();// a b分别表示栈顶元素 也就是链表的尾元素
			int b = s2.empty() ? 0 : s2.top();
			if (!s1.empty())s1.pop();//删除栈顶元素 继续向下遍历栈内元素
			if (!s2.empty())s2.pop();
			int cur = a + b + carry;//cur表示两链表末尾元素数字之和
			carry = cur / 10;//carry表示进位
			res =cur% 10;//cur表示余数
			auto curnode = new ListNode(res);//以当前计算得到的余数为节点,作为新链表的第一个节点;先构建个位,再构建十位,从右向左的顺序 
			curnode->next = temp;//新节点的下一个节点指向ans
			temp = curnode;//将curnode更新为下一个位置,继续向下遍历
		}
		return temp;//返回ans是新链表的头节点
	}
};

leetcode 70 爬楼梯 【字节】

题目:假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

思路:
**第n阶台阶只能从第n-1或第n-2个上来。到第n-1个台阶的走法+到第n-2个台阶的走法=到第n个台阶的走法**

动态规划解法:

class Solution {
public:
    int climbStairs(int n) 
    {
        vector<int>dp;
        for(int i=0;i<=n;i++)
        {
            if(i==0||i==1)dp.push_back(1);//初始情况
            else dp.push_back(dp[i-1]+dp[i-2]);
        }
        return dp[n];
    }
};

剑指offer II 008 【爬楼梯的最小成本】

数组的每个下标作为一个阶梯,第 i 个阶梯对应着一个非负数的体力花费值 cost[i](下标从 0 开始)。
每当爬上一个阶梯都要花费对应的体力值,一旦支付了相应的体力值,就可以选择向上爬一个阶梯或者爬两个阶梯。
请找出达到楼层顶部的最低花费。在开始时,你可以选择从下标为 0 或 1 的元素作为初始阶梯。
例如:
输入:cost = [10, 15, 20]
输出:15
解释:最低花费是从 cost[1] 开始,然后走两步即可到阶梯顶,一共花费 15 。

dp[i]的定义:到达下标为i的台阶所花费的最少体力为dp[i]
有两个途径可以得到dp[i],一个是dp[i-1]一个是dp[i-2],所以到达顶层的肯定是二者其中最小的

因为初始位置可以从下标0或者下标1开始,因此结束位置也相应的有两个,倒数第一个和倒数第二个,所以最后的结果取二者中的最小值

动态规划实现:

//代码随想录 动态规划解法
class Solution
{
public:
	int minCostClimbingStairs(vector<int>& cost)
	{
		vector<int>dp(cost.size());
		dp[0] = cost[0];//初始化最简单的情况
		dp[1] = cost[1];
		for (int i = 2; i < cost.size(); i++)
		{
			dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i];
		}
		return min(dp[cost.size() - 1], dp[cost.size() - 2]);//返回两种情况里的最小值;
		//当从下标为0的楼梯开始爬的时候 向上爬的总的楼梯数目为cost.size()-1;
		//当从下标为1的楼梯开始爬的时候 向上爬的总的楼梯数目为cost.size()-2;
	}
};

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mr.liang呀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值